Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/infixes #583

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>

<PropertyGroup>
<HarmonyVersion>2.3.0.1</HarmonyVersion>
<HarmonyVersion>3.0.0.0</HarmonyVersion>
<HarmonyPrerelease></HarmonyPrerelease>
<MonoModCoreVersion>1.1.0</MonoModCoreVersion>
</PropertyGroup>
Expand Down
8 changes: 5 additions & 3 deletions Harmony/Internal/MethodPatcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,14 @@ internal class MethodPatcher
readonly List<MethodInfo> postfixes;
readonly List<MethodInfo> transpilers;
readonly List<MethodInfo> finalizers;
readonly List<MethodInfo> infixes;
readonly int idx;
readonly Type returnType;
readonly DynamicMethodDefinition patch;
readonly ILGenerator il;
readonly Emitter emitter;

internal MethodPatcher(MethodBase original, MethodBase source, List<MethodInfo> prefixes, List<MethodInfo> postfixes, List<MethodInfo> transpilers, List<MethodInfo> finalizers, bool debug)
internal MethodPatcher(MethodBase original, MethodBase source, List<MethodInfo> prefixes, List<MethodInfo> postfixes, List<MethodInfo> transpilers, List<MethodInfo> finalizers, List<MethodInfo> infixes, bool debug)
{
if (original is null)
throw new ArgumentNullException(nameof(original));
Expand All @@ -47,14 +48,15 @@ internal MethodPatcher(MethodBase original, MethodBase source, List<MethodInfo>
this.postfixes = postfixes;
this.transpilers = transpilers;
this.finalizers = finalizers;
this.infixes = infixes;

if (debug)
{
FileLog.LogBuffered($"### Patch: {original.FullDescription()}");
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)
Expand All @@ -68,7 +70,7 @@ internal MethodInfo CreateReplacement(out Dictionary<int, CodeInstruction> final
{
var originalVariables = DeclareOriginalLocalVariables(il, source ?? original);
var privateVars = new Dictionary<string, LocalBuilder>();
var fixes = prefixes.Union(postfixes).Union(finalizers).ToList();
var fixes = prefixes.Union(postfixes).Union(finalizers).Union(infixes).ToList();

LocalBuilder resultVariable = null;
if (idx > 0)
Expand Down
17 changes: 3 additions & 14 deletions Harmony/Internal/PatchFunctions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,10 @@

namespace HarmonyLib
{
/// <summary>Patch function helpers</summary>
internal static class PatchFunctions
{
/// <summary>Sorts patch methods by their priority rules</summary>
/// <param name="original">The original method</param>
/// <param name="patches">Patches to sort</param>
/// <param name="debug">Use debug mode</param>
/// <returns>The sorted patch methods</returns>
///
internal static List<MethodInfo> GetSortedPatchMethods(MethodBase original, Patch[] patches, bool debug) => new PatchSorter(patches, debug).Sort(original);

/// <summary>Creates new replacement method with the latest patches and detours the original method</summary>
/// <param name="original">The original method</param>
/// <param name="patchInfo">Information describing the patches</param>
/// <returns>The newly created replacement method</returns>
///
internal static MethodInfo UpdateWrapper(MethodBase original, PatchInfo patchInfo)
{
var debug = patchInfo.Debugging || Harmony.DEBUG;
Expand All @@ -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()}");

Expand Down Expand Up @@ -62,7 +51,7 @@ internal static MethodInfo ReversePatch(HarmonyMethod standin, MethodBase origin
if (postTranspiler is not null) transpilers.Add(postTranspiler);

var empty = new List<MethodInfo>();
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()}");

Expand Down
11 changes: 7 additions & 4 deletions Harmony/Internal/PatchModels.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,6 @@

namespace HarmonyLib
{
// PatchJobs holds the information during correlation
// of methods and patches while processing attribute patches
//
internal class PatchJobs<T>
{
internal class Job
Expand All @@ -18,6 +15,7 @@ internal class Job
internal List<HarmonyMethod> postfixes = [];
internal List<HarmonyMethod> transpilers = [];
internal List<HarmonyMethod> finalizers = [];
internal List<HarmonyMethod> infixes = [];

internal void AddPatch(AttributePatch patch)
{
Expand All @@ -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;
}
}
}
Expand All @@ -58,7 +59,8 @@ internal List<Job> GetJobs()
job.prefixes.Count +
job.postfixes.Count +
job.transpilers.Count +
job.finalizers.Count > 0
job.finalizers.Count +
job.infixes.Count > 0
).ToList();
}

Expand All @@ -75,6 +77,7 @@ internal class AttributePatch
HarmonyPatchType.Transpiler,
HarmonyPatchType.Finalizer,
HarmonyPatchType.ReversePatch,
HarmonyPatchType.Infix
];

internal HarmonyMethod info;
Expand Down
45 changes: 5 additions & 40 deletions Harmony/Internal/PatchSorter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,9 @@ internal class PatchSorter
HashSet<PatchSortingWrapper> handledPatches;
List<PatchSortingWrapper> result;
List<PatchSortingWrapper> waitingList;
internal Patch[] sortedPatchArray;
Patch[] sortedPatchArray;
readonly bool debug;

/// <summary>Creates a patch sorter</summary>
/// <param name="patches">Array of patches that will be sorted</param>
/// <param name="debug">Use debugging</param>
internal PatchSorter(Patch[] patches, bool debug)
{
// Build the list of all patches first to be able to create dependency relationships.
Expand All @@ -34,10 +31,6 @@ internal PatchSorter(Patch[] patches, bool debug)
this.patches.Sort();
}

/// <summary>Sorts internal PatchSortingWrapper collection and caches the results.
/// After first run the result is provided from the cache.</summary>
/// <param name="original">The original method</param>
/// <returns>The sorted patch methods</returns>
internal List<MethodInfo> Sort(MethodBase original)
{
// Check if cache exists and the method was used before.
Expand Down Expand Up @@ -84,18 +77,13 @@ internal List<MethodInfo> Sort(MethodBase original)
return sortedPatchArray.Select(x => x.GetMethod(original)).ToList();
}

/// <summary>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.</summary>
/// <param name="patches">List of patches to check against</param>
/// <returns>true if equal</returns>
internal bool ComparePatchLists(Patch[] patches)
{
if (sortedPatchArray is null) _ = Sort(null);
return patches is not null && sortedPatchArray.Length == patches.Length
&& sortedPatchArray.All(x => patches.Contains(x, new PatchDetailedComparer()));
}

/// <summary>Removes one unresolved dependency from the least important patch.</summary>
void CullDependency()
{
// Waiting list is already sorted on priority so start from the end.
Expand All @@ -118,7 +106,6 @@ void CullDependency()
}
}

/// <summary>Outputs all unblocked patches from the waiting list to results list</summary>
void ProcessWaitingList()
{
// Need to change loop limit as patches are removed from the waiting list.
Expand All @@ -144,51 +131,35 @@ void ProcessWaitingList()
}
}

/// <summary>Adds patch to both results list and handled patches set</summary>
/// <param name="node">Patch to add</param>
void AddNodeToResult(PatchSortingWrapper node)
internal void AddNodeToResult(PatchSortingWrapper node)
{
result.Add(node);
_ = handledPatches.Add(node);
}

/// <summary>Wrapper used over the Patch object to allow faster dependency access and
/// dependency removal in case of cyclic dependencies</summary>
class PatchSortingWrapper : IComparable
internal class PatchSortingWrapper : IComparable
{
internal readonly HashSet<PatchSortingWrapper> after;
internal readonly HashSet<PatchSortingWrapper> before;
internal readonly Patch innerPatch;

/// <summary>Create patch wrapper object used for sorting</summary>
/// <param name="patch">Patch to wrap</param>
internal PatchSortingWrapper(Patch patch)
{
innerPatch = patch;
before = [];
after = [];
}

/// <summary>Determines how patches sort</summary>
/// <param name="obj">The other patch</param>
/// <returns>integer to define sort order (-1, 0, 1)</returns>
public int CompareTo(object obj)
{
var p = obj as PatchSortingWrapper;
return PatchInfoSerialization.PriorityComparer(p?.innerPatch, innerPatch.index, innerPatch.priority);
}

/// <summary>Determines whether patches are equal</summary>
/// <param name="obj">The other patch</param>
/// <returns>true if equal</returns>
public override bool Equals(object obj) => obj is PatchSortingWrapper wrapper && innerPatch.PatchMethod == wrapper.innerPatch.PatchMethod;

/// <summary>Hash function</summary>
/// <returns>A hash code</returns>
public override int GetHashCode() => innerPatch.PatchMethod.GetHashCode();

/// <summary>Bidirectionally registers Patches as after dependencies</summary>
/// <param name="dependencies">List of dependencies to register</param>
internal void AddBeforeDependency(IEnumerable<PatchSortingWrapper> dependencies)
{
foreach (var i in dependencies)
Expand All @@ -198,8 +169,6 @@ internal void AddBeforeDependency(IEnumerable<PatchSortingWrapper> dependencies)
}
}

/// <summary>Bidirectionally registers Patches as before dependencies</summary>
/// <param name="dependencies">List of dependencies to register</param>
internal void AddAfterDependency(IEnumerable<PatchSortingWrapper> dependencies)
{
foreach (var i in dependencies)
Expand All @@ -209,24 +178,20 @@ internal void AddAfterDependency(IEnumerable<PatchSortingWrapper> dependencies)
}
}

