You are viewing a potentially older version of this package. View all versions.
DolfeMods-HarmonyPatchExtensions-1.2.1 icon

HarmonyPatchExtensions

Adds mixin like patching to Harmony

Date uploaded 7 hours ago
Version 1.2.1
Download link DolfeMods-HarmonyPatchExtensions-1.2.1.zip
Downloads 17
Dependency string DolfeMods-HarmonyPatchExtensions-1.2.1

This mod requires the following mods to function

BepInEx-BepInExPack-5.4.2305 icon
BepInEx-BepInExPack

BepInEx pack for Mono Unity games. Preconfigured and ready to use.

Preferred version: 5.4.2305

README

Harmony.PatchExtensions

Attribute based extensions for Harmony that lets you define mixin style patches.

Features

  • Attribute based patches
  • Injection points: HEAD (prefix), POSTFIX, RETURN (At every return), INVOKE (insert before call), REDIRECT (replace call), AFTER (after call)
  • Occurrence and start-index targeting
  • Conflict detection
  • Optional wrapper for overwriting prefixes that return the target's return type
  • Patching variable declarations in methods

Install

Use one of the following:

  • Reference the project directly in your solution
  • Install the published NuGet package: (Insert package when uploaded here)
  • If using publicizers add <DoNotPublicize Include="Harmony.PatchExtensions" /> to an ItemGroup so documentations shows up in the code editor

Quick Start

using System.Reflection;
using HarmonyLib;
using HarmonyLib.PatchExtensions;

public static class Program
{
    public static void Main()
    {
        var harmony = new Harmony("example.patching");
        MixinLoader.ApplyPatches(harmony, Assembly.GetExecutingAssembly());
        // Or for using a single patch class 
        // MixinLoader.ApplyPatches(harmony, Assembly.GetExecutingAssembly(), typeof(Patches));
    }
}

Patch Examples

HEAD (prefix)

using HarmonyLib.PatchExtensions;

public static class Patches
{
    [Patch(typeof(Target), "Add", AT.HEAD)]
    private static void AddPrefix(int a, int b)
    {
        // Will run before Target.Add
        a += 1;
    }
}

Result:

public class Target
{
    private int Add(int a, int b) 
    { 
+       a += 1;
        return a + b;
    }
}

HEAD overwrite (skip original)

If overwriting: true, the patch can return bool (Harmony style) or the same return type as the target.

using HarmonyLib.PatchExtensions;

public static class OverwritePatches
{
    [Patch(typeof(Target), "Add", AT.HEAD, overwriting: true)]
    private static int AddOverwrite(int a, int b)
    {
        return 68; // the wrapper sets __result and skips original automatically
    }
}

Result:

public class Target
{
    private int Add(int a, int b) 
    { 
+        return 68;
-        return a + b;
    }
}

POSTFIX (Just harmony postfix)

using HarmonyLib.PatchExtensions;

public static class PostfixPatches
{
    [Patch(typeof(Target), "Add", AT.POSTFIX)]
    private static void AddPostfix(int a, int b, ref int __result)
    {
        __result += 1;
    }
}

Result:

public class Target
{
    private int Add(int a, int b) 
    { 
-        return a + b;
+        __result = a + b;
+        __result += 1;
         return __result;
    }
}

RETURN

using HarmonyLib.PatchExtensions;

public static class ReturnPatches
{
    [Patch(typeof(Target), "Foo", AT.RETURN)]
    private static void AddReturn(int a, int b, bool c, ref int __result)
    {
        __result += 1;
    }
}

Result:

public class Target
{
    public int Foo(int a, int b, bool c)
    {
        if (c)
        {
            __result = a;
+           __result += 1;
            return __result;
        }
        else
        {
            __result = b;
+           __result += 1;
            return __result;
        }
    }
}

INVOKE (insert before call)

using HarmonyLib.PatchExtensions;

public static class InvokePatches
{
    [Patch(typeof(TargetCalls), "Foo", AT.INVOKE, target: "Helper.DoThing", occurrence: 2)] // occurrence: 0 matches all
    private static void BeforeSecondCall()
    {
        // Injected before the 2nd call to Helper.DoThing
    }
}

Result:

public class TargetCalls
{
    private void Foo()
    {
        Helper.DoThing();
+        InvokePatches.BeforeSecondCall();
        Helper.DoThing();
    }
}

REDIRECT (replace call)

using HarmonyLib.PatchExtensions;

public static class RedirectPatches
{
    [Patch(typeof(TargetCalls), "Foo", AT.REDIRECT, target: "Helper.DoThing", occurrence: 0)] // occurrence can also be specified to target
    private static void ReplaceCall()
    {
        // Replaces Helper.DoThing with this method
    }
}

Result:

public class TargetCalls
{
    private void Foo()
    {
-        Helper.DoThing();
-        Helper.DoThing();
+        RedirectPatches.ReplaceCall();
+        RedirectPatches.ReplaceCall();
    }
}

REDIRECT with occurrence

using HarmonyLib.PatchExtensions;

public static class RedirectPatches
{
    [Patch(typeof(TargetCalls), "Foo", AT.REDIRECT, target: "Helper.DoThing", occurrence: 1)] // occurrence can also be specified to target
    private static void ReplaceCall()
    {
        // Replaces Helper.DoThing with this method
    }
}

Result:

public class TargetCalls
{
    private void Foo()
    {
-        Helper.DoThing();
+        RedirectPatches.ReplaceCall();
        Helper.DoThing();
    }
}

AFTER (after call)

using HarmonyLib.PatchExtensions;

public static class AfterPatches
{
    [Patch(typeof(TargetCalls), "Foo", AT.AFTER, target: "Helper.DoThing", occurrence: 2)]
    private static void AfterCall()
    {
        // Puts code after call
    }
}

Result:

public class TargetCalls
{
    private void Foo()
    {
        Helper.DoThing();
        Helper.DoThing();
+       AfterPatches.AfterCall();
    }
}

Conflict Resolution

When multiple patches/transpilers target the same method, set the resolution strategy:

MixinLoader.ConflictResolutionMethod = MixinLoader.ConflictResolver.Warn;

Options:

  • Warn (default): log conflicts and continue
  • Error: throw and stop
  • SkipConflicts: skip conflicting targets

License

Apache-2.0. See LICENSE.md.

CHANGELOG

1.2.1

Updated xml comments Fixed some PatchAttribute logic Updated readme with more examples

1.2.0

WARNING: Changed AT.RETURN to AT.POSTFIX

AT.RETURN now injects the code before every return in a method

Added warning when a mod is referencing an outdated version Internally extracted some classes into their own files Fixed nullref exceptions when using REDIRECT/INVOKE/AFTER with a null target Added signature validation to REDIRECT

1.1.0

Update readme to add a note for publicizers

1.0.0

Uploaded