IL2CPPDetourMethodPatcher.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348
  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,
  68. CallingConvention.Cdecl);
  69. var detourPtr = Marshal.GetFunctionPointerForDelegate(unmanagedTrampolineMethod.CreateDelegate(unmanagedDelegateType));
  70. nativeDetour = new FastNativeDetour(originalNativeMethodInfo->methodPointer, detourPtr);
  71. nativeDetour.Apply();
  72. // TODO: Add an ILHook for the original unhollowed method to go directly to managedHookedMethod
  73. // Right now it goes through three times as much interop conversion as it needs to, when being called from managed side
  74. return managedHookedMethod;
  75. }
  76. /// <inheritdoc />
  77. public override DynamicMethodDefinition CopyOriginal()
  78. {
  79. var dmd = new DynamicMethodDefinition(Original);
  80. dmd.Definition.Name = "UnhollowedWrapper_" + dmd.Definition.Name;
  81. var cursor = new ILCursor(new ILContext(dmd.Definition));
  82. // Remove il2cpp_object_get_virtual_method
  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. }
  90. else
  91. {
  92. cursor.Goto(0)
  93. .GotoNext(x => x.MatchLdsfld(UnhollowerUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(Original)))
  94. .Remove();
  95. }
  96. // Replace original IL2CPPMethodInfo pointer with a modified one that points to the trampoline
  97. cursor
  98. .Emit(Mono.Cecil.Cil.OpCodes.Ldc_I8, ((IntPtr)modifiedNativeMethodInfo).ToInt64())
  99. .Emit(Mono.Cecil.Cil.OpCodes.Conv_I);
  100. return dmd;
  101. }
  102. /// <summary>
  103. /// A handler for <see cref="PatchManager.ResolvePatcher"/> that checks if a method doesn't have a body
  104. /// (e.g. it's icall or marked with <see cref="DynDllImportAttribute"/>) and thus can be patched with
  105. /// <see cref="NativeDetour"/>.
  106. /// </summary>
  107. /// <param name="sender">Not used</param>
  108. /// <param name="args">Patch resolver arguments</param>
  109. ///
  110. public static void TryResolve(object sender, PatchManager.PatcherResolverEventArgs args)
  111. {
  112. if (args.Original.DeclaringType?.IsSubclassOf(typeof(Il2CppObjectBase)) == true)
  113. args.MethodPatcher = new IL2CPPDetourMethodPatcher(args.Original);
  114. }
  115. private DynamicMethodDefinition GenerateNativeToManagedTrampoline(MethodInfo targetManagedMethodInfo)
  116. {
  117. // managedParams are the unhollower types used on the managed side
  118. // unmanagedParams are IntPtr references that are used by IL2CPP compiled assembly
  119. var managedParams = Original.GetParameters().Select(x => x.ParameterType).ToArray();
  120. var unmanagedParams = new Type[managedParams.Length + 2]; // +1 for thisptr at the start, +1 for methodInfo at the end
  121. // TODO: Check if this breaks for static IL2CPP methods
  122. unmanagedParams[0] = typeof(IntPtr);
  123. unmanagedParams[unmanagedParams.Length - 1] = typeof(Il2CppMethodInfo*);
  124. Array.Copy(managedParams.Select(ConvertManagedTypeToIL2CPPType).ToArray(), 0,
  125. unmanagedParams, 1, managedParams.Length);
  126. var managedReturnType = AccessTools.GetReturnedType(Original);
  127. var unmanagedReturnType = ConvertManagedTypeToIL2CPPType(managedReturnType);
  128. var dmd = new DynamicMethodDefinition("(il2cpp -> managed) " + Original.Name,
  129. unmanagedReturnType,
  130. unmanagedParams
  131. );
  132. var il = dmd.GetILGenerator();
  133. il.BeginExceptionBlock();
  134. // Declare a list of variables to dereference back to the original pointers.
  135. // This is required due to the needed unhollower type conversions, so we can't directly pass some addresses as byref types
  136. LocalBuilder[] indirectVariables = new LocalBuilder[managedParams.Length];
  137. if (!Original.IsStatic)
  138. {
  139. // Load thisptr as arg0
  140. il.Emit(OpCodes.Ldarg_0);
  141. EmitConvertArgumentToManaged(il, Original.DeclaringType, out _);
  142. }
  143. for (int i = 0; i < managedParams.Length; ++i)
  144. {
  145. il.Emit(OpCodes.Ldarg_S, i + 1);
  146. EmitConvertArgumentToManaged(il, managedParams[i], out indirectVariables[i]);
  147. }
  148. // Run the managed method
  149. il.Emit(OpCodes.Call, targetManagedMethodInfo);
  150. // Store the managed return type temporarily (if there was one)
  151. LocalBuilder managedReturnVariable = null;
  152. if (managedReturnType != typeof(void))
  153. {
  154. managedReturnVariable = il.DeclareLocal(managedReturnType);
  155. il.Emit(OpCodes.Stloc, managedReturnVariable);
  156. }
  157. // Convert any managed byref values into their relevant IL2CPP types, and then store the values into their relevant dereferenced pointers
  158. for (int i = 0; i < managedParams.Length; ++i)
  159. {
  160. if (indirectVariables[i] == null)
  161. continue;
  162. il.Emit(OpCodes.Ldarg_S, i + 1);
  163. il.Emit(OpCodes.Ldloc, indirectVariables[i]);
  164. EmitConvertManagedTypeToIL2CPP(il, managedParams[i].GetElementType());
  165. il.Emit(OpCodes.Stind_I);
  166. }
  167. // Handle any lingering exceptions
  168. il.BeginCatchBlock(typeof(Exception));
  169. il.Emit(OpCodes.Call, ReportExceptionMethodInfo);
  170. il.EndExceptionBlock();
  171. // Convert the return value back to an IL2CPP friendly type (if there was a return value), and then return
  172. if (managedReturnVariable != null)
  173. {
  174. il.Emit(OpCodes.Ldloc, managedReturnVariable);
  175. EmitConvertManagedTypeToIL2CPP(il, managedReturnType);
  176. }
  177. il.Emit(OpCodes.Ret);
  178. return dmd;
  179. }
  180. private static void ReportException(Exception ex)
  181. {
  182. DetourLogger.LogError(ex.ToString());
  183. }
  184. private static Type ConvertManagedTypeToIL2CPPType(Type managedType)
  185. {
  186. if (managedType.IsByRef)
  187. {
  188. Type directType = managedType.GetElementType();
  189. if (directType == typeof(string) || directType.IsSubclassOf(typeof(Il2CppObjectBase)))
  190. {
  191. return typeof(IntPtr*);
  192. }
  193. }
  194. else if (managedType == typeof(string) || managedType.IsSubclassOf(typeof(Il2CppObjectBase)))
  195. {
  196. return typeof(IntPtr);
  197. }
  198. return managedType;
  199. }
  200. private static void EmitConvertManagedTypeToIL2CPP(ILGenerator il, Type returnType)
  201. {
  202. if (returnType == typeof(string))
  203. {
  204. il.Emit(OpCodes.Call, ManagedToIL2CPPStringMethodInfo);
  205. }
  206. else if (!returnType.IsValueType && returnType.IsSubclassOf(typeof(Il2CppObjectBase)))
  207. {
  208. il.Emit(OpCodes.Call, ObjectBaseToPtrMethodInfo);
  209. }
  210. }
  211. private static void EmitConvertArgumentToManaged(ILGenerator il, Type managedParamType, out LocalBuilder variable)
  212. {
  213. variable = null;
  214. if (managedParamType.IsValueType) // don't need to convert blittable types
  215. return;
  216. void EmitCreateIl2CppObject()
  217. {
  218. Label endLabel = il.DefineLabel();
  219. Label notNullLabel = il.DefineLabel();
  220. il.Emit(OpCodes.Dup);
  221. il.Emit(OpCodes.Brtrue_S, notNullLabel);
  222. il.Emit(OpCodes.Pop);
  223. il.Emit(OpCodes.Ldnull);
  224. il.Emit(OpCodes.Br_S, endLabel);
  225. il.MarkLabel(notNullLabel);
  226. il.Emit(OpCodes.Newobj, AccessTools.DeclaredConstructor(managedParamType, new[] { typeof(IntPtr) }));
  227. il.MarkLabel(endLabel);
  228. }
  229. void HandleTypeConversion(Type originalType)
  230. {
  231. if (originalType == typeof(string))
  232. {
  233. il.Emit(OpCodes.Call, IL2CPPToManagedStringMethodInfo);
  234. }
  235. else if (originalType.IsSubclassOf(typeof(Il2CppObjectBase)))
  236. {
  237. EmitCreateIl2CppObject();
  238. }
  239. }
  240. if (managedParamType.IsByRef)
  241. {
  242. Type directType = managedParamType.GetElementType();
  243. variable = il.DeclareLocal(directType);
  244. il.Emit(OpCodes.Ldind_I);
  245. HandleTypeConversion(directType);
  246. il.Emit(OpCodes.Stloc, variable);
  247. il.Emit(OpCodes.Ldloca, variable);
  248. }
  249. else
  250. {
  251. HandleTypeConversion(managedParamType);
  252. }
  253. }
  254. }
  255. }