mirror of
https://github.com/BepInEx/BepInEx.git
synced 2026-10-19 15:51:45 +00:00
BepInEx.IL2CPP: retarget to net6.0, switch to Il2CppInterop
This commit is contained in:
1 parent
514d273371
commit
57658bdbb7
16 files changed
+379
-523
No files matched your search
@@ -32,8 +32,35 @@ internal class WindowsConsoleDriver : IConsoleDriver
|
||||
|
||||
private bool useWinApiEncoder;
|
||||
|
||||
private int ConsoleWidth => getWindowWidth?.Invoke() ?? 0;
|
||||
private int ConsoleHeight => getWindowHeight?.Invoke() ?? 0;
|
||||
private int ConsoleWidth
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return getWindowWidth?.Invoke() ?? 0;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private int ConsoleHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return getWindowHeight?.Invoke() ?? 0;
|
||||
}
|
||||
catch (IOException)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TextWriter StandardOut { get; private set; }
|
||||
public TextWriter ConsoleOut { get; private set; }
|
||||
@@ -60,14 +87,7 @@ internal class WindowsConsoleDriver : IConsoleDriver
|
||||
|
||||
public void CreateConsole(uint codepage)
|
||||
{
|
||||
try
|
||||
{
|
||||
ConsoleWindow.Attach();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Can't allocate console; we can still continue on in case there is stdout set up
|
||||
}
|
||||
ConsoleWindow.Attach();
|
||||
|
||||
// Make sure of ConsoleEncoding helper class because on some Monos
|
||||
// Encoding.GetEncoding throws NotImplementedException on most codepages
|
||||
@@ -90,8 +110,7 @@ internal class WindowsConsoleDriver : IConsoleDriver
|
||||
AutoFlush = true
|
||||
};
|
||||
|
||||
var consoleOutStream =
|
||||
OpenFileStream(ConsoleWindow.ConsoleOutHandle != IntPtr.Zero ? ConsoleWindow.ConsoleOutHandle : stdout);
|
||||
var consoleOutStream = OpenFileStream(ConsoleWindow.ConsoleOutHandle);
|
||||
// Can't use Console.OutputEncoding because it can be null (i.e. not preference by user)
|
||||
ConsoleOut = new StreamWriter(consoleOutStream,
|
||||
useWinApiEncoder ? ConsoleEncoding.OutputEncoding : Utility.UTF8NoBom)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.Logging;
|
||||
using UnhollowerBaseLib;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Logger = BepInEx.Logging.Logger;
|
||||
|
||||
namespace BepInEx.IL2CPP;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Description>BepInEx support library for Il2Cpp games</Description>
|
||||
<TargetFramework>netstandard2.1</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<AssemblyTitle>BepInEx.IL2CPP</AssemblyTitle>
|
||||
<Product>BepInEx.IL2CPP</Product>
|
||||
@@ -24,14 +24,11 @@
|
||||
<ProjectReference Include="..\BepInEx.Preloader.Core\BepInEx.Preloader.Core.csproj" PrivateAssets="All"/>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="BepInEx.Il2Cpp.TlsAdapter" Version="1.0.1"/>
|
||||
<PackageReference Include="HarmonyX" Version="2.10.0"/>
|
||||
<PackageReference Include="Iced" Version="1.17.0"/>
|
||||
<PackageReference Include="Il2CppAssemblyUnhollower.BaseLib" Version="0.4.33.3"/>
|
||||
<PackageReference Include="Il2CppAssemblyUnhollower.BaseLib.Legacy" Version="0.4.33.3"/>
|
||||
<PackageReference Include="Il2CppAssemblyUnhollower.Lib" Version="0.4.33.3"/>
|
||||
<PackageReference Include="Il2CppDumper" Version="6.6.4"/>
|
||||
<PackageReference Include="Il2CppReferenceLibs.Core" Version="2.0.0" IncludeAssets="compile" PrivateAssets="all"/>
|
||||
<PackageReference Include="Il2CppInterop.Generator" Version="1.0.0-pre.1"/>
|
||||
<PackageReference Include="Il2CppInterop.ReferenceLibs" Version="1.0.0-pre.1"/>
|
||||
<PackageReference Include="Il2CppInterop.Runtime" Version="1.0.0-pre.1"/>
|
||||
<PackageReference Include="MonoMod.RuntimeDetour" Version="22.5.1.1"/>
|
||||
<PackageReference Include="Samboy063.Cpp2IL.Core" Version="2022.0.5"/>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -7,12 +7,13 @@ using System.Runtime.InteropServices;
|
||||
using BepInEx.Logging;
|
||||
using HarmonyLib;
|
||||
using HarmonyLib.Public.Patching;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppInterop.Runtime.Runtime;
|
||||
using Il2CppInterop.Runtime.Runtime.VersionSpecific.MethodInfo;
|
||||
using MonoMod.Cil;
|
||||
using MonoMod.RuntimeDetour;
|
||||
using MonoMod.Utils;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerBaseLib.Runtime;
|
||||
using UnhollowerBaseLib.Runtime.VersionSpecific.MethodInfo;
|
||||
using Logger = BepInEx.Logging.Logger;
|
||||
using ValueType = Il2CppSystem.ValueType;
|
||||
using Void = Il2CppSystem.Void;
|
||||
|
||||
@@ -21,16 +22,16 @@ namespace BepInEx.IL2CPP.Hook;
|
||||
public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher
|
||||
{
|
||||
private static readonly MethodInfo IL2CPPToManagedStringMethodInfo
|
||||
= AccessTools.Method(typeof(UnhollowerBaseLib.IL2CPP),
|
||||
nameof(UnhollowerBaseLib.IL2CPP.Il2CppStringToManaged));
|
||||
= AccessTools.Method(typeof(Il2CppInterop.Runtime.IL2CPP),
|
||||
nameof(Il2CppInterop.Runtime.IL2CPP.Il2CppStringToManaged));
|
||||
|
||||
private static readonly MethodInfo ManagedToIL2CPPStringMethodInfo
|
||||
= AccessTools.Method(typeof(UnhollowerBaseLib.IL2CPP),
|
||||
nameof(UnhollowerBaseLib.IL2CPP.ManagedStringToIl2Cpp));
|
||||
= AccessTools.Method(typeof(Il2CppInterop.Runtime.IL2CPP),
|
||||
nameof(Il2CppInterop.Runtime.IL2CPP.ManagedStringToIl2Cpp));
|
||||
|
||||
private static readonly MethodInfo ObjectBaseToPtrMethodInfo
|
||||
= AccessTools.Method(typeof(UnhollowerBaseLib.IL2CPP),
|
||||
nameof(UnhollowerBaseLib.IL2CPP.Il2CppObjectBaseToPtr));
|
||||
= AccessTools.Method(typeof(Il2CppInterop.Runtime.IL2CPP),
|
||||
nameof(Il2CppInterop.Runtime.IL2CPP.Il2CppObjectBaseToPtr));
|
||||
|
||||
private static readonly MethodInfo ReportExceptionMethodInfo
|
||||
= AccessTools.Method(typeof(IL2CPPDetourMethodPatcher), nameof(ReportException));
|
||||
@@ -78,12 +79,12 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher
|
||||
{
|
||||
try
|
||||
{
|
||||
var methodField = UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Original);
|
||||
var methodField = Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Original);
|
||||
|
||||
if (methodField == null)
|
||||
{
|
||||
var fieldInfoField =
|
||||
UnhollowerUtils.GetIl2CppFieldInfoPointerFieldForGeneratedFieldAccessor(Original);
|
||||
Il2CppInteropUtils.GetIl2CppFieldInfoPointerFieldForGeneratedFieldAccessor(Original);
|
||||
|
||||
if (fieldInfoField != null)
|
||||
throw new
|
||||
@@ -95,7 +96,7 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher
|
||||
|
||||
// Get the native MethodInfo struct for the target method
|
||||
originalNativeMethodInfo =
|
||||
UnityVersionHandler.Wrap((Il2CppMethodInfo*)(IntPtr)methodField.GetValue(null));
|
||||
UnityVersionHandler.Wrap((Il2CppMethodInfo*) (IntPtr) methodField.GetValue(null));
|
||||
|
||||
// Create a trampoline from the original target method
|
||||
var trampolinePtr =
|
||||
@@ -159,16 +160,16 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher
|
||||
|
||||
// Remove il2cpp_object_get_virtual_method
|
||||
if (cursor.TryGotoNext(x => x.MatchLdarg(0),
|
||||
x => x.MatchCall(typeof(UnhollowerBaseLib.IL2CPP),
|
||||
nameof(UnhollowerBaseLib.IL2CPP.Il2CppObjectBaseToPtr)),
|
||||
x => x.MatchCall(typeof(Il2CppInterop.Runtime.IL2CPP),
|
||||
nameof(Il2CppInterop.Runtime.IL2CPP.Il2CppObjectBaseToPtr)),
|
||||
x => x.MatchLdsfld(out _),
|
||||
x => x.MatchCall(typeof(UnhollowerBaseLib.IL2CPP),
|
||||
nameof(UnhollowerBaseLib.IL2CPP.il2cpp_object_get_virtual_method))))
|
||||
x => x.MatchCall(typeof(Il2CppInterop.Runtime.IL2CPP),
|
||||
nameof(Il2CppInterop.Runtime.IL2CPP.il2cpp_object_get_virtual_method))))
|
||||
cursor.RemoveRange(4);
|
||||
else
|
||||
cursor.Goto(0)
|
||||
.GotoNext(x =>
|
||||
x.MatchLdsfld(UnhollowerUtils
|
||||
x.MatchLdsfld(Il2CppInteropUtils
|
||||
.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Original)))
|
||||
.Remove();
|
||||
|
||||
@@ -199,7 +200,7 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher
|
||||
|
||||
private DynamicMethodDefinition GenerateNativeToManagedTrampoline(MethodInfo targetManagedMethodInfo)
|
||||
{
|
||||
// managedParams are the unhollower types used on the managed side
|
||||
// managedParams are the interop types used on the managed side
|
||||
// unmanagedParams are IntPtr references that are used by IL2CPP compiled assembly
|
||||
var paramStartIndex = 0;
|
||||
|
||||
@@ -240,7 +241,7 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher
|
||||
il.BeginExceptionBlock();
|
||||
|
||||
// Declare a list of variables to dereference back to the original pointers.
|
||||
// This is required due to the needed unhollower type conversions, so we can't directly pass some addresses as byref types
|
||||
// This is required due to the needed interop type conversions, so we can't directly pass some addresses as byref types
|
||||
var indirectVariables = new LocalBuilder[managedParams.Length];
|
||||
|
||||
if (!Original.IsStatic)
|
||||
@@ -283,8 +284,9 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher
|
||||
if (hasReturnBuffer)
|
||||
{
|
||||
uint align = 0;
|
||||
var size = UnhollowerBaseLib.IL2CPP.il2cpp_class_value_size(Il2CppTypeToClassPointer(managedReturnType),
|
||||
ref align);
|
||||
var size =
|
||||
Il2CppInterop.Runtime.IL2CPP.il2cpp_class_value_size(Il2CppTypeToClassPointer(managedReturnType),
|
||||
ref align);
|
||||
|
||||
il.Emit(OpCodes.Ldarg_0);
|
||||
il.Emit(OpCodes.Ldloc, managedReturnVariable);
|
||||
@@ -326,14 +328,16 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher
|
||||
// Struct that's passed on the stack => handle as general struct
|
||||
uint align = 0;
|
||||
var fixedSize =
|
||||
UnhollowerBaseLib.IL2CPP.il2cpp_class_value_size(Il2CppTypeToClassPointer(managedType), ref align);
|
||||
Il2CppInterop.Runtime.IL2CPP.il2cpp_class_value_size(Il2CppTypeToClassPointer(managedType), ref align);
|
||||
return GetFixedSizeStructType(fixedSize);
|
||||
}
|
||||
else if (managedType == typeof(string) || managedType.IsSubclassOf(typeof(Il2CppObjectBase))
|
||||
) // General reference type
|
||||
{
|
||||
return typeof(IntPtr);
|
||||
} else if(managedType == typeof(bool)) {
|
||||
}
|
||||
else if (managedType == typeof(bool))
|
||||
{
|
||||
// bool is byte in Il2Cpp, but int in CLR => force size to be correct
|
||||
return typeof(byte);
|
||||
}
|
||||
@@ -353,8 +357,8 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher
|
||||
{
|
||||
if (type == typeof(void))
|
||||
return Il2CppClassPointerStore<Void>.NativeClassPtr;
|
||||
return (IntPtr)typeof(Il2CppClassPointerStore<>).MakeGenericType(type).GetField("NativeClassPtr")
|
||||
.GetValue(null);
|
||||
return (IntPtr) typeof(Il2CppClassPointerStore<>).MakeGenericType(type).GetField("NativeClassPtr")
|
||||
.GetValue(null);
|
||||
}
|
||||
|
||||
private static void EmitConvertArgumentToManaged(ILGenerator il,
|
||||
@@ -373,8 +377,8 @@ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher
|
||||
// We don't handle byref structs on x86 yet but we're yet to encounter those
|
||||
il.Emit(Environment.Is64BitProcess ? OpCodes.Ldarg : OpCodes.Ldarga_S, argIndex);
|
||||
il.Emit(OpCodes.Call,
|
||||
AccessTools.Method(typeof(UnhollowerBaseLib.IL2CPP),
|
||||
nameof(UnhollowerBaseLib.IL2CPP.il2cpp_value_box)));
|
||||
AccessTools.Method(typeof(Il2CppInterop.Runtime.IL2CPP),
|
||||
nameof(Il2CppInterop.Runtime.IL2CPP.il2cpp_value_box)));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System;
|
||||
using UnhollowerRuntimeLib;
|
||||
using Il2CppInterop.Runtime.Injection;
|
||||
|
||||
namespace BepInEx.IL2CPP.Hook;
|
||||
|
||||
|
||||
@@ -12,10 +12,9 @@ using BepInEx.Logging;
|
||||
using BepInEx.Preloader.Core;
|
||||
using BepInEx.Preloader.Core.Logging;
|
||||
using HarmonyLib.Public.Patching;
|
||||
using Il2Cpp.TlsAdapter;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppInterop.Runtime.Injection;
|
||||
using MonoMod.Utils;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using Logger = BepInEx.Logging.Logger;
|
||||
|
||||
@@ -24,7 +23,6 @@ namespace BepInEx.IL2CPP;
|
||||
public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
{
|
||||
private static RuntimeInvokeDetourDelegate originalInvoke;
|
||||
private static InstallUnityTlsInterfaceDelegate originalInstallUnityTlsInterface;
|
||||
|
||||
private static readonly ConfigEntry<bool> ConfigUnityLogging = ConfigFile.CoreConfig.Bind(
|
||||
"Logging", "UnityLogListening",
|
||||
@@ -37,7 +35,6 @@ public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
"Include unity log messages in log file output.");
|
||||
|
||||
private static FastNativeDetour RuntimeInvokeDetour { get; set; }
|
||||
private static FastNativeDetour InstallUnityTlsInterfaceDetour { get; set; }
|
||||
|
||||
public static IL2CPPChainloader Instance { get; set; }
|
||||
|
||||
@@ -57,11 +54,12 @@ public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
|
||||
public override void Initialize(string gameExePath = null)
|
||||
{
|
||||
GeneratedDatabasesUtil.DatabasesLocationOverride = Preloader.IL2CPPUnhollowedPath;
|
||||
PatchManager.ResolvePatcher += IL2CPPDetourMethodPatcher.TryResolve;
|
||||
|
||||
base.Initialize(gameExePath);
|
||||
Instance = this;
|
||||
Logger.Log(LogLevel.Info, $"Runtime version: {Environment.Version}");
|
||||
Logger.Log(LogLevel.Info, $"Runtime information: {RuntimeInformation.FrameworkDescription}");
|
||||
|
||||
ClassInjector.Detour = new UnhollowerDetourHandler();
|
||||
|
||||
@@ -82,30 +80,12 @@ public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
FastNativeDetour.CreateAndApply(runtimeInvokePtr, OnInvokeMethod, out originalInvoke,
|
||||
CallingConvention.Cdecl);
|
||||
|
||||
if (gameAssemblyModule.BaseAddress.TryGetFunction("il2cpp_unity_install_unitytls_interface",
|
||||
out var installTlsPtr))
|
||||
InstallUnityTlsInterfaceDetour =
|
||||
FastNativeDetour.CreateAndApply(installTlsPtr, OnInstallUnityTlsInterface,
|
||||
out originalInstallUnityTlsInterface, CallingConvention.Cdecl);
|
||||
|
||||
Logger.Log(LogLevel.Debug, "Initializing TLS adapters");
|
||||
Il2CppTlsAdapter.Initialize();
|
||||
|
||||
PreloaderLogger.Log.Log(LogLevel.Debug, "Runtime invoke patched");
|
||||
}
|
||||
|
||||
private static void OnInstallUnityTlsInterface(IntPtr unityTlsInterfaceStruct)
|
||||
{
|
||||
Logger.Log(LogLevel.Debug, $"Captured UnityTls interface at {unityTlsInterfaceStruct.ToInt64():x8}");
|
||||
Il2CppTlsAdapter.Options.UnityTlsInterface = unityTlsInterfaceStruct;
|
||||
originalInstallUnityTlsInterface(unityTlsInterfaceStruct);
|
||||
InstallUnityTlsInterfaceDetour.Dispose();
|
||||
InstallUnityTlsInterfaceDetour = null;
|
||||
}
|
||||
|
||||
private static IntPtr OnInvokeMethod(IntPtr method, IntPtr obj, IntPtr parameters, IntPtr exc)
|
||||
{
|
||||
var methodName = Marshal.PtrToStringAnsi(UnhollowerBaseLib.IL2CPP.il2cpp_method_get_name(method));
|
||||
var methodName = Marshal.PtrToStringAnsi(Il2CppInterop.Runtime.IL2CPP.il2cpp_method_get_name(method));
|
||||
|
||||
var unhook = false;
|
||||
|
||||
@@ -157,7 +137,7 @@ public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
{
|
||||
var type = pluginAssembly.GetType(pluginInfo.TypeName);
|
||||
|
||||
var pluginInstance = (BasePlugin)Activator.CreateInstance(type);
|
||||
var pluginInstance = (BasePlugin) Activator.CreateInstance(type);
|
||||
|
||||
pluginInstance.Load();
|
||||
|
||||
@@ -166,7 +146,4 @@ public class IL2CPPChainloader : BaseChainloader<BasePlugin>
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate IntPtr RuntimeInvokeDetourDelegate(IntPtr method, IntPtr obj, IntPtr parameters, IntPtr exc);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private delegate void InstallUnityTlsInterfaceDelegate(IntPtr unityTlsInterfaceStruct);
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Http;
|
||||
using System.Reflection;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.Logging;
|
||||
using Cpp2IL.Core;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Generator;
|
||||
using Il2CppInterop.Runtime;
|
||||
using LibCpp2IL;
|
||||
using Mono.Cecil;
|
||||
|
||||
namespace BepInEx.IL2CPP;
|
||||
|
||||
internal static class Il2CppInteropManager
|
||||
{
|
||||
private static readonly ConfigEntry<bool> UpdateInteropAssemblies =
|
||||
ConfigFile.CoreConfig.Bind("IL2CPP",
|
||||
"UpdateInteropAssemblies",
|
||||
true,
|
||||
new StringBuilder()
|
||||
.AppendLine("Whether to run Il2CppInterop automatically to generate Il2Cpp support assemblies when they are outdated.")
|
||||
.AppendLine("If disabled assemblies in `BepInEx/interop` won't be updated between game or BepInEx updates!")
|
||||
.ToString());
|
||||
|
||||
private static readonly ConfigEntry<string> UnityBaseLibrariesSource = ConfigFile.CoreConfig.Bind(
|
||||
"IL2CPP", "UnityBaseLibrariesSource",
|
||||
"https://unity.bepinex.dev/libraries/{VERSION}.zip",
|
||||
new StringBuilder()
|
||||
.AppendLine("URL to the ZIP of managed Unity base libraries.")
|
||||
.AppendLine("The base libraries are used by Il2CppInterop to generate unhollowed Unity assemblies.")
|
||||
.AppendLine("The URL can include {VERSION} template which will be replaced with the game's Unity engine version.")
|
||||
.ToString());
|
||||
|
||||
private static readonly ConfigEntry<bool> DumpDummyAssemblies = ConfigFile.CoreConfig.Bind(
|
||||
"IL2CPP", "DumpDummyAssemblies",
|
||||
false,
|
||||
"If enabled, BepInEx will save dummy assemblies generated by an Cpp2IL dumper into BepInEx/dummy.");
|
||||
|
||||
private static readonly ManualLogSource Logger = BepInEx.Logging.Logger.CreateLogSource("InteropManager");
|
||||
|
||||
public static string GameAssemblyPath => Path.Combine(Paths.GameRootPath, "GameAssembly.dll");
|
||||
|
||||
private static string HashPath => Path.Combine(IL2CPPInteropAssemblyPath, "assembly-hash.txt");
|
||||
|
||||
private static string UnityBaseLibsDirectory => Path.Combine(Paths.BepInExRootPath, "unity-libs");
|
||||
|
||||
internal static string IL2CPPInteropAssemblyPath => Path.Combine(Paths.BepInExRootPath, "interop");
|
||||
|
||||
private static string ComputeHash()
|
||||
{
|
||||
using var md5 = MD5.Create();
|
||||
|
||||
static void HashFile(ICryptoTransform hash, string file)
|
||||
{
|
||||
const int defaultCopyBufferSize = 81920;
|
||||
using var fs = File.OpenRead(file);
|
||||
var buffer = new byte[defaultCopyBufferSize];
|
||||
int read;
|
||||
while ((read = fs.Read(buffer)) > 0)
|
||||
hash.TransformBlock(buffer, 0, read, buffer, 0);
|
||||
}
|
||||
|
||||
static void HashString(ICryptoTransform hash, string str)
|
||||
{
|
||||
var buffer = Encoding.UTF8.GetBytes(str);
|
||||
hash.TransformBlock(buffer, 0, buffer.Length, buffer, 0);
|
||||
}
|
||||
|
||||
HashFile(md5, GameAssemblyPath);
|
||||
|
||||
if (Directory.Exists(UnityBaseLibsDirectory))
|
||||
foreach (var file in Directory.EnumerateFiles(UnityBaseLibsDirectory, "*.dll",
|
||||
SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
HashString(md5, Path.GetFileName(file));
|
||||
HashFile(md5, file);
|
||||
}
|
||||
|
||||
// Hash some common dependencies as they can affect output
|
||||
HashString(md5, typeof(InteropAssemblyGenerator).Assembly.GetName().Version.ToString());
|
||||
HashString(md5, typeof(Cpp2IlApi).Assembly.GetName().Version.ToString());
|
||||
|
||||
md5.TransformFinalBlock(new byte[0], 0, 0);
|
||||
|
||||
return Utility.ByteArrayToString(md5.Hash);
|
||||
}
|
||||
|
||||
private static bool CheckIfGenerationRequired()
|
||||
{
|
||||
static bool NeedGenerationOrSkip()
|
||||
{
|
||||
if (!UpdateInteropAssemblies.Value)
|
||||
{
|
||||
var hash = ComputeHash();
|
||||
Logger.LogWarning($"Interop assemblies are possibly out of date. To disable this message, create file {HashPath} with the following contents: {hash}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(IL2CPPInteropAssemblyPath))
|
||||
return true;
|
||||
|
||||
if (!File.Exists(HashPath))
|
||||
return NeedGenerationOrSkip();
|
||||
|
||||
if (ComputeHash() != File.ReadAllText(HashPath) && NeedGenerationOrSkip())
|
||||
{
|
||||
Logger.LogInfo("Detected outdated interop assemblies, will regenerate them now");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Assembly ResolveInteropAssemblies(object sender, ResolveEventArgs args)
|
||||
{
|
||||
var assemblyName = new AssemblyName(args.Name);
|
||||
return Utility.TryResolveDllAssembly(assemblyName, IL2CPPInteropAssemblyPath, out var foundAssembly)
|
||||
? foundAssembly
|
||||
: null;
|
||||
}
|
||||
|
||||
public static void Initialize()
|
||||
{
|
||||
GeneratedDatabasesUtil.DatabasesLocationOverride = IL2CPPInteropAssemblyPath;
|
||||
|
||||
var interopLogger = BepInEx.Logging.Logger.CreateLogSource("Il2CppInterop");
|
||||
Il2CppInterop.Runtime.Logger.InfoHandler += s => interopLogger.LogInfo(s.Trim());
|
||||
Il2CppInterop.Runtime.Logger.WarningHandler += s => interopLogger.LogWarning(s.Trim());
|
||||
Il2CppInterop.Runtime.Logger.TraceHandler += s => interopLogger.LogDebug(s.Trim());
|
||||
Il2CppInterop.Runtime.Logger.ErrorHandler += s => interopLogger.LogError(s.Trim());
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyResolve += ResolveInteropAssemblies;
|
||||
|
||||
if (!CheckIfGenerationRequired())
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
Directory.CreateDirectory(IL2CPPInteropAssemblyPath);
|
||||
Directory.EnumerateFiles(IL2CPPInteropAssemblyPath, "*.dll").Do(File.Delete);
|
||||
|
||||
AppDomain.CurrentDomain.AddCecilPlatformAssemblies(UnityBaseLibsDirectory);
|
||||
var unityVersion = Preloader.UnityVersion.ToString(3);
|
||||
DownloadUnityAssemblies(unityVersion);
|
||||
var dummyAssemblies = RunCpp2Il();
|
||||
|
||||
if (DumpDummyAssemblies.Value)
|
||||
{
|
||||
var dummyPath = Path.Combine(Paths.BepInExRootPath, "dummy");
|
||||
Directory.CreateDirectory(dummyPath);
|
||||
foreach (var assemblyDefinition in dummyAssemblies)
|
||||
assemblyDefinition.Write(Path.Combine(dummyPath, $"{assemblyDefinition.Name.Name}.dll"));
|
||||
}
|
||||
|
||||
RunIl2CppInteropGenerator(dummyAssemblies);
|
||||
|
||||
File.WriteAllText(HashPath, ComputeHash());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.LogError($"Failed to generate Il2Cpp interop assemblies: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void DownloadUnityAssemblies(string unityVersion)
|
||||
{
|
||||
var source =
|
||||
UnityBaseLibrariesSource.Value.Replace("{VERSION}", unityVersion);
|
||||
|
||||
if (!string.IsNullOrEmpty(source))
|
||||
{
|
||||
Logger.LogMessage("Downloading unity base libraries");
|
||||
|
||||
Directory.CreateDirectory(UnityBaseLibsDirectory);
|
||||
Directory.EnumerateFiles(UnityBaseLibsDirectory, "*.dll").Do(File.Delete);
|
||||
|
||||
using var httpClient = new HttpClient();
|
||||
using var zipStream = httpClient.GetStreamAsync(source).GetAwaiter().GetResult();
|
||||
using var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Read);
|
||||
|
||||
Logger.LogMessage("Extracting downloaded unity base libraries");
|
||||
zipArchive.ExtractToDirectory(UnityBaseLibsDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<AssemblyDefinition> RunCpp2Il()
|
||||
{
|
||||
Logger.LogMessage("Running Cpp2IL to generate dummy assemblies");
|
||||
|
||||
|
||||
var metadataPath = Path.Combine(Paths.GameRootPath,
|
||||
$"{Paths.ProcessName}_Data",
|
||||
"il2cpp_data",
|
||||
"Metadata",
|
||||
"global-metadata.dat");
|
||||
|
||||
List<AssemblyDefinition> sourceAssemblies;
|
||||
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
var cpp2IlLogger = BepInEx.Logging.Logger.CreateLogSource("Cpp2IL");
|
||||
|
||||
Cpp2IL.Core.Logger.VerboseLog += (message, s) =>
|
||||
cpp2IlLogger.LogDebug($"[{s}] {message.Trim()}");
|
||||
Cpp2IL.Core.Logger.InfoLog += (message, s) =>
|
||||
cpp2IlLogger.LogInfo($"[{s}] {message.Trim()}");
|
||||
Cpp2IL.Core.Logger.WarningLog += (message, s) =>
|
||||
cpp2IlLogger.LogWarning($"[{s}] {message.Trim()}");
|
||||
Cpp2IL.Core.Logger.ErrorLog += (message, s) =>
|
||||
cpp2IlLogger.LogError($"[{s}] {message.Trim()}");
|
||||
|
||||
var cpp2IlUnityVersion =
|
||||
Cpp2IlApi.DetermineUnityVersion(Paths.ExecutablePath,
|
||||
Path.Combine(Paths.GameRootPath, $"{Paths.ProcessName}_Data"));
|
||||
Cpp2IlApi.InitializeLibCpp2Il(GameAssemblyPath, metadataPath, cpp2IlUnityVersion, false);
|
||||
sourceAssemblies = Cpp2IlApi.MakeDummyDLLs();
|
||||
Cpp2IlApi.RunAttributeRestorationForAllAssemblies(null,
|
||||
LibCpp2IlMain.MetadataVersion >= 29 ||
|
||||
LibCpp2IlMain.Binary!.InstructionSet is InstructionSet.X86_32
|
||||
or InstructionSet.X86_64);
|
||||
Cpp2IlApi.DisposeAndCleanupAll();
|
||||
|
||||
stopwatch.Stop();
|
||||
Logger.LogInfo($"Cpp2IL finished in {stopwatch.Elapsed}");
|
||||
|
||||
return sourceAssemblies;
|
||||
}
|
||||
|
||||
private static void RunIl2CppInteropGenerator(List<AssemblyDefinition> sourceAssemblies)
|
||||
{
|
||||
var opts = new GeneratorOptions
|
||||
{
|
||||
GameAssemblyPath = GameAssemblyPath,
|
||||
Source = sourceAssemblies,
|
||||
OutputDir = IL2CPPInteropAssemblyPath,
|
||||
UnityBaseLibsDir = Directory.Exists(UnityBaseLibsDirectory) ? UnityBaseLibsDirectory : null,
|
||||
NoCopyRuntimeLibs = true
|
||||
};
|
||||
|
||||
var renameMapLocation = Path.Combine(Paths.BepInExRootPath, "DeobfuscationMap.csv.gz");
|
||||
if (File.Exists(renameMapLocation))
|
||||
{
|
||||
Logger.LogInfo("Parsing deobfuscation rename mappings");
|
||||
|
||||
using var fileStream =
|
||||
new FileStream(renameMapLocation, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||
using var gzipStream = new GZipStream(fileStream, CompressionMode.Decompress);
|
||||
|
||||
using var reader = new StreamReader(gzipStream, Encoding.UTF8, false);
|
||||
while (!reader.EndOfStream)
|
||||
{
|
||||
var line = reader.ReadLine();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(line) || line.StartsWith("#"))
|
||||
continue;
|
||||
|
||||
var mapping = line.Split(';');
|
||||
|
||||
if (mapping.Length != 2)
|
||||
continue;
|
||||
|
||||
opts.RenameMap[mapping[0]] = mapping[1];
|
||||
}
|
||||
}
|
||||
|
||||
Logger.LogInfo("Generating interop assemblies");
|
||||
|
||||
InteropAssemblyGenerator.GenerateInteropAssemblies(opts);
|
||||
|
||||
sourceAssemblies.Do(x => x.Dispose());
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ public class IL2CPPLogSource : ILogSource
|
||||
public IL2CPPLogSource()
|
||||
{
|
||||
var loggerPointer = Marshal.GetFunctionPointerForDelegate(new IL2CPPLogCallbackDelegate(IL2CPPLogCallback));
|
||||
UnhollowerBaseLib.IL2CPP.il2cpp_register_log_callback(loggerPointer);
|
||||
Il2CppInterop.Runtime.IL2CPP.il2cpp_register_log_callback(loggerPointer);
|
||||
}
|
||||
|
||||
public string SourceName { get; } = "IL2CPP";
|
||||
|
||||
@@ -10,9 +10,9 @@ public class IL2CPPUnityLogSource : ILogSource
|
||||
{
|
||||
Application.s_LogCallbackHandler = new Action<string, string, LogType>(UnityLogCallback);
|
||||
|
||||
UnhollowerBaseLib.IL2CPP
|
||||
.ResolveICall<
|
||||
SetLogCallbackDefinedDelegate>("UnityEngine.Application::SetLogCallbackDefined")(true);
|
||||
Il2CppInterop.Runtime.IL2CPP
|
||||
.ResolveICall<
|
||||
SetLogCallbackDefinedDelegate>("UnityEngine.Application::SetLogCallbackDefined")(true);
|
||||
}
|
||||
|
||||
public string SourceName { get; } = "Unity";
|
||||
|
||||
@@ -7,8 +7,7 @@ using BepInEx.Preloader.Core;
|
||||
using BepInEx.Preloader.Core.Logging;
|
||||
using BepInEx.Preloader.Core.Patching;
|
||||
using BepInEx.Preloader.RuntimeFixes;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerBaseLib.Runtime;
|
||||
using Il2CppInterop.Runtime.Runtime;
|
||||
|
||||
namespace BepInEx.IL2CPP;
|
||||
|
||||
@@ -23,12 +22,9 @@ public static class Preloader
|
||||
|
||||
#endregion
|
||||
|
||||
public static string IL2CPPUnhollowedPath => ProxyAssemblyGenerator.IL2CPPUnhollowedPath;
|
||||
|
||||
private static PreloaderConsoleListener PreloaderLog { get; set; }
|
||||
|
||||
internal static ManualLogSource Log => PreloaderLogger.Log;
|
||||
internal static ManualLogSource UnhollowerLog { get; set; }
|
||||
|
||||
// TODO: This is not needed, maybe remove? (Instance is saved in IL2CPPChainloader itself)
|
||||
private static IL2CPPChainloader Chainloader { get; set; }
|
||||
@@ -55,20 +51,11 @@ public static class Preloader
|
||||
ChainloaderLogHelper.PrintLogInfo(Log);
|
||||
|
||||
Log.Log(LogLevel.Debug, $"Game executable path: {Paths.ExecutablePath}");
|
||||
Log.Log(LogLevel.Debug, $"Unhollowed assembly directory: {IL2CPPUnhollowedPath}");
|
||||
Log.Log(LogLevel.Debug, $"Unhollowed assembly directory: {Il2CppInteropManager.IL2CPPInteropAssemblyPath}");
|
||||
Log.Log(LogLevel.Debug, $"BepInEx root path: {Paths.BepInExRootPath}");
|
||||
|
||||
UnhollowerLog = Logger.CreateLogSource("Unhollower");
|
||||
LogSupport.InfoHandler += UnhollowerLog.LogInfo;
|
||||
LogSupport.WarningHandler += UnhollowerLog.LogWarning;
|
||||
LogSupport.TraceHandler += UnhollowerLog.LogDebug;
|
||||
LogSupport.ErrorHandler += UnhollowerLog.LogError;
|
||||
|
||||
InitializeUnityVersion();
|
||||
|
||||
if (ProxyAssemblyGenerator.CheckIfGenerationRequired())
|
||||
ProxyAssemblyGenerator.GenerateAssemblies();
|
||||
|
||||
Il2CppInteropManager.Initialize();
|
||||
UnityVersionHandler.Initialize(UnityVersion.Major, UnityVersion.Minor, UnityVersion.Build);
|
||||
|
||||
using (var assemblyPatcher = new AssemblyPatcher())
|
||||
@@ -77,7 +64,7 @@ public static class Preloader
|
||||
|
||||
Log.LogInfo($"{assemblyPatcher.PatcherContext.PatcherPlugins.Count} patcher plugin{(assemblyPatcher.PatcherContext.PatcherPlugins.Count == 1 ? "" : "s")} loaded");
|
||||
|
||||
assemblyPatcher.LoadAssemblyDirectories(IL2CPPUnhollowedPath);
|
||||
assemblyPatcher.LoadAssemblyDirectories(Il2CppInteropManager.IL2CPPInteropAssemblyPath);
|
||||
|
||||
Log.LogInfo($"{assemblyPatcher.PatcherContext.PatcherPlugins.Count} assemblies discovered");
|
||||
|
||||
|
||||
@@ -1,410 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Http;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using AssemblyUnhollower;
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.Logging;
|
||||
using Cpp2IL.Core;
|
||||
using HarmonyLib;
|
||||
using Il2CppDumper;
|
||||
using LibCpp2IL;
|
||||
using Mono.Cecil;
|
||||
using UnhollowerBaseLib;
|
||||
using Logger = BepInEx.Logging.Logger;
|
||||
|
||||
namespace BepInEx.IL2CPP;
|
||||
|
||||
internal static class ProxyAssemblyGenerator
|
||||
{
|
||||
private static readonly ConfigEntry<bool> ConfigUpdateUnhollowedAssemblies =
|
||||
ConfigFile.CoreConfig.Bind("IL2CPP",
|
||||
"UpdateUnhollowedAssemblies",
|
||||
true,
|
||||
new StringBuilder()
|
||||
.AppendLine("Whether to run Il2CppAssemblyUnhollower automatically to generate Il2Cpp support assemblies when they are outdated.")
|
||||
.AppendLine("If disabled assemblies in `BepInEx/unhollowed` won't be updated between game or BepInEx updates!")
|
||||
.ToString());
|
||||
|
||||
private static readonly ConfigEntry<string> ConfigUnityBaseLibrariesSource = ConfigFile.CoreConfig.Bind(
|
||||
"IL2CPP", "UnityBaseLibrariesSource",
|
||||
"http://unity.bepinex.dev/libraries/{VERSION}.zip",
|
||||
new StringBuilder()
|
||||
.AppendLine("URL to the ZIP of managed Unity base libraries.")
|
||||
.AppendLine("The base libraries are used by Il2CppUnhollower to generate unhollowed Unity assemblies")
|
||||
.AppendLine("The URL template MUST use HTTP.")
|
||||
.AppendLine("The URL can include {VERSION} template which will be replaced with the game's Unity engine version")
|
||||
.ToString());
|
||||
|
||||
private static readonly ConfigEntry<IL2CPPDumperType> ConfigIl2CppDumperType = ConfigFile.CoreConfig.Bind(
|
||||
"IL2CPP", "Il2CppDumperType",
|
||||
IL2CPPDumperType.Cpp2IL,
|
||||
new StringBuilder()
|
||||
.AppendLine("The IL2CPP metadata dumper tool to use when generating dummy assemblies for Il2CppAssemblyUnhollower.")
|
||||
.AppendLine("Il2CppDumper - Default. The traditional choice that has been used by BepInEx.")
|
||||
.AppendLine("Cpp2IL - Experimental, may provide better results than Il2CppDumper. Required for use with BepInEx.MelonLoader.Loader.")
|
||||
.ToString());
|
||||
|
||||
private static readonly ConfigEntry<bool> ConfigDumpDummyAssemblies = ConfigFile.CoreConfig.Bind(
|
||||
"IL2CPP", "DumpDummyAssemblies",
|
||||
false,
|
||||
"If enabled, BepInEx will save dummy assemblies generated by an il2cpp dumper into BepInEx/dummy.");
|
||||
|
||||
private static readonly ConfigEntry<string> UnhollowedAssembliesPath = ConfigFile.CoreConfig.Bind(
|
||||
"IL2CPP", "UnhollowedAssembliesPath",
|
||||
"{BepInEx}",
|
||||
new StringBuilder()
|
||||
.AppendLine("The path to the folder where unhollowed assemblies are stored.")
|
||||
.AppendLine("Supports the following placeholders:")
|
||||
.AppendLine("{BepInEx} - Path to the BepInEx folder.")
|
||||
.AppendLine("{ProcessName} - Name of the current process")
|
||||
.ToString());
|
||||
|
||||
private static readonly ConfigEntry<string> ConfigUnhollowerDeobfuscationRegex = ConfigFile.CoreConfig.Bind(
|
||||
"IL2CPP", "UnhollowerDeobfuscationRegex",
|
||||
string.Empty,
|
||||
new StringBuilder()
|
||||
.AppendLine("The RegEx string to pass to Il2CppAssemblyUnhollower for renaming obfuscated names.")
|
||||
.AppendLine("All types and members matching this RegEx will get a name based on their signature,")
|
||||
.AppendLine("resulting in names that persist after game updates.")
|
||||
.ToString());
|
||||
|
||||
private static ManualLogSource Il2cppDumperLogger;
|
||||
|
||||
private static string unhollowedBasePath;
|
||||
|
||||
public static string GameAssemblyPath => Path.Combine(Paths.GameRootPath, "GameAssembly.dll");
|
||||
|
||||
private static string HashPath => Path.Combine(Preloader.IL2CPPUnhollowedPath, "assembly-hash.txt");
|
||||
|
||||
private static string UnhollowedBasePath
|
||||
{
|
||||
get
|
||||
{
|
||||
if (unhollowedBasePath != null)
|
||||
return unhollowedBasePath;
|
||||
var path = Utility.GetCommandLineArgValue("--unhollowed-path") ?? UnhollowedAssembliesPath.Value;
|
||||
unhollowedBasePath = path.Replace("{BepInEx}", Paths.BepInExRootPath)
|
||||
.Replace("{ProcessName}", Paths.ProcessName);
|
||||
return unhollowedBasePath;
|
||||
}
|
||||
}
|
||||
|
||||
private static string UnityBaseLibsDirectory => Path.Combine(UnhollowedBasePath, "unity-libs");
|
||||
|
||||
internal static string IL2CPPUnhollowedPath => Path.Combine(UnhollowedBasePath, "unhollowed");
|
||||
|
||||
private static string ComputeHash()
|
||||
{
|
||||
using var md5 = MD5.Create();
|
||||
|
||||
static void HashFile(ICryptoTransform hash, string file)
|
||||
{
|
||||
const int defaultCopyBufferSize = 81920;
|
||||
using var fs = File.OpenRead(file);
|
||||
var buffer = new byte[defaultCopyBufferSize];
|
||||
int read;
|
||||
while ((read = fs.Read(buffer)) > 0)
|
||||
hash.TransformBlock(buffer, 0, read, buffer, 0);
|
||||
}
|
||||
|
||||
static void HashString(ICryptoTransform hash, string str)
|
||||
{
|
||||
var buffer = Encoding.UTF8.GetBytes(str);
|
||||
hash.TransformBlock(buffer, 0, buffer.Length, buffer, 0);
|
||||
}
|
||||
|
||||
HashFile(md5, GameAssemblyPath);
|
||||
|
||||
if (Directory.Exists(UnityBaseLibsDirectory))
|
||||
foreach (var file in Directory.EnumerateFiles(UnityBaseLibsDirectory, "*.dll",
|
||||
SearchOption.TopDirectoryOnly))
|
||||
{
|
||||
HashString(md5, Path.GetFileName(file));
|
||||
HashFile(md5, file);
|
||||
}
|
||||
|
||||
// Hash some common dependencies as they can affect output
|
||||
HashString(md5, typeof(UnhollowedAssemblyGenerator).Assembly.GetName().Version.ToString());
|
||||
HashString(md5, typeof(Cpp2IlApi).Assembly.GetName().Version.ToString());
|
||||
HashString(md5, typeof(Il2CppDumper.Il2CppDumper).Assembly.GetName().Version.ToString());
|
||||
|
||||
md5.TransformFinalBlock(new byte[0], 0, 0);
|
||||
|
||||
return Utility.ByteArrayToString(md5.Hash);
|
||||
}
|
||||
|
||||
public static bool CheckIfGenerationRequired()
|
||||
{
|
||||
static bool NeedGenerationOrSkip()
|
||||
{
|
||||
if (!ConfigUpdateUnhollowedAssemblies.Value)
|
||||
{
|
||||
var hash = ComputeHash();
|
||||
Preloader.Log
|
||||
.Log(LogLevel.Warning,
|
||||
$"Unhollowed assemblies are possibly out of date. To disable this message, create file {HashPath} with the following contents: {hash}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!Directory.Exists(Preloader.IL2CPPUnhollowedPath))
|
||||
return true;
|
||||
|
||||
if (!File.Exists(HashPath))
|
||||
return NeedGenerationOrSkip();
|
||||
|
||||
if (ComputeHash() != File.ReadAllText(HashPath) && NeedGenerationOrSkip())
|
||||
{
|
||||
Preloader.Log.Log(LogLevel.Info, "Detected outdated proxy assemblies, will regenerate them now");
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void GenerateAssemblies()
|
||||
{
|
||||
Il2cppDumperLogger ??= Logger.CreateLogSource(ConfigIl2CppDumperType.Value == IL2CPPDumperType.Il2CppDumper
|
||||
? "Il2CppDumper"
|
||||
: "Cpp2IL");
|
||||
|
||||
var domain = AppDomainHelper.CreateDomain("GeneratorDomain", new AppDomainHelper.AppDomainSetup
|
||||
{
|
||||
ApplicationBase = Paths.BepInExAssemblyDirectory
|
||||
});
|
||||
|
||||
var runner =
|
||||
(AppDomainRunner) AppDomainHelper.CreateInstanceAndUnwrap(domain,
|
||||
typeof(AppDomainRunner).Assembly.FullName,
|
||||
typeof(AppDomainRunner).FullName);
|
||||
|
||||
runner.Setup(Paths.ExecutablePath, Paths.BepInExRootPath, Paths.ManagedPath);
|
||||
var ok = runner.GenerateAssemblies(new AppDomainListener(), Preloader.UnityVersion.ToString(3),
|
||||
ConfigIl2CppDumperType.Value);
|
||||
|
||||
AppDomain.Unload(domain);
|
||||
|
||||
if (ok)
|
||||
File.WriteAllText(HashPath, ComputeHash());
|
||||
}
|
||||
|
||||
internal enum IL2CPPDumperType
|
||||
{
|
||||
Il2CppDumper,
|
||||
Cpp2IL
|
||||
}
|
||||
|
||||
private static class AppDomainHelper
|
||||
{
|
||||
static AppDomainHelper()
|
||||
{
|
||||
var appDomain = typeof(AppDomain);
|
||||
var evidenceType = typeof(AppDomain).Assembly.GetType("System.Security.Policy.Evidence");
|
||||
AppDomainSetupType = typeof(AppDomain).Assembly.GetType("System.AppDomainSetup");
|
||||
CreateDomainInternal =
|
||||
MethodInvoker.GetHandler(AccessTools.Method(appDomain, "CreateDomain",
|
||||
new[]
|
||||
{
|
||||
typeof(string), evidenceType, AppDomainSetupType
|
||||
}));
|
||||
CreateInstanceAndUnwrap =
|
||||
AccessTools.MethodDelegate<Func<AppDomain, string, string, object>>(AccessTools.Method(appDomain,
|
||||
nameof(CreateInstanceAndUnwrap), new[] { typeof(string), typeof(string) }));
|
||||
}
|
||||
|
||||
public static Func<AppDomain, string, string, object> CreateInstanceAndUnwrap { get; }
|
||||
private static FastInvokeHandler CreateDomainInternal { get; }
|
||||
private static Type AppDomainSetupType { get; }
|
||||
|
||||
public static AppDomain CreateDomain(string name, AppDomainSetup setup)
|
||||
{
|
||||
var realSetup = AccessTools.CreateInstance(AppDomainSetupType);
|
||||
Traverse.IterateProperties(setup, realSetup, (pSrc, pTgt) => pTgt.SetValue(pSrc.GetValue()));
|
||||
return CreateDomainInternal(null, name, null, realSetup) as AppDomain;
|
||||
}
|
||||
|
||||
public class AppDomainSetup
|
||||
{
|
||||
public string ApplicationBase { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class AppDomainListener : MarshalByRefObject
|
||||
{
|
||||
public void DoPreloaderLog(object data, LogLevel level) => Preloader.Log.Log(level, data);
|
||||
|
||||
public void DoDumperLog(object data, LogLevel level) => Il2cppDumperLogger.Log(level, data);
|
||||
|
||||
public void DoUnhollowerLog(object data, LogLevel level) => Preloader.UnhollowerLog.Log(level, data);
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
private class AppDomainRunner : MarshalByRefObject
|
||||
{
|
||||
public void Setup(string executablePath, string bepinPath, string managedPath)
|
||||
{
|
||||
Paths.SetExecutablePath(executablePath, bepinPath, managedPath);
|
||||
AppDomain.CurrentDomain.AddCecilPlatformAssemblies(Paths.ManagedPath);
|
||||
AppDomain.CurrentDomain.AddCecilPlatformAssemblies(UnityBaseLibsDirectory);
|
||||
}
|
||||
|
||||
public bool GenerateAssemblies(AppDomainListener listener,
|
||||
string unityVersion,
|
||||
IL2CPPDumperType dumperType)
|
||||
{
|
||||
try
|
||||
{
|
||||
GenerateAssembliesInternal(listener, unityVersion, dumperType);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
listener.DoUnhollowerLog($"Failed to generate unhollowed assemblies: {e}", LogLevel.Fatal);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void GenerateAssembliesInternal(AppDomainListener listener,
|
||||
string unityVersion,
|
||||
IL2CPPDumperType dumperType)
|
||||
{
|
||||
var source =
|
||||
ConfigUnityBaseLibrariesSource.Value.Replace("{VERSION}", unityVersion);
|
||||
|
||||
if (!string.IsNullOrEmpty(source))
|
||||
{
|
||||
listener.DoPreloaderLog("Downloading unity base libraries", LogLevel.Message);
|
||||
|
||||
Directory.CreateDirectory(UnityBaseLibsDirectory);
|
||||
Directory.EnumerateFiles(UnityBaseLibsDirectory, "*.dll").Do(File.Delete);
|
||||
|
||||
using var httpClient = new HttpClient();
|
||||
using var zipStream = httpClient.GetStreamAsync(source).GetAwaiter().GetResult();
|
||||
using var zipArchive = new ZipArchive(zipStream, ZipArchiveMode.Read);
|
||||
|
||||
listener.DoPreloaderLog("Extracting downloaded unity base libraries", LogLevel.Message);
|
||||
zipArchive.ExtractToDirectory(UnityBaseLibsDirectory);
|
||||
}
|
||||
|
||||
listener.DoPreloaderLog("Generating Il2CppUnhollower assemblies", LogLevel.Message);
|
||||
|
||||
Directory.CreateDirectory(Preloader.IL2CPPUnhollowedPath);
|
||||
Directory.EnumerateFiles(Preloader.IL2CPPUnhollowedPath, "*.dll").Do(File.Delete);
|
||||
|
||||
var metadataPath = Path.Combine(Paths.GameRootPath,
|
||||
$"{Paths.ProcessName}_Data",
|
||||
"il2cpp_data",
|
||||
"Metadata",
|
||||
"global-metadata.dat");
|
||||
|
||||
List<AssemblyDefinition> sourceAssemblies;
|
||||
|
||||
var stopwatch = new Stopwatch();
|
||||
stopwatch.Start();
|
||||
|
||||
if (dumperType == IL2CPPDumperType.Il2CppDumper)
|
||||
{
|
||||
listener.DoPreloaderLog("Generating Il2CppDumper intermediate assemblies", LogLevel.Info);
|
||||
|
||||
Il2CppDumper.Il2CppDumper.Init(GameAssemblyPath,
|
||||
metadataPath,
|
||||
new Config
|
||||
{
|
||||
GenerateStruct = false,
|
||||
GenerateDummyDll = true
|
||||
},
|
||||
s => listener.DoDumperLog(s, LogLevel.Debug),
|
||||
out var metadata,
|
||||
out var il2Cpp);
|
||||
|
||||
var executor = new Il2CppExecutor(metadata, il2Cpp);
|
||||
var dummy = new DummyAssemblyGenerator(executor, true);
|
||||
sourceAssemblies = dummy.Assemblies;
|
||||
}
|
||||
else // if (dumperType == IL2CPPDumperType.Cpp2IL)
|
||||
{
|
||||
Cpp2IL.Core.Logger.VerboseLog += (message, s) =>
|
||||
listener.DoDumperLog($"[{s}] {message.Trim()}", LogLevel.Debug);
|
||||
Cpp2IL.Core.Logger.InfoLog += (message, s) =>
|
||||
listener.DoDumperLog($"[{s}] {message.Trim()}", LogLevel.Info);
|
||||
Cpp2IL.Core.Logger.WarningLog += (message, s) =>
|
||||
listener.DoDumperLog($"[{s}] {message.Trim()}", LogLevel.Warning);
|
||||
Cpp2IL.Core.Logger.ErrorLog += (message, s) =>
|
||||
listener.DoDumperLog($"[{s}] {message.Trim()}", LogLevel.Error);
|
||||
|
||||
var cpp2IlUnityVersion =
|
||||
Cpp2IlApi.DetermineUnityVersion(Paths.ExecutablePath,
|
||||
Path.Combine(Paths.GameRootPath, $"{Paths.ProcessName}_Data"));
|
||||
|
||||
Cpp2IlApi.InitializeLibCpp2Il(GameAssemblyPath, metadataPath, cpp2IlUnityVersion, false);
|
||||
|
||||
sourceAssemblies = Cpp2IlApi.MakeDummyDLLs();
|
||||
|
||||
Cpp2IlApi.RunAttributeRestorationForAllAssemblies(null,
|
||||
parallel: LibCpp2IlMain.MetadataVersion >= 29 ||
|
||||
LibCpp2IlMain.Binary!.InstructionSet is
|
||||
InstructionSet.X86_32
|
||||
or InstructionSet.X86_64);
|
||||
|
||||
Cpp2IlApi.DisposeAndCleanupAll();
|
||||
}
|
||||
|
||||
stopwatch.Stop();
|
||||
listener.DoDumperLog("Total time: " + stopwatch.Elapsed, LogLevel.Info);
|
||||
|
||||
if (ConfigDumpDummyAssemblies.Value)
|
||||
{
|
||||
var dummyPath = Path.Combine(Paths.BepInExRootPath, "dummy");
|
||||
Directory.CreateDirectory(dummyPath);
|
||||
|
||||
foreach (var assemblyDefinition in sourceAssemblies)
|
||||
assemblyDefinition.Write(Path.Combine(dummyPath, assemblyDefinition.Name.Name + ".dll"));
|
||||
}
|
||||
|
||||
var unhollowerOptions = new UnhollowerOptions
|
||||
{
|
||||
GameAssemblyPath = GameAssemblyPath,
|
||||
MscorlibPath = Path.Combine(Paths.ManagedPath, "mscorlib.dll"),
|
||||
Source = sourceAssemblies,
|
||||
OutputDir = Preloader.IL2CPPUnhollowedPath,
|
||||
UnityBaseLibsDir = Directory.Exists(UnityBaseLibsDirectory) ? UnityBaseLibsDirectory : null,
|
||||
NoCopyUnhollowerLibs = true,
|
||||
ObfuscatedNamesRegex = !string.IsNullOrEmpty(ConfigUnhollowerDeobfuscationRegex.Value) ? new Regex(ConfigUnhollowerDeobfuscationRegex.Value) : null
|
||||
};
|
||||
|
||||
var renameMapLocation = Path.Combine(Paths.BepInExRootPath, "DeobfuscationMap.csv.gz");
|
||||
if (File.Exists(renameMapLocation))
|
||||
{
|
||||
listener.DoPreloaderLog("Parsing deobfuscation rename mappings", LogLevel.Info);
|
||||
unhollowerOptions.ReadRenameMap(renameMapLocation);
|
||||
}
|
||||
|
||||
listener.DoPreloaderLog("Executing Il2CppUnhollower generator", LogLevel.Info);
|
||||
|
||||
LogSupport.InfoHandler += s => listener.DoUnhollowerLog(s.Trim(), LogLevel.Info);
|
||||
LogSupport.WarningHandler += s => listener.DoUnhollowerLog(s.Trim(), LogLevel.Warning);
|
||||
LogSupport.TraceHandler += s => listener.DoUnhollowerLog(s.Trim(), LogLevel.Debug);
|
||||
LogSupport.ErrorHandler += s => listener.DoUnhollowerLog(s.Trim(), LogLevel.Error);
|
||||
|
||||
try
|
||||
{
|
||||
UnhollowedAssemblyGenerator.GenerateUnhollowedAssemblies(unhollowerOptions);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
listener.DoUnhollowerLog($"Exception while unhollowing: {e}", LogLevel.Error);
|
||||
}
|
||||
|
||||
sourceAssemblies.Do(x => x.Dispose());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -333,8 +333,8 @@ public enum KeyCode
|
||||
public static class Input
|
||||
{
|
||||
private static readonly GetKeyIntDelegate GetKeyInt_Value
|
||||
= UnhollowerBaseLib.IL2CPP
|
||||
.ResolveICall<GetKeyIntDelegate>("UnityEngine.Input::GetKeyInt(UnityEngine.KeyCode)");
|
||||
= Il2CppInterop.Runtime.IL2CPP
|
||||
.ResolveICall<GetKeyIntDelegate>("UnityEngine.Input::GetKeyInt(UnityEngine.KeyCode)");
|
||||
|
||||
public static bool GetKeyInt(KeyCode key) => GetKeyInt_Value(key);
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
@@ -20,13 +19,13 @@ internal static class UnityPreloaderRunner
|
||||
EnvVars.DOORSTOP_DLL_SEARCH_DIRS);
|
||||
|
||||
// Cecil 0.11 requires one to manually set up list of trusted assemblies for assembly resolving
|
||||
// The main BCL path
|
||||
AppDomain.CurrentDomain.AddCecilPlatformAssemblies(Paths.ManagedPath);
|
||||
|
||||
Preloader.IL2CPPUnhollowedPath = Path.Combine(Paths.BepInExRootPath, "unhollowed");
|
||||
// The parent path -> .NET has some extra managed DLLs in there
|
||||
AppDomain.CurrentDomain.AddCecilPlatformAssemblies(Path.GetDirectoryName(Paths.ManagedPath));
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyResolve += LocalResolve;
|
||||
|
||||
|
||||
Preloader.Run();
|
||||
}
|
||||
|
||||
@@ -42,8 +41,7 @@ internal static class UnityPreloaderRunner
|
||||
|
||||
if (Utility.TryResolveDllAssembly(assemblyName, Paths.BepInExAssemblyDirectory, out foundAssembly)
|
||||
|| Utility.TryResolveDllAssembly(assemblyName, Paths.PatcherPluginPath, out foundAssembly)
|
||||
|| Utility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly)
|
||||
|| Utility.TryResolveDllAssembly(assemblyName, Preloader.IL2CPPUnhollowedPath, out foundAssembly))
|
||||
|| Utility.TryResolveDllAssembly(assemblyName, Paths.PluginPath, out foundAssembly))
|
||||
return foundAssembly;
|
||||
|
||||
return null;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections;
|
||||
using Il2CppInterop.Runtime.Injection;
|
||||
using Il2CppSystem;
|
||||
using UnhollowerRuntimeLib;
|
||||
using ArgumentNullException = System.ArgumentNullException;
|
||||
using IEnumerator = Il2CppSystem.Collections.IEnumerator;
|
||||
using IntPtr = System.IntPtr;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection.Emit;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppInterop.Runtime.Injection;
|
||||
using Il2CppSystem;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using ArgumentNullException = System.ArgumentNullException;
|
||||
using Il2CppIEnumerator = Il2CppSystem.Collections.IEnumerator;
|
||||
using IntPtr = System.IntPtr;
|
||||
@@ -67,7 +67,7 @@ public class Il2CppManagedEnumerator : Object
|
||||
il.Emit(OpCodes.Stloc, loc);
|
||||
il.Emit(OpCodes.Ldloca, loc);
|
||||
il.Emit(OpCodes.Call,
|
||||
typeof(UnhollowerBaseLib.IL2CPP).GetMethod(nameof(UnhollowerBaseLib.IL2CPP.il2cpp_value_box)));
|
||||
typeof(Il2CppInterop.Runtime.IL2CPP).GetMethod(nameof(Il2CppInterop.Runtime.IL2CPP.il2cpp_value_box)));
|
||||
il.Emit(OpCodes.Newobj, typeof(Object).GetConstructor(new[] { typeof(IntPtr) }));
|
||||
il.Emit(OpCodes.Ret);
|
||||
|
||||
@@ -80,7 +80,7 @@ public class Il2CppManagedEnumerator : Object
|
||||
{
|
||||
var t = obj.GetType();
|
||||
if (obj is string s)
|
||||
return new Object(UnhollowerBaseLib.IL2CPP.ManagedStringToIl2Cpp(s));
|
||||
return new Object(Il2CppInterop.Runtime.IL2CPP.ManagedStringToIl2Cpp(s));
|
||||
if (t.IsPrimitive)
|
||||
return GetValueBoxer(t)(obj);
|
||||
throw new NotSupportedException($"Type {t} cannot be converted directly to an Il2Cpp object");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppInterop.Runtime.Injection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BepInEx.IL2CPP.Utils;
|
||||
|
||||
Reference in new issue
Block a user