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.

332 lines
13KB

  1. using GCMM.Properties;
  2. using Microsoft.Win32;
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Diagnostics;
  6. using System.Drawing;
  7. using System.IO;
  8. using System.Linq;
  9. using System.Net;
  10. using System.Text;
  11. using System.Text.RegularExpressions;
  12. using System.Threading.Tasks;
  13. using System.Windows.Forms;
  14. namespace GCMM
  15. {
  16. partial class MainForm
  17. {
  18. public void UpdateButton(Button button, bool enabled)
  19. {
  20. if (enabled)
  21. {
  22. button.ForeColor = Color.Lime;
  23. button.FlatAppearance.MouseOverBackColor = Color.FromArgb(0, 40, 0);
  24. button.FlatAppearance.MouseDownBackColor = Color.Green;
  25. }
  26. else
  27. {
  28. button.ForeColor = Color.Green;
  29. button.FlatAppearance.MouseOverBackColor = Color.Black;
  30. button.FlatAppearance.MouseDownBackColor = Color.Black;
  31. }
  32. }
  33. public string GetGameFolder()
  34. {
  35. string libs;
  36. if (Environment.OSVersion.Platform == PlatformID.Win32NT)
  37. libs = steamPath + @"\steamapps\libraryfolders.vdf";
  38. else
  39. return null;
  40. foreach (var line in File.ReadAllLines(libs).Concat(new[] { "\t\"19\"\t\t\"" + steamPath + "\"" }))
  41. {
  42. var regex = new Regex("\\t\"\\d+\"\\t\\t\"(.+)\"");
  43. var match = regex.Match(line);
  44. if (!match.Success)
  45. continue;
  46. string library = match.Groups[1].Value.Replace("\\\\", "\\");
  47. library += @"\steamapps\common\";
  48. if (GetExe(library + "Gamecraft") != null)
  49. return library + "Gamecraft";
  50. if (GetExe(library + "RobocraftX") != null)
  51. return library + "RobocraftX";
  52. }
  53. return null;
  54. }
  55. public string SelectGameFolder()
  56. {
  57. var ofd = new OpenFileDialog();
  58. ofd.Filter = "Gamecraft executable|Gamecraft.exe|Gamecraft Preview executable|GamecraftPreview.exe";
  59. ofd.Title = "Game location";
  60. ofd.InitialDirectory = steamPath + @"\steamapps\common\";
  61. ofd.CheckFileExists = true;
  62. ofd.ShowDialog();
  63. if (string.IsNullOrWhiteSpace(ofd.FileName))
  64. return null;
  65. return Directory.GetParent(ofd.FileName).FullName;
  66. }
  67. public (string, int) AskForSteamLogin()
  68. {
  69. while (MessageBox.Show("Couid not find your Steam configuration to set launch options.\n\n" +
  70. "Please make sure you are logged into Steam and click Retry or click Cancel to skip setting this up.",
  71. "Steam config not found", MessageBoxButtons.RetryCancel) != DialogResult.Cancel)
  72. {
  73. var ret = GetSteamLocationAndUser();
  74. if (ret != (null, 0))
  75. return ret;
  76. }
  77. return (null, 0);
  78. }
  79. /// <summary>
  80. /// Does not return the current value if setting it is not possible because Steam is running.
  81. /// </summary>
  82. /// <param name="autoLaunch">The value to set or null to keep as is</param>
  83. /// <returns>The current value, unless setting it while Steam is running</returns>
  84. public bool UpdateOrGetSteamConfigToAutoStart(bool? autoLaunch)
  85. {
  86. string commandToUse = Application.ExecutablePath + " -start %command%";
  87. if (autoLaunch.HasValue && Process.GetProcessesByName("steam").Length > 0)
  88. { //Setting it while Steam is running
  89. if (MessageBox.Show("Cannot set launch options while Steam is running." +
  90. (autoLaunch.Value
  91. ? " Do you want to do it manually? If so, it will be copied on your clipboard." +
  92. " Right click the game, select Properties and paste it in the launch options field."
  93. : " Do you want to do it manually?" +
  94. " If so, right click the game, select Properties and remove the text from the launch options field."),
  95. "Launch options in Steam", MessageBoxButtons.YesNo) == DialogResult.Yes && autoLaunch.Value)
  96. Clipboard.SetText(commandToUse);
  97. return false;
  98. }
  99. var regex = new Regex(@"(\t{6}""LaunchOptions""\t+"")(.*)("")");
  100. string path = steamPath + @"\userdata\" + Settings.Default.SteamUserID + @"\config\localconfig.vdf";
  101. var lines = File.ReadAllLines(path);
  102. bool shouldMatch = false;
  103. bool ret = false;
  104. for (int i = 0; i < lines.Length; i++)
  105. {
  106. if (lines[i] == "\t\t\t\t\t\"1078000\"")
  107. shouldMatch = true; //Found the game
  108. else if(shouldMatch)
  109. {
  110. var match = regex.Match(lines[i]);
  111. if (!match.Success)
  112. continue;
  113. ret = match.Groups[2].Value.Contains("GCMM.exe");
  114. string enabledCommand = match.Groups[1].Value + commandToUse.Replace("\\", "\\\\") + match.Groups[3].Value;
  115. if (autoLaunch.HasValue)
  116. {
  117. if (autoLaunch.Value)
  118. lines[i] = enabledCommand;
  119. else
  120. lines[i] = match.Groups[1].Value + match.Groups[3].Value;
  121. File.WriteAllLines(path, lines);
  122. }
  123. else if (ret && lines[i] != enabledCommand) //GCMM got moved or something and it's only queried, not set
  124. {
  125. lines[i] = enabledCommand;
  126. File.WriteAllLines(path, lines);
  127. }
  128. break;
  129. }
  130. }
  131. return ret;
  132. }
  133. public (string, int) GetSteamLocationAndUser()
  134. {
  135. if (Environment.OSVersion.Platform != PlatformID.Win32NT) return (null, 0);
  136. using (var key = Registry.CurrentUser.OpenSubKey(@"Software\Valve\Steam\ActiveProcess"))
  137. {
  138. string path = (string)key?.GetValue("SteamClientDll");
  139. path = path != null ? Directory.GetParent(path).FullName : null;
  140. return (path, (int)(key?.GetValue("ActiveUser") ?? 0));
  141. }
  142. }
  143. public void DetectConfigLocationAndAutoStart(string steamPath, ref int user)
  144. {
  145. string path = steamPath + @"\userdata";
  146. if (user == 0)
  147. {
  148. var dirs = Directory.GetDirectories(path);
  149. var goodPaths = (from dir in dirs
  150. where File.Exists(dir + @"\config\localconfig.vdf")
  151. select dir).ToArray();
  152. if (goodPaths.Length != 1)
  153. {
  154. (_, user) = AskForSteamLogin();
  155. path += user;
  156. }
  157. else
  158. {
  159. path = goodPaths[0];
  160. user = int.Parse(Path.GetFileName(path));
  161. }
  162. }
  163. else
  164. path += "\\" + user;
  165. path += @"\config\localconfig.vdf";
  166. if (path != null && user != 0 && File.Exists(path))
  167. {
  168. Settings.Default.SteamUserID = user;
  169. UpdateOrGetSteamConfigToAutoStart(true);
  170. }
  171. else
  172. Settings.Default.AutoLaunch = false;
  173. }
  174. private (EventHandler, Task) CheckStartGame(string command)
  175. {
  176. var tcs = new TaskCompletionSource<object>();
  177. return ((sender, e) =>
  178. {
  179. Action act = async () =>
  180. {
  181. if (((sender as Process)?.ExitCode ?? 0) != 0)
  182. {
  183. status.Text = "Status: Patching failed";
  184. return;
  185. }
  186. if (CheckIfPatched() == GameState.Patched || unpatched.Checked)
  187. if (command != null)
  188. {
  189. if (sender is Process) //Patched just now
  190. CheckCompatibilityAndDisableMods();
  191. await CheckModUpdatesAsync();
  192. Process.Start(command);
  193. }
  194. else if (Environment.OSVersion.Platform == PlatformID.Win32NT)
  195. Process.Start("steam://run/1078000/");
  196. else
  197. Process.Start("xdg-open", "steam://run/1078000/");
  198. EndWork(false);
  199. tcs.SetResult(null);
  200. };
  201. if (InvokeRequired)
  202. Invoke(act);
  203. else
  204. act();
  205. }, tcs.Task);
  206. }
  207. private void CheckCompatibilityAndDisableMods()
  208. {
  209. if (!unpatched.Checked && MessageBox.Show("If the game updated just now, some mods may be incompatible or they may work just fine." +
  210. " Do you want to try running with mods?" +
  211. "\n\nClick Yes to start the game with mods (after a small update or if you just installed GCMM)" +
  212. "\nClick No to disable mods before starting the game (after a major update)" +
  213. "\n\nYou can always enable/disable mods by launching GCMM.",
  214. "Possible incompatibility warning", MessageBoxButtons.YesNo) == DialogResult.No)
  215. unpatched.Checked = true;
  216. }
  217. private async Task CheckModUpdatesAsync()
  218. {
  219. var updatable = mods.Values.Where(mod => mod.Updatable).ToArray();
  220. if (updatable.Length == 0)
  221. return;
  222. if (MessageBox.Show("Mod update(s) available!\n\n"
  223. + updatable.Select(mod => mod.Name + " " + mod.LatestVersion).Aggregate((a, b) => a + "\n")
  224. + "\n\nDo you want to update them now? You can also update later by opening GCMM.",
  225. "Update(s) available", MessageBoxButtons.YesNo) == DialogResult.No)
  226. return;
  227. foreach (var mod in updatable)
  228. await InstallMod(mod);
  229. MessageBox.Show("Mods updated");
  230. }
  231. public WebClient GetClient()
  232. {
  233. var client = new WebClient();
  234. if (!Settings.Default.UseProxy)
  235. client.Proxy = null;
  236. client.Headers.Clear();
  237. client.Headers[HttpRequestHeader.Accept] = "application/json";
  238. client.BaseAddress = "https://git.exmods.org";
  239. return client;
  240. }
  241. private bool working = false;
  242. /// <summary>
  243. /// Some simple "locking", only allow one operation at a time
  244. /// </summary>
  245. /// <returns>Whether the work can begin</returns>
  246. public bool BeginWork()
  247. {
  248. if (working) return false;
  249. working = true;
  250. UpdateButton(playbtn, false);
  251. UpdateButton(installbtn, false);
  252. UpdateButton(uninstallbtn, false);
  253. UpdateButton(settingsbtn, false);
  254. unpatched.Enabled = false;
  255. return true;
  256. }
  257. public void EndWork(bool desc = true)
  258. {
  259. working = false;
  260. UpdateButton(playbtn, true);
  261. UpdateButton(settingsbtn, true);
  262. if (desc)
  263. modlist_SelectedIndexChanged(modlist, null);
  264. unpatched.Enabled = true;
  265. }
  266. /// <summary>
  267. /// Path must start with \
  268. /// </summary>
  269. /// <param name="path"></param>
  270. /// <param name="gamepath"></param>
  271. /// <returns></returns>
  272. public string GamePath(string path, string gamepath = null)
  273. {
  274. return ((gamepath ?? Settings.Default.GamePath) + path).Replace('\\', Path.DirectorySeparatorChar);
  275. }
  276. public string GetExe(string path = null)
  277. {
  278. if (File.Exists(GamePath("\\Gamecraft.exe", path)))
  279. return "Gamecraft.exe";
  280. if (File.Exists(GamePath("\\GamecraftPreview.exe", path)))
  281. return "GamecraftPreview.exe";
  282. return null;
  283. }
  284. private bool CheckNoExe()
  285. {
  286. return CheckNoExe(out _);
  287. }
  288. private bool CheckNoExe(out string path)
  289. {
  290. path = GetExe();
  291. if (path == null)
  292. {
  293. MessageBox.Show("Gamecraft not found! Set the correct path in Settings.");
  294. return true;
  295. }
  296. return false;
  297. }
  298. public async Task<DateTime> GetLastGameUpdateTime()
  299. {
  300. /*using (var client = GetClient())
  301. {
  302. string html = await client.DownloadStringTaskAsync("https://api.steamcmd.net/v1/info/1078000");
  303. var regex = new Regex("<i>timeupdated:</i>[^<]*<b>([^<]*)</b>");
  304. var match = regex.Match(html);
  305. if (!match.Success)
  306. return default;
  307. return new DateTime(1970, 1, 1).AddSeconds(long.Parse(match.Groups[1].Value));
  308. }*/
  309. //return new DateTime(2020, 12, 28);
  310. return default;
  311. }
  312. }
  313. }