Files
MRCC/launcher/MRCC.Launcher/MainWindow.xaml.cs
T

156 lines
5.3 KiB
C#

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;
}
}