/// <summary>Bidirectionally removes Patch from after dependencies</summary>
/// <param name="afterNode">Patch to remove</param>
internal void RemoveAfterDependency(PatchSortingWrapper afterNode)
{
_ = after.Remove(afterNode);
_ = afterNode.before.Remove(this);
}

/// <summary>Bidirectionally removes Patch from before dependencies</summary>
/// <param name="beforeNode">Patch to remove</param>
internal void RemoveBeforeDependency(PatchSortingWrapper beforeNode)
void RemoveBeforeDependency(PatchSortingWrapper beforeNode)
{
_ = before.Remove(beforeNode);
_ = beforeNode.after.Remove(this);
}
}

internal class PatchDetailedComparer : IEqualityComparer<Patch>
class PatchDetailedComparer : IEqualityComparer<Patch>
{
public bool Equals(Patch x, Patch y)
{
Expand Down
4 changes: 3 additions & 1 deletion Harmony/Public/Attributes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ public enum HarmonyPatchType
/// <summary>A finalizer</summary>
Finalizer,
/// <summary>A reverse patch</summary>
ReversePatch
ReversePatch,
/// <summary>An infix patch</summary>
Infix
}

/// <summary>Specifies the type of reverse patch</summary>
Expand Down
5 changes: 4 additions & 1 deletion Harmony/Public/Harmony.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,17 @@ public void PatchCategory(Assembly assembly, string category)
/// <param name="postfix">An optional postfix method wrapped in a <see cref="HarmonyMethod"/> object</param>
/// <param name="transpiler">An optional transpiler method wrapped in a <see cref="HarmonyMethod"/> object</param>
/// <param name="finalizer">An optional finalizer method wrapped in a <see cref="HarmonyMethod"/> object</param>
/// <param name="infix">An optional infix method wrapped in a <see cref="HarmonyMethod"/> object</param>
/// <returns>The replacement method that was created to patch the original method</returns>
///
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();
}

