123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 |
- 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;
- using Logger = BepInEx.Logging.Logger;
- 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 MethodInfo ReportExceptionMethodInfo
- = AccessTools.Method(typeof(IL2CPPDetourMethodPatcher), nameof(ReportException));
- private static readonly ManualLogSource DetourLogger = Logger.CreateLogSource("Detour");
- private FastNativeDetour nativeDetour;
- private Il2CppMethodInfo* originalNativeMethodInfo;
- private Il2CppMethodInfo* modifiedNativeMethodInfo;
- private bool isValid;
- /// <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()
- {
- try
- {
- // 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;
- isValid = true;
- }
- catch (Exception e)
- {
- DetourLogger.LogWarning($"Failed to init IL2CPP patch backend for {Original.FullDescription()}, using normal patch handlers: {e.Message}");
- }
- }
- /// <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,
- CallingConvention.Cdecl);
- var detourPtr = Marshal.GetFunctionPointerForDelegate(unmanagedTrampolineMethod.CreateDelegate(unmanagedDelegateType));
- 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
- 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);
- }
- else
- {
- cursor.Goto(0)
- .GotoNext(x => x.MatchLdsfld(UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Original)))
- .Remove();
- }
- // Replace original IL2CPPMethodInfo pointer with a modified one that points to the trampoline
- 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)
- {
- var backend = new IL2CPPDetourMethodPatcher(args.Original);
- if (backend.isValid)
- args.MethodPatcher = backend;
- }
- }
- 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 paramStartIndex = Original.IsStatic ? 0 : 1;
-
- var managedParams = Original.GetParameters().Select(x => x.ParameterType).ToArray();
- var unmanagedParams = new Type[managedParams.Length + paramStartIndex + 1]; // +1 for thisptr if needed, +1 for methodInfo at the end
-
- if (!Original.IsStatic)
- unmanagedParams[0] = typeof(IntPtr);
- unmanagedParams[unmanagedParams.Length - 1] = typeof(Il2CppMethodInfo*);
- Array.Copy(managedParams.Select(ConvertManagedTypeToIL2CPPType).ToArray(), 0,
- unmanagedParams, paramStartIndex, managedParams.Length);
- var managedReturnType = AccessTools.GetReturnedType(Original);
- var unmanagedReturnType = ConvertManagedTypeToIL2CPPType(managedReturnType);
- var dmd = new DynamicMethodDefinition("(il2cpp -> managed) " + Original.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];
- if (!Original.IsStatic)
- {
- // Load thisptr as arg0
- il.Emit(OpCodes.Ldarg_0);
- EmitConvertArgumentToManaged(il, Original.DeclaringType, out _);
- }
- for (int i = 0; i < managedParams.Length; ++i)
- {
- il.Emit(OpCodes.Ldarg_S, i + paramStartIndex);
- 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 + paramStartIndex);
- 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, ReportExceptionMethodInfo);
- 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);
- }
- }
- }
- }
|