Techblox Mod Manager / Launcher
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.

209 satır
9.1KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO.Compression;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Net;
  8. using System.Threading.Tasks;
  9. using System.Windows.Forms;
  10. namespace TBMM
  11. {
  12. partial class MainForm
  13. {
  14. public GameState CheckIfPatched()
  15. {
  16. Dictionary<GameState, (string Status, string Extra, string Play)> statusTexts = new()
  17. {
  18. { GameState.NotFound, ("Game not found", "Specify the game's location in settings", "") },
  19. { GameState.InGame, ("Game is running", "", "In-game") },
  20. { GameState.NoPatcher, ("Patcher missing", "Clicking Play will install it", "") },
  21. { GameState.OldPatcher, ("Patcher outdated", "nClicking play will update it", "") },
  22. { GameState.Unpatched, ("Unpatched", "", "") },
  23. { GameState.Patched, ("Patched", "", "")}
  24. };
  25. void SetStatusText(GameState state, bool patched)
  26. {
  27. var (statusText, extra, play) = statusTexts[state];
  28. if (extra.Length == 0) extra = patched ? "Cannot join online mode" : "Online mode available";
  29. if (play.Length == 0) play = "Play modded";
  30. status.Text = $"Status: {statusText}\n{extra}";
  31. playbtn.Text = play;
  32. }
  33. if (GetExe() == null)
  34. {
  35. SetStatusText(GameState.NotFound, false);
  36. return GameState.NotFound;
  37. }
  38. bool gameIsRunning = CheckIfGameIsRunning();
  39. if (gameIsRunning)
  40. {
  41. UpdateButton(playbtn, false); //Don't allow (un)installing mods if game is running
  42. UpdateButton(installbtn, false);
  43. UpdateButton(uninstallbtn, false);
  44. modlist.Enabled = false;
  45. }
  46. else
  47. {
  48. if (!working) UpdateButton(playbtn, true);
  49. modlist.Enabled = true;
  50. }
  51. GameState GetPatchedState()
  52. {
  53. if (!File.Exists(GamePath(@"\IPA.exe")))
  54. return GameState.NoPatcher;
  55. if (gcipa.Updatable && !(gcipa.Version == new Version(1, 0, 0, 0) &&
  56. gcipa.LatestVersion == new Version(4, 0, 0, 0))
  57. && !gameIsRunning)
  58. return GameState.OldPatcher;
  59. string gc = GetExe(withExtension: false);
  60. string backups = GamePath(@"\IPA\Backups\" + gc);
  61. if (!Directory.Exists(backups))
  62. return GameState.Unpatched;
  63. string backup = Directory.EnumerateDirectories(backups)
  64. .OrderByDescending(Directory.GetLastWriteTimeUtc).FirstOrDefault();
  65. if (backup == null)
  66. return GameState.Unpatched;
  67. if (File.GetLastWriteTime(GamePath($@"\{gc}_Data\Managed\Assembly-CSharp.dll"))
  68. > //If the file was updated at least 2 minutes after patching
  69. Directory.GetLastWriteTime(backup).AddMinutes(2)
  70. || !File.Exists(GamePath($@"\{gc}_Data\Managed\IllusionInjector.dll")))
  71. return GameState.Unpatched;
  72. return GameState.Patched;
  73. }
  74. var patchedState = GetPatchedState();
  75. var finalState = gameIsRunning ? GameState.InGame : patchedState;
  76. SetStatusText(finalState, patchedState == GameState.Patched);
  77. return finalState;
  78. }
  79. public async Task<bool?> PatchStartGame(string command = null)
  80. {
  81. if (!BeginWork()) return false;
  82. foreach (ListViewItem item in modlist.SelectedItems)
  83. item.Selected = false;
  84. bool? retOpenedWindowShouldStay = null;
  85. void EnsureShown(bool stay)
  86. {
  87. if (!Visible)
  88. {
  89. Show();
  90. retOpenedWindowShouldStay = stay;
  91. TopMost = true; //It opens in the background otherwise - should be fine since it only shows for a couple seconds
  92. }
  93. }
  94. var status = CheckIfPatched();
  95. //bool justDownloadedPatcherSoDontWarnAboutIncompatibility = false;
  96. switch (status)
  97. {
  98. case GameState.NotFound:
  99. MessageBox.Show("Techblox not found! Set the correct path in Settings.");
  100. EndWork(status, false);
  101. return retOpenedWindowShouldStay;
  102. case GameState.NoPatcher:
  103. case GameState.OldPatcher:
  104. {
  105. EnsureShown(false);
  106. if (MessageBox.Show((status == GameState.NoPatcher
  107. ? "The patcher (GCIPA) is not found. It's necessary to load the mods."
  108. : "There is a patcher update available!"
  109. ) + "\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.",
  110. "Patcher download needed", MessageBoxButtons.OKCancel) == DialogResult.Cancel)
  111. {
  112. EndWork(status);
  113. return retOpenedWindowShouldStay;
  114. }
  115. this.status.Text = "Status: Patching...";
  116. int C = 0;
  117. while (gcipa.DownloadURL == null && C < 20)
  118. await Task.Delay(500); //The EnsureShown() call should download info about GCIPA
  119. if (gcipa.DownloadURL == null)
  120. {
  121. MessageBox.Show("Could not get information about GCIPA in time. Please run TBMM manually.");
  122. return retOpenedWindowShouldStay;
  123. }
  124. using (WebClient client = GetClient())
  125. {
  126. string url = gcipa.DownloadURL;
  127. await client.DownloadFileTaskAsync(url, "IPA.zip");
  128. using (var fs = new FileStream("IPA.zip", FileMode.Open))
  129. using (var za = new ZipArchive(fs))
  130. za.ExtractToDirectory(Configuration.GamePath, true); //Overwrite files that were left from a previous install of the patcher
  131. File.Delete("IPA.zip");
  132. }
  133. }
  134. GetInstalledMods(); //Update patcher state, should be fine for this rare event
  135. status = CheckIfPatched();
  136. break;
  137. }
  138. switch (status)
  139. {
  140. case GameState.NoPatcher: //Make sure it actually worked
  141. case GameState.OldPatcher:
  142. EndWork(status, false);
  143. return retOpenedWindowShouldStay;
  144. case GameState.Unpatched:
  145. { //TODO: Wine
  146. EnsureShown(false);
  147. var (handler, task) = CheckStartGame(command);
  148. var process = ExecutePatcher(true);
  149. process.Exited += handler;
  150. await task;
  151. }
  152. break;
  153. case GameState.Patched:
  154. {
  155. //CheckStartGame(command)(null, null);
  156. var (handler, task) = CheckStartGame(command);
  157. handler(null, null);
  158. await task;
  159. }
  160. break;
  161. }
  162. return retOpenedWindowShouldStay;
  163. }
  164. private Process ExecutePatcher(bool patch)
  165. {
  166. var psi = new ProcessStartInfo(GamePath(@"\IPA.exe"), $"{GetExe()} --nowait {(patch ? "" : "--revert")}")
  167. {
  168. UseShellExecute = false,
  169. RedirectStandardError = true,
  170. RedirectStandardOutput = true,
  171. WorkingDirectory = Configuration.GamePath,
  172. CreateNoWindow = true
  173. };
  174. var process = Process.Start(psi);
  175. process.BeginErrorReadLine();
  176. process.BeginOutputReadLine();
  177. process.EnableRaisingEvents = true;
  178. modinfobox.Text = "";
  179. DataReceivedEventHandler onoutput = (sender, e) =>
  180. {
  181. Invoke((Action)(() => modinfobox.Text += e.Data + Environment.NewLine));
  182. };
  183. process.OutputDataReceived += onoutput;
  184. process.ErrorDataReceived += onoutput;
  185. return process;
  186. }
  187. public enum GameState
  188. {
  189. NotFound,
  190. NoPatcher,
  191. OldPatcher,
  192. Unpatched,
  193. Patched,
  194. /// <summary>
  195. /// Doesn't matter if patched, don't do anything if the game is running
  196. /// </summary>
  197. InGame
  198. }
  199. }
  200. }