IL2CPPDetourMethodPatcher.cs 11 KB

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