using Microsoft.Extensions.Logging.Console; using MySql.Data.MySqlClient; using SinMaiLauncher.ChildProcessHolder; using SinMaiLauncher.Interops; using SinMaiLauncher.Models; namespace SinMaiLauncher; public partial class MainForm : Form { private ILogger logger; private readonly IEnumerator txtAni = TextAnimation.LoadingTextSeq(8).GetEnumerator(); private SettingReader setting; private Dictionary childProcessControlGroups; private string mainControlStatusText = "摸鱼中"; private bool mainControlStatusTextAnimation = false; public MainForm() { InitializeComponent(); } private async void MainForm_Shown(object sender, EventArgs e) { TopMost = false; TopMost = true; tcMain.SelectedTab = tpConsoles; ConsoleInterops.CurrentProcess.ConsoleAllocate(); ConsoleInterops.CurrentProcess.ConsoleDisableQuickEdit(); ConsoleInterops.CurrentProcess.ConsoleWindowSetParent(tpConsoleLauncher); ConsoleInterops.CurrentProcess.ConsoleWindowFill(tpConsoleLauncher); tpConsoleLauncher.Resize += delegate { ConsoleInterops.CurrentProcess.ConsoleWindowFill(tpConsoleLauncher); }; //start host application var builder = Host.CreateDefaultBuilder(); builder.ConfigureServices((ctx, services) => { //控制台日志格式 services.AddLogging(opt => { opt.AddSimpleConsole(p => { p.TimestampFormat = "[dd HH:mm:ss] "; p.SingleLine = true; p.ColorBehavior = LoggerColorBehavior.Enabled; }) .AddDebug() //.AddProvider(loggingIntoEventBus) ; services.AddSingleton(opt); }); services.AddSingleton(this); }); var host = builder.Build(); var services = host.Services; host.Start(); logger = services.GetRequiredService>(); var conf = services.GetRequiredService(); var settingModel = new SinMaiLauncherSettingModel(); conf.GetSection("SinMaiLauncher").Bind(settingModel); setting = new(settingModel); childProcessControlGroups = new Dictionary { { ChildProcessKind.MariaDb, new ChildProcessControlGroup { StateBag = new ProcessStateBagMariaDb(services.GetRequiredService>(), setting), ConsoleTab = tpConsoleMaria, PidLabel = lblPidMaria, StatusLabel = lblSubControlMaria, StartButton = btnSubControlMariaStart, StopButton = btnSubControlMariaStop } }, { ChildProcessKind.AquaDx, new ChildProcessControlGroup { StateBag = new ProcessStateBagAquaDx(services.GetRequiredService>(), setting), Dep = ChildProcessKind.MariaDb, ConsoleTab = tpConsoleAquaDx, PidLabel = lblPidAquaDx, StatusLabel = lblSubControlAquaDx, StartButton = btnSubControlAquaDxStart, StopButton = btnSubControlAquaDxStop } }, { ChildProcessKind.Injector, new ChildProcessControlGroup { StateBag = new ChildProcessStateBagForConsole(ChildProcessKind.Injector, setting.Injector), Dep = ChildProcessKind.AquaDx, ConsoleTab = tpConsoleInjector, PidLabel = lblPidInjector, StatusLabel = lblSubControlInjector, StartButton = btnSubControlInjectorStart, StopButton = btnSubControlInjectorStop } }, { ChildProcessKind.SinMai, new ChildProcessControlGroup { StateBag = new ChildProcessStateBagForWin(ChildProcessKind.SinMai, setting.SinMai), Dep = ChildProcessKind.Injector, ConsoleTab = null, PidLabel = lblPidSinMai, StatusLabel = lblSubControlSinMai, StartButton = btnSubControlSinMaiStart, StopButton = btnSubControlSinMaiStop } } }; foreach (var gr in childProcessControlGroups.Values) { var tab = gr.ConsoleTab; var cpb = gr.StateBag; var btnStart = gr.StartButton; var btnStop = gr.StopButton; var lblPid = gr.PidLabel; var lblStatus = gr.StatusLabel; //绑定tab控制台窗口填充 if (cpb is ChildProcessStateBagForConsole con) { tab.Resize += delegate { if (con.HWndConsole.HasValue) WindowInterops.FillWindow(con.HWndConsole.Value, tab); }; } btnStart.Click += async delegate { if (cpb.IsAlive) { MessageBox.Show("它在运行了,别重复点启动啊"); return; } if (gr.Dep.HasValue && childProcessControlGroups[gr.Dep.Value].StateBag.Status != ChildProcessStatus.Ready) { MessageBox.Show("依赖的进程未就绪,启动不了的,等就绪了再启动。要按顺序启动。"); return; } await cpb.StartAsync(); }; btnStop.Click += async delegate { if (cpb.IsAlive == false) { MessageBox.Show("都没启动,咋停止啊?"); return; } await cpb.StopAsync(TimeSpan.FromSeconds(5)); }; cpb.StatusUpdated += async delegate { void UpdateUi() { lblPid.Text = cpb.Pid.HasValue ? cpb.Pid.Value.ToString() : "-"; lblStatus.Text = cpb.Status switch { ChildProcessStatus.NoLaunched => "未启动", ChildProcessStatus.PreLaunching => "准备启动", ChildProcessStatus.MissingWorkingDir => "丢失工作目录", ChildProcessStatus.MissingExeFile => "丢失exe文件", ChildProcessStatus.Launching => "正在启动", ChildProcessStatus.WaitingReady => "等待就绪...", ChildProcessStatus.Ready => "就绪", ChildProcessStatus.Stopping => "正在停止", ChildProcessStatus.Stopped => "已停止", _ => "?" }; if (cpb.Status == ChildProcessStatus.WaitingReady && cpb is ChildProcessStateBagForConsole con && cpb.IsAlive && cpb.Pid.HasValue && con.HWndConsole.HasValue) { if (tab != null) { //console 收纳 Program.DisableQuickEdit(cpb.Pid.Value); WindowInterops.RemoveBorderVisible(con.HWndConsole.Value); WindowInterops.SetParent(con.HWndConsole.Value, tab.Handle); WindowInterops.FillWindow(con.HWndConsole.Value, tab); Height++; Height--; } } if (cpb.Status == ChildProcessStatus.Ready && cpb is ChildProcessStateBagForWin win) { //chkExplorerAuto } } if (InvokeRequired) await InvokeAsync(UpdateUi); else UpdateUi(); }; } logger.LogInformation("Started"); Application.DoEvents(); await Task.Delay(1000); tcMain.Enabled = true; //#if DEBUG // tcMain.SelectedTab = tpSubControls; //#else tcMain.SelectedTab = tpMainControl; //#endif } private void MainForm_FormClosing(object sender, FormClosingEventArgs e) { if ( childProcessControlGroups[ChildProcessKind.MariaDb].StateBag.Status == ChildProcessStatus.Ready || childProcessControlGroups[ChildProcessKind.AquaDx].StateBag.Status == ChildProcessStatus.Ready ) { e.Cancel = MessageBox.Show($"基础设施进程仍在运行。{Environment.NewLine}为了避免进度数据裂开,请确保安全停止再关闭。{Environment.NewLine}要强制关闭吗?", "关闭启动器", MessageBoxButtons.YesNo, MessageBoxIcon.Warning, MessageBoxDefaultButton.Button2) == DialogResult.No; } } private void MainForm_FormClosed(object sender, FormClosedEventArgs e) { //Kill all child process childProcessControlGroups[ChildProcessKind.SinMai].StateBag.StopAsync(TimeSpan.FromSeconds(10)).Wait(TimeSpan.FromSeconds(10)); childProcessControlGroups[ChildProcessKind.Injector].StateBag.StopAsync(TimeSpan.FromSeconds(10)).Wait(TimeSpan.FromSeconds(10)); childProcessControlGroups[ChildProcessKind.AquaDx].StateBag.StopAsync(TimeSpan.FromSeconds(10)).Wait(TimeSpan.FromSeconds(10)); childProcessControlGroups[ChildProcessKind.MariaDb].StateBag.StopAsync(TimeSpan.FromSeconds(10)).Wait(TimeSpan.FromSeconds(10)); } private void tcConsoles_SelectedIndexChanged(object sender, EventArgs e) { Height++; Height--; } private class ChildProcessControlGroup { public ChildProcessStateBag StateBag { get; set; } public ChildProcessKind? Dep { get; set; } public TabPage? ConsoleTab { get; set; } // 可空,窗口进程为 null public Label PidLabel { get; set; } public Label StatusLabel { get; set; } public Button StartButton { get; set; } public Button StopButton { get; set; } } private async void btnMainControlStart_Click(object sender, EventArgs e) { btnMainControlStart.Enabled = false; mainControlStatusTextAnimation = true; var orderedProcesses = new List<(ChildProcessKind kind, TimeSpan timeout)> { (ChildProcessKind.MariaDb, TimeSpan.FromSeconds(30)), (ChildProcessKind.AquaDx,TimeSpan.FromSeconds(90)), (ChildProcessKind.Injector,TimeSpan.FromSeconds(10)), (ChildProcessKind.SinMai,TimeSpan.FromSeconds(10)), }; var startedProcesses = new List(); try { foreach (var (kind, timeout) in orderedProcesses) { var gr = childProcessControlGroups[kind]; var cpb = gr.StateBag; if (cpb.IsAlive) { logger.LogWarning("{Kind} is already running, skipping.", kind); startedProcesses.Add(kind); // 已运行的也记录,便于回滚 continue; } mainControlStatusText = $"正在启动 {kind}"; await cpb.StartAsync(); await WaitForReadyAsync(cpb, timeout); if (cpb.Status != ChildProcessStatus.Ready) { logger.LogError($"{kind} failed to reach Ready state (current: {cpb.Status}), rolling back."); await RollbackAsync(startedProcesses); return; } startedProcesses.Add(kind); } mainControlStatusText = $"已启动!"; btnMainControlStop.Enabled = true; mainControlStatusTextAnimation = false; } catch (Exception exception) { logger.LogError(exception, "Error during startup, rolling back."); await RollbackAsync(startedProcesses); } async Task WaitForReadyAsync(ChildProcessStateBag cpb, TimeSpan timeout) { if (cpb.Status == ChildProcessStatus.Ready || cpb.Status == ChildProcessStatus.MissingWorkingDir || cpb.Status == ChildProcessStatus.MissingExeFile || cpb.Status == ChildProcessStatus.Stopped) return; var tcs = new TaskCompletionSource(); EventHandler handler = null; handler = delegate { if (cpb.Status == ChildProcessStatus.Ready || cpb.Status == ChildProcessStatus.MissingWorkingDir || cpb.Status == ChildProcessStatus.MissingExeFile || cpb.Status == ChildProcessStatus.Stopped ) { cpb.StatusUpdated -= handler; tcs.TrySetResult(true); } }; cpb.StatusUpdated += handler; try { // 等待 Ready 或超时 await Task.WhenAny(tcs.Task, Task.Delay(timeout)); if (!tcs.Task.IsCompleted) { cpb.StatusUpdated -= handler; throw new TimeoutException($"{cpb.Kind} did not reach Ready state within {timeout.TotalSeconds} seconds."); } } finally { cpb.StatusUpdated -= handler; // 确保清理 } } async Task RollbackAsync(List startedProcesses) { // 按相反顺序停止 foreach (var kind in startedProcesses.AsEnumerable().Reverse()) { mainControlStatusText = $"正在停止 {kind}"; var group = childProcessControlGroups[kind]; var cpb = group.StateBag; if (cpb.IsAlive) { logger.LogInformation("Stopping {Kind} during rollback...", kind); await InvokeAsync(() => group.StatusLabel.Text = "正在停止..."); await cpb.StopAsync(TimeSpan.FromSeconds(5)); } } mainControlStatusText = "启动失败,已回滚到启动之前"; btnMainControlStart.Enabled = true; mainControlStatusTextAnimation = false; } } private async void btnMainControlStop_Click(object sender, EventArgs e) { var orderedProcesses = new List<(ChildProcessKind kind, TimeSpan timeout)> { (ChildProcessKind.SinMai,TimeSpan.FromSeconds(5)), (ChildProcessKind.Injector,TimeSpan.FromSeconds(10)), (ChildProcessKind.AquaDx,TimeSpan.FromSeconds(90)), (ChildProcessKind.MariaDb, TimeSpan.FromSeconds(30)), }; foreach (var (kind, timeout) in orderedProcesses) { mainControlStatusText = $"正在停止 {kind}"; var gr = childProcessControlGroups[kind]; if (gr.StateBag.IsAlive) { await gr.StateBag.StopAsync(timeout); } } btnMainControlStart.Enabled = true; btnMainControlStop.Enabled = false; mainControlStatusText = $"已停止"; mainControlStatusTextAnimation = false; } private DateTime lastCheckSinMaiMainWinPos = DateTime.Now; private DateTime lastCheckSysVolume = DateTime.Now; private void tmrMain_Tick(object sender, EventArgs e) { if (mainControlStatusTextAnimation) { txtAni.MoveNext(); lblMainControlStatus.Text = $"{mainControlStatusText} {txtAni.Current}"; } else { lblMainControlStatus.Text = $"{mainControlStatusText}"; } //窗口调整 if (chkWinTrickAuto.Checked && lastCheckSinMaiMainWinPos < DateTime.Now.AddSeconds(-5)) { lastCheckSinMaiMainWinPos = DateTime.Now; var pb = (ChildProcessStateBagForWin)childProcessControlGroups[ChildProcessKind.SinMai].StateBag; if (pb.Status == ChildProcessStatus.Ready && pb.HWndMainWindow.HasValue) { MoveSinMaiMainWindow(); } } //显示系统音量 if (lastCheckSysVolume < DateTime.Now.AddSeconds(-1)) { lastCheckSinMaiMainWinPos = DateTime.Now; var sv = SystemAudioVolumeInterop.GetVolume(); lblSysVolume.Text = sv.HasValue ? $"{sv:N2}%" : "-"; } } private void btnAimeWrite_Click(object sender, EventArgs e) { var input = cboAimeCard.Text; if (string.IsNullOrEmpty(input)) { MessageBox.Show("倒是输入aime卡号啊?"); return; } if (Directory.Exists(setting.AimeFleSinMaiDir) == false) { MessageBox.Show("找不到写aime卡文件的目录"); return; } if (Directory.Exists(setting.AimeFleSinMaiPath)) { MessageBox.Show("找不到写aime卡的文件"); return; } var aime = input.Replace("-", ""); File.WriteAllText(setting.AimeFleSinMaiPath, aime); MessageBox.Show($"已将选定的卡号已写入 aime.txt !{Environment.NewLine}在游戏登录时按回车键读卡{Environment.NewLine}(按一次回车不行就多按几次回车键直到读卡成功🙄)"); } private void btnWinTrick_Click(object sender, EventArgs e) { var pb = (ChildProcessStateBagForWin)childProcessControlGroups[ChildProcessKind.SinMai].StateBag; if (pb.Status != ChildProcessStatus.Ready || !pb.HWndMainWindow.HasValue) { MessageBox.Show("游戏启动之后再点这个按钮"); return; } var moveWindowResult = MoveSinMaiMainWindow(); switch (moveWindowResult) { case WindowInterops.MoveWindowResult.FailGet: MessageBox.Show("操作不成功:读取失败"); break; case WindowInterops.MoveWindowResult.FailSet: MessageBox.Show("操作不成功:设置失败"); break; case WindowInterops.MoveWindowResult.Success: MessageBox.Show("操作成功"); break; case WindowInterops.MoveWindowResult.NoChange: MessageBox.Show("没有改变"); break; } } private WindowInterops.MoveWindowResult MoveSinMaiMainWindow() { var pb = (ChildProcessStateBagForWin)childProcessControlGroups[ChildProcessKind.SinMai].StateBag; //WS_CLIPSIBLINGS WS_POPUPWINDOW WS_VISIBLE var rc = setting.Settings.AutoRect; return WindowInterops.MoveWindow(pb.HWndMainWindow.Value, rc.Left, rc.Top, rc.Width, rc.Height); } private void btnAimeRefresh_Click(object sender, EventArgs e) { if (childProcessControlGroups[ChildProcessKind.MariaDb].StateBag.Status != ChildProcessStatus.Ready) { MessageBox.Show("启动数据库先啊!"); return; } var conf = setting.Settings.Infra.MariaDb; var connectionString = $"Server={conf.Host};Port={conf.Port};Uid={conf.Usr};Pwd={conf.Pwd};"; using var conn = new MySqlConnection(connectionString); conn.Open(); using var cmd = conn.CreateCommand(); cmd.CommandText = "SELECT `luid` FROM `aqua`.`sega_card`"; using var dr = cmd.ExecuteReader(); cboAimeCard.Items.Clear(); while (dr.Read()) { var aimeR = dr["luid"].ToString(); var aimeH = string.Join("-", aimeR.Substring(0, 4), aimeR.Substring(4, 4), aimeR.Substring(8, 4), aimeR.Substring(12, 4), aimeR.Substring(16, 4)); cboAimeCard.Items.Add(aimeH); } MessageBox.Show($"从数据库中读取到 {cboAimeCard.Items.Count} 个卡号已载入下拉列表"); } private void btnExplorerKill_Click(object sender, EventArgs e) { MessageBox.Show("还没做!"); } private void btnExplorerRestore_Click(object sender, EventArgs e) { MessageBox.Show("还没做!"); } private void btnProcessLoopBackVirtualNic_Click(object sender, EventArgs e) { MessageBox.Show("还没做!"); } private void btnAppDataOverwrite_Click(object sender, EventArgs e) { MessageBox.Show("还没做!"); } private void btnSysVolumeDec_Click(object sender, EventArgs e) { var sv = SystemAudioVolumeInterop.GetVolume(); if (!sv.HasValue) { MessageBox.Show("无法获取当前音量"); return; } sv -= 5; if (sv < 0) sv = 0; if (SystemAudioVolumeInterop.SetVolume(sv.Value) == false) MessageBox.Show("音量调整失败"); } private void btnSysVolumeInc_Click(object sender, EventArgs e) { var sv = SystemAudioVolumeInterop.GetVolume(); if (!sv.HasValue) { MessageBox.Show("无法获取当前音量"); return; } sv += 5; if (sv > 100) sv = 100; if (SystemAudioVolumeInterop.SetVolume(sv.Value) == false) MessageBox.Show("音量调整失败"); } }