From b03b63294ee37774e0e7b85a60c0fb89caa717bd Mon Sep 17 00:00:00 2001 From: NorbiPeti Date: Wed, 17 Jun 2020 15:08:22 +0200 Subject: [PATCH] Installing works, buttons are disabled while working --- GCMM/GCMM.csproj | 4 ++ GCMM/MainForm.Designer.cs | 13 +++--- GCMM/MainForm.cs | 46 ++++++++++++++++++++ GCMM/MainModInstaller.cs | 58 ++++++++++++++++++++++++++ GCMM/{MainModder.cs => MainModList.cs} | 45 +++++++++++++++----- GCMM/MainPatcher.cs | 5 --- GCMM/MainUtils.cs | 24 +++++++++++ GCMM/ModInfo.cs | 4 +- GCMM/ZipArchiveExtensions.cs | 42 +++++++++++++++++++ GCMM/modlist.txt | 9 ++++ 10 files changed, 226 insertions(+), 24 deletions(-) create mode 100644 GCMM/MainModInstaller.cs rename GCMM/{MainModder.cs => MainModList.cs} (64%) create mode 100644 GCMM/ZipArchiveExtensions.cs create mode 100644 GCMM/modlist.txt diff --git a/GCMM/GCMM.csproj b/GCMM/GCMM.csproj index ec9ebe6..8a462a8 100644 --- a/GCMM/GCMM.csproj +++ b/GCMM/GCMM.csproj @@ -17,6 +17,7 @@ + @@ -35,6 +36,9 @@ + + PreserveNewest + SettingsSingleFileGenerator Settings.Designer.cs diff --git a/GCMM/MainForm.Designer.cs b/GCMM/MainForm.Designer.cs index b9e4d73..7c090e6 100644 --- a/GCMM/MainForm.Designer.cs +++ b/GCMM/MainForm.Designer.cs @@ -38,6 +38,7 @@ System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm)); this.modlist = new System.Windows.Forms.ListView(); this.modName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.modAuthor = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.modVersion = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.modTimestamp = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.status = new System.Windows.Forms.Label(); @@ -46,7 +47,6 @@ this.modinfobox = new System.Windows.Forms.TextBox(); this.playbtn = new System.Windows.Forms.Button(); this.settingsbtn = new System.Windows.Forms.Button(); - this.modAuthor = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); this.SuspendLayout(); // // modlist @@ -90,6 +90,11 @@ this.modName.Text = "Name"; this.modName.Width = 148; // + // modAuthor + // + this.modAuthor.Text = "Author"; + this.modAuthor.Width = 142; + // // modVersion // this.modVersion.Text = "Version"; @@ -126,6 +131,7 @@ this.installbtn.TabIndex = 2; this.installbtn.Text = "Install mod"; this.installbtn.UseVisualStyleBackColor = true; + this.installbtn.Click += new System.EventHandler(this.installbtn_Click); // // uninstallbtn // @@ -186,11 +192,6 @@ this.settingsbtn.UseVisualStyleBackColor = true; this.settingsbtn.Click += new System.EventHandler(this.settingsbtn_Click); // - // modAuthor - // - this.modAuthor.Text = "Author"; - this.modAuthor.Width = 142; - // // MainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); diff --git a/GCMM/MainForm.cs b/GCMM/MainForm.cs index c8b0ef5..dc8c6dc 100644 --- a/GCMM/MainForm.cs +++ b/GCMM/MainForm.cs @@ -51,7 +51,9 @@ namespace GCMM private void playbtn_Click(object sender, EventArgs e) { if (playbtn.ForeColor == Color.Green) return; //Disabled + if (!BeginWork()) return; PatchGame(); + EndWork(); } private void settingsbtn_Click(object sender, EventArgs e) @@ -63,7 +65,51 @@ namespace GCMM private void modlist_SelectedIndexChanged(object sender, EventArgs e) { + if (working) return; + switch (modlist.SelectedItems.Count) + { + case 0: + modinfobox.Text = ""; + UpdateButton(installbtn, false); + UpdateButton(uninstallbtn, false); + break; + case 1: + default: + installbtn.Text = "Install mod"; + UpdateButton(installbtn, false); + UpdateButton(uninstallbtn, false); + foreach (ListViewItem item in modlist.SelectedItems) + { + var mod = mods[item.Name]; + if (modlist.SelectedItems.Count == 1) + modinfobox.Text = mod.Description?.Replace("\n", Environment.NewLine); + else + modinfobox.Text = modlist.SelectedItems.Count + " mods selected"; + if (mod.DownloadURL != null && !(mod.LatestVersion <= mod.Version)) + { + UpdateButton(installbtn, true); + if (mod.Version != null) + installbtn.Text = "Update mod"; + else + installbtn.Text = "Install mod"; + } + if (mod.Version != null) + UpdateButton(uninstallbtn, true); + } + break; + } + } + private async void installbtn_Click(object sender, EventArgs e) + { + if (installbtn.ForeColor == Color.Green) return; //Disabled + if (!BeginWork()) return; + foreach (ListViewItem item in modlist.SelectedItems) + { + if (item.Group.Name == "installed") continue; + await InstallMod(mods[item.Name]); + } + EndWork(); } } } diff --git a/GCMM/MainModInstaller.cs b/GCMM/MainModInstaller.cs new file mode 100644 index 0000000..5827d91 --- /dev/null +++ b/GCMM/MainModInstaller.cs @@ -0,0 +1,58 @@ +using GCMM.Properties; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace GCMM +{ + partial class MainForm + { + public async Task InstallMod(ModInfo mod) + { + if (mod.DownloadURL == null) return; + if (!File.Exists(Settings.Default.GamePath + @"\Gamecraft.exe")) + { + MessageBox.Show("Gamecraft not found. Set the correct path in Settings."); + return; + } + var tmp = Directory.CreateDirectory("temp"); + var plugins = Directory.CreateDirectory(Settings.Default.GamePath + @"\Plugins"); + string tmppath = tmp.FullName + "\\" + mod.Name; + using (var client = GetClient()) + { + await client.DownloadFileTaskAsync(mod.DownloadURL, tmppath); + string disposition = client.ResponseHeaders["Content-Disposition"]; + string filename = disposition.Substring(disposition.IndexOf("filename=") + 10).Replace("\"", ""); + if (filename.EndsWith(".dll")) + File.Move(tmppath, plugins.FullName + "\\" + filename); + else if (filename.EndsWith(".zip")) + { + bool pluginOnly = true; + using (var archive = ZipFile.OpenRead(tmppath)) + { + foreach (var entry in archive.Entries) + { + if (entry.FullName == "Plugins/") + { + pluginOnly = false; + break; + } + } + archive.ExtractToDirectory(pluginOnly ? plugins.FullName : Settings.Default.GamePath, true); + } + } + else + { + MessageBox.Show("Don't know how to install file: " + filename); + return; + } + GetInstalledMods(); //Update list + } + } + } +} diff --git a/GCMM/MainModder.cs b/GCMM/MainModList.cs similarity index 64% rename from GCMM/MainModder.cs rename to GCMM/MainModList.cs index 4401ff9..f18a80c 100644 --- a/GCMM/MainModder.cs +++ b/GCMM/MainModList.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using System.Drawing; using System.IO; using System.Linq; +using System.Net; using System.Reflection; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; @@ -23,7 +25,7 @@ namespace GCMM { var an = AssemblyName.GetAssemblyName(modPath); if (an.Name == "0Harmony") continue; - AddUpdateModInList(new ModInfo { Name = an.Name, Version = an.Version.ToString(), LastUpdated = File.GetLastWriteTime(modPath) }); + AddUpdateModInList(new ModInfo { Name = an.Name, Version = an.Version, LastUpdated = File.GetLastWriteTime(modPath) }); } catch (BadImageFormatException) { //Not a .NET assembly @@ -33,7 +35,7 @@ namespace GCMM public async void GetAvailableMods() { - string reposURL = "/api/v1/repos/search?sort=updated&order=desc"; + /*string reposURL = "/api/v1/repos/search?sort=updated&order=desc"; using (var client = GetClient()) { var obj = JObject.Parse(await client.DownloadStringTaskAsync(reposURL)); @@ -49,6 +51,17 @@ namespace GCMM if (await FetchModInfo(mod)) //If it's actually a mod AddUpdateModInList(mod); } + }*/ + foreach (string line in File.ReadLines("modlist.txt")) + { + var sp = line.Split('\t'); + var mod = new ModInfo + { + Author = sp[0], + Name = sp[1] + }; + if (await FetchModInfo(mod)) //If it's actually a mod + AddUpdateModInList(mod); } } @@ -57,17 +70,25 @@ namespace GCMM string repoURL = "/api/v1/repos/" + mod.Author + "/" + mod.Name + "/releases"; using (var client = GetClient()) { - var obj = JArray.Parse(await client.DownloadStringTaskAsync(repoURL)); - var release = obj.FirstOrDefault(rel => !(bool)rel["prerelease"] && !(bool)rel["draft"]); + var arr = JArray.Parse(await client.DownloadStringTaskAsync(repoURL)); + var release = arr.FirstOrDefault(rel => !(bool)rel["prerelease"] && !(bool)rel["draft"]); if (release == null) return false; - else - { + if (release["assets"].Count() == 1) mod.DownloadURL = release["assets"].First["browser_download_url"].ToString(); - mod.LastUpdated = (DateTime)release["published_at"]; - mod.LatestVersion = release["tag_name"].ToString().Replace("v", ""); - return true; + mod.LastUpdated = (DateTime)release["published_at"]; + var ver = release["tag_name"].ToString().Replace("v", "").Split('.').Select(str => int.Parse(str)).ToArray(); + int getver(byte i) => ver.Length > i ? ver[i] : 0; //By default it sets values not present to -1, but we need them to be 0 + mod.LatestVersion = new Version(getver(0), getver(1), getver(2), getver(3)); + try + { + var obj = JObject.Parse(await client.DownloadStringTaskAsync("/api/v1/repos/" + mod.Author + "/" + mod.Name + "/contents/README.md")); + mod.Description = Encoding.UTF8.GetString(Convert.FromBase64String(obj["content"].ToString())); + } + catch(WebException) + { //It returns a HTTP 500 if it doesn't exist... } + return true; } } @@ -84,8 +105,10 @@ namespace GCMM omod.Version = mod.Version ?? omod.Version; omod.LatestVersion = mod.LatestVersion ?? omod.LatestVersion; omod.LastUpdated = mod.LastUpdated == default ? omod.LastUpdated : mod.LastUpdated; + omod.Description = mod.Description ?? omod.Description; + omod.DownloadURL = mod.DownloadURL ?? omod.DownloadURL; items[1].Text = omod.Author ?? ""; - items[2].Text = omod.Version ?? omod.LatestVersion; + items[2].Text = (omod.Version ?? omod.LatestVersion)?.ToString(); items[3].Text = omod.LastUpdated.ToString(); item.Group = omod.Installed ? modlist.Groups["installed"] : modlist.Groups["available"]; if (mod.Version != mod.LatestVersion) @@ -94,7 +117,7 @@ namespace GCMM else { mods.Add(mod.Name, mod); - var item = new ListViewItem(new[] { mod.Name, mod.Author ?? "", mod.Version ?? mod.LatestVersion ?? "", mod.LastUpdated.ToString() }, modlist.Groups[mod.Installed ? "installed" : "available"]); + var item = new ListViewItem(new[] { mod.Name, mod.Author ?? "", (mod.Version ?? mod.LatestVersion)?.ToString() ?? "", mod.LastUpdated.ToString() }, modlist.Groups[mod.Installed ? "installed" : "available"]); item.Name = mod.Name; modlist.Items.Add(item); } diff --git a/GCMM/MainPatcher.cs b/GCMM/MainPatcher.cs index c1974de..ebda246 100644 --- a/GCMM/MainPatcher.cs +++ b/GCMM/MainPatcher.cs @@ -15,7 +15,6 @@ namespace GCMM { partial class MainForm { - public bool? CheckIfPatched() { if (!File.Exists(Settings.Default.GamePath + @"\IPA.exe")) @@ -48,10 +47,6 @@ namespace GCMM public async void PatchGame() { - UpdateButton(playbtn, false); - UpdateButton(installbtn, false); - UpdateButton(uninstallbtn, false); - UpdateButton(settingsbtn, false); if (!CheckIfPatched().HasValue) { if (MessageBox.Show("The patcher (GCIPA) is not found. It will be downloaded from https://git.exmods.org/modtainers/GCIPA/releases and ran to patch the game.", "Patcher download needed", MessageBoxButtons.OKCancel) diff --git a/GCMM/MainUtils.cs b/GCMM/MainUtils.cs index 4a59522..e9cdd1c 100644 --- a/GCMM/MainUtils.cs +++ b/GCMM/MainUtils.cs @@ -93,5 +93,29 @@ namespace GCMM client.BaseAddress = "https://git.exmods.org"; return client; } + + 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() + { + working = false; + UpdateButton(playbtn, true); + UpdateButton(settingsbtn, true); + modlist_SelectedIndexChanged(modlist, null); + } } } diff --git a/GCMM/ModInfo.cs b/GCMM/ModInfo.cs index 5206e52..9b82fbe 100644 --- a/GCMM/ModInfo.cs +++ b/GCMM/ModInfo.cs @@ -12,8 +12,8 @@ namespace GCMM /// /// Can be null. /// - public string Version { get; set; } - public string LatestVersion { get; set; } + public Version Version { get; set; } + public Version LatestVersion { get; set; } /// /// Can be null. /// diff --git a/GCMM/ZipArchiveExtensions.cs b/GCMM/ZipArchiveExtensions.cs new file mode 100644 index 0000000..e6f943a --- /dev/null +++ b/GCMM/ZipArchiveExtensions.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace GCMM +{ + public static class ZipArchiveExtensions + { //https://stackoverflow.com/a/14795752/2703239 + public static void ExtractToDirectory(this ZipArchive archive, string destinationDirectoryName, bool overwrite) + { + if (!overwrite) + { + archive.ExtractToDirectory(destinationDirectoryName); + return; + } + + DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName); + string destinationDirectoryFullPath = di.FullName; + + foreach (ZipArchiveEntry file in archive.Entries) + { + string completeFileName = Path.GetFullPath(Path.Combine(destinationDirectoryFullPath, file.FullName)); + + if (!completeFileName.StartsWith(destinationDirectoryFullPath, StringComparison.OrdinalIgnoreCase)) + { + throw new IOException("Trying to extract file outside of destination directory. See this link for more info: https://snyk.io/research/zip-slip-vulnerability"); + } + + if (file.Name == "") + {// Assuming Empty for Directory + Directory.CreateDirectory(Path.GetDirectoryName(completeFileName)); + continue; + } + file.ExtractToFile(completeFileName, true); + } + } + } +} diff --git a/GCMM/modlist.txt b/GCMM/modlist.txt new file mode 100644 index 0000000..1b4bc36 --- /dev/null +++ b/GCMM/modlist.txt @@ -0,0 +1,9 @@ +NGnius GamecraftRichPresenceMod +SnakesOnAGame GamecraftScripting +NGnius Pixi +modtainers GamecraftModdingAPI +ExtraCommands extracommands +NorbiPeti GCDC +NorbiPeti GCMC +NorbiPeti BlockMod +NGnius leadercraft