using System; using System.IO; using System.Net; using System.Text.Json; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.Wpf; namespace MRCC.Launcher; public partial class MainWindow : Window { private WebView2? _wv; public MainWindow() { InitializeComponent(); } private async void OnWindowLoaded(object sender, RoutedEventArgs e) { try { var gameDir = FindGamePath(); if (gameDir == null) { Content = new TextBlock { Text = "游戏文件未找到", Foreground = System.Windows.Media.Brushes.White, FontSize = 14, Margin = new Thickness(20) }; return; } var port = GameServer.Start(gameDir); var url = $"http://localhost:{port}/voxel-world.html"; _wv = new WebView2(); Content = _wv; await _wv.EnsureCoreWebView2Async(); _wv.CoreWebView2.Settings.IsWebMessageEnabled = true; _wv.CoreWebView2.WebMessageReceived += OnWebMessage; _wv.CoreWebView2.Navigate(url); Program.Log($"Launcher UI loaded: {url}"); } catch (Exception ex) { Program.Log($"Launcher load failed: {ex}"); } } private void OnWebMessage(object? sender, CoreWebView2WebMessageReceivedEventArgs e) { try { // WebView2 的 postMessage 会 JSON 编码,需先解一层 var rawJson = e.TryGetWebMessageAsString(); if (string.IsNullOrEmpty(rawJson)) return; using var doc = JsonDocument.Parse(rawJson); var root = doc.RootElement; if (!root.TryGetProperty("action", out var action) || action.GetString() != "launch_game") return; var username = "Player"; if (root.TryGetProperty("username", out var u) && u.ValueKind == JsonValueKind.String) username = u.GetString()!; // 不再启动 Luanti —— HTML5 世界直接在 WebView2 内运行 Program.Log($"Game launch requested by: {username}"); } catch (Exception ex) { Program.Log($"WebMessage error: {ex}"); } } public static string? FindGamePath() { var exeDir = AppContext.BaseDirectory; foreach (var p in new[] { "game", "../game", "../../../prototype", "../../prototype", "../prototype" }) { var full = Path.GetFullPath(Path.Combine(exeDir, p)); if (Directory.Exists(full) && File.Exists(Path.Combine(full, "voxel-world.html"))) return full; } return null; } } // HTTP server for static files public static class GameServer { private static HttpListener? _listener; private static CancellationTokenSource? _cts; private static string _gameDir = ""; public static int Start(string gameDir) { _gameDir = gameDir; var port = FindFreePort(); _cts = new CancellationTokenSource(); _listener = new HttpListener(); _listener.Prefixes.Add($"http://localhost:{port}/"); _listener.Start(); Task.Run(() => ListenLoop(_cts.Token)); return port; } public static void Stop() { _cts?.Cancel(); try { _listener?.Stop(); } catch { } try { _listener?.Close(); } catch { } } private static async Task ListenLoop(CancellationToken ct) { while (!ct.IsCancellationRequested) { try { var ctx = await _listener!.GetContextAsync().WaitAsync(ct); _ = Task.Run(() => Handle(ctx)); } catch (OperationCanceledException) { break; } catch (HttpListenerException) { break; } } } private static async Task Handle(HttpListenerContext ctx) { try { var path = ctx.Request.Url!.AbsolutePath.TrimStart('/'); if (string.IsNullOrEmpty(path)) path = "voxel-world.html"; var fp = Path.GetFullPath(Path.Combine(_gameDir, path)); if (!fp.StartsWith(_gameDir, StringComparison.OrdinalIgnoreCase) || !File.Exists(fp)) { ctx.Response.StatusCode = 404; ctx.Response.Close(); return; } var ct = Path.GetExtension(fp).ToLowerInvariant() switch { ".html" => "text/html; charset=utf-8", ".css" => "text/css", ".js" => "application/javascript", ".json" => "application/json", ".png" => "image/png", ".jpg" or ".jpeg" => "image/jpeg", ".svg" => "image/svg+xml", _ => "application/octet-stream" }; var bytes = await File.ReadAllBytesAsync(fp); ctx.Response.ContentType = ct; ctx.Response.ContentLength64 = bytes.Length; ctx.Response.Headers.Add("Access-Control-Allow-Origin", "*"); ctx.Response.Headers.Add("Cache-Control", "no-cache"); await ctx.Response.OutputStream.WriteAsync(bytes); ctx.Response.Close(); } catch { try { ctx.Response.StatusCode = 500; ctx.Response.Close(); } catch { } } } private static int FindFreePort() { var l = new System.Net.Sockets.TcpListener(System.Net.IPAddress.Loopback, 0); l.Start(); var port = ((System.Net.IPEndPoint)l.LocalEndpoint).Port; l.Stop(); return port; } }