From 054e31f3d6ad07d23cf3a1fb402e4c95a5628493 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Thu, 25 Aug 2022 01:14:40 -0500 Subject: [PATCH 01/11] Start port to MonoMod reorg --- Harmony/Harmony.csproj | 22 +-- Harmony/Internal/PatchFunctions.cs | 4 +- Harmony/Internal/Util/ILHookExtensions.cs | 45 ----- Harmony/Internal/Util/StackTraceFixes.cs | 52 ++---- .../Public/Patching/ManagedMethodPatcher.cs | 7 +- .../Patching/NativeDetourMethodPatcher.cs | 176 ------------------ Harmony/Public/Patching/PatchManager.cs | 9 +- HarmonyTests/HarmonyTests.csproj | 2 +- HarmonyTests/TestTools.cs | 2 +- 9 files changed, 41 insertions(+), 278 deletions(-) delete mode 100644 Harmony/Internal/Util/ILHookExtensions.cs delete mode 100644 Harmony/Public/Patching/NativeDetourMethodPatcher.cs diff --git a/Harmony/Harmony.csproj b/Harmony/Harmony.csproj index 1b9b7274..3a07b3f0 100644 --- a/Harmony/Harmony.csproj +++ b/Harmony/Harmony.csproj @@ -46,9 +46,9 @@ - - - + + + @@ -66,13 +66,13 @@ - + - + - + @@ -80,20 +80,20 @@ $(MSBuildThisFileDirectory)bin\$(Configuration) - + - + - + - + - + diff --git a/Harmony/Internal/PatchFunctions.cs b/Harmony/Internal/PatchFunctions.cs index 2af2a308..6bde474f 100644 --- a/Harmony/Internal/PatchFunctions.cs +++ b/Harmony/Internal/PatchFunctions.cs @@ -146,7 +146,7 @@ static void PrintInfo(StringBuilder sb, ICollection methods, string // TODO: Handle new debugType Logger.Log(Logger.LogChannel.IL, () => $"Generated reverse patcher ({ctx.Method.FullName}):\n{ctx.Body.ToILDasmString()}", debug); - }, new ILHookConfig { ManualApply = true }); + }, applyByDefault: false); try { @@ -157,7 +157,7 @@ static void PrintInfo(StringBuilder sb, ICollection methods, string throw HarmonyException.Create(ex, patchBody); } - var replacement = hook.GetCurrentTarget() as MethodInfo; + var replacement = hook.Method as MethodInfo; PatchTools.RememberObject(standin.method, replacement); return replacement; } diff --git a/Harmony/Internal/Util/ILHookExtensions.cs b/Harmony/Internal/Util/ILHookExtensions.cs deleted file mode 100644 index 34c84d51..00000000 --- a/Harmony/Internal/Util/ILHookExtensions.cs +++ /dev/null @@ -1,45 +0,0 @@ -using MonoMod.Cil; -using System; -using System.Reflection; -using System.Reflection.Emit; -using MonoMod.RuntimeDetour; -using MonoMod.Utils; - -namespace HarmonyLib.Internal.Util -{ - internal class ILHookExt : ILHook - { - public string dumpPath; - - public ILHookExt(MethodBase @from, ILContext.Manipulator manipulator, ILHookConfig config) : base(@from, manipulator, config) - { - } - } - - internal static class ILHookExtensions - { - private static readonly MethodInfo IsAppliedSetter = - AccessTools.PropertySetter(typeof(ILHook), nameof(ILHook.IsApplied)); - - public static readonly Action SetIsApplied = IsAppliedSetter.CreateDelegate>(); - - private static Func GetAppliedDetour; - - static ILHookExtensions() - { - var detourGetter = new DynamicMethodDefinition("GetDetour", typeof(Detour), new[] {typeof(ILHook)}); - var il = detourGetter.GetILGenerator(); - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Call, AccessTools.PropertyGetter(typeof(ILHook), "_Ctx")); - il.Emit(OpCodes.Ldfld, AccessTools.Field(AccessTools.Inner(typeof(ILHook), "Context"), "Detour")); - il.Emit(OpCodes.Ret); - GetAppliedDetour = detourGetter.Generate().CreateDelegate>(); - } - - public static MethodBase GetCurrentTarget(this ILHook hook) - { - var detour = GetAppliedDetour(hook); - return detour.Target; - } - } -} diff --git a/Harmony/Internal/Util/StackTraceFixes.cs b/Harmony/Internal/Util/StackTraceFixes.cs index 8bf74059..7fcad07f 100644 --- a/Harmony/Internal/Util/StackTraceFixes.cs +++ b/Harmony/Internal/Util/StackTraceFixes.cs @@ -4,6 +4,7 @@ using System.Reflection; using HarmonyLib.Tools; using MonoMod.RuntimeDetour; +using System.Linq; namespace HarmonyLib.Internal.RuntimeFixes { @@ -18,9 +19,7 @@ internal static class StackTraceFixes private static readonly Dictionary RealMethodMap = new Dictionary(); - private static Func _realGetAss; - private static Func _origGetMethod; - private static Action _origRefresh; + private static Hook stackTraceHook; public static void Install() { @@ -29,17 +28,11 @@ public static void Install() try { - var refreshDet = new Detour(AccessTools.Method(AccessTools.Inner(typeof(ILHook), "Context"), "Refresh"), - AccessTools.Method(typeof(StackTraceFixes), nameof(OnILChainRefresh))); - _origRefresh = refreshDet.GenerateTrampoline>(); + DetourManager.ILHookApplied += OnILChainRefresh; + DetourManager.ILHookUndone += OnILChainRefresh; - var getMethodDet = new Detour(AccessTools.Method(typeof(StackFrame), nameof(StackFrame.GetMethod)), - AccessTools.Method(typeof(StackTraceFixes), nameof(GetMethodFix))); - _origGetMethod = getMethodDet.GenerateTrampoline>(); - - var nat = new NativeDetour(AccessTools.Method(typeof(Assembly), nameof(Assembly.GetExecutingAssembly)), + stackTraceHook = new Hook(AccessTools.Method(typeof(Assembly), nameof(Assembly.GetExecutingAssembly)), AccessTools.Method(typeof(StackTraceFixes), nameof(GetAssemblyFix))); - _realGetAss = nat.GenerateTrampoline>(); } catch (Exception e) { @@ -48,37 +41,30 @@ public static void Install() _applied = true; } - // Fix StackFrame's GetMethod to map patched method to unpatched one instead - private static MethodBase GetMethodFix(StackFrame self) - { - var m = _origGetMethod(self); - if (m == null) - return null; - lock (RealMethodMap) - { - return RealMethodMap.TryGetValue(m, out var real) ? real : m; - } - } - // We need to force GetExecutingAssembly make use of stack trace // This is to fix cases where calling assembly is actually the patch // This solves issues with code where it uses the method to get current filepath etc - private static Assembly GetAssemblyFix() + private static Assembly GetAssemblyFix(Func orig) { - return new StackFrame(1).GetMethod()?.Module.Assembly ?? _realGetAss(); + var method = new StackTrace().GetFrames().SkipWhile(frame => frame.GetMethod() != stackTraceHook.DetourInfo.Entry).Skip(1).First().GetMethod(); + if (RealMethodMap.TryGetValue(method, out var real)) + { + return real.Module.Assembly; + } + return orig(); } - // Helper to save the detour info after patch is complete - private static void OnILChainRefresh(object self) - { - _origRefresh(self); + private static readonly AccessTools.FieldRef GetDetourState = AccessTools.FieldRefAccess(AccessTools.DeclaredField(typeof(MethodDetourInfo), "state")); - if (!(AccessTools.Field(self.GetType(), "Detour").GetValue(self) is Detour detour)) - return; + private static readonly AccessTools.FieldRef GetEndOfChain = + AccessTools.FieldRefAccess(AccessTools.DeclaredField(typeof(DetourManager).GetNestedType("DetourState", AccessTools.all), "EndOfChain")); + // Helper to save the detour info after patch is complete + private static void OnILChainRefresh(ILHookInfo self) + { lock (RealMethodMap) { - RealMethodMap[detour.Target] = detour.Method; + RealMethodMap[GetEndOfChain(GetDetourState(self.Method))] = self.Method.Method; } } } diff --git a/Harmony/Public/Patching/ManagedMethodPatcher.cs b/Harmony/Public/Patching/ManagedMethodPatcher.cs index 0fc7fc39..db898961 100644 --- a/Harmony/Public/Patching/ManagedMethodPatcher.cs +++ b/Harmony/Public/Patching/ManagedMethodPatcher.cs @@ -33,18 +33,17 @@ public override DynamicMethodDefinition PrepareOriginal() /// public override MethodBase DetourTo(MethodBase replacement) { - ilHook ??= new ILHook(Original, Manipulator, new ILHookConfig {ManualApply = true}); - // Reset IsApplied to force MonoMod to reapply the ILHook without removing it - ILHookExtensions.SetIsApplied(ilHook, false); + ilHook ??= new ILHook(Original, Manipulator, applyByDefault: false); try { + ilHook.Undo(); ilHook.Apply(); } catch (Exception e) { throw HarmonyException.Create(e, hookBody); } - return ilHook.GetCurrentTarget(); + return ilHook.Method; } /// diff --git a/Harmony/Public/Patching/NativeDetourMethodPatcher.cs b/Harmony/Public/Patching/NativeDetourMethodPatcher.cs deleted file mode 100644 index 25659810..00000000 --- a/Harmony/Public/Patching/NativeDetourMethodPatcher.cs +++ /dev/null @@ -1,176 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; -using HarmonyLib.Tools; -using MonoMod.RuntimeDetour; -using MonoMod.Utils; - -namespace HarmonyLib.Public.Patching -{ - /// - /// A method patcher that uses to patch internal calls, - /// methods marked with and any other managed method that CLR managed-to-native - /// trampolines for and which has no IL body defined. - /// - public class NativeDetourMethodPatcher : MethodPatcher - { - private static readonly Dictionary TrampolineCache = new Dictionary(); - private static int counter; - private static readonly object CounterLock = new object(); - - private static readonly MethodInfo GetTrampolineMethod = - AccessTools.Method(typeof(NativeDetourMethodPatcher), nameof(GetTrampoline)); - - private string[] argTypeNames; - private Type[] argTypes; - - private int currentOriginal = -1, newOriginal; - private MethodInfo invokeTrampolineMethod; - private NativeDetour nativeDetour; - private Type returnType; - private Type trampolineDelegateType; - - /// - /// Constructs a new instance of method patcher. - /// - /// - public NativeDetourMethodPatcher(MethodBase original) : base(original) - { - Init(); - } - - private void Init() - { - if (AccessTools.IsNetCoreRuntime) - Logger.Log(Logger.LogChannel.Warn, () => - $"Patch target {Original.FullDescription()} is marked as extern. " + - "Extern methods may not be patched because of inlining behaviour of coreclr (refer to https://github.com/dotnet/coreclr/pull/8263)." + - "If you need to patch externs, consider using pure NativeDetour instead."); - - var orig = Original; - - var args = orig.GetParameters(); - var offs = orig.IsStatic ? 0 : 1; - argTypes = new Type[args.Length + offs]; - argTypeNames = new string[args.Length + offs]; - returnType = (orig as MethodInfo)?.ReturnType; - - if (!orig.IsStatic) - { - argTypes[0] = orig.GetThisParamType(); - argTypeNames[0] = "this"; - } - - for (var i = 0; i < args.Length; i++) - { - argTypes[i + offs] = args[i].ParameterType; - argTypeNames[i + offs] = args[i].Name; - } - - trampolineDelegateType = DelegateTypeFactory.instance.CreateDelegateType(returnType, argTypes); - invokeTrampolineMethod = AccessTools.Method(trampolineDelegateType, "Invoke"); - } - - /// - public override DynamicMethodDefinition PrepareOriginal() - { - return GenerateManagedOriginal(); - } - - /// - public override MethodBase DetourTo(MethodBase replacement) - { - nativeDetour?.Dispose(); - - nativeDetour = new NativeDetour(Original, replacement, new NativeDetourConfig {ManualApply = true}); - - lock (TrampolineCache) - { - if (currentOriginal >= 0) - TrampolineCache.Remove(currentOriginal); - currentOriginal = newOriginal; - TrampolineCache[currentOriginal] = CreateDelegate(trampolineDelegateType, - nativeDetour.GenerateTrampoline(invokeTrampolineMethod)); - } - - nativeDetour.Apply(); - return replacement; - } - - /// - public override DynamicMethodDefinition CopyOriginal() - { - if (!(Original is MethodInfo mi)) - return null; - var dmd = new DynamicMethodDefinition("OrigWrapper", returnType, argTypes); - var il = dmd.GetILGenerator(); - for (var i = 0; i < argTypes.Length; i++) - il.Emit(OpCodes.Ldarg, i); - il.Emit(OpCodes.Call, mi); - il.Emit(OpCodes.Ret); - return dmd; - } - - private Delegate CreateDelegate(Type delegateType, MethodBase mb) - { - if (mb is DynamicMethod dm) - return dm.CreateDelegate(delegateType); - - return Delegate.CreateDelegate(delegateType, - mb as MethodInfo ?? throw new InvalidCastException($"Unexpected method type: {mb.GetType()}")); - } - - private static Delegate GetTrampoline(int hash) - { - lock (TrampolineCache) - return TrampolineCache[hash]; - } - - private DynamicMethodDefinition GenerateManagedOriginal() - { - // Here we generate the "managed" version of the native method - // It simply calls the trampoline generated by MonoMod - // As a result, we can pass the managed original to HarmonyManipulator like a normal method - - var orig = Original; - - lock (CounterLock) - { - newOriginal = counter; - counter++; - } - - var dmd = new DynamicMethodDefinition($"NativeDetour_Wrapper<{orig.GetID(simple: true)}>?{newOriginal}", returnType, argTypes); - - var def = dmd.Definition; - for (var i = 0; i < argTypeNames.Length; i++) - def.Parameters[i].Name = argTypeNames[i]; - - var il = dmd.GetILGenerator(); - - il.Emit(OpCodes.Ldc_I4, newOriginal); - il.Emit(OpCodes.Call, GetTrampolineMethod); - for (var i = 0; i < argTypes.Length; i++) - il.Emit(OpCodes.Ldarg, i); - il.Emit(OpCodes.Call, invokeTrampolineMethod); - il.Emit(OpCodes.Ret); - - return dmd; - } - - /// - /// A handler for that checks if a method doesn't have a body - /// (e.g. it's icall or marked with ) and thus can be patched with - /// . - /// - /// Not used - /// Patch resolver arguments - /// - public static void TryResolve(object sender, PatchManager.PatcherResolverEventArgs args) - { - if (args.Original.GetMethodBody() == null) - args.MethodPatcher = new NativeDetourMethodPatcher(args.Original); - } - } -} diff --git a/Harmony/Public/Patching/PatchManager.cs b/Harmony/Public/Patching/PatchManager.cs index 4b3bffee..9930b1db 100644 --- a/Harmony/Public/Patching/PatchManager.cs +++ b/Harmony/Public/Patching/PatchManager.cs @@ -3,7 +3,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; -using MonoMod.RuntimeDetour; +using MonoMod.Core.Platforms; namespace HarmonyLib.Public.Patching { @@ -25,7 +25,6 @@ public static class PatchManager static PatchManager() { ResolvePatcher += ManagedMethodPatcher.TryResolve; - ResolvePatcher += NativeDetourMethodPatcher.TryResolve; } /// @@ -132,8 +131,8 @@ internal static MethodBase FindReplacement(StackFrame frame) } else { - var baseMethod = DetourHelper.Runtime.GetIdentifiable(frameMethod); - methodStart = baseMethod.GetNativeStart().ToInt64(); + var baseMethod = PlatformTriple.Current.Runtime.GetIdentifiable(frameMethod); + methodStart = PlatformTriple.Current.Runtime.GetMethodEntryPoint(baseMethod).ToInt64(); } // Failed to find any usable method, if `frameMethod` is null, we can not find any @@ -143,7 +142,7 @@ internal static MethodBase FindReplacement(StackFrame frame) lock (ReplacementToOriginals) return ReplacementToOriginals - .FirstOrDefault(kv => kv.Key.IsAlive && ((MethodBase)kv.Key.Target).GetNativeStart().ToInt64() == methodStart).Key.Target as MethodBase; + .FirstOrDefault(kv => kv.Key.IsAlive && PlatformTriple.Current.Runtime.GetMethodEntryPoint((MethodBase)kv.Key.Target).ToInt64() == methodStart).Key.Target as MethodBase; } internal static void AddReplacementOriginal(MethodBase original, MethodInfo replacement) diff --git a/HarmonyTests/HarmonyTests.csproj b/HarmonyTests/HarmonyTests.csproj index 214c335a..e32c3053 100644 --- a/HarmonyTests/HarmonyTests.csproj +++ b/HarmonyTests/HarmonyTests.csproj @@ -62,7 +62,7 @@ - + diff --git a/HarmonyTests/TestTools.cs b/HarmonyTests/TestTools.cs index 7b64659b..67dc7b5a 100644 --- a/HarmonyTests/TestTools.cs +++ b/HarmonyTests/TestTools.cs @@ -32,7 +32,7 @@ public static long GetMethodStart(MethodBase method, out Exception exception) try { exception = null; - return method.Pin().GetNativeStart().ToInt64(); + return MonoMod.Core.Platforms.PlatformTriple.Current.GetNativeMethodBody(method).ToInt64(); } catch (Exception e) { From 4184cf0dbc584af0f00b7648c55a0d8e1c85db80 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Mon, 26 Sep 2022 18:39:06 -0500 Subject: [PATCH 02/11] Fix netstandard2.0 --- Harmony/Harmony.csproj | 1 + HarmonyTests/HarmonyTests.csproj | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Harmony/Harmony.csproj b/Harmony/Harmony.csproj index 3a07b3f0..a760dbe6 100644 --- a/Harmony/Harmony.csproj +++ b/Harmony/Harmony.csproj @@ -73,6 +73,7 @@ + diff --git a/HarmonyTests/HarmonyTests.csproj b/HarmonyTests/HarmonyTests.csproj index e32c3053..224e1aed 100644 --- a/HarmonyTests/HarmonyTests.csproj +++ b/HarmonyTests/HarmonyTests.csproj @@ -63,6 +63,8 @@ + + From d5ab22175a4ea99c2d8142b74e3082e9a7a907e9 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Tue, 22 Nov 2022 23:10:07 -0600 Subject: [PATCH 03/11] temp commit --- .gitignore | 1 + .idea/.idea.Harmony/.idea/.gitignore | 13 ------------- .idea/.idea.Harmony/.idea/.name | 1 - .idea/.idea.Harmony/.idea/CakeRider.xml | 6 ------ .../.idea/codeStyles/codeStyleConfig.xml | 5 ----- .idea/.idea.Harmony/.idea/discord.xml | 6 ------ .idea/.idea.Harmony/.idea/indexLayout.xml | 8 -------- .idea/.idea.Harmony/.idea/misc.xml | 6 ------ .idea/.idea.Harmony/.idea/vcs.xml | 13 ------------- Harmony/Harmony.csproj | 4 ++-- Harmony/Internal/Util/StackTraceFixes.cs | 5 +++-- Harmony/Public/Harmony.cs | 4 ++-- Harmony/Tools/AccessTools.cs | 10 ++-------- HarmonyTests/HarmonyTests.csproj | 16 ++++++++-------- HarmonyTests/IL/Instructions.cs | 1 + HarmonyTests/Patching/Specials.cs | 2 +- nuget.config | 3 +++ 17 files changed, 23 insertions(+), 81 deletions(-) delete mode 100644 .idea/.idea.Harmony/.idea/.gitignore delete mode 100644 .idea/.idea.Harmony/.idea/.name delete mode 100644 .idea/.idea.Harmony/.idea/CakeRider.xml delete mode 100644 .idea/.idea.Harmony/.idea/codeStyles/codeStyleConfig.xml delete mode 100644 .idea/.idea.Harmony/.idea/discord.xml delete mode 100644 .idea/.idea.Harmony/.idea/indexLayout.xml delete mode 100644 .idea/.idea.Harmony/.idea/misc.xml delete mode 100644 .idea/.idea.Harmony/.idea/vcs.xml create mode 100644 nuget.config diff --git a/.gitignore b/.gitignore index 5754fe49..7ed0fc89 100644 --- a/.gitignore +++ b/.gitignore @@ -204,3 +204,4 @@ ModelManifest.xml /Harmony/Documentation/api/*.html /Harmony/Documentation/api/*.yml /Harmony/Documentation/Documentation.dll +.idea/ diff --git a/.idea/.idea.Harmony/.idea/.gitignore b/.idea/.idea.Harmony/.idea/.gitignore deleted file mode 100644 index a7238f99..00000000 --- a/.idea/.idea.Harmony/.idea/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Rider ignored files -/contentModel.xml -/.idea.Harmony.iml -/modules.xml -/projectSettingsUpdater.xml -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml -# Editor-based HTTP Client requests -/httpRequests/ diff --git a/.idea/.idea.Harmony/.idea/.name b/.idea/.idea.Harmony/.idea/.name deleted file mode 100644 index f56b32f7..00000000 --- a/.idea/.idea.Harmony/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -Harmony \ No newline at end of file diff --git a/.idea/.idea.Harmony/.idea/CakeRider.xml b/.idea/.idea.Harmony/.idea/CakeRider.xml deleted file mode 100644 index 5f6d6687..00000000 --- a/.idea/.idea.Harmony/.idea/CakeRider.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/.idea.Harmony/.idea/codeStyles/codeStyleConfig.xml b/.idea/.idea.Harmony/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index a55e7a17..00000000 --- a/.idea/.idea.Harmony/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/.idea.Harmony/.idea/discord.xml b/.idea/.idea.Harmony/.idea/discord.xml deleted file mode 100644 index cd711a0e..00000000 --- a/.idea/.idea.Harmony/.idea/discord.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/.idea.Harmony/.idea/indexLayout.xml b/.idea/.idea.Harmony/.idea/indexLayout.xml deleted file mode 100644 index 7b08163c..00000000 --- a/.idea/.idea.Harmony/.idea/indexLayout.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/.idea.Harmony/.idea/misc.xml b/.idea/.idea.Harmony/.idea/misc.xml deleted file mode 100644 index 28a804d8..00000000 --- a/.idea/.idea.Harmony/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/.idea.Harmony/.idea/vcs.xml b/.idea/.idea.Harmony/.idea/vcs.xml deleted file mode 100644 index 13df3bb0..00000000 --- a/.idea/.idea.Harmony/.idea/vcs.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/Harmony/Harmony.csproj b/Harmony/Harmony.csproj index a760dbe6..28eab4c0 100644 --- a/Harmony/Harmony.csproj +++ b/Harmony/Harmony.csproj @@ -11,7 +11,7 @@ Andreas Pardeike, Geoffrey Horsington, ManlyMarco et al. 0Harmony true - 2.10.1 + 2.11.0-p1 LICENSE https://github.com/BepInEx/HarmonyX false @@ -69,7 +69,7 @@ - + diff --git a/Harmony/Internal/Util/StackTraceFixes.cs b/Harmony/Internal/Util/StackTraceFixes.cs index 7fcad07f..10d98072 100644 --- a/Harmony/Internal/Util/StackTraceFixes.cs +++ b/Harmony/Internal/Util/StackTraceFixes.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Reflection; using HarmonyLib.Tools; +using MonoMod.Core.Utils; using MonoMod.RuntimeDetour; using System.Linq; @@ -46,7 +47,7 @@ public static void Install() // This solves issues with code where it uses the method to get current filepath etc private static Assembly GetAssemblyFix(Func orig) { - var method = new StackTrace().GetFrames().SkipWhile(frame => frame.GetMethod() != stackTraceHook.DetourInfo.Entry).Skip(1).First().GetMethod(); + var method = new StackTrace().GetFrames()!.SkipWhile(frame => frame.GetMethod() != stackTraceHook.DetourInfo.Entry).Skip(1).First().GetMethod(); if (RealMethodMap.TryGetValue(method, out var real)) { return real.Module.Assembly; @@ -57,7 +58,7 @@ private static Assembly GetAssemblyFix(Func orig) private static readonly AccessTools.FieldRef GetDetourState = AccessTools.FieldRefAccess(AccessTools.DeclaredField(typeof(MethodDetourInfo), "state")); private static readonly AccessTools.FieldRef GetEndOfChain = - AccessTools.FieldRefAccess(AccessTools.DeclaredField(typeof(DetourManager).GetNestedType("DetourState", AccessTools.all), "EndOfChain")); + AccessTools.FieldRefAccess(AccessTools.DeclaredField(typeof(DetourManager).GetNestedType("ManagedDetourState", AccessTools.all), "EndOfChain")); // Helper to save the detour info after patch is complete private static void OnILChainRefresh(ILHookInfo self) diff --git a/Harmony/Public/Harmony.cs b/Harmony/Public/Harmony.cs index 8bd3a4be..5e23ecdb 100644 --- a/Harmony/Public/Harmony.cs +++ b/Harmony/Public/Harmony.cs @@ -9,6 +9,7 @@ using HarmonyLib.Internal.Util; using HarmonyLib.Public.Patching; using HarmonyLib.Tools; +using MonoMod.Core.Platforms; namespace HarmonyLib { @@ -67,8 +68,7 @@ public Harmony(string id) if (string.IsNullOrEmpty(location)) location = new Uri(assembly.CodeBase).LocalPath; var ptrRuntime = IntPtr.Size; - var ptrEnv = PlatformHelper.Current; - sb.AppendLine($"### Harmony id={id}, version={version}, location={location}, env/clr={environment}, platform={platform}, ptrsize:runtime/env={ptrRuntime}/{ptrEnv}"); + sb.AppendLine($"### Harmony id={id}, version={version}, location={location}, env/clr={environment}, platform={platform}, ptrsize:runtime={ptrRuntime}"); if (callingMethod?.DeclaringType is object) { var callingAssembly = callingMethod.DeclaringType.Assembly; diff --git a/Harmony/Tools/AccessTools.cs b/Harmony/Tools/AccessTools.cs index 10513956..602f44c7 100644 --- a/Harmony/Tools/AccessTools.cs +++ b/Harmony/Tools/AccessTools.cs @@ -1553,10 +1553,7 @@ public static DelegateType MethodDelegate(MethodInfo method, objec var dmd = new DynamicMethodDefinition( "OpenInstanceDelegate_" + method.Name, method.ReturnType, - parameterTypes) - { - OwnerType = declaringType - }; + parameterTypes); var ilGen = dmd.GetILGenerator(); if (declaringType != null && declaringType.IsValueType) ilGen.Emit(OpCodes.Ldarga_S, 0); @@ -1593,10 +1590,7 @@ public static DelegateType MethodDelegate(MethodInfo method, objec var dmd = new DynamicMethodDefinition( "LdftnDelegate_" + method.Name, delegateType, - new[] { typeof(object) }) - { - OwnerType = delegateType - }; + new[] { typeof(object) }); var ilGen = dmd.GetILGenerator(); ilGen.Emit(OpCodes.Ldarg_0); ilGen.Emit(OpCodes.Ldftn, method); diff --git a/HarmonyTests/HarmonyTests.csproj b/HarmonyTests/HarmonyTests.csproj index 224e1aed..26fb70d0 100644 --- a/HarmonyTests/HarmonyTests.csproj +++ b/HarmonyTests/HarmonyTests.csproj @@ -1,7 +1,7 @@ - net35;net45;netcoreapp3.1;net6.0 + net35;net45;netcoreapp3.1;net6.0;net7.0 true latest false @@ -23,7 +23,7 @@ - + @@ -49,8 +49,8 @@ - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -59,20 +59,20 @@ - + - + - + - + diff --git a/HarmonyTests/IL/Instructions.cs b/HarmonyTests/IL/Instructions.cs index 5b2b63a1..1ae6dc53 100644 --- a/HarmonyTests/IL/Instructions.cs +++ b/HarmonyTests/IL/Instructions.cs @@ -5,6 +5,7 @@ using HarmonyLibTests.Assets; using mmc::MonoMod.Utils; using Mono.Cecil.Cil; +using MonoMod.Core.Utils; using NUnit.Framework; using System; using System.Collections.Generic; diff --git a/HarmonyTests/Patching/Specials.cs b/HarmonyTests/Patching/Specials.cs index 7800c3ad..1171ffc9 100644 --- a/HarmonyTests/Patching/Specials.cs +++ b/HarmonyTests/Patching/Specials.cs @@ -248,7 +248,7 @@ public void Test_Patch_ConcreteClass() TestTools.Log($"Running patched ConcreteClass_Patch done"); } - [Test, NonParallelizable] + //[Test, NonParallelizable] public void Test_Patch_Returning_Structs([Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)] int n, [Values("I", "S")] string type) { var name = $"{type}M{n:D2}"; diff --git a/nuget.config b/nuget.config new file mode 100644 index 00000000..f072f570 --- /dev/null +++ b/nuget.config @@ -0,0 +1,3 @@ + + + From c3f7b4c7a1cd36b605d8df2a6eafbd73cba3fd1a Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Sun, 2 Jul 2023 17:11:51 -0500 Subject: [PATCH 04/11] Final update to MM 25.0.0 --- Harmony/Harmony.csproj | 2 +- Harmony/Internal/Util/StackTraceFixes.cs | 1 - HarmonyTests/HarmonyTests.csproj | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Harmony/Harmony.csproj b/Harmony/Harmony.csproj index 28eab4c0..999bf512 100644 --- a/Harmony/Harmony.csproj +++ b/Harmony/Harmony.csproj @@ -69,7 +69,7 @@ - + diff --git a/Harmony/Internal/Util/StackTraceFixes.cs b/Harmony/Internal/Util/StackTraceFixes.cs index 10d98072..6c46c11c 100644 --- a/Harmony/Internal/Util/StackTraceFixes.cs +++ b/Harmony/Internal/Util/StackTraceFixes.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Reflection; using HarmonyLib.Tools; -using MonoMod.Core.Utils; using MonoMod.RuntimeDetour; using System.Linq; diff --git a/HarmonyTests/HarmonyTests.csproj b/HarmonyTests/HarmonyTests.csproj index 26fb70d0..7abfa2a7 100644 --- a/HarmonyTests/HarmonyTests.csproj +++ b/HarmonyTests/HarmonyTests.csproj @@ -62,7 +62,7 @@ - + From e46a53696ed0cb5f6f39e549d5ca136d23a9fb53 Mon Sep 17 00:00:00 2001 From: Razgriz <47901762+rrazgriz@users.noreply.github.com> Date: Tue, 6 Sep 2022 07:45:53 -0400 Subject: [PATCH 05/11] Add [HarmonyPatchCategory], PatchCategory, PatchAllUncategorized keeps PatchAll untouched for API stability --- Harmony/Public/Attributes.cs | 16 +++++++++- Harmony/Public/Harmony.cs | 43 +++++++++++++++++++++++++-- Harmony/Public/HarmonyMethod.cs | 4 +++ Harmony/Public/PatchClassProcessor.cs | 5 ++++ 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/Harmony/Public/Attributes.cs b/Harmony/Public/Attributes.cs index b397d971..2ef7d2aa 100644 --- a/Harmony/Public/Attributes.cs +++ b/Harmony/Public/Attributes.cs @@ -112,7 +112,21 @@ public class HarmonyAttribute : Attribute public HarmonyMethod info = new HarmonyMethod(); } - /// Annotation to define targets of your Harmony patch methods + /// Annotation to define a category for use with PatchCategory + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class HarmonyPatchCategory : HarmonyAttribute + { + /// Annotation specifying the category + /// Name of patch category + /// + public HarmonyPatchCategory(string category) + { + info.category = category; + } + } + + /// Annotation to define your Harmony patch methods /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Delegate | AttributeTargets.Method, AllowMultiple = true)] public class HarmonyPatch : HarmonyAttribute diff --git a/Harmony/Public/Harmony.cs b/Harmony/Public/Harmony.cs index 5e23ecdb..bc6069d3 100644 --- a/Harmony/Public/Harmony.cs +++ b/Harmony/Public/Harmony.cs @@ -144,7 +144,7 @@ public void PatchAll(Assembly assembly) { AccessTools.GetTypesFromAssembly(assembly).Do(type => CreateClassProcessor(type).Patch()); } - + /// Searches the given type for Harmony annotation and uses them to create patches /// The type to search /// @@ -162,7 +162,8 @@ public void PatchAll(Type type) /// An optional ilmanipulator method wrapped in a /// The replacement method that was created to patch the original method /// - public MethodInfo Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null, HarmonyMethod finalizer = null, HarmonyMethod ilmanipulator = null) + public MethodInfo Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null, HarmonyMethod finalizer = null, + HarmonyMethod ilmanipulator = null) { var processor = CreateProcessor(original); _ = processor.AddPrefix(prefix); @@ -173,6 +174,44 @@ public MethodInfo Patch(MethodBase original, HarmonyMethod prefix = null, Harmon return processor.Patch(); } + /// Searches an assembly for Harmony-annotated classes without category annotations and uses them to create patches + /// + public void PatchAllUncategorized() + { + var method = new StackTrace().GetFrame(1).GetMethod(); + var assembly = method.ReflectedType.Assembly; + PatchAllUncategorized(assembly); + } + + /// Searches an assembly for Harmony-annotated classes without category annotations and uses them to create patches + /// The assembly + /// + public void PatchAllUncategorized(Assembly assembly) + { + PatchClassProcessor[] patchClasses = AccessTools.GetTypesFromAssembly(assembly).Select(CreateClassProcessor).ToArray(); + patchClasses.DoIf((patchClass => String.IsNullOrEmpty(patchClass.Category)), (patchClass => patchClass.Patch())); + } + + /// Searches an assembly for Harmony annotations with a specific category and uses them to create patches + /// Name of patch category + /// + public void PatchCategory(string category) + { + var method = new StackTrace().GetFrame(1).GetMethod(); + var assembly = method.ReflectedType.Assembly; + PatchCategory(assembly, category); + } + + /// Searches an assembly for Harmony annotations with a specific category and uses them to create patches + /// The assembly + /// Name of patch category + /// + public void PatchCategory(Assembly assembly, string category) + { + PatchClassProcessor[] patchClasses = AccessTools.GetTypesFromAssembly(assembly).Select(CreateClassProcessor).ToArray(); + patchClasses.DoIf((patchClass => patchClass.Category == category), (patchClass => patchClass.Patch())); + } + /// Creates patches by manually specifying the methods /// The original method/constructor /// An optional prefix method wrapped in a object diff --git a/Harmony/Public/HarmonyMethod.cs b/Harmony/Public/HarmonyMethod.cs index bb4e2c87..6239bad1 100644 --- a/Harmony/Public/HarmonyMethod.cs +++ b/Harmony/Public/HarmonyMethod.cs @@ -14,6 +14,10 @@ public class HarmonyMethod /// public MethodInfo method; // need to be called 'method' + /// Patch Category + /// + public string category = null; + /// Class/type declaring this patch /// public Type declaringType; diff --git a/Harmony/Public/PatchClassProcessor.cs b/Harmony/Public/PatchClassProcessor.cs index 838579b5..3da2093c 100644 --- a/Harmony/Public/PatchClassProcessor.cs +++ b/Harmony/Public/PatchClassProcessor.cs @@ -27,6 +27,9 @@ public class PatchClassProcessor typeof(HarmonyTargetMethods) }; + /// Name of the patch class's category + public string Category { get; set; } + /// Creates a patch class processor by pointing out a class. Similar to PatchAll() but without searching through all classes. /// The Harmony instance /// The class to process (need to have at least a [HarmonyPatch] attribute if allowUnannotatedType is set to false) @@ -56,6 +59,8 @@ public PatchClassProcessor(Harmony instance, Type type, bool allowUnannotatedTyp containerAttributes = HarmonyMethod.Merge(harmonyAttributes); if (containerAttributes.methodType is null) // MethodType default is Normal containerAttributes.methodType = MethodType.Normal; + + this.Category = containerAttributes.category; auxilaryMethods = new Dictionary(); foreach (var auxType in auxilaryTypes) From f618ca73f962b851a31ef4cb23c540814bfd6e02 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Sun, 2 Jul 2023 17:39:31 -0500 Subject: [PATCH 06/11] Add AccessTools.AsyncMoveNext and the Async MethodType Based on https://github.com/pardeike/Harmony/pull/514 --- Harmony/Internal/PatchTools.cs | 5 +++++ Harmony/Public/Attributes.cs | 3 ++- Harmony/Public/Harmony.cs | 2 +- Harmony/Tools/AccessTools.cs | 32 ++++++++++++++++++++++++++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/Harmony/Internal/PatchTools.cs b/Harmony/Internal/PatchTools.cs index d8c4f396..76de8dfe 100644 --- a/Harmony/Internal/PatchTools.cs +++ b/Harmony/Internal/PatchTools.cs @@ -86,6 +86,11 @@ internal static MethodBase GetOriginalMethod(this HarmonyMethod attr) return null; return AccessTools.EnumeratorMoveNext(AccessTools.DeclaredMethod(attr.GetDeclaringType(), attr.methodName, attr.argumentTypes)); + + case MethodType.Async: + if (attr.methodName is null) + return null; + return AccessTools.AsyncMoveNext(AccessTools.DeclaredMethod(attr.GetDeclaringType(), attr.methodName, attr.argumentTypes)); } } catch (AmbiguousMatchException ex) diff --git a/Harmony/Public/Attributes.cs b/Harmony/Public/Attributes.cs index 2ef7d2aa..80eb0103 100644 --- a/Harmony/Public/Attributes.cs +++ b/Harmony/Public/Attributes.cs @@ -21,7 +21,8 @@ public enum MethodType StaticConstructor, /// This is an enumerator (, or UniTask coroutine) /// This path will target the method that contains the actual enumerator code - Enumerator + Enumerator, + Async } /// Specifies the type of argument diff --git a/Harmony/Public/Harmony.cs b/Harmony/Public/Harmony.cs index bc6069d3..33798df6 100644 --- a/Harmony/Public/Harmony.cs +++ b/Harmony/Public/Harmony.cs @@ -144,7 +144,7 @@ public void PatchAll(Assembly assembly) { AccessTools.GetTypesFromAssembly(assembly).Do(type => CreateClassProcessor(type).Patch()); } - + /// Searches the given type for Harmony annotation and uses them to create patches /// The type to search /// diff --git a/Harmony/Tools/AccessTools.cs b/Harmony/Tools/AccessTools.cs index 602f44c7..e42c7da1 100644 --- a/Harmony/Tools/AccessTools.cs +++ b/Harmony/Tools/AccessTools.cs @@ -542,6 +542,38 @@ public static MethodInfo EnumeratorMoveNext(MethodBase enumerator) return moveNext; } + private static readonly Type _stateMachineAttributeType = typeof(object).Assembly.GetType("System.Runtime.CompilerServices.AsyncStateMachineAttribute"); + private static readonly MethodInfo _stateMachineTypeGetter = _stateMachineAttributeType?.GetProperty("StateMachineType").GetGetMethod(); + + /// Gets the method of an async method's state machine + /// Async method that creates the state machine internally + /// The internal method of the async state machine or null if no valid async method is detected + public static MethodInfo AsyncMoveNext(MethodBase method) + { + if (method is null) + { + FileLog.Debug("AccessTools.AsyncMoveNext: method is null"); + return null; + } + + var asyncAttribute = method.GetCustomAttributes(false).FirstOrDefault(a => a.GetType() == _stateMachineAttributeType); + if (asyncAttribute == null) + { + FileLog.Debug($"AccessTools.AsyncMoveNext: Could not find AsyncStateMachine for {method.FullDescription()}"); + return null; + } + + var asyncStateMachineType = (Type)_stateMachineTypeGetter.Invoke(method, null); + var asyncMethodBody = DeclaredMethod(asyncStateMachineType, "MoveNext"); + if (asyncMethodBody == null) + { + FileLog.Debug($"AccessTools.AsyncMoveNext: Could not find async method body for {method.FullDescription()}"); + return null; + } + + return asyncMethodBody; + } + /// Gets the names of all method that are declared in a type /// The declaring class/type /// A list of method names From 6287e3e8dfbbc4562604a97199b9ecb9893877c9 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Sun, 2 Jul 2023 17:41:56 -0500 Subject: [PATCH 07/11] Bump version to 2.11.0 --- Harmony/Harmony.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Harmony/Harmony.csproj b/Harmony/Harmony.csproj index 999bf512..f4cd0f06 100644 --- a/Harmony/Harmony.csproj +++ b/Harmony/Harmony.csproj @@ -11,7 +11,7 @@ Andreas Pardeike, Geoffrey Horsington, ManlyMarco et al. 0Harmony true - 2.11.0-p1 + 2.11.0 LICENSE https://github.com/BepInEx/HarmonyX false From 0c4897c38aa919b8ac1ddda705f5255c2408401b Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Sun, 2 Jul 2023 19:44:12 -0500 Subject: [PATCH 08/11] First pass at NativeMethodPatcher rewrite --- .../Public/Patching/NativeMethodPatcher.cs | 102 ++++++++++++++++++ Harmony/Public/Patching/PatchManager.cs | 1 + HarmonyTests/Patching/Specials.cs | 2 +- 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 Harmony/Public/Patching/NativeMethodPatcher.cs diff --git a/Harmony/Public/Patching/NativeMethodPatcher.cs b/Harmony/Public/Patching/NativeMethodPatcher.cs new file mode 100644 index 00000000..e65b9d01 --- /dev/null +++ b/Harmony/Public/Patching/NativeMethodPatcher.cs @@ -0,0 +1,102 @@ +using Mono.Cecil; +using MonoMod.Cil; +using MonoMod.Core.Platforms; +using MonoMod.Utils; +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace HarmonyLib.Public.Patching; + +/// +public class NativeMethodPatcher : MethodPatcher +{ + private PlatformTriple.NativeDetour? _hook; + private Delegate _replacementDelegate; + + private readonly Type _returnType; + private readonly Type[] _parameterTypes; + private readonly Type _altEntryDelegateType; + private readonly DataScope _altEntryDelegateStore; + private readonly MethodInfo _altEntryDelegateInvoke; + + /// + public NativeMethodPatcher(MethodBase original) : base(original) + { + var originalParameters = Original.GetParameters(); + + var offset = Original.IsStatic ? 0 : 1; + + _parameterTypes = new Type[originalParameters.Length + offset]; + + if (!Original.IsStatic) + { + _parameterTypes[0] = Original.GetThisParamType(); + } + + for (int i = 0; i < originalParameters.Length; i++) + { + _parameterTypes[i + offset] = originalParameters[i].ParameterType; + } + + _returnType = Original is MethodInfo { ReturnType: var ret } ? ret : typeof(void); + + _altEntryDelegateType = DelegateTypeFactory.instance.CreateDelegateType(_returnType, _parameterTypes); + _altEntryDelegateInvoke = _altEntryDelegateType.GetMethod("Invoke"); + + _altEntryDelegateStore = DynamicReferenceManager.AllocReference(default(Delegate), out _); + } + + /// + public override DynamicMethodDefinition PrepareOriginal() + { + var dmd = new DynamicMethodDefinition( + $"NativeHookProxy<{Original.DeclaringType.FullName}:{Original.Name}>", + _returnType, + _parameterTypes + ); + + var il = new ILContext(dmd.Definition); + + var c = new ILCursor(il); + + c.EmitLoadReference(_altEntryDelegateStore.Data); + for (int i = 0; i < _parameterTypes.Length; i++) + { + c.EmitLdarg(i); + } + c.EmitCallvirt(_altEntryDelegateInvoke); + c.EmitRet(); + + return dmd; + } + + /// + public override MethodBase DetourTo(MethodBase replacement) + { + _replacementDelegate = replacement.CreateDelegate(_altEntryDelegateType); + var replacementDelegatePtr = Marshal.GetFunctionPointerForDelegate(_replacementDelegate); + if (_hook is { } hook) + { + hook.Simple.ChangeTarget(replacementDelegatePtr); + } + else + { + _hook = PlatformTriple.Current.CreateNativeDetour(PlatformTriple.Current.GetNativeMethodBody(Original), replacementDelegatePtr); + DynamicReferenceManager.SetValue(_altEntryDelegateStore.Data, Marshal.GetDelegateForFunctionPointer(_hook.Value.AltEntry, _altEntryDelegateType)); + } + return replacement; + } + + /// + public override DynamicMethodDefinition CopyOriginal() + { + return null; + } + + public static void TryResolve(object _, PatchManager.PatcherResolverEventArgs args) + { + if (args.Original.GetMethodBody() == null) + args.MethodPatcher = new NativeMethodPatcher(args.Original); + } +} diff --git a/Harmony/Public/Patching/PatchManager.cs b/Harmony/Public/Patching/PatchManager.cs index 9930b1db..1634c96e 100644 --- a/Harmony/Public/Patching/PatchManager.cs +++ b/Harmony/Public/Patching/PatchManager.cs @@ -25,6 +25,7 @@ public static class PatchManager static PatchManager() { ResolvePatcher += ManagedMethodPatcher.TryResolve; + ResolvePatcher += NativeMethodPatcher.TryResolve; } /// diff --git a/HarmonyTests/Patching/Specials.cs b/HarmonyTests/Patching/Specials.cs index 1171ffc9..7800c3ad 100644 --- a/HarmonyTests/Patching/Specials.cs +++ b/HarmonyTests/Patching/Specials.cs @@ -248,7 +248,7 @@ public void Test_Patch_ConcreteClass() TestTools.Log($"Running patched ConcreteClass_Patch done"); } - //[Test, NonParallelizable] + [Test, NonParallelizable] public void Test_Patch_Returning_Structs([Values(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)] int n, [Values("I", "S")] string type) { var name = $"{type}M{n:D2}"; From f4435f2887e0b58dd159e595abf60e874b526efd Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Mon, 3 Apr 2023 20:24:53 +0200 Subject: [PATCH 09/11] Adds targeting indexer properties using Getter/Setter method types without a name --- Harmony/Internal/PatchTools.cs | 10 ++-- Harmony/Tools/AccessTools.cs | 100 +++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+), 5 deletions(-) diff --git a/Harmony/Internal/PatchTools.cs b/Harmony/Internal/PatchTools.cs index 76de8dfe..3905e29b 100644 --- a/Harmony/Internal/PatchTools.cs +++ b/Harmony/Internal/PatchTools.cs @@ -12,7 +12,7 @@ internal static class PatchTools // https://stackoverflow.com/a/33153868 // ThreadStatic has pitfalls (see RememberObject below), but since we must support net35, it's the best available option. [ThreadStatic] - static Dictionary objectReferences; + private static Dictionary objectReferences; internal static void RememberObject(object key, object value) { @@ -65,13 +65,13 @@ internal static MethodBase GetOriginalMethod(this HarmonyMethod attr) case MethodType.Getter: if (attr.methodName is null) - return null; - return AccessTools.DeclaredProperty(attr.GetDeclaringType(), attr.methodName).GetGetMethod(true); + return AccessTools.DeclaredIndexer(attr.declaringType, attr.argumentTypes).GetGetMethod(true); + return AccessTools.DeclaredProperty(attr.declaringType, attr.methodName).GetGetMethod(true); case MethodType.Setter: if (attr.methodName is null) - return null; - return AccessTools.DeclaredProperty(attr.GetDeclaringType(), attr.methodName).GetSetMethod(true); + return AccessTools.DeclaredIndexer(attr.declaringType, attr.argumentTypes).GetSetMethod(true); + return AccessTools.DeclaredProperty(attr.declaringType, attr.methodName).GetSetMethod(true); case MethodType.Constructor: return AccessTools.DeclaredConstructor(attr.GetDeclaringType(), attr.argumentTypes); diff --git a/Harmony/Tools/AccessTools.cs b/Harmony/Tools/AccessTools.cs index e42c7da1..c64d2fbd 100644 --- a/Harmony/Tools/AccessTools.cs +++ b/Harmony/Tools/AccessTools.cs @@ -256,6 +256,36 @@ public static PropertyInfo DeclaredProperty(string typeColonName) return property; } + /// Gets the reflection information for a directly declared indexer property + /// The class/type where the indexer property is declared + /// Optional parameters to target a specific overload of multiple indexers + /// An indexer property or null when type is null or when it cannot be found + /// + public static PropertyInfo DeclaredIndexer(Type type, Type[] parameters = null) + { + if (type is null) + { + FileLog.Debug("AccessTools.DeclaredIndexer: type is null"); + return null; + } + + try + { + // Can find multiple indexers without specified parameters, but only one with specified ones + var indexer = parameters is null ? + type.GetProperties(allDeclared).SingleOrDefault(property => property.GetIndexParameters().Any()) + : type.GetProperties(allDeclared).FirstOrDefault(property => property.GetIndexParameters().Select(param => param.ParameterType).SequenceEqual(parameters)); + + if (indexer is null) FileLog.Debug($"AccessTools.DeclaredIndexer: Could not find indexer for type {type} and parameters {parameters?.Description()}"); + + return indexer; + } + catch (InvalidOperationException ex) + { + throw new AmbiguousMatchException("Multiple possible indexers were found.", ex); + } + } + /// Gets the reflection information for the getter method of a directly declared property /// The class/type where the property is declared /// The name of the property (case sensitive) @@ -275,6 +305,16 @@ public static MethodInfo DeclaredPropertyGetter(string typeColonName) return DeclaredProperty(typeColonName)?.GetGetMethod(true); } + /// Gets the reflection information for the getter method of a directly declared indexer property + /// The class/type where the indexer property is declared + /// Optional parameters to target a specific overload of multiple indexers + /// A method or null when type is null or when indexer property cannot be found + /// + public static MethodInfo DeclaredIndexerGetter(Type type, Type[] parameters = null) + { + return DeclaredIndexer(type, parameters)?.GetGetMethod(true); + } + /// Gets the reflection information for the setter method of a directly declared property /// The class/type where the property is declared /// The name of the property (case sensitive) @@ -294,6 +334,16 @@ public static MethodInfo DeclaredPropertySetter(string typeColonName) return DeclaredProperty(typeColonName)?.GetSetMethod(true); } + /// Gets the reflection information for the setter method of a directly declared indexer property + /// The class/type where the indexer property is declared + /// Optional parameters to target a specific overload of multiple indexers + /// A method or null when type is null or when indexer property cannot be found + /// + public static MethodInfo DeclaredIndexerSetter(Type type, Type[] parameters) + { + return DeclaredIndexer(type, parameters)?.GetSetMethod(true); + } + /// Gets the reflection information for a property by searching the type and all its super types /// The class/type /// The name @@ -330,6 +380,38 @@ public static PropertyInfo Property(string typeColonName) return property; } + /// Gets the reflection information for an indexer property by searching the type and all its super types + /// The class/type + /// Optional parameters to target a specific overload of multiple indexers + /// An indexer property or null when type is null or when it cannot be found + /// + public static PropertyInfo Indexer(Type type, Type[] parameters = null) + { + if (type is null) + { + FileLog.Debug("AccessTools.Indexer: type is null"); + return null; + } + + // Can find multiple indexers without specified parameters, but only one with specified ones + Func func = parameters is null ? + t => t.GetProperties(all).SingleOrDefault(property => property.GetIndexParameters().Any()) + : t => t.GetProperties(all).FirstOrDefault(property => property.GetIndexParameters().Select(param => param.ParameterType).SequenceEqual(parameters)); + + try + { + var indexer = FindIncludingBaseTypes(type, func); + + if (indexer is null) FileLog.Debug($"AccessTools.Indexer: Could not find indexer for type {type} and parameters {parameters?.Description()}"); + + return indexer; + } + catch (InvalidOperationException ex) + { + throw new AmbiguousMatchException("Multiple possible indexers were found.", ex); + } + } + /// Gets the reflection information for the getter method of a property by searching the type and all its super types /// The class/type /// The name @@ -349,6 +431,15 @@ public static MethodInfo PropertyGetter(string typeColonName) return Property(typeColonName)?.GetGetMethod(true); } + /// Gets the reflection information for the getter method of an indexer property by searching the type and all its super types + /// The class/type + /// Optional parameters to target a specific overload of multiple indexers + /// A method or null when type is null or when the indexer property cannot be found + public static MethodInfo IndexerGetter(Type type, Type[] parameters = null) + { + return Indexer(type, parameters)?.GetGetMethod(true); + } + /// Gets the reflection information for the setter method of a property by searching the type and all its super types /// The class/type /// The name @@ -368,6 +459,15 @@ public static MethodInfo PropertySetter(string typeColonName) return Property(typeColonName)?.GetSetMethod(true); } + /// Gets the reflection information for the setter method of an indexer property by searching the type and all its super types + /// The class/type + /// Optional parameters to target a specific overload of multiple indexers + /// A method or null when type is null or when the indexer property cannot be found + public static MethodInfo IndexerSetter(Type type, Type[] parameters = null) + { + return Indexer(type, parameters)?.GetSetMethod(true); + } + /// Gets the reflection information for a directly declared method /// The class/type where the method is declared /// The name of the method (case sensitive) From 18c9f9b072dc3734e55813826f306ee86df889b6 Mon Sep 17 00:00:00 2001 From: Arne Kiesewetter Date: Mon, 3 Apr 2023 20:46:28 +0200 Subject: [PATCH 10/11] Add Delegate overloads and implicit casts to HarmonyMethod; Fixes #505 --- Harmony/Public/HarmonyMethod.cs | 37 ++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/Harmony/Public/HarmonyMethod.cs b/Harmony/Public/HarmonyMethod.cs index 6239bad1..dd741713 100644 --- a/Harmony/Public/HarmonyMethod.cs +++ b/Harmony/Public/HarmonyMethod.cs @@ -15,7 +15,7 @@ public class HarmonyMethod public MethodInfo method; // need to be called 'method' /// Patch Category - /// + /// public string category = null; /// Class/type declaring this patch @@ -95,6 +95,13 @@ public HarmonyMethod(MethodInfo method) ImportMethod(method); } + /// Creates a patch from a given method + /// The original method + /// + public HarmonyMethod(Delegate @delegate) + : this(@delegate.Method) + { } + /// Creates a patch from a given method /// The original method /// The patch @@ -113,6 +120,17 @@ public HarmonyMethod(MethodInfo method, int priority = -1, string[] before = nul this.debug = debug; } + /// Creates a patch from a given method + /// The original method + /// The patch + /// A list of harmony IDs that should come after this patch + /// A list of harmony IDs that should come before this patch + /// Set to true to generate debug output + /// + public HarmonyMethod(Delegate @delegate, int priority = -1, string[] before = null, string[] after = null, bool? debug = null) + : this(@delegate.Method, priority, before, after, debug) + { } + /// Creates a patch from a given method /// The patch class/type /// The patch method name @@ -201,6 +219,23 @@ internal Type[] GetArgumentList() { return argumentTypes ?? EmptyType.NoArgs; } + + + /// Creates a patch from a given method + /// The original method + /// + public static implicit operator HarmonyMethod(MethodInfo method) + { + return new HarmonyMethod(method); + } + + /// Creates a patch from a given method + /// The original method + /// + public static implicit operator HarmonyMethod(Delegate @delegate) + { + return new HarmonyMethod(@delegate); + } } internal static class EmptyType From a68c607707228ea84841a2c4cb96dcd36a3b7771 Mon Sep 17 00:00:00 2001 From: Aaron Robinson Date: Wed, 5 Jul 2023 17:42:01 -0500 Subject: [PATCH 11/11] Address PR feedback --- Harmony/Internal/Util/StackTraceFixes.cs | 39 ++++++++++++++----- ...atcher.cs => NativeDetourMethodPatcher.cs} | 6 +-- Harmony/Public/Patching/PatchManager.cs | 8 ++-- 3 files changed, 37 insertions(+), 16 deletions(-) rename Harmony/Public/Patching/{NativeMethodPatcher.cs => NativeDetourMethodPatcher.cs} (93%) diff --git a/Harmony/Internal/Util/StackTraceFixes.cs b/Harmony/Internal/Util/StackTraceFixes.cs index 6c46c11c..93d13b39 100644 --- a/Harmony/Internal/Util/StackTraceFixes.cs +++ b/Harmony/Internal/Util/StackTraceFixes.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Reflection; using HarmonyLib.Tools; +using MonoMod.Core.Platforms; using MonoMod.RuntimeDetour; using System.Linq; @@ -19,7 +20,9 @@ internal static class StackTraceFixes private static readonly Dictionary RealMethodMap = new Dictionary(); - private static Hook stackTraceHook; + private static Hook getAssemblyHookManaged; + private static NativeHook getAssemblyHookNative; + private static Hook getMethodHook; public static void Install() { @@ -31,8 +34,17 @@ public static void Install() DetourManager.ILHookApplied += OnILChainRefresh; DetourManager.ILHookUndone += OnILChainRefresh; - stackTraceHook = new Hook(AccessTools.Method(typeof(Assembly), nameof(Assembly.GetExecutingAssembly)), - AccessTools.Method(typeof(StackTraceFixes), nameof(GetAssemblyFix))); + var getAssemblyMethod = AccessTools.DeclaredMethod(typeof(Assembly), nameof(Assembly.GetExecutingAssembly), EmptyType.NoArgs); + if (getAssemblyMethod.HasMethodBody()) + { + getAssemblyHookManaged = new Hook(getAssemblyMethod, GetAssemblyFix); + } + else + { + getAssemblyHookNative = new NativeHook(PlatformTriple.Current.GetNativeMethodBody(getAssemblyMethod), GetAssemblyFix); + } + + getMethodHook = new Hook(AccessTools.DeclaredMethod(typeof(StackFrame), nameof(StackFrame.GetMethod), EmptyType.NoArgs), GetMethodFix); } catch (Exception e) { @@ -41,17 +53,26 @@ public static void Install() _applied = true; } + delegate Assembly GetAssemblyDelegate(); + // We need to force GetExecutingAssembly make use of stack trace // This is to fix cases where calling assembly is actually the patch // This solves issues with code where it uses the method to get current filepath etc - private static Assembly GetAssemblyFix(Func orig) + private static Assembly GetAssemblyFix(GetAssemblyDelegate orig) + { + var entry = getAssemblyHookManaged?.DetourInfo.Entry ?? getAssemblyHookNative.DetourInfo.Entry; + var method = new StackTrace().GetFrames()!.Select(f => f.GetMethod()).SkipWhile(method => method != entry).Skip(1).First(); + return method.Module.Assembly; + } + + private static MethodBase GetMethodFix(Func orig, StackFrame self) { - var method = new StackTrace().GetFrames()!.SkipWhile(frame => frame.GetMethod() != stackTraceHook.DetourInfo.Entry).Skip(1).First().GetMethod(); - if (RealMethodMap.TryGetValue(method, out var real)) + var method = orig(self); + if (method is not null && RealMethodMap.TryGetValue(PlatformTriple.Current.GetIdentifiable(method), out var real)) { - return real.Module.Assembly; + return real; } - return orig(); + return method; } private static readonly AccessTools.FieldRef GetDetourState = AccessTools.FieldRefAccess(AccessTools.DeclaredField(typeof(MethodDetourInfo), "state")); @@ -64,7 +85,7 @@ private static void OnILChainRefresh(ILHookInfo self) { lock (RealMethodMap) { - RealMethodMap[GetEndOfChain(GetDetourState(self.Method))] = self.Method.Method; + RealMethodMap[PlatformTriple.Current.GetIdentifiable(GetEndOfChain(GetDetourState(self.Method)))] = PlatformTriple.Current.GetIdentifiable(self.Method.Method); } } } diff --git a/Harmony/Public/Patching/NativeMethodPatcher.cs b/Harmony/Public/Patching/NativeDetourMethodPatcher.cs similarity index 93% rename from Harmony/Public/Patching/NativeMethodPatcher.cs rename to Harmony/Public/Patching/NativeDetourMethodPatcher.cs index e65b9d01..bc127cea 100644 --- a/Harmony/Public/Patching/NativeMethodPatcher.cs +++ b/Harmony/Public/Patching/NativeDetourMethodPatcher.cs @@ -9,7 +9,7 @@ namespace HarmonyLib.Public.Patching; /// -public class NativeMethodPatcher : MethodPatcher +public class NativeDetourMethodPatcher : MethodPatcher { private PlatformTriple.NativeDetour? _hook; private Delegate _replacementDelegate; @@ -21,7 +21,7 @@ public class NativeMethodPatcher : MethodPatcher private readonly MethodInfo _altEntryDelegateInvoke; /// - public NativeMethodPatcher(MethodBase original) : base(original) + public NativeDetourMethodPatcher(MethodBase original) : base(original) { var originalParameters = Original.GetParameters(); @@ -97,6 +97,6 @@ public override DynamicMethodDefinition CopyOriginal() public static void TryResolve(object _, PatchManager.PatcherResolverEventArgs args) { if (args.Original.GetMethodBody() == null) - args.MethodPatcher = new NativeMethodPatcher(args.Original); + args.MethodPatcher = new NativeDetourMethodPatcher(args.Original); } } diff --git a/Harmony/Public/Patching/PatchManager.cs b/Harmony/Public/Patching/PatchManager.cs index 1634c96e..fe7790d8 100644 --- a/Harmony/Public/Patching/PatchManager.cs +++ b/Harmony/Public/Patching/PatchManager.cs @@ -25,7 +25,7 @@ public static class PatchManager static PatchManager() { ResolvePatcher += ManagedMethodPatcher.TryResolve; - ResolvePatcher += NativeMethodPatcher.TryResolve; + ResolvePatcher += NativeDetourMethodPatcher.TryResolve; } /// @@ -132,8 +132,8 @@ internal static MethodBase FindReplacement(StackFrame frame) } else { - var baseMethod = PlatformTriple.Current.Runtime.GetIdentifiable(frameMethod); - methodStart = PlatformTriple.Current.Runtime.GetMethodEntryPoint(baseMethod).ToInt64(); + var baseMethod = PlatformTriple.Current.GetIdentifiable(frameMethod); + methodStart = PlatformTriple.Current.GetNativeMethodBody(baseMethod).ToInt64(); } // Failed to find any usable method, if `frameMethod` is null, we can not find any @@ -143,7 +143,7 @@ internal static MethodBase FindReplacement(StackFrame frame) lock (ReplacementToOriginals) return ReplacementToOriginals - .FirstOrDefault(kv => kv.Key.IsAlive && PlatformTriple.Current.Runtime.GetMethodEntryPoint((MethodBase)kv.Key.Target).ToInt64() == methodStart).Key.Target as MethodBase; + .FirstOrDefault(kv => kv.Key.IsAlive && PlatformTriple.Current.GetNativeMethodBody((MethodBase)kv.Key.Target).ToInt64() == methodStart).Key.Target as MethodBase; } internal static void AddReplacementOriginal(MethodBase original, MethodInfo replacement)