2020-06-17 13:08:22 +00:00
using GCMM.Properties ;
2020-06-17 16:31:20 +00:00
using Newtonsoft.Json ;
2020-06-17 13:08:22 +00:00
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 ;
2020-08-18 18:56:19 +00:00
if ( CheckNoExe ( ) )
2020-06-17 13:08:22 +00:00
return ;
2020-07-18 20:25:52 +00:00
if ( mod . Name ! = "GamecraftModdingAPI" )
await UpdateAPI ( ) ;
2020-06-17 13:08:22 +00:00
var tmp = Directory . CreateDirectory ( "temp" ) ;
2020-07-03 00:00:08 +00:00
var plugins = Directory . CreateDirectory ( GamePath ( @"\Plugins" ) ) ;
string tmppath = tmp . FullName + Path . DirectorySeparatorChar + mod . Name ;
2020-06-17 13:08:22 +00:00
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" ) )
2020-08-23 21:38:10 +00:00
{
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 ) ;
}
2020-06-17 13:08:22 +00:00
else if ( filename . EndsWith ( ".zip" ) )
{
bool pluginOnly = true ;
using ( var archive = ZipFile . OpenRead ( tmppath ) )
{
2020-06-17 16:31:20 +00:00
bool modFound = false ;
2020-06-17 13:08:22 +00:00
foreach ( var entry in archive . Entries )
{
if ( entry . FullName = = "Plugins/" )
pluginOnly = false ;
2020-06-17 16:31:20 +00:00
if ( entry . FullName = = "Plugins/" + mod . Name + ".dll" )
2020-06-18 16:28:07 +00:00
{
modFound = true ;
pluginOnly = false ; //The directory-only entry may be missing
}
else if ( pluginOnly & & entry . FullName = = mod . Name + ".dll" )
2020-06-17 16:31:20 +00:00
modFound = true ;
if ( ! pluginOnly & & modFound ) break ;
2020-06-17 13:08:22 +00:00
}
2020-06-18 16:28:07 +00:00
if ( ! modFound )
2020-06-17 21:56:18 +00:00
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 )
2020-06-17 16:31:20 +00:00
return ;
ExtractMod ( archive , pluginOnly ? plugins . FullName : Settings . Default . GamePath , mod ) ;
2020-06-17 13:08:22 +00:00
}
2020-06-17 16:31:20 +00:00
File . Delete ( tmppath ) ;
2020-06-17 13:08:22 +00:00
}
else
{
2020-06-18 16:28:07 +00:00
MessageBox . Show ( "Don't know how to install file: " + filename + "\nThe file remains in the temp folder near the mod manager" ) ;
2020-06-17 13:08:22 +00:00
return ;
}
GetInstalledMods ( ) ; //Update list
}
}
2020-06-17 16:31:20 +00:00
public void ExtractMod ( ZipArchive archive , string destinationDirectoryName , ModInfo mod )
{
LoadFileList ( mod ) ;
DirectoryInfo di = Directory . CreateDirectory ( destinationDirectoryName ) ;
string destinationDirectoryFullPath = di . FullName ;
2020-10-03 23:08:19 +00:00
bool? skipExisting = null ;
2020-06-17 16:31:20 +00:00
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" ) ;
}
2020-06-18 16:28:07 +00:00
Directory . CreateDirectory ( Path . GetDirectoryName ( completeFileName ) ) ; //Sometimes there are no directory-only entries
2020-06-17 16:31:20 +00:00
if ( file . Name = = "" )
{ // Assuming Empty for Directory
continue ;
}
2020-06-18 18:05:14 +00:00
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)
{
2020-10-03 23:08:19 +00:00
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 )
2020-06-18 16:28:07 +00:00
continue ;
2020-06-17 16:31:20 +00:00
}
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 )
{
2020-10-03 11:32:53 +00:00
try
2020-06-17 16:31:20 +00:00
{
2020-10-03 11:32:53 +00:00
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 )
{
2020-10-03 23:08:19 +00:00
if ( ! File . Exists ( file ) ) continue ; //If the folders don't exist then it errors
2020-10-03 11:32:53 +00:00
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
2020-06-17 16:31:20 +00:00
{
2020-10-03 11:32:53 +00:00
mods . Remove ( mod . Name ) ;
modlist . Items [ mod . Name ] . Remove ( ) ;
2020-06-17 16:31:20 +00:00
}
}
2020-10-03 11:32:53 +00:00
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 ) ;
}
2020-06-17 16:31:20 +00:00
}
2020-07-18 20:25:52 +00:00
public async Task UpdateAPI ( )
{
2020-08-18 18:56:19 +00:00
if ( ! mods . ContainsKey ( "GamecraftModdingAPI" ) )
return ;
2020-07-18 20:25:52 +00:00
var gcmapi = mods [ "GamecraftModdingAPI" ] ;
if ( ! gcmapi . Installed | | gcmapi . Updatable )
{
2020-10-03 13:26:05 +00:00
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 )
2020-07-18 20:25:52 +00:00
await InstallMod ( gcmapi ) ;
}
}
2020-06-17 13:08:22 +00:00
}
}