@@ -49,6 +49,7 @@ | |||
this.findlog = new System.Windows.Forms.Button(); | |||
this.unpatched = new System.Windows.Forms.CheckBox(); | |||
this.modinfobox = new System.Windows.Forms.RichTextBox(); | |||
this.refreshbtn = new System.Windows.Forms.Button(); | |||
this.SuspendLayout(); | |||
// | |||
// modlist | |||
@@ -79,9 +80,9 @@ | |||
listViewItem1.Group = listViewGroup1; | |||
this.modlist.Items.AddRange(new System.Windows.Forms.ListViewItem[] { | |||
listViewItem1}); | |||
this.modlist.Location = new System.Drawing.Point(12, 12); | |||
this.modlist.Location = new System.Drawing.Point(12, 47); | |||
this.modlist.Name = "modlist"; | |||
this.modlist.Size = new System.Drawing.Size(491, 468); | |||
this.modlist.Size = new System.Drawing.Size(491, 433); | |||
this.modlist.TabIndex = 0; | |||
this.modlist.UseCompatibleStateImageBehavior = false; | |||
this.modlist.View = System.Windows.Forms.View.Details; | |||
@@ -220,12 +221,27 @@ | |||
this.modinfobox.TabIndex = 9; | |||
this.modinfobox.Text = ""; | |||
// | |||
// refreshbtn | |||
// | |||
this.refreshbtn.FlatAppearance.MouseDownBackColor = System.Drawing.Color.Green; | |||
this.refreshbtn.FlatAppearance.MouseOverBackColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(40)))), ((int)(((byte)(0))))); | |||
this.refreshbtn.FlatStyle = System.Windows.Forms.FlatStyle.Flat; | |||
this.refreshbtn.Font = new System.Drawing.Font("Microsoft Sans Serif", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(238))); | |||
this.refreshbtn.Location = new System.Drawing.Point(12, 12); | |||
this.refreshbtn.Name = "refreshbtn"; | |||
this.refreshbtn.Size = new System.Drawing.Size(81, 29); | |||
this.refreshbtn.TabIndex = 10; | |||
this.refreshbtn.Text = "Refresh"; | |||
this.refreshbtn.UseVisualStyleBackColor = true; | |||
this.refreshbtn.Click += new System.EventHandler(this.refreshbtn_Click); | |||
// | |||
// MainForm | |||
// | |||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); | |||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; | |||
this.BackColor = System.Drawing.Color.Black; | |||
this.ClientSize = new System.Drawing.Size(784, 561); | |||
this.Controls.Add(this.refreshbtn); | |||
this.Controls.Add(this.modinfobox); | |||
this.Controls.Add(this.unpatched); | |||
this.Controls.Add(this.findlog); | |||
@@ -260,6 +276,7 @@ | |||
private System.Windows.Forms.Button findlog; | |||
private System.Windows.Forms.CheckBox unpatched; | |||
private System.Windows.Forms.RichTextBox modinfobox; | |||
private System.Windows.Forms.Button refreshbtn; | |||
} | |||
} | |||
@@ -72,9 +72,7 @@ You may also want to verify the game's files by right clicking the game in Steam | |||
DeleteEmptyPluginsDir(out bool pexists, out bool dexists); | |||
if (!pexists && dexists) | |||
unpatched.Checked = true; //It will call the event but that won't do anything | |||
CheckIfPatched(); | |||
GetInstalledMods(); | |||
GetAvailableMods(); | |||
refreshbtn_Click(refreshbtn, null); | |||
} | |||
private async void playbtn_Click(object sender, EventArgs e) | |||
@@ -183,12 +181,16 @@ You may also want to verify the game's files by right clicking the game in Steam | |||
{ | |||
if (Environment.OSVersion.Platform == PlatformID.Win32NT) | |||
{ | |||
Process.Start("explorer.exe", $@"/select,{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}Low\Freejam\{GetExe().Replace(".exe", "")}\Player.log"); | |||
if (CheckNoExe(out string exe)) | |||
return; | |||
Process.Start("explorer.exe", $@"/select,{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)}Low\Freejam\{exe.Replace(".exe", "")}\Player.log"); | |||
} | |||
} | |||
private void unpatched_CheckedChanged(object sender, EventArgs e) | |||
{ //Not using the patcher's revert option because sometimes it restores the wrong files - the game can be patched without mods | |||
if (CheckNoExe()) | |||
return; | |||
CheckIfPatched(); | |||
modlist_SelectedIndexChanged(modlist, null); | |||
string plugins = GamePath("\\Plugins"); | |||
@@ -231,5 +233,13 @@ You may also want to verify the game's files by right clicking the game in Steam | |||
dexists = false; | |||
} | |||
} | |||
private void refreshbtn_Click(object sender, EventArgs e) | |||
{ | |||
CheckIfPatched(); | |||
var mods = GetInstalledMods(); | |||
GetAvailableMods(); | |||
CheckUninstalledMods(mods); | |||
} | |||
} | |||
} |
@@ -16,11 +16,8 @@ namespace GCMM | |||
public async Task InstallMod(ModInfo mod) | |||
{ | |||
if (mod.DownloadURL == null) return; | |||
if (GetExe() == null) | |||
{ | |||
MessageBox.Show("Gamecraft not found. Set the correct path in Settings."); | |||
if (CheckNoExe()) | |||
return; | |||
} | |||
if (mod.Name != "GamecraftModdingAPI") | |||
await UpdateAPI(); | |||
var tmp = Directory.CreateDirectory("temp"); | |||
@@ -140,6 +137,8 @@ namespace GCMM | |||
public async Task UpdateAPI() | |||
{ | |||
if (!mods.ContainsKey("GamecraftModdingAPI")) | |||
return; | |||
var gcmapi = mods["GamecraftModdingAPI"]; | |||
if (!gcmapi.Installed || gcmapi.Updatable) | |||
{ | |||
@@ -17,13 +17,14 @@ namespace GCMM | |||
partial class MainForm | |||
{ | |||
public void GetInstalledMods() | |||
public HashSet<string> GetInstalledMods() | |||
{ | |||
bool disabled = false; | |||
if (!Directory.Exists(GamePath("\\Plugins"))) | |||
if (Directory.Exists(GamePath("\\Plugins_Disabled"))) | |||
disabled = true; | |||
else return; | |||
else return new HashSet<string>(); | |||
var installed = new HashSet<string>(); | |||
foreach (var modPath in Directory.GetFiles(GamePath(disabled ? @"\Plugins_Disabled" : @"\Plugins"), "*.dll")) | |||
{ | |||
try | |||
@@ -31,12 +32,15 @@ namespace GCMM | |||
var an = AssemblyName.GetAssemblyName(modPath); | |||
if (an.Name == "0Harmony") continue; | |||
//Use filename to avoid differences between repository & assembly name casing | |||
AddUpdateModInList(new ModInfo { Name = Path.GetFileNameWithoutExtension(modPath), Version = an.Version, LastUpdated = File.GetLastWriteTime(modPath) }); | |||
var mod = new ModInfo { Name = Path.GetFileNameWithoutExtension(modPath), Version = an.Version, LastUpdated = File.GetLastWriteTime(modPath) }; | |||
AddUpdateModInList(mod); | |||
installed.Add(mod.Name); | |||
} | |||
catch (BadImageFormatException) | |||
{ //Not a .NET assembly | |||
} | |||
} | |||
return installed; | |||
} | |||
public async void GetAvailableMods() | |||
@@ -127,7 +131,7 @@ namespace GCMM | |||
item = modlist.Items[mod.Name]; | |||
var items = item.SubItems; | |||
omod.Author = mod.Author ?? omod.Author; | |||
omod.Version = mod.Version ?? omod.Version; | |||
omod.Version = mod.Version ?? omod.Version; //If the object comes from the dictionary then it's directly modified (uninstall) | |||
omod.LatestVersion = mod.LatestVersion ?? omod.LatestVersion; | |||
omod.LastUpdated = mod.LastUpdated == default ? omod.LastUpdated : mod.LastUpdated; | |||
omod.Description = mod.Description ?? omod.Description; | |||
@@ -152,10 +156,14 @@ namespace GCMM | |||
item.ForeColor = modlist.ForeColor; | |||
} | |||
public void RemoveModFromList(ModInfo mod) | |||
public void CheckUninstalledMods(HashSet<string> installed) | |||
{ | |||
if (mods.Remove(mod.Name)) | |||
modlist.Items.RemoveByKey(mod.Name); | |||
foreach (string name in mods.Keys.Except(installed)) | |||
{ | |||
var mod = mods[name]; | |||
mod.Version = null; | |||
AddUpdateModInList(mod); | |||
} | |||
} | |||
} | |||
} |
@@ -15,14 +15,19 @@ namespace GCMM | |||
{ | |||
partial class MainForm | |||
{ | |||
public bool? CheckIfPatched() | |||
public GameState CheckIfPatched() | |||
{ | |||
if (GetExe() == null) | |||
{ | |||
status.Text = "Status: Game not found"; | |||
return GameState.NotFound; | |||
} | |||
string pnp = "Patch && Play"; | |||
if (!File.Exists(GamePath(@"\IPA.exe"))) | |||
{ | |||
status.Text = "Status: Patcher missing\nClicking Play will install it"; | |||
playbtn.Text = pnp; | |||
return null; | |||
return GameState.NoPatcher; | |||
} | |||
string nopatch = "Status: Unpatched\nClicking Play patches it"; | |||
string gc = GetExe().Replace(".exe", ""); | |||
@@ -31,14 +36,14 @@ namespace GCMM | |||
{ | |||
status.Text = nopatch; | |||
playbtn.Text = pnp; | |||
return false; | |||
return GameState.Unpatched; | |||
} | |||
string backup = Directory.EnumerateDirectories(backups).OrderByDescending(s => s).FirstOrDefault(); | |||
if (backup == null) | |||
{ | |||
status.Text = nopatch; | |||
playbtn.Text = pnp; | |||
return false; | |||
return GameState.Unpatched; | |||
} | |||
if (File.GetLastWriteTime(GamePath($@"\{gc}_Data\Managed\Assembly-CSharp.dll")) | |||
> //If the file was updated at least 2 minutes after patching | |||
@@ -46,11 +51,11 @@ namespace GCMM | |||
{ | |||
status.Text = nopatch; | |||
playbtn.Text = pnp; | |||
return false; | |||
return GameState.Unpatched; | |||
} | |||
status.Text = "Status: " + (unpatched.Checked ? "Mods disabled" : "Patched"); | |||
playbtn.Text = "Play" + (unpatched.Checked ? " unmodded" : ""); | |||
return true; | |||
return GameState.Patched; | |||
} | |||
public async Task PatchStartGame() | |||
@@ -58,55 +63,76 @@ namespace GCMM | |||
if (!BeginWork()) return; | |||
foreach (ListViewItem item in modlist.SelectedItems) | |||
item.Selected = false; | |||
if (!CheckIfPatched().HasValue) | |||
var status = CheckIfPatched(); | |||
switch (status) | |||
{ | |||
if (MessageBox.Show("The patcher (GCIPA) is not found. It's necessary to load the mods. It will be downloaded from https://git.exmods.org/modtainers/GCIPA/releases and ran to patch the game. You can unpatch to run without mods at any time.", "Patcher download needed", MessageBoxButtons.OKCancel) | |||
== DialogResult.Cancel) | |||
{ | |||
EndWork(); | |||
case GameState.NotFound: | |||
MessageBox.Show("Gamecraft not found! Set the correct path in Settings."); | |||
EndWork(false); | |||
return; | |||
} | |||
string releases = "/api/v1/repos/modtainers/GCIPA/releases"; | |||
string url; | |||
this.status.Text = "Status: Patching..."; | |||
using (WebClient client = GetClient()) | |||
{ | |||
url = JArray.Parse(await client.DownloadStringTaskAsync(releases)).First["assets"].First["browser_download_url"].ToString(); | |||
await client.DownloadFileTaskAsync(url, "IPA.zip"); | |||
ZipFile.ExtractToDirectory("IPA.zip", Settings.Default.GamePath); | |||
} | |||
case GameState.NoPatcher: | |||
{ | |||
if (MessageBox.Show("The patcher (GCIPA) is not found. It's necessary to load the mods. It will be downloaded from https://git.exmods.org/modtainers/GCIPA/releases and ran to patch the game. You can unpatch to run without mods at any time.", "Patcher download needed", MessageBoxButtons.OKCancel) | |||
== DialogResult.Cancel) | |||
{ | |||
EndWork(); | |||
return; | |||
} | |||
string releases = "/api/v1/repos/modtainers/GCIPA/releases"; | |||
string url; | |||
this.status.Text = "Status: Patching..."; | |||
using (WebClient client = GetClient()) | |||
{ | |||
url = JArray.Parse(await client.DownloadStringTaskAsync(releases)).First["assets"].First["browser_download_url"].ToString(); | |||
await client.DownloadFileTaskAsync(url, "IPA.zip"); | |||
ZipFile.ExtractToDirectory("IPA.zip", Settings.Default.GamePath); | |||
} | |||
} | |||
break; | |||
} | |||
bool? status = CheckIfPatched(); | |||
if (!status.HasValue) //Make sure it actually worked | |||
if (status != GameState.NotFound && status != GameState.NoPatcher) | |||
status = CheckIfPatched(); | |||
switch (status) | |||
{ | |||
EndWork(false); | |||
return; | |||
} | |||
if (!status.Value) | |||
{ //TODO: Wine | |||
var psi = new ProcessStartInfo(GamePath(@"\IPA.exe"), GetExe() + " --nowait") | |||
{ | |||
UseShellExecute = false, | |||
RedirectStandardError = true, | |||
RedirectStandardOutput = true, | |||
WorkingDirectory = Settings.Default.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; | |||
process.Exited += CheckStartGame; | |||
case GameState.NoPatcher: //Make sure it actually worked | |||
EndWork(false); | |||
return; | |||
case GameState.Unpatched: | |||
{ //TODO: Wine | |||
var psi = new ProcessStartInfo(GamePath(@"\IPA.exe"), GetExe() + " --nowait") | |||
{ | |||
UseShellExecute = false, | |||
RedirectStandardError = true, | |||
RedirectStandardOutput = true, | |||
WorkingDirectory = Settings.Default.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; | |||
process.Exited += CheckStartGame; | |||
} | |||
break; | |||
case GameState.Patched: | |||
CheckStartGame(null, null); | |||
break; | |||
} | |||
else | |||
CheckStartGame(null, null); | |||
} | |||
public enum GameState | |||
{ | |||
NotFound, | |||
NoPatcher, | |||
Unpatched, | |||
Patched | |||
} | |||
} | |||
} |
@@ -76,7 +76,7 @@ namespace GCMM | |||
status.Text = "Status: Patching failed"; | |||
return; | |||
} | |||
if ((CheckIfPatched() ?? false) || unpatched.Checked) | |||
if (CheckIfPatched() == GameState.Patched || unpatched.Checked) | |||
if (Environment.OSVersion.Platform == PlatformID.Win32NT) | |||
Process.Start("steam://run/1078000/"); | |||
else | |||
@@ -144,5 +144,21 @@ namespace GCMM | |||
return "GamecraftPreview.exe"; | |||
return null; | |||
} | |||
private bool CheckNoExe() | |||
{ | |||
return CheckNoExe(out _); | |||
} | |||
private bool CheckNoExe(out string path) | |||
{ | |||
path = GetExe(); | |||
if (path == null) | |||
{ | |||
MessageBox.Show("Gamecraft not found! Set the correct path in Settings."); | |||
return true; | |||
} | |||
return false; | |||
} | |||
} | |||
} |