From 9c4b12629bbfc41d83037c399883be553236b76d Mon Sep 17 00:00:00 2001 From: Andreas Pardeike Date: Wed, 28 Feb 2024 16:12:48 +0100 Subject: [PATCH 1/2] wip --- Harmony/Public/InnerFix.cs | 129 +++++++++++ Harmony/Public/InnerMethod.cs | 59 +++++ Harmony/Public/InnerPostfix.cs | 23 ++ Harmony/Public/InnerPrefix.cs | 27 +++ Harmony/Public/Patch.cs | 261 ----------------------- Harmony/Public/PatchInfo.cs | 156 ++++++++++++++ Harmony/Public/PatchInfoSerialization.cs | 121 +++++++++++ Harmony/Public/Transpilers.cs | 11 + 8 files changed, 526 insertions(+), 261 deletions(-) create mode 100644 Harmony/Public/InnerFix.cs create mode 100644 Harmony/Public/InnerMethod.cs create mode 100644 Harmony/Public/InnerPostfix.cs create mode 100644 Harmony/Public/InnerPrefix.cs create mode 100644 Harmony/Public/PatchInfo.cs create mode 100644 Harmony/Public/PatchInfoSerialization.cs diff --git a/Harmony/Public/InnerFix.cs b/Harmony/Public/InnerFix.cs new file mode 100644 index 00000000..7dae39bb --- /dev/null +++ b/Harmony/Public/InnerFix.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text.Json.Serialization; + +namespace HarmonyLib +{ + /// The base class for InnerPrefix and InnerPostfix + /// + [Serializable] + public abstract partial class InnerFix : IComparable + { + /// Zero-based index + /// + public readonly int index; + + /// The owner (Harmony ID) + /// + public readonly string owner; + + /// The priority, see + /// + public readonly int priority; + + /// Keep this patch before the patches indicated in the list of Harmony IDs + /// + public readonly string[] before; + + /// Keep this patch after the patches indicated in the list of Harmony IDs + /// + public readonly string[] after; + + /// A flag that will log the replacement method via every time this patch is used to build the replacement, even in the future + /// + public readonly bool debug; + + /// The type of an InnerFix + /// + public enum PatchType + { + /// An inner prefix + /// + Prefix, + /// An inner postfix + /// + Postfix + } + + /// The type of an InnerFix + /// + public abstract PatchType Type { get; } + + /// The method call to patch + /// + public InnerMethod InnerMethod { get; set; } + + /// The patch to apply + /// + [NonSerialized] + private MethodInfo patchMethod; + private int methodToken; + private string moduleGUID; + + /// The method of the static patch method + /// +#if NET5_0_OR_GREATER + [JsonIgnore] +#endif + public MethodInfo PatchMethod + { + get + { + if (patchMethod is null) + { + var mdl = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.FullName.StartsWith("Microsoft.VisualStudio")) + .SelectMany(a => a.GetLoadedModules()) + .First(m => m.ModuleVersionId.ToString() == moduleGUID); + patchMethod = (MethodInfo)mdl.ResolveMethod(methodToken); + } + return patchMethod; + } + set + { + patchMethod = value; + methodToken = patchMethod.MetadataToken; + moduleGUID = patchMethod.Module.ModuleVersionId.ToString(); + } + } + + /// If defined will be used to calculate Target from a given methods content + /// + public Func, InnerMethod> TargetFinder { get; set; } + + /// Creates an infix for an implicit defined method call + /// The method call to patch + /// The patch to apply + /// + public InnerFix(InnerMethod target, MethodInfo patch) + { + Target = target; + TargetFinder = null; + Patch = patch; + } + + /// Creates an infix for an indirectly defined method call + /// Calculates Target from a given methods content + /// The patch to apply + /// + public InnerFix(Func, InnerMethod> targetFinder, MethodInfo patch) + { + Target = null; + TargetFinder = targetFinder; + Patch = patch; + } + + /// Applies this fix to a method + /// The method that contains the target method call(s) + /// The instructions of the method + /// The new instructions of the method + /// + public IEnumerable Apply(MethodBase original, IEnumerable instructions) + { + _ = original; + foreach (var instruction in instructions) yield return instruction; + } + } +} diff --git a/Harmony/Public/InnerMethod.cs b/Harmony/Public/InnerMethod.cs new file mode 100644 index 00000000..afd3a816 --- /dev/null +++ b/Harmony/Public/InnerMethod.cs @@ -0,0 +1,59 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Text.Json.Serialization; + +namespace HarmonyLib +{ + /// Occcurances of a method that is called inside some outer method + /// + [Serializable] + public class InnerMethod + { + [NonSerialized] + private MethodInfo method; + private int methodToken; + private string moduleGUID; + + /// Which occcurances (1-based) of the method, negative numbers are counting from the end, empty array means all occurances + /// + public int[] positions; + + /// Creates an InnerMethod + /// The inner method + /// Which occcurances (1-based) of the method, negative numbers are counting from the end, empty array means all occurances + /// + public InnerMethod(MethodInfo method, params int[] positions) + { + Method = method; + this.positions = positions; + } + + /// The inner method + /// +#if NET5_0_OR_GREATER + [JsonIgnore] +#endif + public MethodInfo Method + { + get + { + if (method is null) + { + var mdl = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.FullName.StartsWith("Microsoft.VisualStudio")) + .SelectMany(a => a.GetLoadedModules()) + .First(m => m.ModuleVersionId.ToString() == moduleGUID); + method = (MethodInfo)mdl.ResolveMethod(methodToken); + } + return method; + } + set + { + method = value; + methodToken = method.MetadataToken; + moduleGUID = method.Module.ModuleVersionId.ToString(); + } + } + } +} diff --git a/Harmony/Public/InnerPostfix.cs b/Harmony/Public/InnerPostfix.cs new file mode 100644 index 00000000..f9a0fe4b --- /dev/null +++ b/Harmony/Public/InnerPostfix.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace HarmonyLib +{ + /// An inner postfix that is applied inside some method call inside a method + public class InnerPostfix : InnerFix + { + /// This InnerFix is a InnerFixType.Postfix + public override PatchType Type => PatchType.Postfix; + + /// Creates an inner postfix for an implicit defined method call + /// The method call to patch + /// The patch to apply + public InnerPostfix(InnerMethod target, MethodInfo patch) : base(target, patch) { } + + /// Creates an inner postfix for an indirectly defined method call + /// Calculates Target from a given methods content + /// The patch to apply + public InnerPostfix(Func, InnerMethod> targetFinder, MethodInfo patch) : base(targetFinder, patch) { } + } +} diff --git a/Harmony/Public/InnerPrefix.cs b/Harmony/Public/InnerPrefix.cs new file mode 100644 index 00000000..789d6283 --- /dev/null +++ b/Harmony/Public/InnerPrefix.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace HarmonyLib +{ + /// An inner prefix that is applied inside some method call inside a method + /// + public class InnerPrefix : InnerFix + { + /// This InnerFix is a InnerFixType.Prefix + /// + public override PatchType Type => PatchType.Prefix; + + /// Creates an inner prefix for an implicit defined method call + /// The method call to patch + /// The patch to apply + /// + public InnerPrefix(InnerMethod target, MethodInfo patch) : base(target, patch) { } + + /// Creates an inner prefix for an indirectly defined method call + /// Calculates Target from a given methods content + /// The patch to apply + /// + public InnerPrefix(Func, InnerMethod> targetFinder, MethodInfo patch) : base(targetFinder, patch) { } + } +} diff --git a/Harmony/Public/Patch.cs b/Harmony/Public/Patch.cs index cb13b923..7280b637 100644 --- a/Harmony/Public/Patch.cs +++ b/Harmony/Public/Patch.cs @@ -3,273 +3,12 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; -using System.Runtime.Serialization; -using System.Runtime.Serialization.Formatters.Binary; #if NET5_0_OR_GREATER -using System.Text.Json; using System.Text.Json.Serialization; #endif namespace HarmonyLib { - /// Patch serialization - /// - internal static class PatchInfoSerialization - { -#if NET5_0_OR_GREATER - static readonly JsonSerializerOptions serializerOptions = new() { IncludeFields = true }; - internal static bool? useBinaryFormatter = null; - internal static bool UseBinaryFormatter - { - get - { - if (!useBinaryFormatter.HasValue) - { - // https://github.com/dotnet/runtime/blob/208e377a5329ad6eb1db5e5fb9d4590fa50beadd/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/LocalAppContextSwitches.cs#L14 - var hasSwitch = AppContext.TryGetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", out var isEnabled); - if (hasSwitch) - useBinaryFormatter = isEnabled; - else - { - // Default true, in line with Microsoft - https://github.com/dotnet/runtime/blob/208e377a5329ad6eb1db5e5fb9d4590fa50beadd/src/libraries/Common/src/System/LocalAppContextSwitches.Common.cs#L54 - useBinaryFormatter = true; - } - } - return useBinaryFormatter.Value; - } - } -#endif - - class Binder : SerializationBinder - { - /// Control the binding of a serialized object to a type - /// Specifies the assembly name of the serialized object - /// Specifies the type name of the serialized object - /// The type of the object the formatter creates a new instance of - /// - public override Type BindToType(string assemblyName, string typeName) - { - var types = new Type[] { - typeof(PatchInfo), - typeof(Patch[]), - typeof(Patch) - }; - foreach (var type in types) - if (typeName == type.FullName) - return type; - var typeToDeserialize = Type.GetType(string.Format("{0}, {1}", typeName, assemblyName)); - return typeToDeserialize; - } - } - internal static readonly BinaryFormatter binaryFormatter = new() { Binder = new Binder() }; - - /// Serializes a patch info - /// The - /// The serialized data - /// - internal static byte[] Serialize(this PatchInfo patchInfo) - { -#if NET5_0_OR_GREATER - if (UseBinaryFormatter) - { -#endif - using var streamMemory = new MemoryStream(); - binaryFormatter.Serialize(streamMemory, patchInfo); - return streamMemory.GetBuffer(); -#if NET5_0_OR_GREATER - } - else - return JsonSerializer.SerializeToUtf8Bytes(patchInfo); -#endif - } - - /// Deserialize a patch info - /// The serialized data - /// A - /// - internal static PatchInfo Deserialize(byte[] bytes) - { -#if NET5_0_OR_GREATER - if (UseBinaryFormatter) - { -#endif - using var streamMemory = new MemoryStream(bytes); - return (PatchInfo)binaryFormatter.Deserialize(streamMemory); -#if NET5_0_OR_GREATER - } - else - { - return JsonSerializer.Deserialize(bytes, serializerOptions); - } -#endif - } - - /// Compare function to sort patch priorities - /// The patch - /// Zero-based index - /// The priority - /// A standard sort integer (-1, 0, 1) - /// - internal static int PriorityComparer(object obj, int index, int priority) - { - var trv = Traverse.Create(obj); - var theirPriority = trv.Field("priority").GetValue(); - var theirIndex = trv.Field("index").GetValue(); - - if (priority != theirPriority) - return -(priority.CompareTo(theirPriority)); - - return index.CompareTo(theirIndex); - } - } - - /// Serializable patch information - /// - [Serializable] - public class PatchInfo - { - /// Prefixes as an array of - /// -#if NET5_0_OR_GREATER - [JsonInclude] -#endif - public Patch[] prefixes = []; - - /// Postfixes as an array of - /// -#if NET5_0_OR_GREATER - [JsonInclude] -#endif - public Patch[] postfixes = []; - - /// Transpilers as an array of - /// -#if NET5_0_OR_GREATER - [JsonInclude] -#endif - public Patch[] transpilers = []; - - /// Finalizers as an array of - /// -#if NET5_0_OR_GREATER - [JsonInclude] -#endif - public Patch[] finalizers = []; - - /// Returns if any of the patches wants debugging turned on - /// -#if NET5_0_OR_GREATER - [JsonIgnore] -#endif - public bool Debugging => prefixes.Any(p => p.debug) || postfixes.Any(p => p.debug) || transpilers.Any(p => p.debug) || finalizers.Any(p => p.debug); - - /// Adds prefixes - /// An owner (Harmony ID) - /// The patch methods - /// - internal void AddPrefixes(string owner, params HarmonyMethod[] methods) => prefixes = Add(owner, methods, prefixes); - - /// Adds a prefix - [Obsolete("This method only exists for backwards compatibility since the class is public.")] - public void AddPrefix(MethodInfo patch, string owner, int priority, string[] before, string[] after, bool debug) => AddPrefixes(owner, new HarmonyMethod(patch, priority, before, after, debug)); - - /// Removes prefixes - /// The owner of the prefixes, or * for all - /// - public void RemovePrefix(string owner) => prefixes = Remove(owner, prefixes); - - /// Adds postfixes - /// An owner (Harmony ID) - /// The patch methods - /// - internal void AddPostfixes(string owner, params HarmonyMethod[] methods) => postfixes = Add(owner, methods, postfixes); - - /// Adds a postfix - [Obsolete("This method only exists for backwards compatibility since the class is public.")] - public void AddPostfix(MethodInfo patch, string owner, int priority, string[] before, string[] after, bool debug) => AddPostfixes(owner, new HarmonyMethod(patch, priority, before, after, debug)); - - /// Removes postfixes - /// The owner of the postfixes, or * for all - /// - public void RemovePostfix(string owner) => postfixes = Remove(owner, postfixes); - - /// Adds transpilers - /// An owner (Harmony ID) - /// The patch methods - /// - internal void AddTranspilers(string owner, params HarmonyMethod[] methods) => transpilers = Add(owner, methods, transpilers); - - /// Adds a transpiler - [Obsolete("This method only exists for backwards compatibility since the class is public.")] - public void AddTranspiler(MethodInfo patch, string owner, int priority, string[] before, string[] after, bool debug) => AddTranspilers(owner, new HarmonyMethod(patch, priority, before, after, debug)); - - /// Removes transpilers - /// The owner of the transpilers, or * for all - /// - public void RemoveTranspiler(string owner) => transpilers = Remove(owner, transpilers); - - /// Adds finalizers - /// An owner (Harmony ID) - /// The patch methods - /// - internal void AddFinalizers(string owner, params HarmonyMethod[] methods) => finalizers = Add(owner, methods, finalizers); - - /// Adds a finalizer - [Obsolete("This method only exists for backwards compatibility since the class is public.")] - public void AddFinalizer(MethodInfo patch, string owner, int priority, string[] before, string[] after, bool debug) => AddFinalizers(owner, new HarmonyMethod(patch, priority, before, after, debug)); - - /// Removes finalizers - /// The owner of the finalizers, or * for all - /// - public void RemoveFinalizer(string owner) => finalizers = Remove(owner, finalizers); - - /// Removes a patch using its method - /// The method of the patch to remove - /// - public void RemovePatch(MethodInfo patch) - { - prefixes = prefixes.Where(p => p.PatchMethod != patch).ToArray(); - postfixes = postfixes.Where(p => p.PatchMethod != patch).ToArray(); - transpilers = transpilers.Where(p => p.PatchMethod != patch).ToArray(); - finalizers = finalizers.Where(p => p.PatchMethod != patch).ToArray(); - } - - /// Gets a concatenated list of patches - /// The Harmony instance ID adding the new patches - /// The patches to add - /// The current patches - /// - private static Patch[] Add(string owner, HarmonyMethod[] add, Patch[] current) - { - // avoid copy if no patch added - if (add.Length == 0) - return current; - - // concat lists - var initialIndex = current.Length; - return - [ - .. current -, - .. add - .Where(method => method != null) - .Select((method, i) => new Patch(method, i + initialIndex, owner)) -, - ]; - } - - /// Gets a list of patches with any from the given owner removed - /// The owner of the methods, or * for all - /// The current patches - /// - private static Patch[] Remove(string owner, Patch[] current) - { - return owner == "*" - ? [] - : current.Where(patch => patch.owner != owner).ToArray(); - } - } - /// A serializable patch /// #if NET5_0_OR_GREATER diff --git a/Harmony/Public/PatchInfo.cs b/Harmony/Public/PatchInfo.cs new file mode 100644 index 00000000..5e2d936f --- /dev/null +++ b/Harmony/Public/PatchInfo.cs @@ -0,0 +1,156 @@ +using System; +using System.Linq; +using System.Reflection; +#if NET5_0_OR_GREATER +using System.Text.Json.Serialization; +#endif + +namespace HarmonyLib +{ + /// Serializable patch information + /// + [Serializable] + public class PatchInfo + { + /// Prefixes as an array of + /// +#if NET5_0_OR_GREATER + [JsonInclude] +#endif + public Patch[] prefixes = []; + + /// Postfixes as an array of + /// +#if NET5_0_OR_GREATER + [JsonInclude] +#endif + public Patch[] postfixes = []; + + /// Transpilers as an array of + /// +#if NET5_0_OR_GREATER + [JsonInclude] +#endif + public Patch[] transpilers = []; + + /// Finalizers as an array of + /// +#if NET5_0_OR_GREATER + [JsonInclude] +#endif + public Patch[] finalizers = []; + + /// Returns if any of the patches wants debugging turned on + /// +#if NET5_0_OR_GREATER + [JsonIgnore] +#endif + public bool Debugging => prefixes.Any(p => p.debug) || postfixes.Any(p => p.debug) || transpilers.Any(p => p.debug) || finalizers.Any(p => p.debug); + + /// Adds prefixes + /// An owner (Harmony ID) + /// The patch methods + /// + internal void AddPrefixes(string owner, params HarmonyMethod[] methods) => prefixes = Add(owner, methods, prefixes); + + /// Adds a prefix + [Obsolete("This method only exists for backwards compatibility since the class is public.")] + public void AddPrefix(MethodInfo patch, string owner, int priority, string[] before, string[] after, bool debug) => AddPrefixes(owner, new HarmonyMethod(patch, priority, before, after, debug)); + + /// Removes prefixes + /// The owner of the prefixes, or * for all + /// + public void RemovePrefix(string owner) => prefixes = Remove(owner, prefixes); + + /// Adds postfixes + /// An owner (Harmony ID) + /// The patch methods + /// + internal void AddPostfixes(string owner, params HarmonyMethod[] methods) => postfixes = Add(owner, methods, postfixes); + + /// Adds a postfix + [Obsolete("This method only exists for backwards compatibility since the class is public.")] + public void AddPostfix(MethodInfo patch, string owner, int priority, string[] before, string[] after, bool debug) => AddPostfixes(owner, new HarmonyMethod(patch, priority, before, after, debug)); + + /// Removes postfixes + /// The owner of the postfixes, or * for all + /// + public void RemovePostfix(string owner) => postfixes = Remove(owner, postfixes); + + /// Adds transpilers + /// An owner (Harmony ID) + /// The patch methods + /// + internal void AddTranspilers(string owner, params HarmonyMethod[] methods) => transpilers = Add(owner, methods, transpilers); + + /// Adds a transpiler + [Obsolete("This method only exists for backwards compatibility since the class is public.")] + public void AddTranspiler(MethodInfo patch, string owner, int priority, string[] before, string[] after, bool debug) => AddTranspilers(owner, new HarmonyMethod(patch, priority, before, after, debug)); + + /// Removes transpilers + /// The owner of the transpilers, or * for all + /// + public void RemoveTranspiler(string owner) => transpilers = Remove(owner, transpilers); + + /// Adds finalizers + /// An owner (Harmony ID) + /// The patch methods + /// + internal void AddFinalizers(string owner, params HarmonyMethod[] methods) => finalizers = Add(owner, methods, finalizers); + + /// Adds a finalizer + [Obsolete("This method only exists for backwards compatibility since the class is public.")] + public void AddFinalizer(MethodInfo patch, string owner, int priority, string[] before, string[] after, bool debug) => AddFinalizers(owner, new HarmonyMethod(patch, priority, before, after, debug)); + + /// Removes finalizers + /// The owner of the finalizers, or * for all + /// + public void RemoveFinalizer(string owner) => finalizers = Remove(owner, finalizers); + + /// Removes a patch using its method + /// The method of the patch to remove + /// + public void RemovePatch(MethodInfo patch) + { + prefixes = prefixes.Where(p => p.PatchMethod != patch).ToArray(); + postfixes = postfixes.Where(p => p.PatchMethod != patch).ToArray(); + transpilers = transpilers.Where(p => p.PatchMethod != patch).ToArray(); + finalizers = finalizers.Where(p => p.PatchMethod != patch).ToArray(); + } + + /// Gets a concatenated list of patches + /// The Harmony instance ID adding the new patches + /// The patches to add + /// The current patches + /// + private static Patch[] Add(string owner, HarmonyMethod[] add, Patch[] current) + { + // avoid copy if no patch added + if (add.Length == 0) + return current; + + // concat lists + var initialIndex = current.Length; + return + [ + .. current +, + .. add + .Where(method => method != null) + .Select((method, i) => new Patch(method, i + initialIndex, owner)) +, + ]; + } + + /// Gets a list of patches with any from the given owner removed + /// The owner of the methods, or * for all + /// The current patches + /// + private static Patch[] Remove(string owner, Patch[] current) + { + return owner == "*" + ? [] + : current.Where(patch => patch.owner != owner).ToArray(); + } + } +} diff --git a/Harmony/Public/PatchInfoSerialization.cs b/Harmony/Public/PatchInfoSerialization.cs new file mode 100644 index 00000000..ff82468e --- /dev/null +++ b/Harmony/Public/PatchInfoSerialization.cs @@ -0,0 +1,121 @@ +using System; +using System.IO; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +#if NET5_0_OR_GREATER +using System.Text.Json; +#endif + +namespace HarmonyLib +{ + /// Patch serialization + /// + internal static class PatchInfoSerialization + { +#if NET5_0_OR_GREATER + static readonly JsonSerializerOptions serializerOptions = new() { IncludeFields = true }; + internal static bool? useBinaryFormatter = null; + internal static bool UseBinaryFormatter + { + get + { + if (!useBinaryFormatter.HasValue) + { + // https://github.com/dotnet/runtime/blob/208e377a5329ad6eb1db5e5fb9d4590fa50beadd/src/libraries/System.Runtime.Serialization.Formatters/src/System/Runtime/Serialization/LocalAppContextSwitches.cs#L14 + var hasSwitch = AppContext.TryGetSwitch("System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization", out var isEnabled); + if (hasSwitch) + useBinaryFormatter = isEnabled; + else + { + // Default true, in line with Microsoft - https://github.com/dotnet/runtime/blob/208e377a5329ad6eb1db5e5fb9d4590fa50beadd/src/libraries/Common/src/System/LocalAppContextSwitches.Common.cs#L54 + useBinaryFormatter = true; + } + } + return useBinaryFormatter.Value; + } + } +#endif + + class Binder : SerializationBinder + { + /// Control the binding of a serialized object to a type + /// Specifies the assembly name of the serialized object + /// Specifies the type name of the serialized object + /// The type of the object the formatter creates a new instance of + /// + public override Type BindToType(string assemblyName, string typeName) + { + var types = new Type[] { + typeof(PatchInfo), + typeof(Patch[]), + typeof(Patch) + }; + foreach (var type in types) + if (typeName == type.FullName) + return type; + var typeToDeserialize = Type.GetType(string.Format("{0}, {1}", typeName, assemblyName)); + return typeToDeserialize; + } + } + internal static readonly BinaryFormatter binaryFormatter = new() { Binder = new Binder() }; + + /// Serializes a patch info + /// The + /// The serialized data + /// + internal static byte[] Serialize(this PatchInfo patchInfo) + { +#if NET5_0_OR_GREATER + if (UseBinaryFormatter) + { +#endif + using var streamMemory = new MemoryStream(); + binaryFormatter.Serialize(streamMemory, patchInfo); + return streamMemory.GetBuffer(); +#if NET5_0_OR_GREATER + } + else + return JsonSerializer.SerializeToUtf8Bytes(patchInfo); +#endif + } + + /// Deserialize a patch info + /// The serialized data + /// A + /// + internal static PatchInfo Deserialize(byte[] bytes) + { +#if NET5_0_OR_GREATER + if (UseBinaryFormatter) + { +#endif + using var streamMemory = new MemoryStream(bytes); + return (PatchInfo)binaryFormatter.Deserialize(streamMemory); +#if NET5_0_OR_GREATER + } + else + { + return JsonSerializer.Deserialize(bytes, serializerOptions); + } +#endif + } + + /// Compare function to sort patch priorities + /// The patch + /// Zero-based index + /// The priority + /// A standard sort integer (-1, 0, 1) + /// + internal static int PriorityComparer(object obj, int index, int priority) + { + var trv = Traverse.Create(obj); + var theirPriority = trv.Field("priority").GetValue(); + var theirIndex = trv.Field("index").GetValue(); + + if (priority != theirPriority) + return -(priority.CompareTo(theirPriority)); + + return index.CompareTo(theirIndex); + } + } +} diff --git a/Harmony/Public/Transpilers.cs b/Harmony/Public/Transpilers.cs index 45e5af41..fab61e71 100644 --- a/Harmony/Public/Transpilers.cs +++ b/Harmony/Public/Transpilers.cs @@ -67,5 +67,16 @@ public static IEnumerable DebugLogger(this IEnumerableApplies all infixes to a method + /// The instructions of the method + /// A list of infixes + /// The new instructions of the method + public static IEnumerable ApplyInfixes(this IEnumerable instructions, MethodBase original, params InnerFix[] infixes) + { + foreach (var infix in infixes) + instructions = infix.Apply(original, instructions); + foreach (var instruction in instructions) yield return instruction; + } } } From 3ffd84257287118547ced92fa05efe1dc28ad36d Mon Sep 17 00:00:00 2001 From: Andreas Pardeike Date: Wed, 28 Feb 2024 19:33:56 +0100 Subject: [PATCH 2/2] basics added, non-functional, WIP --- Directory.Build.props | 2 +- Harmony/Internal/MethodPatcher.cs | 8 +- Harmony/Internal/PatchFunctions.cs | 17 +--- Harmony/Internal/PatchModels.cs | 11 ++- Harmony/Internal/PatchSorter.cs | 45 +-------- Harmony/Public/Attributes.cs | 4 +- Harmony/Public/Harmony.cs | 5 +- Harmony/Public/InnerFix.cs | 109 +++------------------- Harmony/Public/InnerMethod.cs | 13 +-- Harmony/Public/InnerPostfix.cs | 28 ++++-- Harmony/Public/InnerPrefix.cs | 25 +++-- Harmony/Public/Patch.cs | 16 ++-- Harmony/Public/PatchClassProcessor.cs | 1 + Harmony/Public/PatchInfo.cs | 48 +++++----- Harmony/Public/PatchProcessor.cs | 27 +++++- Harmony/Public/PatchType.cs | 14 +++ Harmony/Public/Patches.cs | 10 +- Harmony/Tools/AccessTools.cs | 32 +++++++ HarmonyTests/Extras/PatchSerialization.cs | 24 +++-- 19 files changed, 203 insertions(+), 236 deletions(-) create mode 100644 Harmony/Public/PatchType.cs diff --git a/Directory.Build.props b/Directory.Build.props index c6eed398..c96afd39 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,7 +1,7 @@ - 2.3.0.1 + 3.0.0.0 1.1.0 diff --git a/Harmony/Internal/MethodPatcher.cs b/Harmony/Internal/MethodPatcher.cs index c22a64ef..efb2494e 100644 --- a/Harmony/Internal/MethodPatcher.cs +++ b/Harmony/Internal/MethodPatcher.cs @@ -29,13 +29,14 @@ internal class MethodPatcher readonly List postfixes; readonly List transpilers; readonly List finalizers; + readonly List infixes; readonly int idx; readonly Type returnType; readonly DynamicMethodDefinition patch; readonly ILGenerator il; readonly Emitter emitter; - internal MethodPatcher(MethodBase original, MethodBase source, List prefixes, List postfixes, List transpilers, List finalizers, bool debug) + internal MethodPatcher(MethodBase original, MethodBase source, List prefixes, List postfixes, List transpilers, List finalizers, List infixes, bool debug) { if (original is null) throw new ArgumentNullException(nameof(original)); @@ -47,6 +48,7 @@ internal MethodPatcher(MethodBase original, MethodBase source, List this.postfixes = postfixes; this.transpilers = transpilers; this.finalizers = finalizers; + this.infixes = infixes; if (debug) { @@ -54,7 +56,7 @@ internal MethodPatcher(MethodBase original, MethodBase source, List FileLog.FlushBuffer(); } - idx = prefixes.Count + postfixes.Count + finalizers.Count; + idx = prefixes.Count + postfixes.Count + finalizers.Count + infixes.Count; returnType = AccessTools.GetReturnedType(original); patch = CreateDynamicMethod(original, $"_Patch{idx}", debug); if (patch is null) @@ -68,7 +70,7 @@ internal MethodInfo CreateReplacement(out Dictionary final { var originalVariables = DeclareOriginalLocalVariables(il, source ?? original); var privateVars = new Dictionary(); - var fixes = prefixes.Union(postfixes).Union(finalizers).ToList(); + var fixes = prefixes.Union(postfixes).Union(finalizers).Union(infixes).ToList(); LocalBuilder resultVariable = null; if (idx > 0) diff --git a/Harmony/Internal/PatchFunctions.cs b/Harmony/Internal/PatchFunctions.cs index 808c6fe8..b2541049 100644 --- a/Harmony/Internal/PatchFunctions.cs +++ b/Harmony/Internal/PatchFunctions.cs @@ -4,22 +4,10 @@ namespace HarmonyLib { - /// Patch function helpers internal static class PatchFunctions { - /// Sorts patch methods by their priority rules - /// The original method - /// Patches to sort - /// Use debug mode - /// The sorted patch methods - /// internal static List GetSortedPatchMethods(MethodBase original, Patch[] patches, bool debug) => new PatchSorter(patches, debug).Sort(original); - /// Creates new replacement method with the latest patches and detours the original method - /// The original method - /// Information describing the patches - /// The newly created replacement method - /// internal static MethodInfo UpdateWrapper(MethodBase original, PatchInfo patchInfo) { var debug = patchInfo.Debugging || Harmony.DEBUG; @@ -28,8 +16,9 @@ internal static MethodInfo UpdateWrapper(MethodBase original, PatchInfo patchInf var sortedPostfixes = GetSortedPatchMethods(original, patchInfo.postfixes, debug); var sortedTranspilers = GetSortedPatchMethods(original, patchInfo.transpilers, debug); var sortedFinalizers = GetSortedPatchMethods(original, patchInfo.finalizers, debug); + var sortedInfixes = GetSortedPatchMethods(original, patchInfo.infixes, debug); - var patcher = new MethodPatcher(original, null, sortedPrefixes, sortedPostfixes, sortedTranspilers, sortedFinalizers, debug); + var patcher = new MethodPatcher(original, null, sortedPrefixes, sortedPostfixes, sortedTranspilers, sortedFinalizers, sortedInfixes, debug); var replacement = patcher.CreateReplacement(out var finalInstructions); if (replacement is null) throw new MissingMethodException($"Cannot create replacement for {original.FullDescription()}"); @@ -62,7 +51,7 @@ internal static MethodInfo ReversePatch(HarmonyMethod standin, MethodBase origin if (postTranspiler is not null) transpilers.Add(postTranspiler); var empty = new List(); - var patcher = new MethodPatcher(standin.method, original, empty, empty, transpilers, empty, debug); + var patcher = new MethodPatcher(standin.method, original, empty, empty, transpilers, empty, empty, debug); var replacement = patcher.CreateReplacement(out var finalInstructions); if (replacement is null) throw new MissingMethodException($"Cannot create replacement for {standin.method.FullDescription()}"); diff --git a/Harmony/Internal/PatchModels.cs b/Harmony/Internal/PatchModels.cs index ca97c70c..80a65a11 100644 --- a/Harmony/Internal/PatchModels.cs +++ b/Harmony/Internal/PatchModels.cs @@ -5,9 +5,6 @@ namespace HarmonyLib { - // PatchJobs holds the information during correlation - // of methods and patches while processing attribute patches - // internal class PatchJobs { internal class Job @@ -18,6 +15,7 @@ internal class Job internal List postfixes = []; internal List transpilers = []; internal List finalizers = []; + internal List infixes = []; internal void AddPatch(AttributePatch patch) { @@ -35,6 +33,9 @@ internal void AddPatch(AttributePatch patch) case HarmonyPatchType.Finalizer: finalizers.Add(patch.info); break; + case HarmonyPatchType.Infix: + infixes.Add(patch.info); + break; } } } @@ -58,7 +59,8 @@ internal List GetJobs() job.prefixes.Count + job.postfixes.Count + job.transpilers.Count + - job.finalizers.Count > 0 + job.finalizers.Count + + job.infixes.Count > 0 ).ToList(); } @@ -75,6 +77,7 @@ internal class AttributePatch HarmonyPatchType.Transpiler, HarmonyPatchType.Finalizer, HarmonyPatchType.ReversePatch, + HarmonyPatchType.Infix ]; internal HarmonyMethod info; diff --git a/Harmony/Internal/PatchSorter.cs b/Harmony/Internal/PatchSorter.cs index d1f89f01..d14f1e2d 100644 --- a/Harmony/Internal/PatchSorter.cs +++ b/Harmony/Internal/PatchSorter.cs @@ -11,12 +11,9 @@ internal class PatchSorter HashSet handledPatches; List result; List waitingList; - internal Patch[] sortedPatchArray; + Patch[] sortedPatchArray; readonly bool debug; - /// Creates a patch sorter - /// Array of patches that will be sorted - /// Use debugging internal PatchSorter(Patch[] patches, bool debug) { // Build the list of all patches first to be able to create dependency relationships. @@ -34,10 +31,6 @@ internal PatchSorter(Patch[] patches, bool debug) this.patches.Sort(); } - /// Sorts internal PatchSortingWrapper collection and caches the results. - /// After first run the result is provided from the cache. - /// The original method - /// The sorted patch methods internal List Sort(MethodBase original) { // Check if cache exists and the method was used before. @@ -84,10 +77,6 @@ internal List Sort(MethodBase original) return sortedPatchArray.Select(x => x.GetMethod(original)).ToList(); } - /// Checks if the sorter was created with the same patch list and as a result can be reused to - /// get the sorted order of the patches. - /// List of patches to check against - /// true if equal internal bool ComparePatchLists(Patch[] patches) { if (sortedPatchArray is null) _ = Sort(null); @@ -95,7 +84,6 @@ internal bool ComparePatchLists(Patch[] patches) && sortedPatchArray.All(x => patches.Contains(x, new PatchDetailedComparer())); } - /// Removes one unresolved dependency from the least important patch. void CullDependency() { // Waiting list is already sorted on priority so start from the end. @@ -118,7 +106,6 @@ void CullDependency() } } - /// Outputs all unblocked patches from the waiting list to results list void ProcessWaitingList() { // Need to change loop limit as patches are removed from the waiting list. @@ -144,24 +131,18 @@ void ProcessWaitingList() } } - /// Adds patch to both results list and handled patches set - /// Patch to add - void AddNodeToResult(PatchSortingWrapper node) + internal void AddNodeToResult(PatchSortingWrapper node) { result.Add(node); _ = handledPatches.Add(node); } - /// Wrapper used over the Patch object to allow faster dependency access and - /// dependency removal in case of cyclic dependencies - class PatchSortingWrapper : IComparable + internal class PatchSortingWrapper : IComparable { internal readonly HashSet after; internal readonly HashSet before; internal readonly Patch innerPatch; - /// Create patch wrapper object used for sorting - /// Patch to wrap internal PatchSortingWrapper(Patch patch) { innerPatch = patch; @@ -169,26 +150,16 @@ internal PatchSortingWrapper(Patch patch) after = []; } - /// Determines how patches sort - /// The other patch - /// integer to define sort order (-1, 0, 1) public int CompareTo(object obj) { var p = obj as PatchSortingWrapper; return PatchInfoSerialization.PriorityComparer(p?.innerPatch, innerPatch.index, innerPatch.priority); } - /// Determines whether patches are equal - /// The other patch - /// true if equal public override bool Equals(object obj) => obj is PatchSortingWrapper wrapper && innerPatch.PatchMethod == wrapper.innerPatch.PatchMethod; - /// Hash function - /// A hash code public override int GetHashCode() => innerPatch.PatchMethod.GetHashCode(); - /// Bidirectionally registers Patches as after dependencies - /// List of dependencies to register internal void AddBeforeDependency(IEnumerable dependencies) { foreach (var i in dependencies) @@ -198,8 +169,6 @@ internal void AddBeforeDependency(IEnumerable dependencies) } } - /// Bidirectionally registers Patches as before dependencies - /// List of dependencies to register internal void AddAfterDependency(IEnumerable dependencies) { foreach (var i in dependencies) @@ -209,24 +178,20 @@ internal void AddAfterDependency(IEnumerable dependencies) } } - /// Bidirectionally removes Patch from after dependencies - /// Patch to remove internal void RemoveAfterDependency(PatchSortingWrapper afterNode) { _ = after.Remove(afterNode); _ = afterNode.before.Remove(this); } - /// Bidirectionally removes Patch from before dependencies - /// Patch to remove - internal void RemoveBeforeDependency(PatchSortingWrapper beforeNode) + void RemoveBeforeDependency(PatchSortingWrapper beforeNode) { _ = before.Remove(beforeNode); _ = beforeNode.after.Remove(this); } } - internal class PatchDetailedComparer : IEqualityComparer + class PatchDetailedComparer : IEqualityComparer { public bool Equals(Patch x, Patch y) { diff --git a/Harmony/Public/Attributes.cs b/Harmony/Public/Attributes.cs index 1ce22430..940fcfe3 100644 --- a/Harmony/Public/Attributes.cs +++ b/Harmony/Public/Attributes.cs @@ -54,7 +54,9 @@ public enum HarmonyPatchType /// A finalizer Finalizer, /// A reverse patch - ReversePatch + ReversePatch, + /// An infix patch + Infix } /// Specifies the type of reverse patch diff --git a/Harmony/Public/Harmony.cs b/Harmony/Public/Harmony.cs index 65b8e9ed..6d6fc25a 100644 --- a/Harmony/Public/Harmony.cs +++ b/Harmony/Public/Harmony.cs @@ -155,15 +155,17 @@ public void PatchCategory(Assembly assembly, string category) /// An optional postfix method wrapped in a object /// An optional transpiler method wrapped in a object /// An optional finalizer method wrapped in a object + /// An optional infix method wrapped in a object /// 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) + public MethodInfo Patch(MethodBase original, HarmonyMethod prefix = null, HarmonyMethod postfix = null, HarmonyMethod transpiler = null, HarmonyMethod finalizer = null, HarmonyMethod infix = null) { var processor = CreateProcessor(original); _ = processor.AddPrefix(prefix); _ = processor.AddPostfix(postfix); _ = processor.AddTranspiler(transpiler); _ = processor.AddFinalizer(finalizer); + _ = processor.AddInfix(infix); return processor.Patch(); } @@ -193,6 +195,7 @@ public void UnpatchAll(string harmonyID = null) info.Postfixes.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.PatchMethod)); info.Prefixes.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.PatchMethod)); } + info.Infixes.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.PatchMethod)); info.Transpilers.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.PatchMethod)); if (hasBody) info.Finalizers.DoIf(IDCheck, patchInfo => Unpatch(original, patchInfo.PatchMethod)); diff --git a/Harmony/Public/InnerFix.cs b/Harmony/Public/InnerFix.cs index 7dae39bb..7f10c7d0 100644 --- a/Harmony/Public/InnerFix.cs +++ b/Harmony/Public/InnerFix.cs @@ -1,129 +1,42 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text.Json.Serialization; namespace HarmonyLib { /// The base class for InnerPrefix and InnerPostfix - /// + /// [Serializable] - public abstract partial class InnerFix : IComparable + public abstract partial class InnerFix { - /// Zero-based index - /// - public readonly int index; - - /// The owner (Harmony ID) - /// - public readonly string owner; - - /// The priority, see - /// - public readonly int priority; - - /// Keep this patch before the patches indicated in the list of Harmony IDs - /// - public readonly string[] before; - - /// Keep this patch after the patches indicated in the list of Harmony IDs - /// - public readonly string[] after; - - /// A flag that will log the replacement method via every time this patch is used to build the replacement, even in the future - /// - public readonly bool debug; - - /// The type of an InnerFix - /// - public enum PatchType - { - /// An inner prefix - /// - Prefix, - /// An inner postfix - /// - Postfix - } - - /// The type of an InnerFix - /// - public abstract PatchType Type { get; } + internal abstract InnerFixType Type { get; set; } /// The method call to patch /// public InnerMethod InnerMethod { get; set; } - /// The patch to apply - /// - [NonSerialized] - private MethodInfo patchMethod; - private int methodToken; - private string moduleGUID; - - /// The method of the static patch method - /// -#if NET5_0_OR_GREATER - [JsonIgnore] -#endif - public MethodInfo PatchMethod - { - get - { - if (patchMethod is null) - { - var mdl = AppDomain.CurrentDomain.GetAssemblies() - .Where(a => !a.FullName.StartsWith("Microsoft.VisualStudio")) - .SelectMany(a => a.GetLoadedModules()) - .First(m => m.ModuleVersionId.ToString() == moduleGUID); - patchMethod = (MethodInfo)mdl.ResolveMethod(methodToken); - } - return patchMethod; - } - set - { - patchMethod = value; - methodToken = patchMethod.MetadataToken; - moduleGUID = patchMethod.Module.ModuleVersionId.ToString(); - } - } - /// If defined will be used to calculate Target from a given methods content /// public Func, InnerMethod> TargetFinder { get; set; } - /// Creates an infix for an implicit defined method call - /// The method call to patch - /// The patch to apply - /// - public InnerFix(InnerMethod target, MethodInfo patch) + internal InnerFix(InnerFixType type, InnerMethod innerMethod) { - Target = target; + Type = type; + InnerMethod = innerMethod; TargetFinder = null; - Patch = patch; } /// Creates an infix for an indirectly defined method call + /// /// The type of infix /// Calculates Target from a given methods content - /// The patch to apply /// - public InnerFix(Func, InnerMethod> targetFinder, MethodInfo patch) + internal InnerFix(InnerFixType type, Func, InnerMethod> targetFinder) { - Target = null; + Type = type; + InnerMethod = null; TargetFinder = targetFinder; - Patch = patch; } - /// Applies this fix to a method - /// The method that contains the target method call(s) - /// The instructions of the method - /// The new instructions of the method - /// - public IEnumerable Apply(MethodBase original, IEnumerable instructions) - { - _ = original; - foreach (var instruction in instructions) yield return instruction; - } + internal abstract IEnumerable Apply(MethodBase original, IEnumerable instructions); } } diff --git a/Harmony/Public/InnerMethod.cs b/Harmony/Public/InnerMethod.cs index afd3a816..614d8078 100644 --- a/Harmony/Public/InnerMethod.cs +++ b/Harmony/Public/InnerMethod.cs @@ -1,7 +1,9 @@ using System; -using System.Linq; using System.Reflection; + +#if NET5_0_OR_GREATER using System.Text.Json.Serialization; +#endif namespace HarmonyLib { @@ -38,14 +40,7 @@ public MethodInfo Method { get { - if (method is null) - { - var mdl = AppDomain.CurrentDomain.GetAssemblies() - .Where(a => !a.FullName.StartsWith("Microsoft.VisualStudio")) - .SelectMany(a => a.GetLoadedModules()) - .First(m => m.ModuleVersionId.ToString() == moduleGUID); - method = (MethodInfo)mdl.ResolveMethod(methodToken); - } + method ??= AccessTools.GetMethodByModuleAndToken(moduleGUID, methodToken); return method; } set diff --git a/Harmony/Public/InnerPostfix.cs b/Harmony/Public/InnerPostfix.cs index f9a0fe4b..4d377bb2 100644 --- a/Harmony/Public/InnerPostfix.cs +++ b/Harmony/Public/InnerPostfix.cs @@ -5,19 +5,29 @@ namespace HarmonyLib { /// An inner postfix that is applied inside some method call inside a method + /// public class InnerPostfix : InnerFix { - /// This InnerFix is a InnerFixType.Postfix - public override PatchType Type => PatchType.Postfix; + internal override InnerFixType Type + { + get => InnerFixType.Postfix; + set => throw new NotImplementedException(); + } - /// Creates an inner postfix for an implicit defined method call - /// The method call to patch - /// The patch to apply - public InnerPostfix(InnerMethod target, MethodInfo patch) : base(target, patch) { } + /// Creates an infix for an implicit defined method call + /// The method call to apply the fix to + /// + public InnerPostfix(InnerMethod innerMethod) : base(InnerFixType.Postfix, innerMethod) { } - /// Creates an inner postfix for an indirectly defined method call + /// Creates an infix for an indirectly defined method call /// Calculates Target from a given methods content - /// The patch to apply - public InnerPostfix(Func, InnerMethod> targetFinder, MethodInfo patch) : base(targetFinder, patch) { } + /// + public InnerPostfix(Func, InnerMethod> targetFinder) : base(InnerFixType.Postfix, targetFinder) { } + + internal override IEnumerable Apply(MethodBase original, IEnumerable instructions) + { + _ = original; + foreach (var instruction in instructions) yield return instruction; + } } } diff --git a/Harmony/Public/InnerPrefix.cs b/Harmony/Public/InnerPrefix.cs index 789d6283..0a40ebe6 100644 --- a/Harmony/Public/InnerPrefix.cs +++ b/Harmony/Public/InnerPrefix.cs @@ -8,20 +8,25 @@ namespace HarmonyLib /// public class InnerPrefix : InnerFix { - /// This InnerFix is a InnerFixType.Prefix - /// - public override PatchType Type => PatchType.Prefix; + internal override InnerFixType Type { + get => InnerFixType.Prefix; + set => throw new NotImplementedException(); + } - /// Creates an inner prefix for an implicit defined method call - /// The method call to patch - /// The patch to apply + /// Creates an infix for an implicit defined method call + /// The method call to apply the fix to /// - public InnerPrefix(InnerMethod target, MethodInfo patch) : base(target, patch) { } + public InnerPrefix(InnerMethod innerMethod) : base(InnerFixType.Prefix, innerMethod) { } - /// Creates an inner prefix for an indirectly defined method call + /// Creates an infix for an indirectly defined method call /// Calculates Target from a given methods content - /// The patch to apply /// - public InnerPrefix(Func, InnerMethod> targetFinder, MethodInfo patch) : base(targetFinder, patch) { } + public InnerPrefix(Func, InnerMethod> targetFinder) : base(InnerFixType.Prefix, targetFinder) { } + + internal override IEnumerable Apply(MethodBase original, IEnumerable instructions) + { + _ = original; + foreach (var instruction in instructions) yield return instruction; + } } } diff --git a/Harmony/Public/Patch.cs b/Harmony/Public/Patch.cs index 7280b637..89bdbfe8 100644 --- a/Harmony/Public/Patch.cs +++ b/Harmony/Public/Patch.cs @@ -1,8 +1,7 @@ using System; -using System.IO; -using System.Linq; using System.Reflection; using System.Reflection.Emit; + #if NET5_0_OR_GREATER using System.Text.Json.Serialization; #endif @@ -46,6 +45,10 @@ public class Patch : IComparable private int methodToken; private string moduleGUID; + /// For an infix patch, this defines the inner method that we will apply the patch to + /// + public readonly InnerMethod innerMethod; + /// The method of the static patch method /// #if NET5_0_OR_GREATER @@ -55,14 +58,7 @@ public MethodInfo PatchMethod { get { - if (patchMethod is null) - { - var mdl = AppDomain.CurrentDomain.GetAssemblies() - .Where(a => !a.FullName.StartsWith("Microsoft.VisualStudio")) - .SelectMany(a => a.GetLoadedModules()) - .First(m => m.ModuleVersionId.ToString() == moduleGUID); - patchMethod = (MethodInfo)mdl.ResolveMethod(methodToken); - } + patchMethod ??= AccessTools.GetMethodByModuleAndToken(moduleGUID, methodToken); return patchMethod; } set diff --git a/Harmony/Public/PatchClassProcessor.cs b/Harmony/Public/PatchClassProcessor.cs index 2f92a6d9..1ded889b 100644 --- a/Harmony/Public/PatchClassProcessor.cs +++ b/Harmony/Public/PatchClassProcessor.cs @@ -192,6 +192,7 @@ void ProcessPatchJob(PatchJobs.Job job) patchInfo.AddPostfixes(instance.Id, [.. job.postfixes]); patchInfo.AddTranspilers(instance.Id, [.. job.transpilers]); patchInfo.AddFinalizers(instance.Id, [.. job.finalizers]); + patchInfo.AddInfixes(instance.Id, [.. job.infixes]); replacement = PatchFunctions.UpdateWrapper(job.original, patchInfo); HarmonySharedState.UpdatePatchInfo(job.original, replacement, patchInfo); diff --git a/Harmony/Public/PatchInfo.cs b/Harmony/Public/PatchInfo.cs index 5e2d936f..bfa8c465 100644 --- a/Harmony/Public/PatchInfo.cs +++ b/Harmony/Public/PatchInfo.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Reflection; #if NET5_0_OR_GREATER @@ -40,12 +40,19 @@ public class PatchInfo #endif public Patch[] finalizers = []; + /// Infixes as an array of + /// +#if NET5_0_OR_GREATER + [JsonInclude] +#endif + public Patch[] infixes = []; + /// Returns if any of the patches wants debugging turned on /// #if NET5_0_OR_GREATER [JsonIgnore] #endif - public bool Debugging => prefixes.Any(p => p.debug) || postfixes.Any(p => p.debug) || transpilers.Any(p => p.debug) || finalizers.Any(p => p.debug); + public bool Debugging => prefixes.Any(p => p.debug) || postfixes.Any(p => p.debug) || transpilers.Any(p => p.debug) || finalizers.Any(p => p.debug) || infixes.Any(p => p.debug); /// Adds prefixes /// An owner (Harmony ID) @@ -53,10 +60,6 @@ public class PatchInfo /// internal void AddPrefixes(string owner, params HarmonyMethod[] methods) => prefixes = Add(owner, methods, prefixes); - /// Adds a prefix - [Obsolete("This method only exists for backwards compatibility since the class is public.")] - public void AddPrefix(MethodInfo patch, string owner, int priority, string[] before, string[] after, bool debug) => AddPrefixes(owner, new HarmonyMethod(patch, priority, before, after, debug)); - /// Removes prefixes /// The owner of the prefixes, or * for all /// @@ -68,10 +71,6 @@ public class PatchInfo /// internal void AddPostfixes(string owner, params HarmonyMethod[] methods) => postfixes = Add(owner, methods, postfixes); - /// Adds a postfix - [Obsolete("This method only exists for backwards compatibility since the class is public.")] - public void AddPostfix(MethodInfo patch, string owner, int priority, string[] before, string[] after, bool debug) => AddPostfixes(owner, new HarmonyMethod(patch, priority, before, after, debug)); - /// Removes postfixes /// The owner of the postfixes, or * for all /// @@ -83,10 +82,6 @@ public class PatchInfo /// internal void AddTranspilers(string owner, params HarmonyMethod[] methods) => transpilers = Add(owner, methods, transpilers); - /// Adds a transpiler - [Obsolete("This method only exists for backwards compatibility since the class is public.")] - public void AddTranspiler(MethodInfo patch, string owner, int priority, string[] before, string[] after, bool debug) => AddTranspilers(owner, new HarmonyMethod(patch, priority, before, after, debug)); - /// Removes transpilers /// The owner of the transpilers, or * for all /// @@ -98,15 +93,22 @@ public class PatchInfo /// internal void AddFinalizers(string owner, params HarmonyMethod[] methods) => finalizers = Add(owner, methods, finalizers); - /// Adds a finalizer - [Obsolete("This method only exists for backwards compatibility since the class is public.")] - public void AddFinalizer(MethodInfo patch, string owner, int priority, string[] before, string[] after, bool debug) => AddFinalizers(owner, new HarmonyMethod(patch, priority, before, after, debug)); - /// Removes finalizers /// The owner of the finalizers, or * for all /// public void RemoveFinalizer(string owner) => finalizers = Remove(owner, finalizers); + /// Adds infixes + /// An owner (Harmony ID) + /// The patch methods + /// + internal void AddInfixes(string owner, params HarmonyMethod[] methods) => infixes = Add(owner, methods, infixes); + + /// Removes infixes + /// The owner of the infix, or * for all + /// + public void RemoveInfix(string owner) => infixes = Remove(owner, infixes); + /// Removes a patch using its method /// The method of the patch to remove /// @@ -116,13 +118,9 @@ public void RemovePatch(MethodInfo patch) postfixes = postfixes.Where(p => p.PatchMethod != patch).ToArray(); transpilers = transpilers.Where(p => p.PatchMethod != patch).ToArray(); finalizers = finalizers.Where(p => p.PatchMethod != patch).ToArray(); + infixes = infixes.Where(p => p.PatchMethod != patch).ToArray(); } - /// Gets a concatenated list of patches - /// The Harmony instance ID adding the new patches - /// The patches to add - /// The current patches - /// private static Patch[] Add(string owner, HarmonyMethod[] add, Patch[] current) { // avoid copy if no patch added @@ -142,10 +140,6 @@ .. add ]; } - /// Gets a list of patches with any from the given owner removed - /// The owner of the methods, or * for all - /// The current patches - /// private static Patch[] Remove(string owner, Patch[] current) { return owner == "*" diff --git a/Harmony/Public/PatchProcessor.cs b/Harmony/Public/PatchProcessor.cs index 2a0b2f78..b4bd037c 100644 --- a/Harmony/Public/PatchProcessor.cs +++ b/Harmony/Public/PatchProcessor.cs @@ -22,6 +22,7 @@ public class PatchProcessor(Harmony instance, MethodBase original) HarmonyMethod postfix; HarmonyMethod transpiler; HarmonyMethod finalizer; + HarmonyMethod infix; internal static readonly object locker = new(); @@ -105,6 +106,26 @@ public PatchProcessor AddFinalizer(MethodInfo fixMethod) return this; } + /// Adds an infix + /// The infix as a + /// A for chaining calls + /// + public PatchProcessor AddInfix(HarmonyMethod infix) + { + this.infix = infix; + return this; + } + + /// Adds a postfix + /// The infix method + /// A for chaining calls + /// + public PatchProcessor AddInfix(MethodInfo fixMethod) + { + infix = new HarmonyMethod(fixMethod); + return this; + } + /// Gets all patched original methods in the appdomain /// An enumeration of patched method/constructor /// @@ -138,6 +159,7 @@ public MethodInfo Patch() patchInfo.AddPostfixes(instance.Id, postfix); patchInfo.AddTranspilers(instance.Id, transpiler); patchInfo.AddFinalizers(instance.Id, finalizer); + patchInfo.AddInfixes(instance.Id, infix); var replacement = PatchFunctions.UpdateWrapper(original, patchInfo); @@ -166,6 +188,8 @@ public PatchProcessor Unpatch(HarmonyPatchType type, string harmonyID) patchInfo.RemoveTranspiler(harmonyID); if (type == HarmonyPatchType.All || type == HarmonyPatchType.Finalizer) patchInfo.RemoveFinalizer(harmonyID); + if (type == HarmonyPatchType.All || type == HarmonyPatchType.Infix) + patchInfo.RemoveInfix(harmonyID); var replacement = PatchFunctions.UpdateWrapper(original, patchInfo); HarmonySharedState.UpdatePatchInfo(original, replacement, patchInfo); @@ -201,7 +225,7 @@ public static Patches GetPatchInfo(MethodBase method) PatchInfo patchInfo; lock (locker) { patchInfo = HarmonySharedState.GetPatchInfo(method); } if (patchInfo is null) return null; - return new Patches(patchInfo.prefixes, patchInfo.postfixes, patchInfo.transpilers, patchInfo.finalizers); + return new Patches(patchInfo.prefixes, patchInfo.postfixes, patchInfo.transpilers, patchInfo.finalizers, patchInfo.infixes); } /// Sort patch methods by their priority rules @@ -227,6 +251,7 @@ public static Dictionary VersionInfo(out Version currentVersion info.postfixes.Do(fix => assemblies[fix.owner] = fix.PatchMethod.DeclaringType.Assembly); info.transpilers.Do(fix => assemblies[fix.owner] = fix.PatchMethod.DeclaringType.Assembly); info.finalizers.Do(fix => assemblies[fix.owner] = fix.PatchMethod.DeclaringType.Assembly); + info.infixes.Do(fix => assemblies[fix.owner] = fix.PatchMethod.DeclaringType.Assembly); }); var result = new Dictionary(); diff --git a/Harmony/Public/PatchType.cs b/Harmony/Public/PatchType.cs new file mode 100644 index 00000000..97b5eef1 --- /dev/null +++ b/Harmony/Public/PatchType.cs @@ -0,0 +1,14 @@ +namespace HarmonyLib +{ + /// The type of an InnerFix + /// + public enum InnerFixType + { + /// An inner prefix + /// + Prefix, + /// An inner postfix + /// + Postfix + } +} diff --git a/Harmony/Public/Patches.cs b/Harmony/Public/Patches.cs index 8d451ad9..4141241d 100644 --- a/Harmony/Public/Patches.cs +++ b/Harmony/Public/Patches.cs @@ -24,6 +24,10 @@ public class Patches /// public readonly ReadOnlyCollection Finalizers; + /// A collection of infix + /// + public readonly ReadOnlyCollection Infixes; + /// Gets all owners (Harmony IDs) or all known patches /// The patch owners /// @@ -36,6 +40,7 @@ public ReadOnlyCollection Owners result.UnionWith(Postfixes.Select(p => p.owner)); result.UnionWith(Transpilers.Select(p => p.owner)); result.UnionWith(Finalizers.Select(p => p.owner)); + result.UnionWith(Infixes.Select(p => p.owner)); return result.ToList().AsReadOnly(); } } @@ -45,18 +50,21 @@ public ReadOnlyCollection Owners /// An array of postfixes as /// An array of transpileres as /// An array of finalizeres as + /// An array of infixes as /// - public Patches(Patch[] prefixes, Patch[] postfixes, Patch[] transpilers, Patch[] finalizers) + public Patches(Patch[] prefixes, Patch[] postfixes, Patch[] transpilers, Patch[] finalizers, Patch[] infixes) { prefixes ??= []; postfixes ??= []; transpilers ??= []; finalizers ??= []; + infixes ??= []; Prefixes = prefixes.ToList().AsReadOnly(); Postfixes = postfixes.ToList().AsReadOnly(); Transpilers = transpilers.ToList().AsReadOnly(); Finalizers = finalizers.ToList().AsReadOnly(); + Infixes = infixes.ToList().AsReadOnly(); } } } diff --git a/Harmony/Tools/AccessTools.cs b/Harmony/Tools/AccessTools.cs index fe73ec53..3d4484de 100644 --- a/Harmony/Tools/AccessTools.cs +++ b/Harmony/Tools/AccessTools.cs @@ -9,6 +9,9 @@ using System.Reflection.Emit; using System.Threading; +#if NET5_0_OR_GREATER +using System.Threading.Tasks; +#endif #if NET45_OR_GREATER || NETSTANDARD || NETCOREAPP using System.Runtime.CompilerServices; @@ -705,6 +708,35 @@ public static Type GetUnderlyingType(this MemberInfo member) }; } + /// Returns a by searching for module-id and token + /// The module of the method + /// The token of the method + /// + public static MethodInfo GetMethodByModuleAndToken(string moduleGUID, int token) + { +#if NET5_0_OR_GREATER + Module module = null; + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); + var moduleVersionGUID = new Guid(moduleGUID); + Parallel.ForEach(assemblies, (assembly) => + { + var allModules = assembly.GetModules(); + for (var i = 0; i < allModules.Length; i++) + if (allModules[i].ModuleVersionId == moduleVersionGUID) + { + module = allModules[i]; + break; + } + }); +#else + var module = AppDomain.CurrentDomain.GetAssemblies() + .Where(a => !a.FullName.StartsWith("Microsoft.VisualStudio")) + .SelectMany(a => a.GetLoadedModules()) + .First(m => m.ModuleVersionId.ToString() == moduleGUID); +#endif + return module == null ? null : (MethodInfo)module.ResolveMethod(token); + } + /// Test if a class member is actually an concrete implementation /// A member /// True if the member is a declared diff --git a/HarmonyTests/Extras/PatchSerialization.cs b/HarmonyTests/Extras/PatchSerialization.cs index 87a8ae6a..b7576b73 100644 --- a/HarmonyTests/Extras/PatchSerialization.cs +++ b/HarmonyTests/Extras/PatchSerialization.cs @@ -1,18 +1,24 @@ using HarmonyLib; using NUnit.Framework; using System.Linq; + +#if NET5_0_OR_GREATER using System.Text; +#endif namespace HarmonyTests.Extras { [TestFixture, NonParallelizable] class PatchSerialization { + static string[] fixNames = ["prefixes", "postfixes", "transpilers", "finalizers", "infixes"]; + static Patch[][] GetFixes(PatchInfo patchInfo) => [patchInfo.prefixes, patchInfo.postfixes, patchInfo.transpilers, patchInfo.finalizers, patchInfo.infixes]; + static string ExpectedJSON() { var method = SymbolExtensions.GetMethodInfo(() => ExpectedJSON()); var fix = "\"$FIX$\":[{\"index\":0,\"debug\":true,\"owner\":\"$NAME$\",\"priority\":600,\"methodToken\":$MT$,\"moduleGUID\":\"$MGUID$\",\"after\":[],\"before\":[\"p1\",null,\"p2\"]}]"; - var fixes = new[] { "prefixes", "postfixes", "transpilers", "finalizers" } + var fixes = fixNames .Select(name => { return fix @@ -39,6 +45,7 @@ public void Serialize() patchInfo.AddPostfixes("postfixes", [hMethod]); patchInfo.AddTranspilers("transpilers", [hMethod]); patchInfo.AddFinalizers("finalizers", [hMethod]); + patchInfo.AddInfixes("infixes", [hMethod]); PatchInfoSerialization.useBinaryFormatter = false; var result = PatchInfoSerialization.Serialize(patchInfo); @@ -51,17 +58,18 @@ public void Deserialize() { PatchInfoSerialization.useBinaryFormatter = false; + Assert.AreEqual(GetFixes(new PatchInfo()).Length, fixNames.Length); + var data = Encoding.UTF8.GetBytes(ExpectedJSON()); var patchInfo = PatchInfoSerialization.Deserialize(data); var n = 0; - var names = new[] { "prefixes", "postfixes", "transpilers", "finalizers" }; - new[] { patchInfo.prefixes, patchInfo.postfixes, patchInfo.transpilers, patchInfo.finalizers } + GetFixes(patchInfo) .Do(fixes => { Assert.AreEqual(1, fixes.Length); - Assert.AreEqual(names[n++], fixes[0].owner); + Assert.AreEqual(fixNames[n++], fixes[0].owner); Assert.AreEqual(Priority.High, fixes[0].priority); Assert.AreEqual(new[] { "p1", null, "p2" }, fixes[0].before); Assert.AreEqual(0, fixes[0].after.Length); @@ -78,23 +86,25 @@ public void SerializeAndDeserialize() var method = SymbolExtensions.GetMethodInfo(() => ExpectedJSON()); var hMethod = new HarmonyMethod(method, Priority.High, ["p1", null, "p2"], [], true); + Assert.AreEqual(GetFixes(new PatchInfo()).Length, fixNames.Length); + var originalPatchInfo = new PatchInfo(); originalPatchInfo.AddPrefixes("prefixes", [hMethod]); originalPatchInfo.AddPostfixes("postfixes", [hMethod]); originalPatchInfo.AddTranspilers("transpilers", [hMethod]); originalPatchInfo.AddFinalizers("finalizers", [hMethod]); + originalPatchInfo.AddInfixes("infixes", [hMethod]); var data = PatchInfoSerialization.Serialize(originalPatchInfo); var patchInfo = PatchInfoSerialization.Deserialize(data); var n = 0; - var names = new[] { "prefixes", "postfixes", "transpilers", "finalizers" }; - new[] { patchInfo.prefixes, patchInfo.postfixes, patchInfo.transpilers, patchInfo.finalizers } + GetFixes(patchInfo) .Do(fixes => { Assert.AreEqual(1, fixes.Length); - Assert.AreEqual(names[n++], fixes[0].owner); + Assert.AreEqual(fixNames[n++], fixes[0].owner); Assert.AreEqual(Priority.High, fixes[0].priority); Assert.AreEqual(new[] { "p1", null, "p2" }, fixes[0].before); Assert.AreEqual(0, fixes[0].after.Length);