using System; using System.IO; using System.Threading; using System.Threading.Tasks; using XWorld.Server.Gateway; namespace XWorld.Server.Gateway.Runner { public static class Program { public static async Task Main(string[] args) { string gamesRoot = GetArg(args, "--gamesRoot") ?? Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "Server.Host.Tests", "TestGames")); int port = int.TryParse(GetArg(args, "--port"), out int p) ? p : 5005; int tickMs = int.TryParse(GetArg(args, "--tickMs"), out int t) ? t : 100; long matchTimeoutTicks = long.TryParse(GetArg(args, "--matchTimeoutTicks"), out long mt) ? mt : 150; string devToken = GetArg(args, "--devToken"); string advertiseWs = GetArg(args, "--advertiseWs"); string advertiseCdn = GetArg(args, "--advertiseCdn"); bool lan = Array.Exists(args, a => string.Equals(a, "--lan", StringComparison.OrdinalIgnoreCase)); int cdnPort = int.TryParse(GetArg(args, "--cdnPort"), out int cp) ? cp : 15081; // --lan 下若未显式给 CDN 地址,用本机 LAN IP + 约定端口兜底广而告之 CDN 根 // (客户端从根派生两套:minigame/// 与 XWorld//;advertiseWs 缺省由 GameServerHost 内部用 LanIp 兜底) if (lan && string.IsNullOrEmpty(advertiseCdn)) advertiseCdn = $"http://{XWorld.Server.Gateway.LanIp.Resolve()}:{cdnPort}/"; // 鉴权 opt-in:给了 --authDbPath 才启用账号鉴权(/register、/login、/ws 验 token)。 // 密钥优先级:--authSecret(base64) > DB 同目录 auth.key 文件(不存在则生成并持久化)。 string authDbPath = GetArg(args, "--authDbPath"); string authSecretB64 = GetArg(args, "--authSecret"); byte[] authSecret = null; if (!string.IsNullOrEmpty(authDbPath)) { if (!string.IsNullOrEmpty(authSecretB64)) authSecret = Convert.FromBase64String(authSecretB64); else { string keyPath = Path.Combine( Path.GetDirectoryName(Path.GetFullPath(authDbPath)) ?? ".", "auth.key"); if (File.Exists(keyPath)) authSecret = Convert.FromBase64String(File.ReadAllText(keyPath).Trim()); else { authSecret = System.Security.Cryptography.RandomNumberGenerator.GetBytes(32); File.WriteAllText(keyPath, Convert.ToBase64String(authSecret)); } } } var host = new GameServerHost(); await host.StartAsync(gamesRoot, tickMs, matchTimeoutTicks: matchTimeoutTicks, listenPort: port, bindAllInterfaces: lan, devDiscoveryToken: devToken, advertiseGatewayUrl: advertiseWs, advertiseResourceBaseUrl: advertiseCdn, authSecret: authSecret, authDbPath: authDbPath); Console.WriteLine("XWorld Gateway smoke server started."); Console.WriteLine(" gamesRoot: " + gamesRoot); Console.WriteLine(" tickMs: " + tickMs + ", matchTimeoutTicks: " + matchTimeoutTicks); Console.WriteLine(" ws: " + host.WsBaseUrl + "/ws?pid=1"); if (!string.IsNullOrEmpty(devToken)) Console.WriteLine(" dev-discovery: ON (UDP " + host.DevDiscoveryPort + "), advertise ws=" + (string.IsNullOrEmpty(advertiseWs) ? host.WsBaseUrl + "/ws" : advertiseWs) + " cdn=" + advertiseCdn); Console.WriteLine(" auth: " + (authSecret != null ? ("ON (db=" + authDbPath + ", /register /login /ws?token=)") : "OFF (dev ?pid=)")); Console.WriteLine("Press Ctrl+C to stop."); using var stop = new CancellationTokenSource(); Console.CancelKeyPress += (s, e) => { e.Cancel = true; stop.Cancel(); }; try { await Task.Delay(Timeout.InfiniteTimeSpan, stop.Token); } catch (OperationCanceledException) { } await host.StopAsync(); } private static string GetArg(string[] args, string name) { for (int i = 0; i < args.Length - 1; i++) if (string.Equals(args[i], name, StringComparison.OrdinalIgnoreCase)) return args[i + 1]; return null; } } }