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.

290 lines
11KB

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