|
@@ -0,0 +1,337 @@
|
|
|
+using System;
|
|
|
+using System.Linq;
|
|
|
+using System.Reflection;
|
|
|
+using System.Reflection.Emit;
|
|
|
+using System.Runtime.InteropServices;
|
|
|
+using BepInEx.Logging;
|
|
|
+using HarmonyLib;
|
|
|
+using HarmonyLib.Public.Patching;
|
|
|
+using MonoMod.Cil;
|
|
|
+using MonoMod.RuntimeDetour;
|
|
|
+using MonoMod.Utils;
|
|
|
+using UnhollowerBaseLib;
|
|
|
+using UnhollowerBaseLib.Runtime;
|
|
|
+
|
|
|
+namespace BepInEx.IL2CPP.Hook
|
|
|
+{
|
|
|
+ public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher
|
|
|
+ {
|
|
|
+ private static readonly MethodInfo IL2CPPToManagedStringMethodInfo
|
|
|
+ = AccessTools.Method(typeof(UnhollowerBaseLib.IL2CPP), nameof(UnhollowerBaseLib.IL2CPP.Il2CppStringToManaged));
|
|
|
+
|
|
|
+ private static readonly MethodInfo ManagedToIL2CPPStringMethodInfo
|
|
|
+ = AccessTools.Method(typeof(UnhollowerBaseLib.IL2CPP), nameof(UnhollowerBaseLib.IL2CPP.ManagedStringToIl2Cpp));
|
|
|
+
|
|
|
+ private static readonly MethodInfo ObjectBaseToPtrMethodInfo
|
|
|
+ = AccessTools.Method(typeof(UnhollowerBaseLib.IL2CPP), nameof(UnhollowerBaseLib.IL2CPP.Il2CppObjectBaseToPtr));
|
|
|
+
|
|
|
+
|
|
|
+ private static readonly ManualLogSource DetourLogger = Logger.CreateLogSource("Detour");
|
|
|
+
|
|
|
+ private FastNativeDetour nativeDetour;
|
|
|
+
|
|
|
+ private Il2CppMethodInfo* originalNativeMethodInfo;
|
|
|
+ private Il2CppMethodInfo* modifiedNativeMethodInfo;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Constructs a new instance of <see cref="NativeDetour"/> method patcher.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="original"></param>
|
|
|
+ public IL2CPPDetourMethodPatcher(MethodBase original) : base(original)
|
|
|
+ {
|
|
|
+ Init();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Init()
|
|
|
+ {
|
|
|
+ // Get the native MethodInfo struct for the target method
|
|
|
+
|
|
|
+ originalNativeMethodInfo = (Il2CppMethodInfo*)
|
|
|
+ (IntPtr)UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Original).GetValue(null);
|
|
|
+
|
|
|
+ // Create a trampoline from the original target method
|
|
|
+
|
|
|
+ var trampolinePtr = DetourGenerator.CreateTrampolineFromFunction(originalNativeMethodInfo->methodPointer, out _, out _);
|
|
|
+
|
|
|
+ // Create a modified native MethodInfo struct to point towards the trampoline
|
|
|
+
|
|
|
+ modifiedNativeMethodInfo = (Il2CppMethodInfo*)Marshal.AllocHGlobal(Marshal.SizeOf<Il2CppMethodInfo>());
|
|
|
+ Marshal.StructureToPtr(*originalNativeMethodInfo, (IntPtr)modifiedNativeMethodInfo, false);
|
|
|
+
|
|
|
+ modifiedNativeMethodInfo->methodPointer = trampolinePtr;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc />
|
|
|
+ public override DynamicMethodDefinition PrepareOriginal()
|
|
|
+ {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc />
|
|
|
+ public override MethodBase DetourTo(MethodBase replacement)
|
|
|
+ {
|
|
|
+ // Unpatch an existing detour if it exists
|
|
|
+
|
|
|
+ nativeDetour?.Dispose();
|
|
|
+
|
|
|
+ // Generate a new DMD of the modified unhollowed method, and apply harmony patches to it
|
|
|
+
|
|
|
+ var copiedDmd = CopyOriginal();
|
|
|
+
|
|
|
+ HarmonyManipulator.Manipulate(copiedDmd.OriginalMethod, copiedDmd.OriginalMethod.GetPatchInfo(), new ILContext(copiedDmd.Definition));
|
|
|
+
|
|
|
+
|
|
|
+ // Generate the MethodInfo instances
|
|
|
+
|
|
|
+ var managedHookedMethod = copiedDmd.Generate();
|
|
|
+ var unmanagedTrampolineMethod = GenerateNativeToManagedTrampoline(managedHookedMethod).Generate();
|
|
|
+
|
|
|
+
|
|
|
+ // Apply a detour from the unmanaged implementation to the patched harmony method
|
|
|
+
|
|
|
+ var unmanagedDelegateType = DelegateTypeFactory.instance.CreateDelegateType(unmanagedTrampolineMethod.ReturnType,
|
|
|
+ unmanagedTrampolineMethod.GetParameters().Select(x => x.ParameterType).ToArray());
|
|
|
+
|
|
|
+
|
|
|
+ var detourPtr = MonoExtensions.GetFunctionPointerForDelegate(unmanagedTrampolineMethod.CreateDelegate(unmanagedDelegateType),
|
|
|
+ CallingConvention.Cdecl);
|
|
|
+
|
|
|
+ nativeDetour = new FastNativeDetour(originalNativeMethodInfo->methodPointer, detourPtr);
|
|
|
+
|
|
|
+ nativeDetour.Apply();
|
|
|
+
|
|
|
+ // TODO: Add an ILHook for the original unhollowed method to go directly to managedHookedMethod
|
|
|
+ // Right now it goes through three times as much interop conversion as it needs to, when being called from managed side
|
|
|
+
|
|
|
+ return managedHookedMethod;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <inheritdoc />
|
|
|
+ public override DynamicMethodDefinition CopyOriginal()
|
|
|
+ {
|
|
|
+ var dmd = new DynamicMethodDefinition(Original);
|
|
|
+ dmd.Definition.Name = "UnhollowedWrapper_" + dmd.Definition.Name;
|
|
|
+
|
|
|
+ var cursor = new ILCursor(new ILContext(dmd.Definition));
|
|
|
+
|
|
|
+
|
|
|
+ // Remove il2cpp_object_get_virtual_method
|
|
|
+
|
|
|
+ bool foundVirtMethodCall = false;
|
|
|
+
|
|
|
+ if (cursor.TryGotoNext(x => x.MatchLdarg(0),
|
|
|
+ x => x.MatchCall(typeof(UnhollowerBaseLib.IL2CPP), nameof(UnhollowerBaseLib.IL2CPP.Il2CppObjectBaseToPtr)),
|
|
|
+ x => x.MatchLdsfld(out _),
|
|
|
+ x => x.MatchCall(typeof(UnhollowerBaseLib.IL2CPP), nameof(UnhollowerBaseLib.IL2CPP.il2cpp_object_get_virtual_method))))
|
|
|
+ {
|
|
|
+ cursor.RemoveRange(4);
|
|
|
+
|
|
|
+ foundVirtMethodCall = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Replace original IL2CPPMethodInfo pointer with a modified one that points to the trampoline
|
|
|
+
|
|
|
+ if (!foundVirtMethodCall)
|
|
|
+ {
|
|
|
+ cursor.Goto(0)
|
|
|
+ .GotoNext(x => x.MatchLdsfld(UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Original)))
|
|
|
+ .Remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ cursor
|
|
|
+ .Emit(Mono.Cecil.Cil.OpCodes.Ldc_I8, ((IntPtr)modifiedNativeMethodInfo).ToInt64())
|
|
|
+ .Emit(Mono.Cecil.Cil.OpCodes.Conv_I);
|
|
|
+
|
|
|
+ return dmd;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// A handler for <see cref="PatchManager.ResolvePatcher"/> that checks if a method doesn't have a body
|
|
|
+ /// (e.g. it's icall or marked with <see cref="DynDllImportAttribute"/>) and thus can be patched with
|
|
|
+ /// <see cref="NativeDetour"/>.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="sender">Not used</param>
|
|
|
+ /// <param name="args">Patch resolver arguments</param>
|
|
|
+ ///
|
|
|
+ public static void TryResolve(object sender, PatchManager.PatcherResolverEventArgs args)
|
|
|
+ {
|
|
|
+ if (args.Original.DeclaringType?.IsSubclassOf(typeof(Il2CppObjectBase)) == true)
|
|
|
+ args.MethodPatcher = new IL2CPPDetourMethodPatcher(args.Original);
|
|
|
+ }
|
|
|
+
|
|
|
+ private DynamicMethodDefinition GenerateNativeToManagedTrampoline(MethodInfo targetManagedMethodInfo)
|
|
|
+ {
|
|
|
+ // managedParams are the unhollower types used on the managed side
|
|
|
+ // unmanagedParams are IntPtr references that are used by IL2CPP compiled assembly
|
|
|
+
|
|
|
+ var managedParams = targetManagedMethodInfo.GetParameters().Select(x => x.ParameterType).ToArray();
|
|
|
+ var unmanagedParams = managedParams.Select(ConvertManagedTypeToIL2CPPType).ToArray();
|
|
|
+
|
|
|
+ var managedReturnType = AccessTools.GetReturnedType(targetManagedMethodInfo);
|
|
|
+ var unmanagedReturnType = ConvertManagedTypeToIL2CPPType(managedReturnType);
|
|
|
+
|
|
|
+ var dmd = new DynamicMethodDefinition("(il2cpp -> managed) " + targetManagedMethodInfo.Name,
|
|
|
+ unmanagedReturnType,
|
|
|
+ unmanagedParams
|
|
|
+ );
|
|
|
+
|
|
|
+ var il = dmd.GetILGenerator();
|
|
|
+
|
|
|
+ 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
|
|
|
+
|
|
|
+ LocalBuilder[] indirectVariables = new LocalBuilder[managedParams.Length];
|
|
|
+
|
|
|
+ for (int i = 0; i < managedParams.Length; ++i)
|
|
|
+ {
|
|
|
+ il.Emit(OpCodes.Ldarg_S, i);
|
|
|
+ EmitConvertArgumentToManaged(il, managedParams[i], out indirectVariables[i]);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Run the managed method
|
|
|
+
|
|
|
+ il.Emit(OpCodes.Call, targetManagedMethodInfo);
|
|
|
+
|
|
|
+
|
|
|
+ // Store the managed return type temporarily (if there was one)
|
|
|
+
|
|
|
+ LocalBuilder managedReturnVariable = null;
|
|
|
+
|
|
|
+ if (managedReturnType != typeof(void))
|
|
|
+ {
|
|
|
+ managedReturnVariable = il.DeclareLocal(managedReturnType);
|
|
|
+ il.Emit(OpCodes.Stloc, managedReturnVariable);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // Convert any managed byref values into their relevant IL2CPP types, and then store the values into their relevant dereferenced pointers
|
|
|
+
|
|
|
+ for (int i = 0; i < managedParams.Length; ++i)
|
|
|
+ {
|
|
|
+ if (indirectVariables[i] == null)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ il.Emit(OpCodes.Ldarg_S, i);
|
|
|
+ il.Emit(OpCodes.Ldloc, indirectVariables[i]);
|
|
|
+
|
|
|
+ EmitConvertManagedTypeToIL2CPP(il, managedParams[i].GetElementType());
|
|
|
+
|
|
|
+ il.Emit(OpCodes.Stind_I);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Handle any lingering exceptions
|
|
|
+
|
|
|
+ il.BeginCatchBlock(typeof(Exception));
|
|
|
+
|
|
|
+ il.Emit(OpCodes.Call, AccessTools.Method(typeof(IL2CPPDetourMethodPatcher), nameof(ReportException)));
|
|
|
+
|
|
|
+ il.EndExceptionBlock();
|
|
|
+
|
|
|
+ // Convert the return value back to an IL2CPP friendly type (if there was a return value), and then return
|
|
|
+
|
|
|
+ if (managedReturnVariable != null)
|
|
|
+ {
|
|
|
+ il.Emit(OpCodes.Ldloc, managedReturnVariable);
|
|
|
+ EmitConvertManagedTypeToIL2CPP(il, managedReturnType);
|
|
|
+ }
|
|
|
+
|
|
|
+ il.Emit(OpCodes.Ret);
|
|
|
+
|
|
|
+ return dmd;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void ReportException(Exception ex)
|
|
|
+ {
|
|
|
+ DetourLogger.LogError(ex.ToString());
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Type ConvertManagedTypeToIL2CPPType(Type managedType)
|
|
|
+ {
|
|
|
+ if (managedType.IsByRef)
|
|
|
+ {
|
|
|
+ Type directType = managedType.GetElementType();
|
|
|
+ if (directType == typeof(string) || directType.IsSubclassOf(typeof(Il2CppObjectBase)))
|
|
|
+ {
|
|
|
+ return typeof(IntPtr*);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else if (managedType == typeof(string) || managedType.IsSubclassOf(typeof(Il2CppObjectBase)))
|
|
|
+ {
|
|
|
+ return typeof(IntPtr);
|
|
|
+ }
|
|
|
+
|
|
|
+ return managedType;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void EmitConvertManagedTypeToIL2CPP(ILGenerator il, Type returnType)
|
|
|
+ {
|
|
|
+ if (returnType == typeof(string))
|
|
|
+ {
|
|
|
+ il.Emit(OpCodes.Call, ManagedToIL2CPPStringMethodInfo);
|
|
|
+ }
|
|
|
+ else if (!returnType.IsValueType && returnType.IsSubclassOf(typeof(Il2CppObjectBase)))
|
|
|
+ {
|
|
|
+ il.Emit(OpCodes.Call, ObjectBaseToPtrMethodInfo);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void EmitConvertArgumentToManaged(ILGenerator il, Type managedParamType, out LocalBuilder variable)
|
|
|
+ {
|
|
|
+ variable = null;
|
|
|
+
|
|
|
+ if (managedParamType.IsValueType) // don't need to convert blittable types
|
|
|
+ return;
|
|
|
+
|
|
|
+ void EmitCreateIl2CppObject()
|
|
|
+ {
|
|
|
+ Label endLabel = il.DefineLabel();
|
|
|
+ Label notNullLabel = il.DefineLabel();
|
|
|
+
|
|
|
+ il.Emit(OpCodes.Dup);
|
|
|
+ il.Emit(OpCodes.Brtrue_S, notNullLabel);
|
|
|
+
|
|
|
+ il.Emit(OpCodes.Pop);
|
|
|
+ il.Emit(OpCodes.Ldnull);
|
|
|
+ il.Emit(OpCodes.Br_S, endLabel);
|
|
|
+
|
|
|
+ il.MarkLabel(notNullLabel);
|
|
|
+ il.Emit(OpCodes.Newobj, AccessTools.DeclaredConstructor(managedParamType, new[] { typeof(IntPtr) }));
|
|
|
+
|
|
|
+ il.MarkLabel(endLabel);
|
|
|
+ }
|
|
|
+
|
|
|
+ void HandleTypeConversion(Type originalType)
|
|
|
+ {
|
|
|
+ if (originalType == typeof(string))
|
|
|
+ {
|
|
|
+ il.Emit(OpCodes.Call, IL2CPPToManagedStringMethodInfo);
|
|
|
+ }
|
|
|
+ else if (originalType.IsSubclassOf(typeof(Il2CppObjectBase)))
|
|
|
+ {
|
|
|
+ EmitCreateIl2CppObject();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (managedParamType.IsByRef)
|
|
|
+ {
|
|
|
+ Type directType = managedParamType.GetElementType();
|
|
|
+
|
|
|
+ variable = il.DeclareLocal(directType);
|
|
|
+
|
|
|
+ il.Emit(OpCodes.Ldind_I);
|
|
|
+
|
|
|
+ HandleTypeConversion(directType);
|
|
|
+
|
|
|
+ il.Emit(OpCodes.Stloc, variable);
|
|
|
+ il.Emit(OpCodes.Ldloca, variable);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ HandleTypeConversion(managedParamType);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|