Files
BepInEx/BepInEx.Installer/Patching/FrameworkInstaller.cs
T

255 lines
7.5 KiB
C#

using System;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using Ionic.Zip;
using Mono.Cecil;
using Mono.Cecil.Cil;
namespace BepInEx.Installer.Patching
{
public class FrameworkInstaller
{
public TargetGame TargetGame { get; }
public InstallationType InstallationType { get; }
public Config Config { get; }
public FrameworkInstaller(TargetGame targetGame, InstallationType installationType, Config config)
{
TargetGame = targetGame;
InstallationType = installationType;
Config = config;
}
public void Install()
{
try
{
switch (InstallationType)
{
case InstallationType.Doorstop:
DoorstopInstallation();
break;
case InstallationType.CryptoRng:
throw new NotImplementedException();
case InstallationType.AssemblyPatch:
AssemblyPatchInstallation();
break;
default:
throw new ArgumentOutOfRangeException(nameof(InstallationType));
}
MessageBox.Show("Installation completed successfully.", "Installation", MessageBoxButtons.OK, MessageBoxIcon.None);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error during installation", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
private static ZipFile GetPackageZip()
{
return ZipFile.Read(EmbeddedResource.GetStream("BepInEx.Installer.InstallPackage.zip"));
}
#region Doorstop
private const string DoorstopConfig = @"[UnityDoorstop]
# Specifies whether assembly executing is enabled
enabled=true
# Specifies the path (absolute, or relative to the game's exe) to the DLL/EXE that should be executed by Doorstop
targetAssembly=BepInEx\core\BepInEx.Preloader.dll";
private void DoorstopInstallation()
{
string bepinexDirectory = Path.Combine(TargetGame.DirectoryPath, "BepInEx");
string coreDirectory = Path.Combine(bepinexDirectory, "core");
Directory.CreateDirectory(coreDirectory);
string doorstopZipName = TargetGame.Platform == TargetGame.ExecutablePlatform.Win64
? "doorstop_x64.zip"
: "doorstop_x86.zip";
string coreAssemblyZipFolder = TargetGame.UnityEngineType == TargetGame.UnityGameType.v2017Plus
? "v2018"
: "legacy";
using (var packageZip = GetPackageZip())
{
using (var doorstopZip = ZipFile.Read(packageZip[doorstopZipName].ExtractEntry()))
{
doorstopZip["winhttp.dll"].Extract(TargetGame.DirectoryPath, ExtractExistingFileAction.OverwriteSilently);
}
var selectedEntries = packageZip.SelectEntries("*", "shared")
.Concat(packageZip.SelectEntries("*", coreAssemblyZipFolder));
foreach (var entry in selectedEntries)
{
string localFilename = Path.Combine(coreDirectory, Path.GetFileName(entry.FileName));
entry.ExtractEntry(localFilename);
}
}
File.WriteAllText(Path.Combine(bepinexDirectory, "config.ini"), Config.OutputConfig);
File.WriteAllText(Path.Combine(TargetGame.DirectoryPath, "doorstop_config.ini"), DoorstopConfig);
}
#endregion
#region Assembly Patch
private void AssemblyPatchInstallation()
{
string bepinexDirectory = Path.Combine(TargetGame.DirectoryPath, "BepInEx");
string coreDirectory = Path.Combine(bepinexDirectory, "core");
string managedDir = Path.Combine(TargetGame.DataFolder, "Managed");
string unityEngineDllPath = Path.Combine(managedDir, Config.TargetAssembly);
string bootstrapDllPath = Path.Combine(managedDir, "BepInEx.Bootstrap.dll");
if (!File.Exists(unityEngineDllPath))
throw new ArgumentException($"Cannot find target assembly '{unityEngineDllPath}'");
Directory.CreateDirectory(coreDirectory);
string coreAssemblyZipFolder = TargetGame.UnityEngineType == TargetGame.UnityGameType.v2017Plus
? "v2018"
: "legacy";
using (var packageZip = GetPackageZip())
{
var selectedEntries = packageZip.SelectEntries("*", "shared")
.Concat(packageZip.SelectEntries("*", coreAssemblyZipFolder))
.Where(x => !x.FileName.EndsWith("BepInEx.Preloader.dll"));
foreach (var entry in selectedEntries)
{
string localFilename = Path.Combine(coreDirectory, Path.GetFileName(entry.FileName));
entry.ExtractEntry(localFilename);
}
packageZip["bootstrap\\BepInEx.Bootstrap.dll"].ExtractEntry(bootstrapDllPath);
}
if (!PatchUnityGame(managedDir, unityEngineDllPath, bootstrapDllPath, out var message))
throw new Exception(message);
}
bool PatchUnityGame(string managedDir, string unityEngineDll, string bootstrapDll, out string message)
{
var defaultResolver = new DefaultAssemblyResolver();
defaultResolver.AddSearchDirectory(managedDir);
var rp = new ReaderParameters
{
AssemblyResolver = defaultResolver
};
string unityBackupDll = Path.GetFullPath($"{unityEngineDll}.bak");
//determine which assembly to use as a base
AssemblyDefinition unity = AssemblyDefinition.ReadAssembly(unityEngineDll, rp);
if (!VerifyAssembly(unity))
{
//try and fall back to .bak if exists
if (File.Exists(unityBackupDll))
{
unity.Dispose();
unity = AssemblyDefinition.ReadAssembly(unityBackupDll, rp);
if (!VerifyAssembly(unity))
{
//can't use anything
unity.Dispose();
message = "Target assembly already has BepInEx injected, and the backup is not usable.";
return false;
}
}
else
{
//can't use anything
unity.Dispose();
message = "Target assembly already has BepInEx injected, and no backup exists.";
return false;
}
}
else
{
//make a backup of the assembly
File.Copy(unityEngineDll, unityBackupDll, true);
unity.Dispose();
unity = AssemblyDefinition.ReadAssembly(unityBackupDll, rp);
}
//patch
using (unity)
using (AssemblyDefinition injected = AssemblyDefinition.ReadAssembly(bootstrapDll, rp))
{
InjectAssembly(unity, injected);
unity.Write(unityEngineDll);
}
message = null;
return true;
}
void InjectAssembly(AssemblyDefinition targetAssembly, AssemblyDefinition injected)
{
//Entry point
var originalInjectMethod = injected.MainModule.Types.First(x => x.Name == "Entrypoint")
.Methods.First(x => x.Name == "Init");
var injectMethod = targetAssembly.MainModule.ImportReference(originalInjectMethod);
var targetType = targetAssembly.MainModule.Types.FirstOrDefault(x => x.Name == Config.TargetType);
if (targetType == null)
throw new ArgumentException($"Cannot find target type '{Config.TargetType}'");
MethodDefinition targetMethod;
if (Config.TargetMethod == ".cctor")
{
targetMethod = targetType.Methods.FirstOrDefault(m => m.IsConstructor && m.IsStatic);
if (targetMethod == null)
{
targetMethod = new MethodDefinition(".cctor",
MethodAttributes.Static
| MethodAttributes.Private
| MethodAttributes.HideBySig
| MethodAttributes.SpecialName
| MethodAttributes.RTSpecialName,
targetAssembly.MainModule.ImportReference(typeof(void)));
targetType.Methods.Add(targetMethod);
var il = targetMethod.Body.GetILProcessor();
il.Append(il.Create(OpCodes.Ret));
}
}
else
{
targetMethod = targetType.Methods.FirstOrDefault(m => m.Name == Config.TargetMethod);
if (targetMethod == null)
throw new ArgumentException($"Cannot find target method '{Config.TargetType}.{Config.TargetMethod}'");
}
var ilp = targetMethod.Body.GetILProcessor();
ilp.InsertBefore(ilp.Body.Instructions.First(), ilp.Create(OpCodes.Call, injectMethod));
}
static bool VerifyAssembly(AssemblyDefinition unity)
=> !unity.MainModule.AssemblyReferences.Any(x => x.Name.Contains("BepInEx"));
#endregion
}
}