Expand Down Expand Up @@ -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));
Expand Down
42 changes: 42 additions & 0 deletions Harmony/Public/InnerFix.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Reflection;

namespace HarmonyLib
{
/// <summary>The base class for InnerPrefix and InnerPostfix</summary>
///
[Serializable]
public abstract partial class InnerFix
{
internal abstract InnerFixType Type { get; set; }

/// <summary>The method call to patch</summary>
///
public InnerMethod InnerMethod { get; set; }

/// <summary>If defined will be used to calculate Target from a given methods content</summary>
///
public Func<IEnumerable<CodeInstruction>, InnerMethod> TargetFinder { get; set; }

internal InnerFix(InnerFixType type, InnerMethod innerMethod)
{
Type = type;
InnerMethod = innerMethod;
TargetFinder = null;
}

/// <summary>Creates an infix for an indirectly defined method call</summary>
/// /// <param name="type">The type of infix</param>
/// <param name="targetFinder">Calculates Target from a given methods content</param>
///
internal InnerFix(InnerFixType type, Func<IEnumerable<CodeInstruction>, InnerMethod> targetFinder)
{
Type = type;
InnerMethod = null;
TargetFinder = targetFinder;
}

internal abstract IEnumerable<CodeInstruction> Apply(MethodBase original, IEnumerable<CodeInstruction> instructions);
}
}
Loading