File tracking and uninstall
A list of files belonging to a mod is automatically saved
This commit is contained in:
parent
b03b63294e
commit
d219f02a80
5 changed files with 108 additions and 21 deletions
27
GCMM/MainForm.Designer.cs
generated
27
GCMM/MainForm.Designer.cs
generated
|
@ -28,9 +28,9 @@
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void InitializeComponent()
|
private void InitializeComponent()
|
||||||
{
|
{
|
||||||
System.Windows.Forms.ListViewGroup listViewGroup1 = new System.Windows.Forms.ListViewGroup("Installed", System.Windows.Forms.HorizontalAlignment.Center);
|
System.Windows.Forms.ListViewGroup listViewGroup3 = new System.Windows.Forms.ListViewGroup("Installed", System.Windows.Forms.HorizontalAlignment.Center);
|
||||||
System.Windows.Forms.ListViewGroup listViewGroup2 = new System.Windows.Forms.ListViewGroup("Available", System.Windows.Forms.HorizontalAlignment.Center);
|
System.Windows.Forms.ListViewGroup listViewGroup4 = new System.Windows.Forms.ListViewGroup("Available", System.Windows.Forms.HorizontalAlignment.Center);
|
||||||
System.Windows.Forms.ListViewItem listViewItem1 = new System.Windows.Forms.ListViewItem(new string[] {
|
System.Windows.Forms.ListViewItem listViewItem2 = new System.Windows.Forms.ListViewItem(new string[] {
|
||||||
"Mod",
|
"Mod",
|
||||||
"modtainers",
|
"modtainers",
|
||||||
"1.0",
|
"1.0",
|
||||||
|
@ -63,20 +63,20 @@
|
||||||
this.modTimestamp});
|
this.modTimestamp});
|
||||||
this.modlist.ForeColor = System.Drawing.Color.Green;
|
this.modlist.ForeColor = System.Drawing.Color.Green;
|
||||||
this.modlist.FullRowSelect = true;
|
this.modlist.FullRowSelect = true;
|
||||||
listViewGroup1.Header = "Installed";
|
listViewGroup3.Header = "Installed";
|
||||||
listViewGroup1.HeaderAlignment = System.Windows.Forms.HorizontalAlignment.Center;
|
listViewGroup3.HeaderAlignment = System.Windows.Forms.HorizontalAlignment.Center;
|
||||||
listViewGroup1.Name = "installed";
|
listViewGroup3.Name = "installed";
|
||||||
listViewGroup2.Header = "Available";
|
listViewGroup4.Header = "Available";
|
||||||
listViewGroup2.HeaderAlignment = System.Windows.Forms.HorizontalAlignment.Center;
|
listViewGroup4.HeaderAlignment = System.Windows.Forms.HorizontalAlignment.Center;
|
||||||
listViewGroup2.Name = "available";
|
listViewGroup4.Name = "available";
|
||||||
this.modlist.Groups.AddRange(new System.Windows.Forms.ListViewGroup[] {
|
this.modlist.Groups.AddRange(new System.Windows.Forms.ListViewGroup[] {
|
||||||
listViewGroup1,
|
listViewGroup3,
|
||||||
listViewGroup2});
|
listViewGroup4});
|
||||||
this.modlist.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
|
this.modlist.HeaderStyle = System.Windows.Forms.ColumnHeaderStyle.Nonclickable;
|
||||||
this.modlist.HideSelection = false;
|
this.modlist.HideSelection = false;
|
||||||
listViewItem1.Group = listViewGroup1;
|
listViewItem2.Group = listViewGroup3;
|
||||||
this.modlist.Items.AddRange(new System.Windows.Forms.ListViewItem[] {
|
this.modlist.Items.AddRange(new System.Windows.Forms.ListViewItem[] {
|
||||||
listViewItem1});
|
listViewItem2});
|
||||||
this.modlist.Location = new System.Drawing.Point(12, 12);
|
this.modlist.Location = new System.Drawing.Point(12, 12);
|
||||||
this.modlist.Name = "modlist";
|
this.modlist.Name = "modlist";
|
||||||
this.modlist.Size = new System.Drawing.Size(491, 468);
|
this.modlist.Size = new System.Drawing.Size(491, 468);
|
||||||
|
@ -147,6 +147,7 @@
|
||||||
this.uninstallbtn.TabIndex = 3;
|
this.uninstallbtn.TabIndex = 3;
|
||||||
this.uninstallbtn.Text = "Uninstall mod";
|
this.uninstallbtn.Text = "Uninstall mod";
|
||||||
this.uninstallbtn.UseVisualStyleBackColor = true;
|
this.uninstallbtn.UseVisualStyleBackColor = true;
|
||||||
|
this.uninstallbtn.Click += new System.EventHandler(this.uninstallbtn_Click);
|
||||||
//
|
//
|
||||||
// modinfobox
|
// modinfobox
|
||||||
//
|
//
|
||||||
|
|
|
@ -111,5 +111,16 @@ namespace GCMM
|
||||||
}
|
}
|
||||||
EndWork();
|
EndWork();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void uninstallbtn_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (uninstallbtn.ForeColor == Color.Green) return; //Disabled
|
||||||
|
foreach (ListViewItem item in modlist.SelectedItems)
|
||||||
|
{
|
||||||
|
if (item.Group.Name != "installed") continue;
|
||||||
|
UninstallMod(mods[item.Name]);
|
||||||
|
}
|
||||||
|
EndWork(); //Update button states
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using GCMM.Properties;
|
using GCMM.Properties;
|
||||||
|
using Newtonsoft.Json;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -29,22 +30,27 @@ namespace GCMM
|
||||||
string disposition = client.ResponseHeaders["Content-Disposition"];
|
string disposition = client.ResponseHeaders["Content-Disposition"];
|
||||||
string filename = disposition.Substring(disposition.IndexOf("filename=") + 10).Replace("\"", "");
|
string filename = disposition.Substring(disposition.IndexOf("filename=") + 10).Replace("\"", "");
|
||||||
if (filename.EndsWith(".dll"))
|
if (filename.EndsWith(".dll"))
|
||||||
File.Move(tmppath, plugins.FullName + "\\" + filename);
|
File.Move(tmppath, plugins.FullName + "\\" + mod.Name + ".dll"); //Force mod name to make uninstalls & identifying easier
|
||||||
else if (filename.EndsWith(".zip"))
|
else if (filename.EndsWith(".zip"))
|
||||||
{
|
{
|
||||||
bool pluginOnly = true;
|
bool pluginOnly = true;
|
||||||
using (var archive = ZipFile.OpenRead(tmppath))
|
using (var archive = ZipFile.OpenRead(tmppath))
|
||||||
{
|
{
|
||||||
|
bool modFound = false;
|
||||||
foreach (var entry in archive.Entries)
|
foreach (var entry in archive.Entries)
|
||||||
{
|
{
|
||||||
if (entry.FullName == "Plugins/")
|
if (entry.FullName == "Plugins/")
|
||||||
{
|
|
||||||
pluginOnly = false;
|
pluginOnly = false;
|
||||||
break;
|
if (entry.FullName == "Plugins/" + mod.Name + ".dll")
|
||||||
|
modFound = true;
|
||||||
|
if (!pluginOnly && modFound) break;
|
||||||
}
|
}
|
||||||
|
if (!modFound && !pluginOnly)
|
||||||
|
if (MessageBox.Show("The mod was not found in the downloaded archive. It likely means it's using a different name for the dll file. The mod manager will not be able to detect this mod as installed if you continue. Do you want to continue?", "Mod not found in archive", MessageBoxButtons.YesNo) == DialogResult.No)
|
||||||
|
return;
|
||||||
|
ExtractMod(archive, pluginOnly ? plugins.FullName : Settings.Default.GamePath, mod);
|
||||||
}
|
}
|
||||||
archive.ExtractToDirectory(pluginOnly ? plugins.FullName : Settings.Default.GamePath, true);
|
File.Delete(tmppath);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -54,5 +60,72 @@ namespace GCMM
|
||||||
GetInstalledMods(); //Update list
|
GetInstalledMods(); //Update list
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ExtractMod(ZipArchive archive, string destinationDirectoryName, ModInfo mod)
|
||||||
|
{
|
||||||
|
LoadFileList(mod);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(completeFileName) && !mod.ModFiles.Contains(completeFileName))
|
||||||
|
{ //If it's part of the mod files, then it's an update and it didn't originally exist in the game
|
||||||
|
MessageBox.Show("The mod zip contains a file that exists as part of the game. Skipping file: " + file.FullName);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
mod.ModFiles.Add(completeFileName);
|
||||||
|
file.ExtractToFile(completeFileName, true);
|
||||||
|
}
|
||||||
|
SaveFileList(mod);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SaveFileList(ModInfo mod)
|
||||||
|
{
|
||||||
|
if (mod.ModFiles != null)
|
||||||
|
File.WriteAllText(mod.Name + ".json", JsonConvert.SerializeObject(mod.ModFiles));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadFileList(ModInfo mod)
|
||||||
|
{
|
||||||
|
if (File.Exists(mod.Name + ".json"))
|
||||||
|
mod.ModFiles = JsonConvert.DeserializeObject<HashSet<string>>(File.ReadAllText(mod.Name + ".json"));
|
||||||
|
else
|
||||||
|
mod.ModFiles = new HashSet<string>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UninstallMod(ModInfo mod)
|
||||||
|
{
|
||||||
|
LoadFileList(mod);
|
||||||
|
if (mod.ModFiles.Count == 0) //A single DLL
|
||||||
|
File.Delete(Settings.Default.GamePath + @"\Plugins\" + mod.Name + ".dll");
|
||||||
|
else //A ZIP
|
||||||
|
{
|
||||||
|
foreach (string file in mod.ModFiles)
|
||||||
|
{
|
||||||
|
File.Delete(file);
|
||||||
|
var parent = Directory.GetParent(file);
|
||||||
|
if (!parent.EnumerateFiles().Any())
|
||||||
|
parent.Delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File.Delete(mod.Name + ".json");
|
||||||
|
mod.Version = null; //Not installed
|
||||||
|
AddUpdateModInList(mod); //Update list
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,8 @@ namespace GCMM
|
||||||
{
|
{
|
||||||
var an = AssemblyName.GetAssemblyName(modPath);
|
var an = AssemblyName.GetAssemblyName(modPath);
|
||||||
if (an.Name == "0Harmony") continue;
|
if (an.Name == "0Harmony") continue;
|
||||||
AddUpdateModInList(new ModInfo { Name = an.Name, Version = an.Version, LastUpdated = File.GetLastWriteTime(modPath) });
|
//Use filename to avoid differences between repository & assembly name casing
|
||||||
|
AddUpdateModInList(new ModInfo { Name = Path.GetFileNameWithoutExtension(modPath), Version = an.Version, LastUpdated = File.GetLastWriteTime(modPath) });
|
||||||
}
|
}
|
||||||
catch (BadImageFormatException)
|
catch (BadImageFormatException)
|
||||||
{ //Not a .NET assembly
|
{ //Not a .NET assembly
|
||||||
|
@ -94,7 +95,7 @@ namespace GCMM
|
||||||
|
|
||||||
public void AddUpdateModInList(ModInfo mod)
|
public void AddUpdateModInList(ModInfo mod)
|
||||||
{
|
{
|
||||||
if (mods.ContainsKey(mod.Name) ^ modlist.Items.ContainsKey(mod.Name))
|
if (mods.ContainsKey(mod.Name) ^ modlist.Items.ContainsKey(mod.Name)) //The ListView's keys aren't case sensitive
|
||||||
throw new InvalidOperationException("The mod isn't present in one of the two places: " + mod.Name);
|
throw new InvalidOperationException("The mod isn't present in one of the two places: " + mod.Name);
|
||||||
if (modlist.Items.ContainsKey(mod.Name))
|
if (modlist.Items.ContainsKey(mod.Name))
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,7 +10,7 @@ namespace GCMM
|
||||||
{
|
{
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Can be null.
|
/// Installed version. Can be null.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Version Version { get; set; }
|
public Version Version { get; set; }
|
||||||
public Version LatestVersion { get; set; }
|
public Version LatestVersion { get; set; }
|
||||||
|
@ -25,5 +25,6 @@ namespace GCMM
|
||||||
public DateTime LastUpdated { get; set; }
|
public DateTime LastUpdated { get; set; }
|
||||||
public bool Installed => Version != null;
|
public bool Installed => Version != null;
|
||||||
public string DownloadURL { get; set; }
|
public string DownloadURL { get; set; }
|
||||||
|
public HashSet<string> ModFiles { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue