@@ -128,7 +128,9 @@ You may also want to verify the game's files in the launcher. | |||
{ | |||
if (mod.Updatable) | |||
addText("New version available! " + mod.UpdateDetails, Color.Aqua); | |||
if (mod.LastUpdated != default && mod.LastUpdated < lastGameUpdateTime) | |||
if (mod.Broken) | |||
addText("Outdated mod! It has been confirmed that the mod is broken on the current version of the game.", Color.Red); | |||
else if (mod.LastUpdated != default && mod.LastUpdated < lastGameUpdateTime) | |||
addText("Outdated mod! It may not work properly on the current version of the game.", Color.DarkOrange); | |||
if (mod.Description != null) | |||
modinfobox.AppendText(mod.Description.Replace("\n", Environment.NewLine)); | |||
@@ -66,11 +66,22 @@ namespace TBMM | |||
{ | |||
var sp = line.Split('\t'); | |||
if (sp.Length < 2) continue; | |||
DateTime updated = default; | |||
bool broken = false; | |||
if (sp.Length > 2) | |||
{ | |||
if (DateTime.TryParse(sp[2].Trim(), out var updatedAt)) | |||
updated = updatedAt; | |||
else if (sp[2].Trim().ToLower() == "broken") | |||
broken = true; | |||
} | |||
var mod = new ModInfo | |||
{ | |||
Author = sp[0].Trim(), | |||
Name = sp[1].Trim(), | |||
LastUpdated = sp.Length > 2 ? DateTime.Parse(sp[2].Trim()) : default | |||
LastUpdated = updated, | |||
Broken = broken | |||
}; | |||
if (await FetchModInfo(mod, preview, true)) //If it's actually a mod | |||
AddUpdateModInList(mod); | |||
@@ -191,6 +202,8 @@ namespace TBMM | |||
} | |||
if (mod.LatestVersion != null && mod.Version != null && mod.Version < mod.LatestVersion) | |||
item.ForeColor = Color.Blue; | |||
else if(mod.Broken) | |||
item.ForeColor = Color.Red; | |||
else if (mod.LastUpdated != default && mod.LastUpdated < lastGameUpdateTime) | |||
item.ForeColor = Color.OrangeRed; | |||
else | |||
@@ -18,7 +18,7 @@ namespace TBMM | |||
status.Text = "Status: Game not found"; | |||
return GameState.NotFound; | |||
} | |||
string pnp = "Patch && Play"; | |||
string pnp = "Play modded"; | |||
if (!File.Exists(GamePath(@"\IPA.exe"))) | |||
{ | |||
status.Text = "Status: Patcher missing\nClicking Play will install it"; | |||
@@ -31,8 +31,8 @@ namespace TBMM | |||
playbtn.Text = pnp; | |||
return GameState.OldPatcher; | |||
} | |||
string nopatch = "Status: Unpatched\nClicking Play patches it"; | |||
string gc = GetExe().Replace(".exe", ""); | |||
string nopatch = "Status: Unpatched"; | |||
string gc = GetExe(withExtension: false); | |||
string backups = GamePath(@"\IPA\Backups\" + gc); | |||
if (!Directory.Exists(backups)) | |||
{ | |||
@@ -129,26 +129,8 @@ namespace TBMM | |||
case GameState.Unpatched: | |||
{ //TODO: Wine | |||
EnsureShown(false); | |||
var psi = new ProcessStartInfo(GamePath(@"\IPA.exe"), GetExe() + " --nowait") | |||
{ | |||
UseShellExecute = false, | |||
RedirectStandardError = true, | |||
RedirectStandardOutput = true, | |||
WorkingDirectory = Configuration.GamePath, | |||
CreateNoWindow = true | |||
}; | |||
var process = Process.Start(psi); | |||
process.BeginErrorReadLine(); | |||
process.BeginOutputReadLine(); | |||
process.EnableRaisingEvents = true; | |||
modinfobox.Text = ""; | |||
DataReceivedEventHandler onoutput = (sender, e) => | |||
{ | |||
Invoke((Action)(() => modinfobox.Text += e.Data + Environment.NewLine)); | |||
}; | |||
process.OutputDataReceived += onoutput; | |||
process.ErrorDataReceived += onoutput; | |||
var (handler, task) = CheckStartGame(command); | |||
var process = ExecutePatcher(true); | |||
process.Exited += handler; | |||
await task; | |||
} | |||
@@ -165,6 +147,30 @@ namespace TBMM | |||
return retOpenedWindowShouldStay; | |||
} | |||
private Process ExecutePatcher(bool patch) | |||
{ | |||
var psi = new ProcessStartInfo(GamePath(@"\IPA.exe"), $"{GetExe()} --nowait {(patch ? "" : "--revert")}") | |||
{ | |||
UseShellExecute = false, | |||
RedirectStandardError = true, | |||
RedirectStandardOutput = true, | |||
WorkingDirectory = Configuration.GamePath, | |||
CreateNoWindow = true | |||
}; | |||
var process = Process.Start(psi); | |||
process.BeginErrorReadLine(); | |||
process.BeginOutputReadLine(); | |||
process.EnableRaisingEvents = true; | |||
modinfobox.Text = ""; | |||
DataReceivedEventHandler onoutput = (sender, e) => | |||
{ | |||
Invoke((Action)(() => modinfobox.Text += e.Data + Environment.NewLine)); | |||
}; | |||
process.OutputDataReceived += onoutput; | |||
process.ErrorDataReceived += onoutput; | |||
return process; | |||
} | |||
public enum GameState | |||
{ | |||
NotFound, | |||
@@ -73,18 +73,28 @@ namespace TBMM | |||
return; | |||
} | |||
if (CheckIfPatched() == GameState.Patched || unpatched.Checked) | |||
{ | |||
Process process = null; | |||
if (command != null) | |||
{ | |||
if (sender is Process) //Patched just now | |||
CheckCompatibilityAndDisableMods(); | |||
await CheckModUpdatesAsync(); | |||
Process.Start(command); | |||
process = Process.Start(command); | |||
} | |||
else if (Environment.OSVersion.Platform == PlatformID.Win32NT) | |||
Process.Start(new ProcessStartInfo(GamePath("\\" + GetExe())) | |||
{ | |||
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; | |||
} | |||
EndWork(false); | |||
tcs.SetResult(null); | |||
}; | |||
@@ -95,6 +105,20 @@ namespace TBMM | |||
}, tcs.Task); | |||
} | |||
private void HandleGameExit(object sender, EventArgs e) | |||
{ | |||
ExecutePatcher(false).Exited += (o, args) => | |||
{ | |||
if (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 void CheckCompatibilityAndDisableMods() | |||
{ | |||
if (!unpatched.Checked && MessageBox.Show("If the game updated just now, some mods may be incompatible or they may work just fine." + | |||
@@ -168,12 +192,12 @@ namespace TBMM | |||
return ((gamepath ?? Configuration.GamePath) + path).Replace('\\', Path.DirectorySeparatorChar); | |||
} | |||
public string GetExe(string path = null) | |||
public string GetExe(string path = null, bool withExtension = true) | |||
{ | |||
if (File.Exists(GamePath("\\Techblox.exe", path))) | |||
return "Techblox.exe"; | |||
return "Techblox" + (withExtension ? ".exe" : ""); | |||
if (File.Exists(GamePath("\\TechbloxPreview.exe", path))) | |||
return "TechbloxPreview.exe"; | |||
return "TechbloxPreview.exe" + (withExtension ? ".exe" : ""); | |||
return null; | |||
} | |||
@@ -196,7 +220,7 @@ namespace TBMM | |||
public DateTime GetGameVersionAsDate() | |||
{ | |||
if (Configuration.GamePath == null) return default; | |||
using var fs = File.OpenRead(GamePath("\\TechbloxPreview_Data\\globalgamemanagers")); | |||
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) | |||
@@ -31,5 +31,6 @@ namespace TBMM | |||
public HashSet<string> ModFiles { get; set; } | |||
public string UpdateDetails { get; set; } | |||
public bool Updatable => Version != null && LatestVersion != null && Version < LatestVersion; | |||
public bool Broken { get; set; } | |||
} | |||
} |