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.

295 lines
12KB

  1. using System;
  2. using System.Diagnostics;
  3. using System.Drawing;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Threading.Tasks;
  8. using System.Windows.Forms;
  9. using Microsoft.Win32;
  10. namespace TBMM
  11. {
  12. partial class MainForm
  13. {
  14. public void UpdateButton(Button button, bool enabled)
  15. {
  16. if (enabled)
  17. {
  18. if (button.ForeColor == Color.Green)
  19. button.ForeColor = Color.Lime;
  20. else if(button.ForeColor == Color.DarkRed)
  21. button.ForeColor = Color.Red;
  22. else if (button.ForeColor == Color.FromArgb(127, 65, 0))
  23. button.ForeColor = Color.DarkOrange;
  24. button.FlatAppearance.MouseOverBackColor = Color.FromArgb(0, 40, 0);
  25. button.FlatAppearance.MouseDownBackColor = Color.Green;
  26. }
  27. else
  28. {
  29. if (button.ForeColor == Color.Lime)
  30. button.ForeColor = Color.Green;
  31. else if (button.ForeColor == Color.Red)
  32. button.ForeColor = Color.DarkRed;
  33. else if (button.ForeColor == Color.DarkOrange)
  34. button.ForeColor = Color.FromArgb(127, 65, 0);
  35. button.FlatAppearance.MouseOverBackColor = Color.Black;
  36. button.FlatAppearance.MouseDownBackColor = Color.Black;
  37. }
  38. }
  39. public string GetGameFolder()
  40. {
  41. using var key =
  42. Registry.LocalMachine.OpenSubKey(
  43. @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Techblox Launcher") ??
  44. Registry.LocalMachine.OpenSubKey(
  45. @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Techblox Launcher");
  46. string launcherPath = key?.GetValue("DisplayIcon") is string launcherExecutable
  47. ? Directory.GetParent(launcherExecutable)?.FullName
  48. : null;
  49. launcherPath ??= Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
  50. "Techblox Launcher");
  51. string launcherConfig = Path.Combine(launcherPath, "launcher_settings.ini");
  52. if (!File.Exists(launcherConfig)) return null;
  53. string path = File.ReadLines(launcherConfig)
  54. .FirstOrDefault(line => line.StartsWith("133062..GAME_PATH="))
  55. ?.Substring("133062..GAME_PATH=".Length).Replace("/TBMM/", "/");
  56. if (path != null) path = (path + "StandaloneWindows64").Replace('/', Path.DirectorySeparatorChar);
  57. if (path != null && GetExe(path) != null) return path;
  58. return null;
  59. }
  60. public string SelectGameFolder()
  61. {
  62. var ofd = new OpenFileDialog();
  63. ofd.Filter = "Techblox executable|Techblox.exe|Techblox Preview executable|TechbloxPreview.exe";
  64. ofd.Title = "Game location";
  65. ofd.CheckFileExists = true;
  66. ofd.ShowDialog();
  67. return string.IsNullOrWhiteSpace(ofd.FileName) ? null : Directory.GetParent(ofd.FileName)?.FullName;
  68. }
  69. private (EventHandler, Task) CheckStartGame(string command)
  70. {
  71. var tcs = new TaskCompletionSource<object>();
  72. return ((sender, e) =>
  73. {
  74. Action act = async () =>
  75. {
  76. if (((sender as Process)?.ExitCode ?? 0) != 0)
  77. {
  78. status.Text = "Status: Patching failed";
  79. return;
  80. }
  81. var patched = CheckIfPatched();
  82. if (patched == GameState.Patched)
  83. {
  84. Process process = null;
  85. if (command != null)
  86. {
  87. await CheckModUpdatesAsync();
  88. process = Process.Start(command);
  89. }
  90. else if (Environment.OSVersion.Platform == PlatformID.Win32NT)
  91. {
  92. process = Process.Start(new ProcessStartInfo(GamePath("\\" + GetExe()))
  93. {
  94. WorkingDirectory = GamePath("\\") //Mods are only loaded if the working directory is correct
  95. });
  96. }
  97. if (process is null)
  98. throw new NullReferenceException("Game process is null");
  99. process.EnableRaisingEvents = true;
  100. process.Exited += HandleGameExit;
  101. _gameProcess = (process, true);
  102. patched = CheckIfPatched(); // Set in-game status
  103. }
  104. EndWork(patched, false);
  105. tcs.SetResult(null);
  106. };
  107. if (InvokeRequired)
  108. Invoke(act);
  109. else
  110. act();
  111. }, tcs.Task);
  112. }
  113. private void HandleGameExit(object sender, EventArgs e)
  114. {
  115. Debug.WriteLine("Handling game exit");
  116. _gameProcess = (null, false);
  117. if (InvokeMethod(CheckIfPatched) != GameState.Patched || Configuration.KeepPatched)
  118. return;
  119. InvokeMethod(() => ExecutePatcher(false)).Exited += (_, _) =>
  120. {
  121. if (InvokeMethod(CheckIfPatched) == GameState.Patched)
  122. {
  123. MessageBox.Show("Failed to unpatch game, launching through the launcher will fail because of anticheat. " +
  124. "Check the output in the panel on the right.\n\n" +
  125. "Please try starting the game again by clicking Play.", "Patcher error",
  126. MessageBoxButtons.OK, MessageBoxIcon.Error);
  127. }
  128. };
  129. }
  130. private (Process Process, bool ExitHandlerAdded) _gameProcess = (null, false);
  131. private bool CheckIfGameIsRunning()
  132. {
  133. Debug.WriteLine($"Check if game is running: {_gameProcess}");
  134. switch (_gameProcess.Process)
  135. {
  136. case { HasExited: false }:
  137. Debug.WriteLine("Game has not exited");
  138. return true;
  139. case { HasExited: true }:
  140. Debug.WriteLine("Game has seemingly exited without handling, probably as part of the exit handler");
  141. return false;
  142. default:
  143. Debug.WriteLine($"Process seems to be null: {_gameProcess}");
  144. _gameProcess = (Process.GetProcessesByName(GetExe(withExtension: false)).FirstOrDefault(), false);
  145. Debug.WriteLine($"Game process exited already, got new process object: {_gameProcess}");
  146. if (_gameProcess.Process == null) return false;
  147. if (_gameProcess.Process.HasExited)
  148. {
  149. Debug.WriteLine($"Game has exited already: {_gameProcess}");
  150. _gameProcess = (null, false);
  151. return false;
  152. }
  153. else
  154. {
  155. Debug.WriteLine($"Game is still running");
  156. if (_gameProcess.ExitHandlerAdded) return true;
  157. Debug.WriteLine("Game running and no exit handler yet, adding it");
  158. _gameProcess.Process.Exited += HandleGameExit;
  159. _gameProcess.Process.EnableRaisingEvents = true;
  160. _gameProcess.ExitHandlerAdded = true;
  161. return true;
  162. }
  163. }
  164. }
  165. private async Task CheckModUpdatesAsync()
  166. {
  167. var updatable = mods.Values.Where(mod => mod.Updatable).ToArray();
  168. if (updatable.Length == 0)
  169. return;
  170. if (MessageBox.Show("Mod update(s) available!\n\n"
  171. + updatable.Select(mod => mod.Name + " " + mod.LatestVersion).Aggregate((a, b) => a + "\n")
  172. + "\n\nDo you want to update them now? You can also update later by opening TBMM.",
  173. "Update(s) available", MessageBoxButtons.YesNo) == DialogResult.No)
  174. return;
  175. foreach (var mod in updatable)
  176. await InstallMod(mod);
  177. MessageBox.Show("Mods updated");
  178. }
  179. public WebClient GetClient()
  180. {
  181. var client = new WebClient();
  182. client.Headers.Clear();
  183. client.Headers[HttpRequestHeader.Accept] = "application/json";
  184. client.BaseAddress = "https://git.exmods.org";
  185. return client;
  186. }
  187. public T InvokeMethod<T>(Func<T> func)
  188. {
  189. if (InvokeRequired)
  190. return (T)Invoke(func);
  191. else
  192. return func();
  193. }
  194. private bool working = false;
  195. /// <summary>
  196. /// Some simple "locking", only allow one operation at a time
  197. /// </summary>
  198. /// <returns>Whether the work can begin</returns>
  199. public bool BeginWork()
  200. {
  201. if (working) return false;
  202. working = true;
  203. UpdateButton(playbtn, false);
  204. UpdateButton(installbtn, false);
  205. UpdateButton(uninstallbtn, false);
  206. UpdateButton(settingsbtn, false);
  207. return true;
  208. }
  209. public void EndWork(GameState state, bool desc = true)
  210. {
  211. working = false;
  212. if (state != GameState.InGame)
  213. UpdateButton(playbtn, true);
  214. UpdateButton(settingsbtn, true);
  215. if (desc)
  216. modlist_SelectedIndexChanged(modlist, null);
  217. }
  218. /// <summary>
  219. /// Path must start with \
  220. /// </summary>
  221. /// <param name="path"></param>
  222. /// <param name="gamepath"></param>
  223. /// <returns></returns>
  224. public string GamePath(string path, string gamepath = null)
  225. {
  226. return ((gamepath ?? Configuration.GamePath) + path).Replace('\\', Path.DirectorySeparatorChar);
  227. }
  228. public string GetExe(string path = null, bool withExtension = true)
  229. {
  230. if (File.Exists(GamePath("\\Techblox.exe", path)))
  231. return "Techblox" + (withExtension ? ".exe" : "");
  232. if (File.Exists(GamePath("\\TechbloxPreview.exe", path)))
  233. return "TechbloxPreview" + (withExtension ? ".exe" : "");
  234. return null;
  235. }
  236. private bool CheckNoExe()
  237. {
  238. return CheckNoExe(out _);
  239. }
  240. private bool CheckNoExe(out string path)
  241. {
  242. path = GetExe();
  243. if (path == null)
  244. {
  245. MessageBox.Show("Techblox not found! Set the correct path in Settings.");
  246. return true;
  247. }
  248. return false;
  249. }
  250. public DateTime GetGameVersionAsDate()
  251. {
  252. if (Configuration.GamePath == null) return default;
  253. using var fs = File.OpenRead(GamePath($"\\{GetExe(withExtension: false)}_Data\\globalgamemanagers"));
  254. using var sr = new StreamReader(fs);
  255. char[] data = new char[512];
  256. while(!sr.EndOfStream)
  257. {
  258. Array.Copy(data, 256, data, 0, 256);
  259. int read = sr.ReadBlock(data, 256, 256);
  260. for (int i = 0; i < data.Length - 11; i++)
  261. {
  262. if (data[i] == '2')
  263. {
  264. string date = new string(data, i, 11);
  265. if (date.StartsWith("202") && DateTime.TryParse(date, out var ret))
  266. return ret;
  267. }
  268. }
  269. }
  270. return default;
  271. }
  272. }
  273. }