using System; using System.Collections.Generic; using System.Diagnostics; using System.IO.Compression; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; using System.Windows.Forms; namespace TBMM { partial class MainForm { public GameState CheckIfPatched() => CheckIfPatched(out _); public GameState CheckIfPatched(out bool patched) { Dictionary statusTexts = new() { { GameState.NotFound, ("Game not found", "Specify the game's location in settings", "") }, { GameState.InGame, ("Game is running", "", "In-game") }, { GameState.NoPatcher, ("Patcher missing", "Clicking Play will install it", "") }, { GameState.OldPatcher, ("Patcher outdated", "nClicking play will update it", "") }, { GameState.Unpatched, ("Unpatched", "", "") }, { GameState.Patched, ("Patched", "", "") } }; void SetStatusText(GameState state, bool patched) { var (statusText, extra, play) = statusTexts[state]; if (extra.Length == 0) extra = patched ? "Cannot join online mode" : "Online mode available"; if (play.Length == 0) play = "Play modded"; status.Text = $"Status: {statusText}\n{extra}"; if (Configuration.KeepPatched) status.Text += "\nUnpatch on exit disabled"; playbtn.Text = play; } if (GetExe() == null) { patched = false; SetStatusText(GameState.NotFound, false); return GameState.NotFound; } bool gameIsRunning = CheckIfGameIsRunning(); if (gameIsRunning) { UpdateButton(playbtn, false); //Don't allow (un)installing mods if game is running UpdateButton(installbtn, false); UpdateButton(uninstallbtn, false); modlist.Enabled = false; } else { if (!working) UpdateButton(playbtn, true); modlist.Enabled = true; } GameState GetPatchedState() { if (!File.Exists(GamePath(@"\IPA.exe"))) return GameState.NoPatcher; if (gcipa.Updatable && !(gcipa.Version == new Version(1, 0, 0, 0) && gcipa.LatestVersion == new Version(4, 0, 0, 0)) && !gameIsRunning) return GameState.OldPatcher; string gc = GetExe(withExtension: false); string backups = GamePath(@"\IPA\Backups\" + gc); if (!Directory.Exists(backups)) return GameState.Unpatched; string backup = Directory.EnumerateDirectories(backups) .OrderByDescending(Directory.GetLastWriteTimeUtc).FirstOrDefault(); if (backup == null) return GameState.Unpatched; if (File.GetLastWriteTime(GamePath($@"\{gc}_Data\Managed\Assembly-CSharp.dll")) > //If the file was updated at least 2 minutes after patching Directory.GetLastWriteTime(backup).AddMinutes(2) || !File.Exists(GamePath($@"\{gc}_Data\Managed\IllusionInjector.dll"))) return GameState.Unpatched; return GameState.Patched; } var patchedState = GetPatchedState(); var finalState = gameIsRunning ? GameState.InGame : patchedState; patched = patchedState == GameState.Patched; SetStatusText(finalState, patchedState == GameState.Patched); return finalState; } public async Task PatchStartGame(string command = null) { if (!BeginWork()) return false; foreach (ListViewItem item in modlist.SelectedItems) item.Selected = false; bool? retOpenedWindowShouldStay = null; void EnsureShown(bool stay) { if (!Visible) { Show(); retOpenedWindowShouldStay = stay; TopMost = true; //It opens in the background otherwise - should be fine since it only shows for a couple seconds } } var status = CheckIfPatched(); //bool justDownloadedPatcherSoDontWarnAboutIncompatibility = false; switch (status) { case GameState.NotFound: MessageBox.Show("Techblox not found! Set the correct path in Settings."); EndWork(status, false); return retOpenedWindowShouldStay; case GameState.NoPatcher: case GameState.OldPatcher: { EnsureShown(false); if (MessageBox.Show((status == GameState.NoPatcher ? "The patcher (GCIPA) is not found. It's necessary to load the mods." : "There is a patcher update available!" ) + "\n\nIt will be downloaded from https://git.exmods.org/modtainers/GCIPA/releases and ran to patch the game. You can validate the game to restore the original game files or simply disable mods at any time.", "Patcher download needed", MessageBoxButtons.OKCancel) == DialogResult.Cancel) { EndWork(status); return retOpenedWindowShouldStay; } this.status.Text = "Status: Patching..."; int C = 0; while (gcipa.DownloadURL == null && C < 20) await Task.Delay(500); //The EnsureShown() call should download info about GCIPA if (gcipa.DownloadURL == null) { MessageBox.Show("Could not get information about GCIPA in time. Please run TBMM manually."); return retOpenedWindowShouldStay; } using (WebClient client = GetClient()) { string url = gcipa.DownloadURL; await client.DownloadFileTaskAsync(url, "IPA.zip"); using (var fs = new FileStream("IPA.zip", FileMode.Open)) using (var za = new ZipArchive(fs)) za.ExtractToDirectory(Configuration.GamePath, true); //Overwrite files that were left from a previous install of the patcher File.Delete("IPA.zip"); } } GetInstalledMods(); //Update patcher state, should be fine for this rare event status = CheckIfPatched(); break; } switch (status) { case GameState.NoPatcher: //Make sure it actually worked case GameState.OldPatcher: EndWork(status, false); return retOpenedWindowShouldStay; case GameState.Unpatched: { //TODO: Wine EnsureShown(false); var (handler, task) = CheckStartGame(command); var process = ExecutePatcher(true); process.Exited += handler; await task; } break; case GameState.Patched: { //CheckStartGame(command)(null, null); var (handler, task) = CheckStartGame(command); handler(null, null); await task; } break; } return retOpenedWindowShouldStay; } private Process ExecutePatcher(bool patch) { var psi = new ProcessStartInfo(GamePath(@"\IPA.exe"), $"{GetExe()} --nowait {(patch ? "" : "--revert")}") { UseShellExecute = false, RedirectStandardError = true, RedirectStandardOutput = true, WorkingDirectory = Configuration.GamePath, CreateNoWindow = true }; var process = Process.Start(psi); process.BeginErrorReadLine(); process.BeginOutputReadLine(); process.EnableRaisingEvents = true; modinfobox.Text = ""; DataReceivedEventHandler onoutput = (sender, e) => { Invoke((Action)(() => modinfobox.Text += e.Data + Environment.NewLine)); }; process.OutputDataReceived += onoutput; process.ErrorDataReceived += onoutput; return process; } public enum GameState { NotFound, NoPatcher, OldPatcher, Unpatched, Patched, /// /// Doesn't matter if patched, don't do anything if the game is running /// InGame } } }