mirror of
https://github.com/ModOrganizer2/modorganizer-NCC.git
synced 2026-07-27 14:00:52 -07:00
363 lines
15 KiB
C#
363 lines
15 KiB
C#
/* This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>. */
|
|
|
|
using System;
|
|
using System.IO;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Threading;
|
|
using System.Windows.Forms;
|
|
using System.Reflection;
|
|
using ChinhDo.Transactions;
|
|
using SevenZip;
|
|
using Nexus.Client.Games;
|
|
using Nexus.Client.Util;
|
|
using Nexus.Client.Mods;
|
|
using Nexus.Client.UI;
|
|
using Nexus.Client.ModManagement.Scripting;
|
|
using Nexus.Client.ModManagement;
|
|
using Nexus.Client.ModManagement.InstallationLog;
|
|
using Nexus.Client.PluginManagement;
|
|
using Nexus.Client.Settings;
|
|
using Castle.DynamicProxy;
|
|
|
|
|
|
namespace Nexus.Client.CLI
|
|
{
|
|
class ScriptRunner : ThreadedBackgroundTask
|
|
{
|
|
private IScriptExecutor m_Executor;
|
|
private IScript m_Script;
|
|
|
|
public ScriptRunner(IScriptExecutor executor, IScript script)
|
|
{
|
|
m_Executor = executor;
|
|
m_Script = script;
|
|
}
|
|
|
|
public bool Execute()
|
|
{
|
|
Start();
|
|
return true;
|
|
}
|
|
|
|
protected override object DoWork(object[] p_objArgs)
|
|
{
|
|
Thread.Sleep(0); // ensures that the main thread gets an opportunity to start the main loop
|
|
try
|
|
{
|
|
return m_Executor.Execute(m_Script);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
MessageBox.Show(e.Message, "Installation failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
|
|
class Program
|
|
{
|
|
private static IEnvironmentInfo EnvironmentInfo = null;
|
|
/// <summary>
|
|
/// Creates a mod of the appropriate type from the specified file.
|
|
/// Code copy&pasted from class ModRegistry
|
|
/// </summary>
|
|
/// <param name="p_strModPath">The path to the mod file.</param>
|
|
/// <returns>A mod of the appropriate type from the specified file, if the type of hte mod
|
|
/// can be determined; <c>null</c> otherwise.</returns>
|
|
private static IMod CreateMod(string modPath, IModFormatRegistry formatRegistry, IGameMode gameMode)
|
|
{
|
|
List<KeyValuePair<FormatConfidence, IModFormat>> lstFormats = new List<KeyValuePair<FormatConfidence, IModFormat>>();
|
|
foreach (IModFormat mftFormat in formatRegistry.Formats)
|
|
lstFormats.Add(new KeyValuePair<FormatConfidence, IModFormat>(mftFormat.CheckFormatCompliance(modPath), mftFormat));
|
|
lstFormats.Sort((x, y) => -x.Key.CompareTo(y.Key));
|
|
if (lstFormats[0].Key <= FormatConfidence.Convertible)
|
|
{
|
|
Console.WriteLine("failed to determine format for " + modPath);
|
|
return null;
|
|
}
|
|
return lstFormats[0].Value.CreateMod(modPath, gameMode);
|
|
}
|
|
|
|
static int DoInstall(string game, string filename, string installationPath, string profilePath, string gamePath,
|
|
List<string> additionalSearchPaths, string seVersion, ref string errorString)
|
|
{
|
|
|
|
if (game == null)
|
|
{
|
|
errorString = "no game specified";
|
|
return 1;
|
|
}
|
|
if (filename == null)
|
|
{
|
|
errorString = "no file specified";
|
|
return 1;
|
|
}
|
|
if (profilePath == null)
|
|
{
|
|
errorString = "no profile path specified";
|
|
return 1;
|
|
}
|
|
if (gamePath == null)
|
|
{
|
|
errorString = "no game path specified";
|
|
return 1;
|
|
}
|
|
try
|
|
{
|
|
EnvironmentInfo = new EnvironmentInfo(Properties.Settings.Default);
|
|
|
|
string exeLocation = Assembly.GetExecutingAssembly().Location;
|
|
string exePath = System.IO.Path.GetDirectoryName(exeLocation);
|
|
string[] gameModes = Directory.GetFiles(Path.Combine(exePath, "GameModes"), String.Format("{0}.dll", game));
|
|
if (gameModes.Count() == 0)
|
|
{
|
|
errorString = "unknown game";
|
|
return 1;
|
|
}
|
|
|
|
Assembly gameAssembly = Assembly.LoadFrom(gameModes[0]);
|
|
IGameModeFactory gameModeFactory = null;
|
|
Type[] exportedTypes = gameAssembly.GetExportedTypes();
|
|
foreach (Type type in exportedTypes)
|
|
{
|
|
if (typeof(IGameModeFactory).IsAssignableFrom(type) && !type.IsAbstract)
|
|
{
|
|
ConstructorInfo constructor = type.GetConstructor(new Type[] { typeof(EnvironmentInfo) });
|
|
gameModeFactory = (IGameModeFactory)constructor.Invoke(new Object[] { EnvironmentInfo });
|
|
}
|
|
}
|
|
if (gameModeFactory == null)
|
|
{
|
|
errorString = "invalid game";
|
|
return 1;
|
|
}
|
|
|
|
string str7zPath = Path.Combine(EnvironmentInfo.ProgrammeInfoDirectory, EnvironmentInfo.Is64BitProcess ? "7z-64bit.dll" : "7z-32bit.dll");
|
|
SevenZipCompressor.SetLibraryPath(str7zPath);
|
|
|
|
FileUtil fileUtil = new NexusFileUtil(EnvironmentInfo);
|
|
|
|
EnvironmentInfo.Settings.InstallationPaths[gameModeFactory.GameModeDescriptor.ModeId] = installationPath;
|
|
EnvironmentInfo.Settings.ModFolder[gameModeFactory.GameModeDescriptor.ModeId] = installationPath;
|
|
// environmentInfo.Settings.InstallInfoFolder[gameModeFactory.GameModeDescriptor.ModeId] = environmentInfo.TemporaryPath;
|
|
EnvironmentInfo.Settings.InstallInfoFolder[gameModeFactory.GameModeDescriptor.ModeId] = Path.Combine(installationPath, "temp");
|
|
if (EnvironmentInfo.Settings.DelayedSettings[gameModeFactory.GameModeDescriptor.ModeId] == null)
|
|
EnvironmentInfo.Settings.DelayedSettings[gameModeFactory.GameModeDescriptor.ModeId] = new KeyedSettings<string>();
|
|
if (EnvironmentInfo.Settings.DelayedSettings["ALL"] == null)
|
|
EnvironmentInfo.Settings.DelayedSettings["ALL"] = new KeyedSettings<string>();
|
|
|
|
ViewMessage warning = null;
|
|
|
|
IGameMode gameMode = gameModeFactory.BuildGameMode(fileUtil, out warning);
|
|
|
|
IModCacheManager cacheManager = new NexusModCacheManager(EnvironmentInfo.TemporaryPath, gameMode.GameModeEnvironmentInfo.ModDirectory, fileUtil, EnvironmentInfo);
|
|
|
|
IScriptTypeRegistry scriptTypeRegistry = ScriptTypeRegistry.DiscoverScriptTypes(Path.Combine(Path.GetDirectoryName(exeLocation), "ScriptTypes"), gameMode, new List<string>());
|
|
if (scriptTypeRegistry.Types.Count == 0)
|
|
{
|
|
errorString = "No script types were found.";
|
|
return 2;
|
|
}
|
|
|
|
// use a proxy so we can intercept accesses to the IGameMode interface. This allows us to make the additional search paths accessible from
|
|
// the sandbox and feed in the script extender version even though the nmm lib won't find it.
|
|
// This has to happen after DiscoverScriptTypes becaus that function tries to localize the assembly which fails for the dynamic assembly
|
|
// of the proxy. Fortunately DiscoverScriptTypes has no side-effects on the gameMode.
|
|
// This recreates the gamemode object so it's important no code above modifies gameMode
|
|
ProxyGenerator generator = new ProxyGenerator();
|
|
GameModeInterceptor interceptor = new GameModeInterceptor(additionalSearchPaths, seVersion != null ? new Version(seVersion) : null);
|
|
|
|
gameMode = (IGameMode)generator.CreateClassProxy(gameMode.GetType(), new object[] { EnvironmentInfo, fileUtil }, new IInterceptor[] { interceptor });
|
|
|
|
IModFormatRegistry formatRegistry = ModFormatRegistry.DiscoverFormats(cacheManager, gameMode.SupportedFormats, scriptTypeRegistry, Path.Combine(Path.GetDirectoryName(exeLocation), "ModFormats"));
|
|
if (formatRegistry.Formats.Count == 0)
|
|
{
|
|
errorString = "No formats were found.";
|
|
return 2;
|
|
}
|
|
|
|
// we install the mod from the temporary path. Unfortunately this requires the archive to be copied, otherwise the sandbox will
|
|
// prevent the installer script from accessing the archive in its original location
|
|
string fileNameTemporary = Path.Combine(EnvironmentInfo.TemporaryPath, Path.GetFileName(filename));
|
|
File.Copy(filename, fileNameTemporary);
|
|
IMod mod = CreateMod(fileNameTemporary, formatRegistry, gameMode);
|
|
if (mod == null)
|
|
{
|
|
errorString = "failed to initialize mod";
|
|
return 3;
|
|
}
|
|
|
|
System.IO.File.WriteAllText(installationPath + "/__installInfo.txt", mod.ModName + "\n" + mod.HumanReadableVersion + "\n" + mod.Id);
|
|
|
|
if (mod.HasInstallScript)
|
|
{
|
|
//SevenZipExtractor extractor = new SevenZipExtractor(fileNameTemporary);
|
|
string extractPath = Path.Combine(EnvironmentInfo.TemporaryPath, Path.GetFileNameWithoutExtension(filename));
|
|
//extractor.ExtractArchive(extractPath);
|
|
DummyDataFileUtilFactory dummyFactory = null;
|
|
IDataFileUtil dataFileUtility;
|
|
Logger.Info("Detected C# script that relies on files in the actual data folder");
|
|
|
|
string modlistFile = Path.Combine(profilePath, "modlist.txt");
|
|
// ASSUMED mods path is the parent directory of the gameMode.InstallationPath
|
|
string modsPath = Directory.GetParent(gameMode.InstallationPath).FullName;
|
|
// Prepare dummy data directory
|
|
dummyFactory = new DummyDataFileUtilFactory(gameMode.GameModeEnvironmentInfo.InstallationPath, modlistFile, modsPath, gamePath, additionalSearchPaths);
|
|
dataFileUtility = dummyFactory.CreateDummyDataFileUtil();
|
|
|
|
TxFileManager fileManager = new TxFileManager();
|
|
IInstallLog installLog = new DummyInstallLog();
|
|
IIniInstaller iniIniInstaller = new IniInstaller(mod, installLog, fileManager, delegate { return OverwriteResult.No; });
|
|
IPluginManager pluginManager = new DummyPluginManager(Path.Combine(profilePath, "plugins.txt"), gameMode, mod);
|
|
IGameSpecificValueInstaller gameSpecificValueInstaller = gameMode.GetGameSpecificValueInstaller(mod, installLog, fileManager, new NexusFileUtil(EnvironmentInfo), delegate { return OverwriteResult.No; });
|
|
IModFileInstaller fileInstaller = new ModFileInstaller(gameMode.GameModeEnvironmentInfo, mod, installLog, pluginManager, dataFileUtility, fileManager, delegate { return OverwriteResult.No; }, false, extractPath, gameMode.StopFolders);
|
|
InstallerGroup installers = new InstallerGroup(dataFileUtility, fileInstaller, iniIniInstaller, gameSpecificValueInstaller, pluginManager);
|
|
IVirtualModActivator modActivator = new DummyVirtualModActivator(gameMode, EnvironmentInfo);
|
|
IScriptExecutor executor = mod.InstallScript.Type.CreateExecutor(mod, gameMode, EnvironmentInfo, modActivator, installers, SynchronizationContext.Current);
|
|
// run the script in a second thread and start the main loop in the main thread to ensure we can handle message boxes and the like
|
|
ScriptRunner runner = new ScriptRunner(executor, mod.InstallScript);
|
|
|
|
runner.Execute();
|
|
runner.TaskEnded += delegate
|
|
{
|
|
fileInstaller.FinalizeInstall();
|
|
iniIniInstaller.FinalizeInstall();
|
|
gameSpecificValueInstaller.FinalizeInstall();
|
|
|
|
Application.Exit();
|
|
};
|
|
|
|
Application.Run();
|
|
switch (runner.Status)
|
|
{
|
|
case BackgroundTasks.TaskStatus.Cancelled: return 11;
|
|
case BackgroundTasks.TaskStatus.Error: return 6;
|
|
case BackgroundTasks.TaskStatus.Incomplete: return 10;
|
|
default: return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
errorString = "no install script";
|
|
return 4;
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Console.WriteLine("exception: " + e.Message);
|
|
MessageBox.Show(e.Message, "Installation failed", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
return 5;
|
|
}
|
|
}
|
|
|
|
[STAThread]
|
|
static int Main(string[] args)
|
|
{
|
|
string game = null;
|
|
string filename = null;
|
|
string installationPath = null;
|
|
string profilePath = null;
|
|
string gamePath = null;
|
|
string seVersion = null;
|
|
List<string> searchPaths = new List<string>();
|
|
|
|
|
|
// Default log verbosity, see Logger.Level
|
|
int loggerVerbosity = 4; // 0 for none, 9 for all
|
|
|
|
// determine action
|
|
for (int i = 0; i < args.Length; ++i)
|
|
{
|
|
if (args[i].ToLower().Equals("/g") || args[i].ToLower().Equals("-g"))
|
|
{
|
|
if (args.Length - i <= 1)
|
|
{
|
|
Logger.Error("invalid number of parameters, expected game name");
|
|
return 1;
|
|
}
|
|
++i;
|
|
game = args[i];
|
|
}
|
|
|
|
if (args[i].ToLower().Equals("/i") || args[i].ToLower().Equals("-i"))
|
|
{
|
|
if (args.Length - i <= 2)
|
|
{
|
|
Logger.Error("invalid number of parameters, expected filename and installation path");
|
|
return 1;
|
|
}
|
|
filename = args[i + 1];
|
|
installationPath = args[i + 2];
|
|
Logger.SetLogDestination(installationPath);
|
|
i += 2;
|
|
}
|
|
|
|
if (args[i].ToLower().Equals("/p") || args[i].ToLower().Equals("-p"))
|
|
{
|
|
if (args.Length - i <= 1)
|
|
{
|
|
Logger.Error("invalid number of parameters, expected plugins file");
|
|
return 1;
|
|
}
|
|
++i;
|
|
profilePath = args[i];
|
|
}
|
|
|
|
if (args[i].ToLower().Equals("/gd") || args[i].ToLower().Equals("-gd"))
|
|
{
|
|
if (args.Length - i <= 1)
|
|
{
|
|
Logger.Error("invalid number of parameters, expected path name");
|
|
return 1;
|
|
}
|
|
++i;
|
|
gamePath = args[i];
|
|
searchPaths.Add(args[i] + "\\data");
|
|
}
|
|
|
|
if (args[i].ToLower().Equals("/d") || args[i].ToLower().Equals("-d"))
|
|
{
|
|
if (args.Length - i <= 1)
|
|
{
|
|
Logger.Error("invalid number of parameters, expected path name");
|
|
return 1;
|
|
}
|
|
++i;
|
|
searchPaths.Add(args[i]);
|
|
}
|
|
if (args[i].ToLower().Equals("/se") || args[i].ToLower().Equals("-se"))
|
|
{
|
|
if (args.Length - i <= 1)
|
|
{
|
|
Logger.Error("invalid number of parameters, expected path name");
|
|
return 1;
|
|
}
|
|
seVersion = args[++i];
|
|
}
|
|
}
|
|
|
|
// Set logger verbosity level
|
|
Logger.SetVerbosity((Logger.Level)loggerVerbosity);
|
|
|
|
string errorString = "";
|
|
int result = DoInstall(game, filename, installationPath, profilePath, gamePath, searchPaths, seVersion, ref errorString);
|
|
if ((result != 0) && (errorString.Length != 0))
|
|
{
|
|
MessageBox.Show(errorString, "Installation Failed: " + result, MessageBoxButtons.OK, MessageBoxIcon.Error);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
}
|
|
}
|