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 6dfe4f8e..d58d7b7a 100644 --- a/Harmony/Harmony.csproj +++ b/Harmony/Harmony.csproj @@ -11,7 +11,7 @@ Andreas Pardeike, Geoffrey Horsington, ManlyMarco et al. 0Harmony true - 2.10.2 + 2.11.0 LICENSE https://github.com/BepInEx/HarmonyX false @@ -46,9 +46,9 @@ - - - + + + @@ -66,13 +66,14 @@ - + - + - + + @@ -80,20 +81,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/PatchTools.cs b/Harmony/Internal/PatchTools.cs index d8c4f396..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); @@ -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/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..93d13b39 100644 --- a/Harmony/Internal/Util/StackTraceFixes.cs +++ b/Harmony/Internal/Util/StackTraceFixes.cs @@ -3,7 +3,9 @@ using System.Diagnostics; using System.Reflection; using HarmonyLib.Tools; +using MonoMod.Core.Platforms; using MonoMod.RuntimeDetour; +using System.Linq; namespace HarmonyLib.Internal.RuntimeFixes { @@ -18,9 +20,9 @@ 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 getAssemblyHookManaged; + private static NativeHook getAssemblyHookNative; + private static Hook getMethodHook; public static void Install() { @@ -29,17 +31,20 @@ 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 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); + } - var nat = new NativeDetour(AccessTools.Method(typeof(Assembly), nameof(Assembly.GetExecutingAssembly)), - AccessTools.Method(typeof(StackTraceFixes), nameof(GetAssemblyFix))); - _realGetAss = nat.GenerateTrampoline>(); + getMethodHook = new Hook(AccessTools.DeclaredMethod(typeof(StackFrame), nameof(StackFrame.GetMethod), EmptyType.NoArgs), GetMethodFix); } catch (Exception e) { @@ -48,37 +53,39 @@ 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; - } - } + 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() + private static Assembly GetAssemblyFix(GetAssemblyDelegate orig) { - return new StackFrame(1).GetMethod()?.Module.Assembly ?? _realGetAss(); + 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; } - // Helper to save the detour info after patch is complete - private static void OnILChainRefresh(object self) + private static MethodBase GetMethodFix(Func orig, StackFrame self) { - _origRefresh(self); + var method = orig(self); + if (method is not null && RealMethodMap.TryGetValue(PlatformTriple.Current.GetIdentifiable(method), out var real)) + { + return real; + } + return method; + } - if (!(AccessTools.Field(self.GetType(), "Detour").GetValue(self) is Detour detour)) - return; + 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("ManagedDetourState", 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[PlatformTriple.Current.GetIdentifiable(GetEndOfChain(GetDetourState(self.Method)))] = PlatformTriple.Current.GetIdentifiable(self.Method.Method); } } } diff --git a/Harmony/Public/Attributes.cs b/Harmony/Public/Attributes.cs index b397d971..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 @@ -112,7 +113,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 8bd3a4be..33798df6 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; @@ -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..dd741713 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; @@ -91,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 @@ -109,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 @@ -197,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 diff --git a/Harmony/Public/PatchClassProcessor.cs b/Harmony/Public/PatchClassProcessor.cs index 12207a75..d05e55e2 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) 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 index 25659810..bc127cea 100644 --- a/Harmony/Public/Patching/NativeDetourMethodPatcher.cs +++ b/Harmony/Public/Patching/NativeDetourMethodPatcher.cs @@ -1,176 +1,102 @@ +using Mono.Cecil; +using MonoMod.Cil; +using MonoMod.Core.Platforms; +using MonoMod.Utils; using System; -using System.Collections.Generic; using System.Reflection; -using System.Reflection.Emit; -using HarmonyLib.Tools; -using MonoMod.RuntimeDetour; -using MonoMod.Utils; +using System.Runtime.InteropServices; -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"); - } +namespace HarmonyLib.Public.Patching; - /// - public override DynamicMethodDefinition PrepareOriginal() - { - return GenerateManagedOriginal(); - } +/// +public class NativeDetourMethodPatcher : MethodPatcher +{ + private PlatformTriple.NativeDetour? _hook; + private Delegate _replacementDelegate; - /// - public override MethodBase DetourTo(MethodBase replacement) - { - nativeDetour?.Dispose(); + private readonly Type _returnType; + private readonly Type[] _parameterTypes; + private readonly Type _altEntryDelegateType; + private readonly DataScope _altEntryDelegateStore; + private readonly MethodInfo _altEntryDelegateInvoke; - nativeDetour = new NativeDetour(Original, replacement, new NativeDetourConfig {ManualApply = true}); + /// + public NativeDetourMethodPatcher(MethodBase original) : base(original) + { + var originalParameters = Original.GetParameters(); - lock (TrampolineCache) - { - if (currentOriginal >= 0) - TrampolineCache.Remove(currentOriginal); - currentOriginal = newOriginal; - TrampolineCache[currentOriginal] = CreateDelegate(trampolineDelegateType, - nativeDetour.GenerateTrampoline(invokeTrampolineMethod)); - } + var offset = Original.IsStatic ? 0 : 1; - nativeDetour.Apply(); - return replacement; - } + _parameterTypes = new Type[originalParameters.Length + offset]; - /// - public override DynamicMethodDefinition CopyOriginal() + if (!Original.IsStatic) { - 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; + _parameterTypes[0] = Original.GetThisParamType(); } - private Delegate CreateDelegate(Type delegateType, MethodBase mb) + for (int i = 0; i < originalParameters.Length; i++) { - if (mb is DynamicMethod dm) - return dm.CreateDelegate(delegateType); - - return Delegate.CreateDelegate(delegateType, - mb as MethodInfo ?? throw new InvalidCastException($"Unexpected method type: {mb.GetType()}")); + _parameterTypes[i + offset] = originalParameters[i].ParameterType; } - private static Delegate GetTrampoline(int hash) - { - lock (TrampolineCache) - return TrampolineCache[hash]; - } + _returnType = Original is MethodInfo { ReturnType: var ret } ? ret : typeof(void); - 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 + _altEntryDelegateType = DelegateTypeFactory.instance.CreateDelegateType(_returnType, _parameterTypes); + _altEntryDelegateInvoke = _altEntryDelegateType.GetMethod("Invoke"); - var orig = Original; + _altEntryDelegateStore = DynamicReferenceManager.AllocReference(default(Delegate), out _); + } - lock (CounterLock) - { - newOriginal = counter; - counter++; - } + /// + public override DynamicMethodDefinition PrepareOriginal() + { + var dmd = new DynamicMethodDefinition( + $"NativeHookProxy<{Original.DeclaringType.FullName}:{Original.Name}>", + _returnType, + _parameterTypes + ); - var dmd = new DynamicMethodDefinition($"NativeDetour_Wrapper<{orig.GetID(simple: true)}>?{newOriginal}", returnType, argTypes); + var il = new ILContext(dmd.Definition); - var def = dmd.Definition; - for (var i = 0; i < argTypeNames.Length; i++) - def.Parameters[i].Name = argTypeNames[i]; + var c = new ILCursor(il); - var il = dmd.GetILGenerator(); + c.EmitLoadReference(_altEntryDelegateStore.Data); + for (int i = 0; i < _parameterTypes.Length; i++) + { + c.EmitLdarg(i); + } + c.EmitCallvirt(_altEntryDelegateInvoke); + c.EmitRet(); - 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; + } - 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); } - - /// - /// 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) + else { - if (args.Original.GetMethodBody() == null) - args.MethodPatcher = new NativeDetourMethodPatcher(args.Original); + _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 NativeDetourMethodPatcher(args.Original); } } diff --git a/Harmony/Public/Patching/PatchManager.cs b/Harmony/Public/Patching/PatchManager.cs index 4b3bffee..fe7790d8 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 { @@ -132,8 +132,8 @@ internal static MethodBase FindReplacement(StackFrame frame) } else { - var baseMethod = DetourHelper.Runtime.GetIdentifiable(frameMethod); - methodStart = baseMethod.GetNativeStart().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 && ((MethodBase)kv.Key.Target).GetNativeStart().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) diff --git a/Harmony/Tools/AccessTools.cs b/Harmony/Tools/AccessTools.cs index 10513956..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) @@ -542,6 +642,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 @@ -1553,10 +1685,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 +1722,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 214c335a..7abfa2a7 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,18 +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/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) { diff --git a/nuget.config b/nuget.config new file mode 100644 index 00000000..f072f570 --- /dev/null +++ b/nuget.config @@ -0,0 +1,3 @@ + + +