using System; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Net; using System.Threading.Tasks; using System.Windows.Forms; using Microsoft.Win32; namespace TBMM { partial class MainForm { public void UpdateButton(Button button, bool enabled) { if (enabled) { if (button.ForeColor == Color.Green) button.ForeColor = Color.Lime; else if(button.ForeColor == Color.DarkRed) button.ForeColor = Color.Red; else if (button.ForeColor == Color.FromArgb(127, 65, 0)) button.ForeColor = Color.DarkOrange; button.FlatAppearance.MouseOverBackColor = Color.FromArgb(0, 40, 0); button.FlatAppearance.MouseDownBackColor = Color.Green; } else { if (button.ForeColor == Color.Lime) button.ForeColor = Color.Green; else if (button.ForeColor == Color.Red) button.ForeColor = Color.DarkRed; else if (button.ForeColor == Color.DarkOrange) button.ForeColor = Color.FromArgb(127, 65, 0); button.FlatAppearance.MouseOverBackColor = Color.Black; button.FlatAppearance.MouseDownBackColor = Color.Black; } } public string GetGameFolder() { using var key = Registry.LocalMachine.OpenSubKey( @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Techblox Launcher") ?? Registry.LocalMachine.OpenSubKey( @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Techblox Launcher"); string launcherPath = key?.GetValue("DisplayIcon") is string launcherExecutable ? Directory.GetParent(launcherExecutable)?.FullName : null; launcherPath ??= Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Techblox Launcher"); string launcherConfig = Path.Combine(launcherPath, "launcher_settings.ini"); if (!File.Exists(launcherConfig)) return null; string path = File.ReadLines(launcherConfig) .FirstOrDefault(line => line.StartsWith("133062..GAME_PATH=")) ?.Substring("133062..GAME_PATH=".Length).Replace("/TBMM/", "/"); if (path != null) path = (path + "StandaloneWindows64").Replace('/', Path.DirectorySeparatorChar); if (path != null && GetExe(path) != null) return path; return null; } public string SelectGameFolder() { var ofd = new OpenFileDialog(); ofd.Filter = "Techblox executable|Techblox.exe|Techblox Preview executable|TechbloxPreview.exe"; ofd.Title = "Game location"; ofd.CheckFileExists = true; ofd.ShowDialog(); return string.IsNullOrWhiteSpace(ofd.FileName) ? null : Directory.GetParent(ofd.FileName)?.FullName; } private (EventHandler, Task) CheckStartGame(string command) { var tcs = new TaskCompletionSource(); return ((sender, e) => { Action act = async () => { if (((sender as Process)?.ExitCode ?? 0) != 0) { status.Text = "Status: Patching failed"; return; } var patched = CheckIfPatched(); if (patched == GameState.Patched) { Process process = null; if (command != null) { await CheckModUpdatesAsync(); process = Process.Start(command); } else if (Environment.OSVersion.Platform == PlatformID.Win32NT) { process = Process.Start(new ProcessStartInfo(GamePath("\\" + GetExe())) { WorkingDirectory = GamePath("\\") //Mods are only loaded if the working directory is correct }); } if (process is null) throw new NullReferenceException("Game process is null"); process.EnableRaisingEvents = true; process.Exited += HandleGameExit; _gameProcess = (process, true); patched = CheckIfPatched(); // Set in-game status } EndWork(patched, false); tcs.SetResult(null); }; if (InvokeRequired) Invoke(act); else act(); }, tcs.Task); } private void HandleGameExit(object sender, EventArgs e) { Debug.WriteLine("Handling game exit"); _gameProcess = (null, false); if (InvokeMethod(CheckIfPatched) != GameState.Patched || Configuration.KeepPatched) return; InvokeMethod(() => ExecutePatcher(false)).Exited += (_, _) => { if (InvokeMethod(CheckIfPatched) == GameState.Patched) { MessageBox.Show("Failed to unpatch game, launching through the launcher will fail because of anticheat. " + "Check the output in the panel on the right.\n\n" + "Please try starting the game again by clicking Play.", "Patcher error", MessageBoxButtons.OK, MessageBoxIcon.Error); } }; } private (Process Process, bool ExitHandlerAdded) _gameProcess = (null, false); private bool CheckIfGameIsRunning() { Debug.WriteLine($"Check if game is running: {_gameProcess}"); switch (_gameProcess.Process) { case { HasExited: false }: Debug.WriteLine("Game has not exited"); return true; case { HasExited: true }: Debug.WriteLine("Game has seemingly exited without handling, probably as part of the exit handler"); return false; default: Debug.WriteLine($"Process seems to be null: {_gameProcess}"); _gameProcess = (Process.GetProcessesByName(GetExe(withExtension: false)).FirstOrDefault(), false); Debug.WriteLine($"Game process exited already, got new process object: {_gameProcess}"); if (_gameProcess.Process == null) return false; if (_gameProcess.Process.HasExited) { Debug.WriteLine($"Game has exited already: {_gameProcess}"); _gameProcess = (null, false); return false; } else { Debug.WriteLine($"Game is still running"); if (_gameProcess.ExitHandlerAdded) return true; Debug.WriteLine("Game running and no exit handler yet, adding it"); _gameProcess.Process.Exited += HandleGameExit; _gameProcess.Process.EnableRaisingEvents = true; _gameProcess.ExitHandlerAdded = true; return true; } } } private async Task CheckModUpdatesAsync() { var updatable = mods.Values.Where(mod => mod.Updatable).ToArray(); if (updatable.Length == 0) return; if (MessageBox.Show("Mod update(s) available!\n\n" + updatable.Select(mod => mod.Name + " " + mod.LatestVersion).Aggregate((a, b) => a + "\n") + "\n\nDo you want to update them now? You can also update later by opening TBMM.", "Update(s) available", MessageBoxButtons.YesNo) == DialogResult.No) return; foreach (var mod in updatable) await InstallMod(mod); MessageBox.Show("Mods updated"); } public WebClient GetClient() { var client = new WebClient(); client.Headers.Clear(); client.Headers[HttpRequestHeader.Accept] = "application/json"; client.BaseAddress = "https://git.exmods.org"; return client; } public T InvokeMethod(Func func) { if (InvokeRequired) return (T)Invoke(func); else return func(); } private bool working = false; /// /// Some simple "locking", only allow one operation at a time /// /// Whether the work can begin public bool BeginWork() { if (working) return false; working = true; UpdateButton(playbtn, false); UpdateButton(installbtn, false); UpdateButton(uninstallbtn, false); UpdateButton(settingsbtn, false); return true; } public void EndWork(GameState state, bool desc = true) { working = false; if (state != GameState.InGame) UpdateButton(playbtn, true); UpdateButton(settingsbtn, true); if (desc) modlist_SelectedIndexChanged(modlist, null); } /// /// Path must start with \ /// /// /// /// public string GamePath(string path, string gamepath = null) { return ((gamepath ?? Configuration.GamePath) + path).Replace('\\', Path.DirectorySeparatorChar); } public string GetExe(string path = null, bool withExtension = true) { if (File.Exists(GamePath("\\Techblox.exe", path))) return "Techblox" + (withExtension ? ".exe" : ""); if (File.Exists(GamePath("\\TechbloxPreview.exe", path))) return "TechbloxPreview" + (withExtension ? ".exe" : ""); return null; } private bool CheckNoExe() { return CheckNoExe(out _); } private bool CheckNoExe(out string path) { path = GetExe(); if (path == null) { MessageBox.Show("Techblox not found! Set the correct path in Settings."); return true; } return false; } public DateTime GetGameVersionAsDate() { if (Configuration.GamePath == null) return default; using var fs = File.OpenRead(GamePath($"\\{GetExe(withExtension: false)}_Data\\globalgamemanagers")); using var sr = new StreamReader(fs); char[] data = new char[512]; while(!sr.EndOfStream) { Array.Copy(data, 256, data, 0, 256); int read = sr.ReadBlock(data, 256, 256); for (int i = 0; i < data.Length - 11; i++) { if (data[i] == '2') { string date = new string(data, i, 11); if (date.StartsWith("202") && DateTime.TryParse(date, out var ret)) return ret; } } } return default; } } }