Sorting mods when (un)installing Added Apply to all checkbox to a custom message box that replaces the one that shows up when the mod files already exist without the mod manager knowing about it Removing IPA.zip after unzipping Fix deleting files when their folder doesn't exist
182 lines
8.3 KiB
C#
182 lines
8.3 KiB
C#
using GCMM.Properties;
|
|
using Newtonsoft.Json;
|
|
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 (CheckNoExe())
|
|
return;
|
|
if (mod.Name != "GamecraftModdingAPI")
|
|
await UpdateAPI();
|
|
var tmp = Directory.CreateDirectory("temp");
|
|
var plugins = Directory.CreateDirectory(GamePath(@"\Plugins"));
|
|
string tmppath = tmp.FullName + Path.DirectorySeparatorChar + 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"))
|
|
{
|
|
string name = plugins.FullName + Path.DirectorySeparatorChar + mod.Name + ".dll"; //Force mod name to make uninstalls & identifying easier
|
|
if (File.Exists(name))
|
|
File.Delete(name);
|
|
File.Move(tmppath, name);
|
|
}
|
|
else if (filename.EndsWith(".zip"))
|
|
{
|
|
bool pluginOnly = true;
|
|
using (var archive = ZipFile.OpenRead(tmppath))
|
|
{
|
|
bool modFound = false;
|
|
foreach (var entry in archive.Entries)
|
|
{
|
|
if (entry.FullName == "Plugins/")
|
|
pluginOnly = false;
|
|
if (entry.FullName == "Plugins/" + mod.Name + ".dll")
|
|
{
|
|
modFound = true;
|
|
pluginOnly = false; //The directory-only entry may be missing
|
|
}
|
|
else if (pluginOnly && entry.FullName == mod.Name + ".dll")
|
|
modFound = true;
|
|
if (!pluginOnly && modFound) break;
|
|
}
|
|
if (!modFound)
|
|
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 track this mod 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);
|
|
}
|
|
File.Delete(tmppath);
|
|
}
|
|
else
|
|
{
|
|
MessageBox.Show("Don't know how to install file: " + filename + "\nThe file remains in the temp folder near the mod manager");
|
|
return;
|
|
}
|
|
GetInstalledMods(); //Update list
|
|
}
|
|
}
|
|
|
|
public void ExtractMod(ZipArchive archive, string destinationDirectoryName, ModInfo mod)
|
|
{
|
|
LoadFileList(mod);
|
|
DirectoryInfo di = Directory.CreateDirectory(destinationDirectoryName);
|
|
string destinationDirectoryFullPath = di.FullName;
|
|
bool? skipExisting = null;
|
|
|
|
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");
|
|
}
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(completeFileName)); //Sometimes there are no directory-only entries
|
|
if (file.Name == "")
|
|
{// Assuming Empty for Directory
|
|
continue;
|
|
}
|
|
|
|
if ((mod.Version == null || mod.ModFiles.Count != 0) //Negated: The mod is installed and we don't know about any of its files
|
|
&& File.Exists(completeFileName) // OR it's a new file
|
|
&& !mod.ModFiles.Contains(completeFileName) // OR it's known to be part of the mod already
|
|
&& file.FullName != "Plugins/" + mod.Name + ".dll") // OR it's the plugin's DLL (dll->zip release)
|
|
{
|
|
if (!skipExisting.HasValue)
|
|
{
|
|
var mbox = new CustomMessageBox("The mod zip contains a file that exists as part of the game. Do you want to skip this file?\n" + file.FullName + "\nOnly choose No if it's part of a previous installation of the mod.", "File is part of the game");
|
|
var result = mbox.ShowDialog(this);
|
|
if (mbox.ApplyToAll)
|
|
skipExisting = result == DialogResult.Yes;
|
|
if (result == DialogResult.Yes)
|
|
continue;
|
|
}
|
|
else if (skipExisting.Value)
|
|
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)
|
|
{
|
|
try
|
|
{
|
|
LoadFileList(mod);
|
|
if (mod.ModFiles.Count == 0) //A single DLL
|
|
File.Delete(GamePath(@"\Plugins\" + mod.Name + ".dll"));
|
|
else //A ZIP
|
|
{
|
|
foreach (string file in mod.ModFiles)
|
|
{
|
|
if (!File.Exists(file)) continue; //If the folders don't exist then it errors
|
|
File.Delete(file);
|
|
var parent = Directory.GetParent(file);
|
|
if (!parent.EnumerateFileSystemInfos().Any())
|
|
parent.Delete(); //May delete the Plugins dir if empty
|
|
}
|
|
}
|
|
File.Delete(mod.Name + ".json");
|
|
mod.Version = null; //Not installed
|
|
if (mod.Author != null)
|
|
AddUpdateModInList(mod); //Update list
|
|
else
|
|
{
|
|
mods.Remove(mod.Name);
|
|
modlist.Items[mod.Name].Remove();
|
|
}
|
|
}
|
|
catch (Exception e) when (e is UnauthorizedAccessException || e is IOException)
|
|
{
|
|
MessageBox.Show("Could not remove mod files! Make sure the game isn't running.\n" + e.Message);
|
|
}
|
|
}
|
|
|
|
public async Task UpdateAPI()
|
|
{
|
|
if (!mods.ContainsKey("GamecraftModdingAPI"))
|
|
return;
|
|
var gcmapi = mods["GamecraftModdingAPI"];
|
|
if (!gcmapi.Installed || gcmapi.Updatable)
|
|
{
|
|
if (MessageBox.Show(gcmapi.Installed ?
|
|
"GamecraftModdingAPI will be updated as there's a new version available. It's needed for most mods to function."
|
|
: "GamecraftModdingAPI will be installed as most mods need it to work. You can uninstall it if you're sure you don't need it.", "API needed",
|
|
MessageBoxButtons.OKCancel) == DialogResult.OK)
|
|
await InstallMod(gcmapi);
|
|
}
|
|
}
|
|
}
|
|
}
|