using System; using System.IO; using System.Linq; using System.Diagnostics; using System.Text; using System.Threading; using System.Windows.Forms; using BarrageGrab.Modles.JsonEntity; using Newtonsoft.Json.Linq; using Jint.Runtime; namespace BarrageGrab { public class Program { static FormView mainForm = null; static bool exited = false; static bool formExited = false; static WinApi.ControlCtrlDelegate controlCtr = ControlCtrlHandle; static Mutex mutex = new Mutex(false, "DyBarrageGrab"); // 插件所在目录(用于解析相对路径) public static string BaseDir => AppDomain.CurrentDomain.BaseDirectory; // === 启动项 (支持相对路径,相对于插件所在目录) === public static bool AutoLaunchGame = true; public static string GameExePath = @"..\Fantasy.Map.Simulator\FantasyMapSimulator.exe"; public static string GameWorkDir = @"..\Fantasy.Map.Simulator"; // === 配置表路径 (支持相对路径) === public static string ConfigDir = @"..\Fantasy.Map.Simulator\BepInEx\config"; public static string EffectFile = "effect_config.txt"; public static string MacroFile = "macro_config.txt"; public static string GiftFile = "gift_config.txt"; public static string CommandFile = "command_config.txt"; public static string FkeyFile = "fkey_config.txt"; /// 将相对路径解析为绝对路径(基于插件所在目录)。已是绝对路径则原样返回。 public static string ResolveFullPath(string path) { if (string.IsNullOrWhiteSpace(path)) return path; return Path.IsPathRooted(path) ? path : Path.GetFullPath(Path.Combine(BaseDir, path)); } // 解析后的绝对路径(只读) public static string GameExeFullPath => ResolveFullPath(GameExePath); public static string GameWorkFullPath => ResolveFullPath(GameWorkDir); public static string ConfigDirFullPath => ResolveFullPath(ConfigDir); // 配置表完整路径(只读,基于 ConfigDirFullPath) public static string EffectPath => Path.Combine(ConfigDirFullPath, EffectFile); public static string MacroPath => Path.Combine(ConfigDirFullPath, MacroFile); public static string GiftPath => Path.Combine(ConfigDirFullPath, GiftFile); public static string CommandPath => Path.Combine(ConfigDirFullPath, CommandFile); public static string FkeyPath => Path.Combine(ConfigDirFullPath, FkeyFile); public static void SetGamePaths(string exePath, string workDir) { if (!string.IsNullOrWhiteSpace(exePath)) GameExePath = exePath; if (!string.IsNullOrWhiteSpace(workDir)) GameWorkDir = workDir; } public static void SetConfigPaths(string configDir, string effectFile, string macroFile, string giftFile, string cmdFile, string fkeyFile) { if (!string.IsNullOrWhiteSpace(configDir)) ConfigDir = configDir; if (!string.IsNullOrWhiteSpace(effectFile)) EffectFile = effectFile; if (!string.IsNullOrWhiteSpace(macroFile)) MacroFile = macroFile; if (!string.IsNullOrWhiteSpace(giftFile)) GiftFile = giftFile; if (!string.IsNullOrWhiteSpace(cmdFile)) CommandFile = cmdFile; if (!string.IsNullOrWhiteSpace(fkeyFile)) FkeyFile = fkeyFile; } /// 从 GameExeFullPath 提取进程名(不带扩展名) static string GameProcessName { get { try { return Path.GetFileNameWithoutExtension(GameExeFullPath); } catch { return "FantasyMapSimulator"; } } } /// 关闭所有同名游戏进程,返回是否已无残留 static bool CloseGameProcesses() { var procName = GameProcessName; var procs = Process.GetProcessesByName(procName); if (procs.Length == 0) { Logger.PrintColor("未发现运行中的游戏进程 (" + procName + ")", ConsoleColor.DarkGray); return true; } Logger.PrintColor("发现 " + procs.Length + " 个游戏进程 (" + procName + "),正在关闭...", ConsoleColor.Yellow); int killed = 0, failed = 0; foreach (var p in procs) { try { if (!p.HasExited) { try { p.CloseMainWindow(); } catch { } if (!p.WaitForExit(2000)) { p.Kill(); p.WaitForExit(3000); } } killed++; } catch { failed++; } finally { try { p.Close(); } catch { } } } Thread.Sleep(500); var remain = Process.GetProcessesByName(procName); if (remain.Length > 0) { Logger.PrintColor("仍有 " + remain.Length + " 个进程未退出,强制结束...", ConsoleColor.Yellow); foreach (var r in remain) { try { if (!r.HasExited) r.Kill(); } catch { } finally { try { r.Close(); } catch { } } } Thread.Sleep(1000); } bool allClosed = Process.GetProcessesByName(procName).Length == 0; Logger.PrintColor("关闭完成 (成功" + killed + ", 失败" + failed + ")", allClosed ? ConsoleColor.Green : ConsoleColor.Yellow); return allClosed; } public static void LaunchGame() { try { if (!File.Exists(GameExeFullPath)) { MessageBox.Show("找不到游戏文件:\n" + GameExeFullPath + "\n(配置路径: " + GameExePath + ")", "启动失败", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } var existing = Process.GetProcessesByName(GameProcessName); if (existing.Length > 0) { Logger.PrintColor("游戏已在运行中,跳过启动", ConsoleColor.Yellow); foreach (var p in existing) try { p.Close(); } catch { } return; } StartGameProcess(); } catch (Exception ex) { Logger.LogError(ex, "启动游戏失败: " + ex.Message); } } public static void RestartGame() { try { if (!File.Exists(GameExeFullPath)) { MessageBox.Show("找不到游戏文件:\n" + GameExeFullPath + "\n(配置路径: " + GameExePath + ")", "启动失败", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } Logger.PrintColor("── 重启游戏:关闭进程 ──", ConsoleColor.Cyan); bool closed = CloseGameProcesses(); if (!closed) { Logger.PrintColor("警告: 部分游戏进程未能完全关闭,仍尝试重新启动", ConsoleColor.Yellow); } Thread.Sleep(1500); Logger.PrintColor("── 重启游戏:重新启动 ──", ConsoleColor.Cyan); StartGameProcess(); } catch (Exception ex) { Logger.LogError(ex, "重启游戏失败: " + ex.Message); } } private static void StartGameProcess() { var psi = new ProcessStartInfo(GameExeFullPath) { WorkingDirectory = GameWorkFullPath, UseShellExecute = true }; Process.Start(psi); Logger.PrintColor("游戏已启动", ConsoleColor.Green); } /// 加载 game_settings.json (启动项 + 配置表路径) private static void LoadGameSettings() { try { string path = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "game_settings.json"); if (!File.Exists(path)) return; var json = JObject.Parse(File.ReadAllText(path)); // 启动项 string exe = json["gameExePath"]?.Value(); string dir = json["gameWorkDir"]?.Value(); bool auto = json["autoLaunchGame"]?.Value() ?? true; if (!string.IsNullOrWhiteSpace(exe)) GameExePath = exe; if (!string.IsNullOrWhiteSpace(dir)) GameWorkDir = dir; AutoLaunchGame = auto; // 配置表路径 string configDir = json["configDir"]?.Value(); if (!string.IsNullOrWhiteSpace(configDir)) ConfigDir = configDir; var cfg = json["configFiles"]; if (cfg != null) { string eff = cfg["effect"]?.Value(); string mac = cfg["macro"]?.Value(); string gift = cfg["gift"]?.Value(); string cmd = cfg["command"]?.Value(); string fkey = cfg["fkey"]?.Value(); if (!string.IsNullOrWhiteSpace(eff)) EffectFile = eff; if (!string.IsNullOrWhiteSpace(mac)) MacroFile = mac; if (!string.IsNullOrWhiteSpace(gift)) GiftFile = gift; if (!string.IsNullOrWhiteSpace(cmd)) CommandFile = cmd; if (!string.IsNullOrWhiteSpace(fkey)) FkeyFile = fkey; } Logger.PrintColor($"已加载游戏设置: 自动启动={AutoLaunchGame}, 配置目录={ConfigDir}", ConsoleColor.DarkGray); } catch (Exception ex) { Logger.PrintColor($"加载游戏设置失败: {ex.Message}", ConsoleColor.Yellow); } } private const string WatchdogArg = "--watchdog"; private const string WatchdogPidArg = "--pid"; static void Main(string[] args) { if (IsWatchdogMode(args)) { RunWatchdog(args); return; } if (!mutex.WaitOne(TimeSpan.Zero, true)) { Console.WriteLine("另一个实例已在运行。"); Console.ReadKey(); return; } SetTitle("弹幕监听推送"); StartWatchdog(); LoadGameSettings(); try { Init(); } catch (Exception ex) { Logger.LogError(ex, $"程序初始化错误,{ex.Message}"); MessageBox.Show(ex.Message, "程序初始化错误", MessageBoxButtons.OK, MessageBoxIcon.Error); exited = true; } while (!exited) Thread.Sleep(500); if (!AppRuntime.WsServer.IsDisposed) AppRuntime.WsServer.Dispose(); Logger.PrintColor("服务器已关闭..."); WinApi.SetConsoleCtrlHandler(controlCtr, false); } private static void StartWatchdog() { try { var exePath = Application.ExecutablePath; if (string.IsNullOrWhiteSpace(exePath) || !File.Exists(exePath)) return; var pid = Process.GetCurrentProcess().Id; var args = string.Format("{0} {1} {2}", WatchdogArg, WatchdogPidArg, pid); var psi = new ProcessStartInfo(exePath, args) { UseShellExecute = false, CreateNoWindow = true, WindowStyle = ProcessWindowStyle.Hidden }; Process.Start(psi); } catch { } } private static bool IsWatchdogMode(string[] args) { return args != null && args.Any(a => string.Equals(a, WatchdogArg, StringComparison.OrdinalIgnoreCase)); } private static int? TryGetWatchdogPid(string[] args) { if (args == null) return null; for (int i = 0; i < args.Length; i++) { if (!string.Equals(args[i], WatchdogPidArg, StringComparison.OrdinalIgnoreCase)) continue; if (i + 1 >= args.Length) return null; int pid; if (int.TryParse(args[i + 1], out pid)) return pid; return null; } return null; } private static void RunWatchdog(string[] args) { try { var pid = TryGetWatchdogPid(args); if (pid == null || pid <= 0) return; Process target; try { target = Process.GetProcessById(pid.Value); } catch { SafeCloseSystemProxy(); return; } for (; ; ) { if (target.HasExited) break; Thread.Sleep(1000); } SafeCloseSystemProxy(); } catch { try { SafeCloseSystemProxy(); } catch { } } } private static void SafeCloseSystemProxy() { try { if (!AppSetting.Current.UsedProxy) return; WinApi.CloseSystemProxy(); } catch { } } private static void Init() { AppRuntime.Init(); LiveCompanHelper.SwitchSetup(); WinApi.SetConsoleCtrlHandler(controlCtr, true); WinApi.DisableQuickEditMode(); AppRuntime.DisplayConsole(!AppSetting.Current.HideConsole); AppRuntime.WsServer.Grab.Proxy.SetUpstreamProxy(AppSetting.Current.UpstreamProxy); AppRuntime.WsServer.OnClose += (s, e) => { exited = true; }; if (!AppSetting.Current.ComPort.IsNullOrWhiteSpace()) AppRuntime.ComPortServer.OpenStart(); if (AppSetting.Current.ShowWindow) { var uiThread = new Thread(new ThreadStart(() => { mainForm = new FormView(); System.Windows.Forms.Application.Run(mainForm); formExited = true; AppRuntime.WsServer.Dispose(); })); uiThread.SetApartmentState(ApartmentState.STA); uiThread.IsBackground = true; uiThread.Start(); } AppRuntime.WsServer.StartListen(); Logger.PrintColor($"{AppRuntime.WsServer.ServerLocation} 弹幕服务已启动", ConsoleColor.Green); if (AutoLaunchGame) LaunchGame(); Version version = System.Reflection.Assembly.GetAssembly(typeof(Program)).GetName().Version; SetTitle($"弹幕监听推送 v{version} [{AppRuntime.WsServer.ServerLocation}]"); } private static void SetTitle(string title) { if (WinApi.GetConsoleWindow() != IntPtr.Zero) Console.Title = title; } private static bool ControlCtrlHandle(int CtrlType) { switch (CtrlType) { case 0: break; case 2: Logger.PrintColor("2工具被强制关闭"); AppRuntime.WsServer.Dispose(); break; } return false; } } }