mirror of
https://github.com/BepInEx/BepInEx.git
synced 2026-10-19 15:51:45 +00:00
Implement assembly patching in installer
This commit is contained in:
11 files changed
+221
-390
No files matched your search
@@ -68,6 +68,7 @@
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="TargetGame.cs" />
|
||||
<Compile Include="Utility.cs" />
|
||||
<EmbeddedResource Include="formMain.resx">
|
||||
<DependentUpon>formMain.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
|
||||
@@ -21,8 +21,19 @@ dump-assemblies=false";
|
||||
|
||||
public string OutputConfig { get; protected set; }
|
||||
|
||||
public string TargetAssembly { get; protected set; }
|
||||
public string TargetType { get; protected set; }
|
||||
public string TargetMethod { get; protected set; }
|
||||
|
||||
public RuntimePatchesType RuntimePatchesType { get; protected set; }
|
||||
|
||||
public Config(string targetAssembly, string targetType, string targetMethod, RuntimePatchesType runtimePatchesType)
|
||||
{
|
||||
TargetAssembly = targetAssembly;
|
||||
TargetType = targetType;
|
||||
TargetMethod = targetMethod;
|
||||
RuntimePatchesType = runtimePatchesType;
|
||||
|
||||
OutputConfig = ConfigTemplate
|
||||
.Replace("entrypoint-assembly=UnityEngine.dll", $"entrypoint-assembly={targetAssembly}")
|
||||
.Replace("entrypoint-type=Application", $"entrypoint-type={targetType}")
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
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
|
||||
{
|
||||
@@ -22,17 +25,27 @@ namespace BepInEx.Installer.Patching
|
||||
|
||||
public void Install()
|
||||
{
|
||||
switch (InstallationType)
|
||||
try
|
||||
{
|
||||
case InstallationType.Doorstop:
|
||||
DoorstopInstallation();
|
||||
break;
|
||||
case InstallationType.CryptoRng:
|
||||
throw new NotImplementedException();
|
||||
case InstallationType.AssemblyPatch:
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(InstallationType));
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,16 +54,7 @@ namespace BepInEx.Installer.Patching
|
||||
return ZipFile.Read(EmbeddedResource.GetStream("BepInEx.Installer.InstallPackage.zip"));
|
||||
}
|
||||
|
||||
private static MemoryStream ExtractZipEntry(ZipEntry entry)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream((int)entry.UncompressedSize);
|
||||
|
||||
entry.Extract(ms);
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
return ms;
|
||||
}
|
||||
#region Doorstop
|
||||
|
||||
private const string DoorstopConfig = @"[UnityDoorstop]
|
||||
# Specifies whether assembly executing is enabled
|
||||
@@ -75,7 +79,7 @@ targetAssembly=BepInEx\core\BepInEx.Preloader.dll";
|
||||
|
||||
using (var packageZip = GetPackageZip())
|
||||
{
|
||||
using (var doorstopZip = ZipFile.Read(ExtractZipEntry(packageZip[doorstopZipName])))
|
||||
using (var doorstopZip = ZipFile.Read(packageZip[doorstopZipName].ExtractEntry()))
|
||||
{
|
||||
doorstopZip["winhttp.dll"].Extract(TargetGame.DirectoryPath, ExtractExistingFileAction.OverwriteSilently);
|
||||
}
|
||||
@@ -87,8 +91,7 @@ targetAssembly=BepInEx\core\BepInEx.Preloader.dll";
|
||||
{
|
||||
string localFilename = Path.Combine(coreDirectory, Path.GetFileName(entry.FileName));
|
||||
|
||||
using (FileStream fs = new FileStream(localFilename, FileMode.Create))
|
||||
entry.Extract(fs);
|
||||
entry.ExtractEntry(localFilename);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,5 +99,157 @@ targetAssembly=BepInEx\core\BepInEx.Preloader.dll";
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Ionic.Zip;
|
||||
|
||||
namespace BepInEx.Installer
|
||||
{
|
||||
internal static class Utility
|
||||
{
|
||||
public static string CombinePaths(params string[] pathSegments)
|
||||
=> pathSegments.Aggregate(Path.Combine);
|
||||
|
||||
public static MemoryStream ExtractEntry(this ZipEntry entry)
|
||||
{
|
||||
MemoryStream ms = new MemoryStream((int)entry.UncompressedSize);
|
||||
|
||||
entry.Extract(ms);
|
||||
|
||||
ms.Position = 0;
|
||||
|
||||
return ms;
|
||||
}
|
||||
|
||||
public static void ExtractEntry(this ZipEntry entry, string filename)
|
||||
{
|
||||
using (FileStream fs = new FileStream(filename, FileMode.Create))
|
||||
entry.Extract(fs);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="..\packages\ILRepack.2.0.15\build\ILRepack.props" Condition="Exists('..\packages\ILRepack.2.0.15\build\ILRepack.props')" />
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}</ProjectGuid>
|
||||
<OutputType>Exe</OutputType>
|
||||
<RootNamespace>BepInEx.Patcher</RootNamespace>
|
||||
<AssemblyName>BepInEx.Patcher</AssemblyName>
|
||||
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<TargetFrameworkProfile />
|
||||
<NuGetPackageImportStamp>
|
||||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<StartupObject>BepInEx.Patcher.Program</StartupObject>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Legacy|AnyCPU'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>..\bin\patcher\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>embedded</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'v2018|AnyCPU'">
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<OutputPath>..\bin\patcher\</OutputPath>
|
||||
<DefineConstants>TRACE;UNITY_2018</DefineConstants>
|
||||
<Optimize>true</Optimize>
|
||||
<DebugType>embedded</DebugType>
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Mono.Cecil, Version=0.10.0.0, Culture=neutral, PublicKeyToken=50cebf1cceb9d05e, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Mono.Cecil.0.10.0\lib\net35\Mono.Cecil.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="EmbeddedResource.cs" />
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup />
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="..\bin\patcher\BepInEx.Bootstrap.dll" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
|
||||
<PropertyGroup>
|
||||
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
|
||||
</PropertyGroup>
|
||||
<Error Condition="!Exists('..\packages\ILRepack.2.0.15\build\ILRepack.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.2.0.15\build\ILRepack.props'))" />
|
||||
<Error Condition="!Exists('..\packages\ILRepack.MSBuild.Task.1.0.9\build\ILRepack.MSBuild.Task.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\ILRepack.MSBuild.Task.1.0.9\build\ILRepack.MSBuild.Task.targets'))" />
|
||||
</Target>
|
||||
<!-- ILRepack -->
|
||||
<Target Name="AfterBuild">
|
||||
<Move SourceFiles="$(OutputPath)\$(AssemblyName).exe" DestinationFiles="$(OutputPath)\$(AssemblyName)1.exe" />
|
||||
<ItemGroup>
|
||||
<InputAssemblies Include="$(OutputPath)\$(AssemblyName)1.exe" />
|
||||
<InputAssemblies Include="$(OutputPath)\Mono.Cecil.dll" />
|
||||
</ItemGroup>
|
||||
<ILRepack Parallel="true" Internalize="true" DebugInfo="true" PrimaryAssemblyFile="$(OutputPath)\$(AssemblyName)1.exe" InputAssemblies="@(InputAssemblies)" TargetKind="Exe" TargetPlatformVersion="v2" OutputFile="$(OutputPath)\$(AssemblyName).exe" />
|
||||
<Delete Files="@(InputAssemblies)" />
|
||||
<Delete Files="$(OutputPath)\BepInEx.Bootstrap.dll" />
|
||||
</Target>
|
||||
<!-- /ILRepack -->
|
||||
<Import Project="..\packages\ILRepack.MSBuild.Task.1.0.9\build\ILRepack.MSBuild.Task.targets" Condition="Exists('..\packages\ILRepack.MSBuild.Task.1.0.9\build\ILRepack.MSBuild.Task.targets')" />
|
||||
</Project>
|
||||
@@ -1,22 +0,0 @@
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
|
||||
namespace BepInEx.Patcher
|
||||
{
|
||||
internal static class EmbeddedResource
|
||||
{
|
||||
public static byte[] Get(string resourceName)
|
||||
{
|
||||
using (Stream stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceName))
|
||||
{
|
||||
int length = (int)stream.Length;
|
||||
|
||||
byte[] buffer = new byte[length];
|
||||
|
||||
stream.Read(buffer, 0, length);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,206 +0,0 @@
|
||||
using Mono.Cecil;
|
||||
using Mono.Cecil.Cil;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using MethodAttributes = Mono.Cecil.MethodAttributes;
|
||||
|
||||
namespace BepInEx.Patcher
|
||||
{
|
||||
class Program
|
||||
{
|
||||
static void WriteError(string message)
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Red;
|
||||
Console.WriteLine("Failed");
|
||||
Console.ResetColor();
|
||||
Console.WriteLine(message);
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
static void WriteSuccess()
|
||||
{
|
||||
Console.ForegroundColor = ConsoleColor.Green;
|
||||
Console.WriteLine("Success");
|
||||
Console.ResetColor();
|
||||
}
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine($"BepInEx Patcher v{Assembly.GetExecutingAssembly().GetName().Version}");
|
||||
|
||||
if (args.Length >= 1) //short circuit for specific dll patch
|
||||
Environment.Exit(PatchUnityExe(Path.GetDirectoryName(args[0]), args[0], out string message) ? 0 : 9999);
|
||||
|
||||
bool hasFound = false;
|
||||
bool hasFailure = false;
|
||||
int patchCount = 0;
|
||||
|
||||
foreach (string exePath in Directory.GetFiles(Directory.GetCurrentDirectory()))
|
||||
{
|
||||
string gameName = Path.GetFileNameWithoutExtension(exePath);
|
||||
|
||||
string managedDir = Environment.CurrentDirectory + $@"\{gameName}_Data\Managed";
|
||||
|
||||
#if UNITY_2018
|
||||
string unityOutputDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.CoreModule.dll");
|
||||
#else
|
||||
string unityOutputDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll");
|
||||
#endif
|
||||
|
||||
|
||||
if (!Directory.Exists(managedDir) || !File.Exists(unityOutputDLL))
|
||||
continue;
|
||||
|
||||
hasFound = true;
|
||||
|
||||
Console.Write($"Patching {gameName}... ");
|
||||
|
||||
if (PatchUnityExe(managedDir, unityOutputDLL, out string message))
|
||||
{
|
||||
WriteSuccess();
|
||||
patchCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
WriteError(message);
|
||||
hasFailure = true;
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
|
||||
if (!hasFound)
|
||||
Console.WriteLine("Didn't find any games to patch! Exiting.");
|
||||
else
|
||||
Console.WriteLine($"Patched {patchCount} assemblies!");
|
||||
|
||||
if (hasFailure)
|
||||
{
|
||||
Console.WriteLine("Press any key to continue...");
|
||||
Console.ReadKey();
|
||||
}
|
||||
else
|
||||
System.Threading.Thread.Sleep(3000);
|
||||
}
|
||||
|
||||
static bool PatchUnityExe(string managedDir, string unityOutputDLL, out string message)
|
||||
{
|
||||
message = null;
|
||||
|
||||
try
|
||||
{
|
||||
string injectedDLL = Path.GetFullPath($"{managedDir}\\BepInEx.Bootstrap.dll");
|
||||
File.WriteAllBytes(injectedDLL, EmbeddedResource.Get("BepInEx.Patcher.BepInEx.Bootstrap.dll"));
|
||||
|
||||
var defaultResolver = new DefaultAssemblyResolver();
|
||||
defaultResolver.AddSearchDirectory(managedDir);
|
||||
var rp = new ReaderParameters
|
||||
{
|
||||
AssemblyResolver = defaultResolver
|
||||
};
|
||||
|
||||
#if UNITY_2018
|
||||
string unityBackupDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.CoreModule.dll.bak");
|
||||
#else
|
||||
string unityBackupDLL = Path.GetFullPath($"{managedDir}\\UnityEngine.dll.bak");
|
||||
#endif
|
||||
|
||||
//determine which assembly to use as a base
|
||||
AssemblyDefinition unity = AssemblyDefinition.ReadAssembly(unityOutputDLL, rp);
|
||||
|
||||
if (!VerifyAssembly(unity, out message))
|
||||
{
|
||||
//try and fall back to .bak if exists
|
||||
if (File.Exists(unityBackupDLL))
|
||||
{
|
||||
unity.Dispose();
|
||||
unity = AssemblyDefinition.ReadAssembly(unityBackupDLL, rp);
|
||||
|
||||
if (!VerifyAssembly(unity, out message))
|
||||
{
|
||||
//can't use anything
|
||||
unity.Dispose();
|
||||
message += "\r\nThe backup is not usable.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//can't use anything
|
||||
unity.Dispose();
|
||||
message += "\r\nNo backup exists.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//make a backup of the assembly
|
||||
File.Copy(unityOutputDLL, unityBackupDLL, true);
|
||||
unity.Dispose();
|
||||
unity = AssemblyDefinition.ReadAssembly(unityBackupDLL, rp);
|
||||
}
|
||||
|
||||
//patch
|
||||
using (unity)
|
||||
using (AssemblyDefinition injected = AssemblyDefinition.ReadAssembly(injectedDLL, rp))
|
||||
{
|
||||
InjectAssembly(unity, injected);
|
||||
|
||||
unity.Write(unityOutputDLL);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
message = e.ToString();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void InjectAssembly(AssemblyDefinition unity, AssemblyDefinition injected)
|
||||
{
|
||||
//Entry point
|
||||
var originalInjectMethod = injected.MainModule.Types.First(x => x.Name == "Entrypoint")
|
||||
.Methods.First(x => x.Name == "Init");
|
||||
|
||||
var injectMethod = unity.MainModule.ImportReference(originalInjectMethod);
|
||||
|
||||
var sceneManager = unity.MainModule.Types.First(x => x.Name == "Application");
|
||||
|
||||
var voidType = unity.MainModule.ImportReference(typeof(void));
|
||||
var cctor = new MethodDefinition(".cctor",
|
||||
MethodAttributes.Static
|
||||
| MethodAttributes.Private
|
||||
| MethodAttributes.HideBySig
|
||||
| MethodAttributes.SpecialName
|
||||
| MethodAttributes.RTSpecialName,
|
||||
voidType);
|
||||
|
||||
var ilp = cctor.Body.GetILProcessor();
|
||||
ilp.Append(ilp.Create(OpCodes.Call, injectMethod));
|
||||
ilp.Append(ilp.Create(OpCodes.Ret));
|
||||
|
||||
sceneManager.Methods.Add(cctor);
|
||||
}
|
||||
|
||||
static bool VerifyAssembly(AssemblyDefinition unity, out string message)
|
||||
{
|
||||
bool canPatch = true;
|
||||
message = "";
|
||||
|
||||
//check if already patched
|
||||
if (unity.MainModule.AssemblyReferences.Any(x => x.Name.Contains("BepInEx")))
|
||||
{
|
||||
canPatch = false;
|
||||
|
||||
message += "This assembly has already been patched by BepInEx.\r\n";
|
||||
}
|
||||
|
||||
message = message.Trim();
|
||||
return canPatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("BepInEx.Patcher")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("BepInEx.Patcher")]
|
||||
[assembly: AssemblyCopyright("Copyright © Bepis 2018")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("dc89f18b-235b-4c01-ab31-af40dce5c4c7")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("5.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("5.0.0.0")]
|
||||
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<packages>
|
||||
<package id="ILRepack" version="2.0.15" targetFramework="net35" />
|
||||
<package id="ILRepack.MSBuild.Task" version="1.0.9" targetFramework="net35" />
|
||||
<package id="Mono.Cecil" version="0.10.0" targetFramework="net35" />
|
||||
</packages>
|
||||
-10
@@ -5,12 +5,6 @@ VisualStudioVersion = 15.0.27130.2027
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx", "BepInEx\BepInEx.csproj", "{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Patcher", "BepInEx.Patcher\BepInEx.Patcher.csproj", "{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9} = {4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}
|
||||
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB} = {6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BepInEx.Bootstrap", "BepInEx.Bootstrap\BepInEx.Bootstrap.csproj", "{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Submodules", "Submodules", "{BAC58F7E-AAD8-4D0C-9490-9765ACBBA6FB}"
|
||||
@@ -37,9 +31,6 @@ Global
|
||||
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.Legacy|Any CPU.Build.0 = Legacy|Any CPU
|
||||
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
|
||||
{4FFBA620-F5ED-47F9-B90C-DAD1316FD9B9}.v2018|Any CPU.Build.0 = v2018|Any CPU
|
||||
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.InstallerDebug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.Legacy|Any CPU.ActiveCfg = Legacy|Any CPU
|
||||
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7}.v2018|Any CPU.ActiveCfg = v2018|Any CPU
|
||||
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.InstallerDebug|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Legacy|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB}.Legacy|Any CPU.Build.0 = Release|Any CPU
|
||||
@@ -69,7 +60,6 @@ Global
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{DC89F18B-235B-4C01-AB31-AF40DCE5C4C7} = {A9071994-3533-4C1B-89DC-D817B676AB41}
|
||||
{6E6BC1E5-5BE8-4566-B3AE-52C4CB218AEB} = {A9071994-3533-4C1B-89DC-D817B676AB41}
|
||||
{54161CFE-FF42-4DDE-B161-3A49545DB5CD} = {BAC58F7E-AAD8-4D0C-9490-9765ACBBA6FB}
|
||||
{A15D6EE6-F954-415B-8605-8A8470CC87DC} = {BAC58F7E-AAD8-4D0C-9490-9765ACBBA6FB}
|
||||
|
||||
@@ -14,19 +14,18 @@ New-Item $tempPath -ItemType Directory -Force | Out-Null
|
||||
|
||||
#download doorstop
|
||||
|
||||
$doorstopTag = "v2.7.1.0"
|
||||
$doorstopVersion = "2.7.1.0"
|
||||
|
||||
Write-Output "Downloading Doorstop $doorstopTag"
|
||||
|
||||
$wc = New-Object System.Net.WebClient
|
||||
|
||||
$url = "https://github.com/NeighTools/UnityDoorstop/releases/download/$doorstopTag/Doorstop_x64_$doorstopVersion.zip"
|
||||
$url = "https://github.com/NeighTools/UnityDoorstop/releases/download/v$doorstopVersion/Doorstop_x64_$doorstopVersion.zip"
|
||||
$output = "$tempPath\doorstop_x64.zip"
|
||||
|
||||
$wc.DownloadFile($url, $output)
|
||||
|
||||
$url = "https://github.com/NeighTools/UnityDoorstop/releases/download/$doorstopTag/Doorstop_x86_$doorstopVersion.zip"
|
||||
$url = "https://github.com/NeighTools/UnityDoorstop/releases/download/v$doorstopVersion/Doorstop_x86_$doorstopVersion.zip"
|
||||
$output = "$tempPath\doorstop_x86.zip"
|
||||
|
||||
$wc.DownloadFile($url, $output)
|
||||
|
||||
Reference in new issue
Block a user