using System; using System.Drawing; using System.IO; using System.Windows.Forms; // 入口。正常双击 => 打开界面;带 /dryrun => 只做路径自检并把结果写进 launcher.log,不弹界面、不启动任何进程。 internal static class Program { [STAThread] private static int Main(string[] args) { foreach (var a in args) { var t = (a ?? "").Trim().ToLowerInvariant(); if (t == "/dryrun" || t == "--dryrun" || t == "-dryrun") return DryRun(); if (t == "/uitest" || t == "--uitest" || t == "-uitest") return UiTest(args); } Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); return 0; } // 界面冒烟测试:真的把 MainForm 造出来、Show 出来、跑几轮消息循环, // 再把客户区 DrawToBitmap 存成 uitest.png。用来在没法肉眼看界面的环境里 // 既能证明「构造 + 布局 + 定时器」不炸,又能真的看到界面长什么样。 private static int UiTest(string[] args) { // --uitest-stop:拍完自然态后,再强制把服务端按键推成「关闭服务端」态拍第二张。 // 服务端当前没跑时,光靠 --uitest 是看不到「关闭服务端」外观的(红色停止图标)。 bool wantStop = false; foreach (var a in args) { var t = (a ?? "").Trim().ToLowerInvariant(); if (t == "--uitest-stop" || t == "-uitest-stop") wantStop = true; } string root = AppContext.BaseDirectory.TrimEnd('\\', '/'); string log = Path.Combine(root, "tools", "launcher", "launcher.log"); void L(string s) { try { File.AppendAllText(log, DateTime.Now.ToString("HH:mm:ss") + " " + s + Environment.NewLine); } catch { } } string png = Path.Combine(root, "uitest.png"); static void Snap(Form f, string path) { using var bmp = new Bitmap(f.ClientSize.Width, f.ClientSize.Height); f.DrawToBitmap(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height)); bmp.Save(path, System.Drawing.Imaging.ImageFormat.Png); } try { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); using var f = new MainForm(); f.Opacity = 1; L("[UITEST] 1 构造完成"); // ⛑ 必须真的 Show 一次,否则子控件不会 Realize,DrawToBitmap 只拍得到窗体自绘层。 // 挪到屏幕外,避免在用户桌面上闪一下。 f.StartPosition = FormStartPosition.Manual; f.Location = new Point(-4000, -4000); f.Show(); for (int i = 0; i < 10; i++) { Application.DoEvents(); System.Threading.Thread.Sleep(20); } f.PerformLayout(); L("[UITEST] 2 已显示,Size=" + f.ClientSize.Width + "x" + f.ClientSize.Height); Application.DoEvents(); Snap(f, png); if (wantStop) { // ⛑ 强推状态后「立刻」拍,中间不能 DoEvents —— 否则 1 秒轮询会把按键改回自然态 f.ForceServerButtonForTest(true); string png2 = Path.Combine(root, "uitest-stop.png"); Snap(f, png2); L("[UITEST] 已强制「关闭服务端」态,截图 = " + png2); } f.Close(); L("[UITEST] 界面构造 + 布局 + 轮询 OK,截图 = " + png); return 0; } catch (Exception ex) { L("[UITEST] 异常: " + ex); return 3; } } private static int DryRun() { string root = AppContext.BaseDirectory.TrimEnd('\\', '/'); string log = Path.Combine(root, "tools", "launcher", "launcher.log"); void L(string s) { try { File.AppendAllText(log, DateTime.Now.ToString("HH:mm:ss") + " " + s + Environment.NewLine); } catch { } } L("============ DRYRUN(界面版) ============"); L("root = " + root); string startCmd = Path.Combine(root, "START.cmd"); string injector = Path.Combine(root, "tools", "AnantaInjector", "unified_loader.py"); // ⛑ 优先随包分发的便携 python;没有再退回开发者本机的环境(避免分发给别人时报「找不到」)。 string python = Path.Combine(root, "tools", "python", "python.exe"); if (!File.Exists(python)) python = @"C:\Users\Noiro\.workbuddy\binaries\python\envs\default\Scripts\python.exe"; string serverCfg = Path.Combine(root, "config", "private-server.json"); L("START.cmd = " + startCmd + " exists=" + File.Exists(startCmd)); L("injector = " + injector + " exists=" + File.Exists(injector)); L("python = " + python + " exists=" + File.Exists(python)); L("serverCfg = " + serverCfg + " exists=" + File.Exists(serverCfg)); L("[DRYRUN] 路径自检完成,未启动任何进程。"); return File.Exists(startCmd) && File.Exists(injector) && File.Exists(python) ? 0 : 2; } }