@@ -17,6 +17,7 @@ | |||
<ItemGroup> | |||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> | |||
<PackageReference Include="System.IO.Compression" Version="4.3.0" /> | |||
</ItemGroup> | |||
<ItemGroup> | |||
@@ -35,6 +36,9 @@ | |||
</ItemGroup> | |||
<ItemGroup> | |||
<None Update="modlist.txt"> | |||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> | |||
</None> | |||
<None Update="Properties\Settings.settings"> | |||
<Generator>SettingsSingleFileGenerator</Generator> | |||
<LastGenOutput>Settings.Designer.cs</LastGenOutput> | |||
@@ -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); | |||
@@ -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(); | |||
} | |||
} | |||
} |
@@ -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 | |||
} | |||
} | |||
} | |||
} |
@@ -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); | |||
} |
@@ -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) | |||
@@ -93,5 +93,29 @@ namespace GCMM | |||
client.BaseAddress = "https://git.exmods.org"; | |||
return client; | |||
} | |||
private bool working = false; | |||
/// <summary> | |||
/// Some simple "locking", only allow one operation at a time | |||
/// </summary> | |||
/// <returns>Whether the work can begin</returns> | |||
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); | |||
} | |||
} | |||
} |
@@ -12,8 +12,8 @@ namespace GCMM | |||
/// <summary> | |||
/// Can be null. | |||
/// </summary> | |||
public string Version { get; set; } | |||
public string LatestVersion { get; set; } | |||
public Version Version { get; set; } | |||
public Version LatestVersion { get; set; } | |||
/// <summary> | |||
/// Can be null. | |||
/// </summary> | |||
@@ -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); | |||
} | |||
} | |||
} | |||
} |
@@ -0,0 +1,9 @@ | |||
NGnius GamecraftRichPresenceMod | |||
SnakesOnAGame GamecraftScripting | |||
NGnius Pixi | |||
modtainers GamecraftModdingAPI | |||
ExtraCommands extracommands | |||
NorbiPeti GCDC | |||
NorbiPeti GCMC | |||
NorbiPeti BlockMod | |||
NGnius leadercraft |