IL2CPPDetourMethodPatcher.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  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 MethodInfo ReportExceptionMethodInfo
  25. = AccessTools.Method(typeof(IL2CPPDetourMethodPatcher), nameof(ReportException));
  26. private static readonly ManualLogSource DetourLogger = Logger.CreateLogSource("Detour");
  27. private FastNativeDetour nativeDetour;
  28. private Il2CppMethodInfo* originalNativeMethodInfo;
  29. private Il2CppMethodInfo* modifiedNativeMethodInfo;
  30. /// <summary>
  31. /// Constructs a new instance of <see cref="NativeDetour"/> method patcher.
  32. /// </summary>
  33. /// <param name="original"></param>
  34. public IL2CPPDetourMethodPatcher(MethodBase original) : base(original)
  35. {
  36. Init();
  37. }
  38. private void Init()
  39. {
  40. // Get the native MethodInfo struct for the target method
  41. originalNativeMethodInfo = (Il2CppMethodInfo*)
  42. (IntPtr)UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Original).GetValue(null);
  43. // Create a trampoline from the original target method
  44. var trampolinePtr = DetourGenerator.CreateTrampolineFromFunction(originalNativeMethodInfo->methodPointer, out _, out _);
  45. // Create a modified native MethodInfo struct to point towards the trampoline
  46. modifiedNativeMethodInfo = (Il2CppMethodInfo*)Marshal.AllocHGlobal(Marshal.SizeOf<Il2CppMethodInfo>());
  47. Marshal.StructureToPtr(*originalNativeMethodInfo, (IntPtr)modifiedNativeMethodInfo, false);
  48. modifiedNativeMethodInfo->methodPointer = trampolinePtr;
  49. }
  50. /// <inheritdoc />
  51. public override DynamicMethodDefinition PrepareOriginal()
  52. {
  53. return null;
  54. }
  55. /// <inheritdoc />
  56. public override MethodBase DetourTo(MethodBase replacement)
  57. {
  58. // Unpatch an existing detour if it exists
  59. nativeDetour?.Dispose();
  60. // Generate a new DMD of the modified unhollowed method, and apply harmony patches to it
  61. var copiedDmd = CopyOriginal();
  62. HarmonyManipulator.Manipulate(copiedDmd.OriginalMethod, copiedDmd.OriginalMethod.GetPatchInfo(), new ILContext(copiedDmd.Definition));
  63. // Generate the MethodInfo instances
  64. var managedHookedMethod = copiedDmd.Generate();
  65. var unmanagedTrampolineMethod = GenerateNativeToManagedTrampoline(managedHookedMethod).Generate();
  66. // Apply a detour from the unmanaged implementation to the patched harmony method
  67. var unmanagedDelegateType = DelegateTypeFactory.instance.CreateDelegateType(unmanagedTrampolineMethod.ReturnType,
  68. unmanagedTrampolineMethod.GetParameters().Select(x => x.ParameterType).ToArray());
  69. var detourPtr = MonoExtensions.GetFunctionPointerForDelegate(unmanagedTrampolineMethod.CreateDelegate(unmanagedDelegateType),
  70. CallingConvention.Cdecl);
  71. nativeDetour = new FastNativeDetour(originalNativeMethodInfo->methodPointer, detourPtr);
  72. nativeDetour.Apply();
  73. // TODO: Add an ILHook for the original unhollowed method to go directly to managedHookedMethod
  74. // Right now it goes through three times as much interop conversion as it needs to, when being called from managed side
  75. return managedHookedMethod;
  76. }
  77. /// <inheritdoc />
  78. public override DynamicMethodDefinition CopyOriginal()
  79. {
  80. var dmd = new DynamicMethodDefinition(Original);
  81. dmd.Definition.Name = "UnhollowedWrapper_" + dmd.Definition.Name;
  82. var cursor = new ILCursor(new ILContext(dmd.Definition));
  83. // Remove il2cpp_object_get_virtual_method
  84. if (cursor.TryGotoNext(x => x.MatchLdarg(0),
  85. x => x.MatchCall(typeof(UnhollowerBaseLib.IL2CPP), nameof(UnhollowerBaseLib.IL2CPP.Il2CppObjectBaseToPtr)),
  86. x => x.MatchLdsfld(out _),
  87. x => x.MatchCall(typeof(UnhollowerBaseLib.IL2CPP), nameof(UnhollowerBaseLib.IL2CPP.il2cpp_object_get_virtual_method))))
  88. {
  89. cursor.RemoveRange(4);
  90. }
  91. else
  92. {
  93. cursor.Goto(0)
  94. .GotoNext(x => x.MatchLdsfld(UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Original)))
  95. .Remove();
  96. }
  97. // Replace original IL2CPPMethodInfo pointer with a modified one that points to the trampoline
  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 = Original.GetParameters().Select(x => x.ParameterType).ToArray();
  121. var unmanagedParams = new Type[managedParams.Length + 2]; // +1 for thisptr at the start, +1 for methodInfo at the end
  122. // TODO: Check if this breaks for static IL2CPP methods
  123. unmanagedParams[0] = typeof(IntPtr);
  124. unmanagedParams[unmanagedParams.Length - 1] = typeof(Il2CppMethodInfo*);
  125. Array.Copy(managedParams.Select(ConvertManagedTypeToIL2CPPType).ToArray(), 0,
  126. unmanagedParams, 1, managedParams.Length);
  127. var managedReturnType = AccessTools.GetReturnedType(Original);
  128. var unmanagedReturnType = ConvertManagedTypeToIL2CPPType(managedReturnType);
  129. var dmd = new DynamicMethodDefinition("(il2cpp -> managed) " + Original.Name,
  130. unmanagedReturnType,
  131. unmanagedParams
  132. );
  133. var il = dmd.GetILGenerator();
  134. il.BeginExceptionBlock();
  135. // Declare a list of variables to dereference back to the original pointers.
  136. // This is required due to the needed unhollower type conversions, so we can't directly pass some addresses as byref types
  137. LocalBuilder[] indirectVariables = new LocalBuilder[managedParams.Length];
  138. if (!Original.IsStatic)
  139. {
  140. // Load thisptr as arg0
  141. il.Emit(OpCodes.Ldarg_0);
  142. il.Emit(OpCodes.Newobj, AccessTools.DeclaredConstructor(Original.DeclaringType, new[] { typeof(IntPtr) }));
  143. }
  144. for (int i = 0; i < managedParams.Length; ++i)
  145. {
  146. il.Emit(OpCodes.Ldarg_S, i + 1);
  147. EmitConvertArgumentToManaged(il, managedParams[i], out indirectVariables[i]);
  148. }
  149. // Run the managed method
  150. il.Emit(OpCodes.Call, targetManagedMethodInfo);
  151. // Store the managed return type temporarily (if there was one)
  152. LocalBuilder managedReturnVariable = null;
  153. if (managedReturnType != typeof(void))
  154. {
  155. managedReturnVariable = il.DeclareLocal(managedReturnType);
  156. il.Emit(OpCodes.Stloc, managedReturnVariable);
  157. }
  158. // Convert any managed byref values into their relevant IL2CPP types, and then store the values into their relevant dereferenced pointers
  159. for (int i = 0; i < managedParams.Length; ++i)
  160. {
  161. if (indirectVariables[i] == null)
  162. continue;
  163. il.Emit(OpCodes.Ldarg_S, i + 1);
  164. il.Emit(OpCodes.Ldloc, indirectVariables[i]);
  165. EmitConvertManagedTypeToIL2CPP(il, managedParams[i].GetElementType());
  166. il.Emit(OpCodes.Stind_I);
  167. }
  168. // Handle any lingering exceptions
  169. il.BeginCatchBlock(typeof(Exception));
  170. il.Emit(OpCodes.Call, ReportExceptionMethodInfo);
  171. il.EndExceptionBlock();
  172. // Convert the return value back to an IL2CPP friendly type (if there was a return value), and then return
  173. if (managedReturnVariable != null)
  174. {
  175. il.Emit(OpCodes.Ldloc, managedReturnVariable);
  176. EmitConvertManagedTypeToIL2CPP(il, managedReturnType);
  177. }
  178. il.Emit(OpCodes.Ret);
  179. return dmd;
  180. }
  181. private static void ReportException(Exception ex)
  182. {
  183. DetourLogger.LogError(ex.ToString());
  184. }
  185. private static Type ConvertManagedTypeToIL2CPPType(Type managedType)
  186. {
  187. if (managedType.IsByRef)
  188. {
  189. Type directType = managedType.GetElementType();
  190. if (directType == typeof(string) || directType.IsSubclassOf(typeof(Il2CppObjectBase)))
  191. {
  192. return typeof(IntPtr*);
  193. }
  194. }
  195. else if (managedType == typeof(string) || managedType.IsSubclassOf(typeof(Il2CppObjectBase)))
  196. {
  197. return typeof(IntPtr);
  198. }
  199. return managedType;
  200. }
  201. private static void EmitConvertManagedTypeToIL2CPP(ILGenerator il, Type returnType)
  202. {
  203. if (returnType == typeof(string))
  204. {
  205. il.Emit(OpCodes.Call, ManagedToIL2CPPStringMethodInfo);
  206. }
  207. else if (!returnType.IsValueType && returnType.IsSubclassOf(typeof(Il2CppObjectBase)))
  208. {
  209. il.Emit(OpCodes.Call, ObjectBaseToPtrMethodInfo);
  210. }
  211. }
  212. private static void EmitConvertArgumentToManaged(ILGenerator il, Type managedParamType, out LocalBuilder variable)
  213. {
  214. variable = null;
  215. if (managedParamType.IsValueType) // don't need to convert blittable types
  216. return;
  217. void EmitCreateIl2CppObject()
  218. {
  219. Label endLabel = il.DefineLabel();
  220. Label notNullLabel = il.DefineLabel();
  221. il.Emit(OpCodes.Dup);
  222. il.Emit(OpCodes.Brtrue_S, notNullLabel);
  223. il.Emit(OpCodes.Pop);
  224. il.Emit(OpCodes.Ldnull);
  225. il.Emit(OpCodes.Br_S, endLabel);
  226. il.MarkLabel(notNullLabel);
  227. il.Emit(OpCodes.Newobj, AccessTools.DeclaredConstructor(managedParamType, new[] { typeof(IntPtr) }));
  228. il.MarkLabel(endLabel);
  229. }
  230. void HandleTypeConversion(Type originalType)
  231. {
  232. if (originalType == typeof(string))
  233. {
  234. il.Emit(OpCodes.Call, IL2CPPToManagedStringMethodInfo);
  235. }
  236. else if (originalType.IsSubclassOf(typeof(Il2CppObjectBase)))
  237. {
  238. EmitCreateIl2CppObject();
  239. }
  240. }
  241. if (managedParamType.IsByRef)
  242. {
  243. Type directType = managedParamType.GetElementType();
  244. variable = il.DeclareLocal(directType);
  245. il.Emit(OpCodes.Ldind_I);
  246. HandleTypeConversion(directType);
  247. il.Emit(OpCodes.Stloc, variable);
  248. il.Emit(OpCodes.Ldloca, variable);
  249. }
  250. else
  251. {
  252. HandleTypeConversion(managedParamType);
  253. }
  254. }
  255. }
  256. }