Techblox Mod Manager / Launcher
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
7.7KB

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