IL2CPPDetourMethodPatcher.cs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. using System;
  2. using System.Linq;
  3. using System.Reflection;
  4. using System.Reflection.Emit;
  5. using System.Runtime.InteropServices;
  6. using BepInEx.Logging;
  7. using HarmonyLib;
  8. using HarmonyLib.Public.Patching;
  9. using MonoMod.Cil;
  10. using MonoMod.RuntimeDetour;
  11. using MonoMod.Utils;
  12. using UnhollowerBaseLib;
  13. using UnhollowerBaseLib.Runtime;
  14. namespace BepInEx.IL2CPP.Hook
  15. {
  16. public unsafe class IL2CPPDetourMethodPatcher : MethodPatcher
  17. {
  18. private static readonly MethodInfo IL2CPPToManagedStringMethodInfo
  19. = AccessTools.Method(typeof(UnhollowerBaseLib.IL2CPP), nameof(UnhollowerBaseLib.IL2CPP.Il2CppStringToManaged));
  20. private static readonly MethodInfo ManagedToIL2CPPStringMethodInfo
  21. = AccessTools.Method(typeof(UnhollowerBaseLib.IL2CPP), nameof(UnhollowerBaseLib.IL2CPP.ManagedStringToIl2Cpp));
  22. private static readonly MethodInfo ObjectBaseToPtrMethodInfo
  23. = AccessTools.Method(typeof(UnhollowerBaseLib.IL2CPP), nameof(UnhollowerBaseLib.IL2CPP.Il2CppObjectBaseToPtr));
  24. private static readonly ManualLogSource DetourLogger = Logger.CreateLogSource("Detour");
  25. private FastNativeDetour nativeDetour;
  26. private Il2CppMethodInfo* originalNativeMethodInfo;
  27. private Il2CppMethodInfo* modifiedNativeMethodInfo;
  28. /// <summary>
  29. /// Constructs a new instance of <see cref="NativeDetour"/> method patcher.
  30. /// </summary>
  31. /// <param name="original"></param>
  32. public IL2CPPDetourMethodPatcher(MethodBase original) : base(original)
  33. {
  34. Init();
  35. }
  36. private void Init()
  37. {
  38. // Get the native MethodInfo struct for the target method
  39. originalNativeMethodInfo = (Il2CppMethodInfo*)
  40. (IntPtr)UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Original).GetValue(null);
  41. // Create a trampoline from the original target method
  42. var trampolinePtr = DetourGenerator.CreateTrampolineFromFunction(originalNativeMethodInfo->methodPointer, out _, out _);
  43. // Create a modified native MethodInfo struct to point towards the trampoline
  44. modifiedNativeMethodInfo = (Il2CppMethodInfo*)Marshal.AllocHGlobal(Marshal.SizeOf<Il2CppMethodInfo>());
  45. Marshal.StructureToPtr(*originalNativeMethodInfo, (IntPtr)modifiedNativeMethodInfo, false);
  46. modifiedNativeMethodInfo->methodPointer = trampolinePtr;
  47. }
  48. /// <inheritdoc />
  49. public override DynamicMethodDefinition PrepareOriginal()
  50. {
  51. return null;
  52. }
  53. /// <inheritdoc />
  54. public override MethodBase DetourTo(MethodBase replacement)
  55. {
  56. // Unpatch an existing detour if it exists
  57. nativeDetour?.Dispose();
  58. // Generate a new DMD of the modified unhollowed method, and apply harmony patches to it
  59. var copiedDmd = CopyOriginal();
  60. HarmonyManipulator.Manipulate(copiedDmd.OriginalMethod, copiedDmd.OriginalMethod.GetPatchInfo(), new ILContext(copiedDmd.Definition));
  61. // Generate the MethodInfo instances
  62. var managedHookedMethod = copiedDmd.Generate();
  63. var unmanagedTrampolineMethod = GenerateNativeToManagedTrampoline(managedHookedMethod).Generate();
  64. // Apply a detour from the unmanaged implementation to the patched harmony method
  65. var unmanagedDelegateType = DelegateTypeFactory.instance.CreateDelegateType(unmanagedTrampolineMethod.ReturnType,
  66. unmanagedTrampolineMethod.GetParameters().Select(x => x.ParameterType).ToArray());
  67. var detourPtr = MonoExtensions.GetFunctionPointerForDelegate(unmanagedTrampolineMethod.CreateDelegate(unmanagedDelegateType),
  68. CallingConvention.Cdecl);
  69. nativeDetour = new FastNativeDetour(originalNativeMethodInfo->methodPointer, detourPtr);
  70. nativeDetour.Apply();
  71. // TODO: Add an ILHook for the original unhollowed method to go directly to managedHookedMethod
  72. // Right now it goes through three times as much interop conversion as it needs to, when being called from managed side
  73. return managedHookedMethod;
  74. }
  75. /// <inheritdoc />
  76. public override DynamicMethodDefinition CopyOriginal()
  77. {
  78. var dmd = new DynamicMethodDefinition(Original);
  79. dmd.Definition.Name = "UnhollowedWrapper_" + dmd.Definition.Name;
  80. var cursor = new ILCursor(new ILContext(dmd.Definition));
  81. // Remove il2cpp_object_get_virtual_method
  82. bool foundVirtMethodCall = false;
  83. if (cursor.TryGotoNext(x => x.MatchLdarg(0),
  84. x => x.MatchCall(typeof(UnhollowerBaseLib.IL2CPP), nameof(UnhollowerBaseLib.IL2CPP.Il2CppObjectBaseToPtr)),
  85. x => x.MatchLdsfld(out _),
  86. x => x.MatchCall(typeof(UnhollowerBaseLib.IL2CPP), nameof(UnhollowerBaseLib.IL2CPP.il2cpp_object_get_virtual_method))))
  87. {
  88. cursor.RemoveRange(4);
  89. foundVirtMethodCall = true;
  90. }
  91. // Replace original IL2CPPMethodInfo pointer with a modified one that points to the trampoline
  92. if (!foundVirtMethodCall)
  93. {
  94. cursor.Goto(0)
  95. .GotoNext(x => x.MatchLdsfld(UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Original)))
  96. .Remove();
  97. }
  98. cursor
  99. .Emit(Mono.Cecil.Cil.OpCodes.Ldc_I8, ((IntPtr)modifiedNativeMethodInfo).ToInt64())
  100. .Emit(Mono.Cecil.Cil.OpCodes.Conv_I);
  101. return dmd;
  102. }
  103. /// <summary>
  104. /// A handler for <see cref="PatchManager.ResolvePatcher"/> that checks if a method doesn't have a body
  105. /// (e.g. it's icall or marked with <see cref="DynDllImportAttribute"/>) and thus can be patched with
  106. /// <see cref="NativeDetour"/>.
  107. /// </summary>
  108. /// <param name="sender">Not used</param>
  109. /// <param name="args">Patch resolver arguments</param>
  110. ///
  111. public static void TryResolve(object sender, PatchManager.PatcherResolverEventArgs args)
  112. {
  113. if (args.Original.DeclaringType?.IsSubclassOf(typeof(Il2CppObjectBase)) == true)
  114. args.MethodPatcher = new IL2CPPDetourMethodPatcher(args.Original);
  115. }
  116. private DynamicMethodDefinition GenerateNativeToManagedTrampoline(MethodInfo targetManagedMethodInfo)
  117. {
  118. // managedParams are the unhollower types used on the managed side
  119. // unmanagedParams are IntPtr references that are used by IL2CPP compiled assembly
  120. var managedParams = targetManagedMethodInfo.GetParameters().Select(x => x.ParameterType).ToArray();
  121. var unmanagedParams = managedParams.Select(ConvertManagedTypeToIL2CPPType).ToArray();
  122. var managedReturnType = AccessTools.GetReturnedType(targetManagedMethodInfo);
  123. var unmanagedReturnType = ConvertManagedTypeToIL2CPPType(managedReturnType);
  124. var dmd = new DynamicMethodDefinition("(il2cpp -> managed) " + targetManagedMethodInfo.Name,
  125. unmanagedReturnType,
  126. unmanagedParams
  127. );
  128. var il = dmd.GetILGenerator();
  129. il.BeginExceptionBlock();
  130. // Declare a list of variables to dereference back to the original pointers.
  131. // This is required due to the needed unhollower type conversions, so we can't directly pass some addresses as byref types
  132. LocalBuilder[] indirectVariables = new LocalBuilder[managedParams.Length];
  133. for (int i = 0; i < managedParams.Length; ++i)
  134. {
  135. il.Emit(OpCodes.Ldarg_S, i);
  136. EmitConvertArgumentToManaged(il, managedParams[i], out indirectVariables[i]);
  137. }
  138. // Run the managed method
  139. il.Emit(OpCodes.Call, targetManagedMethodInfo);
  140. // Store the managed return type temporarily (if there was one)
  141. LocalBuilder managedReturnVariable = null;
  142. if (managedReturnType != typeof(void))
  143. {
  144. managedReturnVariable = il.DeclareLocal(managedReturnType);
  145. il.Emit(OpCodes.Stloc, managedReturnVariable);
  146. }
  147. // Convert any managed byref values into their relevant IL2CPP types, and then store the values into their relevant dereferenced pointers
  148. for (int i = 0; i < managedParams.Length; ++i)
  149. {
  150. if (indirectVariables[i] == null)
  151. continue;
  152. il.Emit(OpCodes.Ldarg_S, i);
  153. il.Emit(OpCodes.Ldloc, indirectVariables[i]);
  154. EmitConvertManagedTypeToIL2CPP(il, managedParams[i].GetElementType());
  155. il.Emit(OpCodes.Stind_I);
  156. }
  157. // Handle any lingering exceptions
  158. il.BeginCatchBlock(typeof(Exception));
  159. il.Emit(OpCodes.Call, AccessTools.Method(typeof(IL2CPPDetourMethodPatcher), nameof(ReportException)));
  160. il.EndExceptionBlock();
  161. // Convert the return value back to an IL2CPP friendly type (if there was a return value), and then return
  162. if (managedReturnVariable != null)
  163. {
  164. il.Emit(OpCodes.Ldloc, managedReturnVariable);
  165. EmitConvertManagedTypeToIL2CPP(il, managedReturnType);
  166. }
  167. il.Emit(OpCodes.Ret);
  168. return dmd;
  169. }
  170. private static void ReportException(Exception ex)
  171. {
  172. DetourLogger.LogError(ex.ToString());
  173. }
  174. private static Type ConvertManagedTypeToIL2CPPType(Type managedType)
  175. {
  176. if (managedType.IsByRef)
  177. {
  178. Type directType = managedType.GetElementType();
  179. if (directType == typeof(string) || directType.IsSubclassOf(typeof(Il2CppObjectBase)))
  180. {
  181. return typeof(IntPtr*);
  182. }
  183. }
  184. else if (managedType == typeof(string) || managedType.IsSubclassOf(typeof(Il2CppObjectBase)))
  185. {
  186. return typeof(IntPtr);
  187. }
  188. return managedType;
  189. }
  190. private static void EmitConvertManagedTypeToIL2CPP(ILGenerator il, Type returnType)
  191. {
  192. if (returnType == typeof(string))
  193. {
  194. il.Emit(OpCodes.Call, ManagedToIL2CPPStringMethodInfo);
  195. }
  196. else if (!returnType.IsValueType && returnType.IsSubclassOf(typeof(Il2CppObjectBase)))
  197. {
  198. il.Emit(OpCodes.Call, ObjectBaseToPtrMethodInfo);
  199. }
  200. }
  201. private static void EmitConvertArgumentToManaged(ILGenerator il, Type managedParamType, out LocalBuilder variable)
  202. {
  203. variable = null;
  204. if (managedParamType.IsValueType) // don't need to convert blittable types
  205. return;
  206. void EmitCreateIl2CppObject()
  207. {
  208. Label endLabel = il.DefineLabel();
  209. Label notNullLabel = il.DefineLabel();
  210. il.Emit(OpCodes.Dup);
  211. il.Emit(OpCodes.Brtrue_S, notNullLabel);
  212. il.Emit(OpCodes.Pop);
  213. il.Emit(OpCodes.Ldnull);
  214. il.Emit(OpCodes.Br_S, endLabel);
  215. il.MarkLabel(notNullLabel);
  216. il.Emit(OpCodes.Newobj, AccessTools.DeclaredConstructor(managedParamType, new[] { typeof(IntPtr) }));
  217. il.MarkLabel(endLabel);
  218. }
  219. void HandleTypeConversion(Type originalType)
  220. {
  221. if (originalType == typeof(string))
  222. {
  223. il.Emit(OpCodes.Call, IL2CPPToManagedStringMethodInfo);
  224. }
  225. else if (originalType.IsSubclassOf(typeof(Il2CppObjectBase)))
  226. {
  227. EmitCreateIl2CppObject();
  228. }
  229. }
  230. if (managedParamType.IsByRef)
  231. {
  232. Type directType = managedParamType.GetElementType();
  233. variable = il.DeclareLocal(directType);
  234. il.Emit(OpCodes.Ldind_I);
  235. HandleTypeConversion(directType);
  236. il.Emit(OpCodes.Stloc, variable);
  237. il.Emit(OpCodes.Ldloca, variable);
  238. }
  239. else
  240. {
  241. HandleTypeConversion(managedParamType);
  242. }
  243. }
  244. }
  245. }