377 lines
15 KiB
C#
377 lines
15 KiB
C#
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";
|
|
|
|
/// <summary>将相对路径解析为绝对路径(基于插件所在目录)。已是绝对路径则原样返回。</summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>从 GameExeFullPath 提取进程名(不带扩展名)</summary>
|
|
static string GameProcessName
|
|
{
|
|
get
|
|
{
|
|
try { return Path.GetFileNameWithoutExtension(GameExeFullPath); }
|
|
catch { return "FantasyMapSimulator"; }
|
|
}
|
|
}
|
|
|
|
/// <summary>关闭所有同名游戏进程,返回是否已无残留</summary>
|
|
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);
|
|
}
|
|
|
|
/// <summary>加载 game_settings.json (启动项 + 配置表路径)</summary>
|
|
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>();
|
|
string dir = json["gameWorkDir"]?.Value<string>();
|
|
bool auto = json["autoLaunchGame"]?.Value<bool>() ?? true;
|
|
if (!string.IsNullOrWhiteSpace(exe)) GameExePath = exe;
|
|
if (!string.IsNullOrWhiteSpace(dir)) GameWorkDir = dir;
|
|
AutoLaunchGame = auto;
|
|
|
|
// 配置表路径
|
|
string configDir = json["configDir"]?.Value<string>();
|
|
if (!string.IsNullOrWhiteSpace(configDir)) ConfigDir = configDir;
|
|
var cfg = json["configFiles"];
|
|
if (cfg != null)
|
|
{
|
|
string eff = cfg["effect"]?.Value<string>();
|
|
string mac = cfg["macro"]?.Value<string>();
|
|
string gift = cfg["gift"]?.Value<string>();
|
|
string cmd = cfg["command"]?.Value<string>();
|
|
string fkey = cfg["fkey"]?.Value<string>();
|
|
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;
|
|
}
|
|
}
|
|
}
|