Skip to content

Class patches

Geoffrey Horsington edited this page Jan 22, 2020 · 5 revisions

Class patches work by applying HarmonyPatch attribute to a class.

Basic example

Here's an example of a class patch:

public class OriginalType
{
    public static void TargetMethod()
    {
        Console.WriteLine("Target method!");
    }
}


// Specifies the class to be a patch that targets OriginalType.TargetMethod
[HarmonyPatch(typeof(OriginalType), "TargetMethod")]
public class MyPatch
{
    public static void Prepare()
    {
        // Executed before patching
    }
    
    public static void Prefix()
    {
        // Code to run at the start of the original method
        Console.WriteLine("Hello");
    }
    
    public static void Postfix()
    {
        // Code to run after the original method
        Console.WriteLine("Bye");
    }
    
    public static void Cleanup()
    {
        // Executed after patching
    }
}

Class patches are applied by calling Harmony.PatchAll(Assembly) to which you pass the assembly that contains your patch class. After you apply the patch, calling OriginalType.TargetMethod will print

Hello
Target method!
Bye

Patch methods

Class patch provides various methods to control patching and lifetime of Harmony patches. Note that all methods are optional. Only define what you need.

Here is a list of all methods supported by class patches.

Note about syntax: the [] brackets inside the method arguments denote optional arguments.

TargetMethod and TargetMethods

// Patch single method
static MethodBase TargetMethod([Harmony instance])
// or
[HarmonyTargetMethod]
static MethodBase CalculateMethod([Harmony instance])

// Patch multiple methods
static IEnumerable<MethodBase> TargetMethods([Harmony instance])
// or
[HarmonyTargetMethods]
static IEnumerable<MethodBase> CalculateMethods([Harmony instance])

Most of the times, you will use a combination of HarmonyPatch annotations on the class to define the method you want to patch. Sometimes it is necessary to calculate the method with code (i.e. generic method, dynamic method). You can use TargetMethod for this.

TargetMethod must return a method that should be patched by this class. You can use AccessTools or plain reflection to locate the method.

TargetMethods (note the s) allows you to specify multiple methods to patch at once.

Prepare

static bool Prepare([MethodBase original, Harmony instance])
// or
[HarmonyPrepare]
static bool MyPrepare([MethodBase original, Harmony instance])

Before the patching, Harmony gives you a chance to prepare your state. If prepare method exists, it is expected to return a boolean that controls if patching will happen. If Prepare returns true, the patch should be applied. If returned false, Harmony skips applying the patch specified by the patch class.

Cleanup

static void Cleanup([MethodBase original, Harmony instance])
// or
[HarmonyCleanup]
static void MyCleanup([MethodBase original, Harmony instance])

After patching, Harmony can call this cleanup method. Harmony calls this method after every single target method has been patched.

Prefix

static (void|bool) Prefix([...])
// or
[HarmonyPrefix]
static (void|bool) MyPrefix([...])

This method defines the code that is executed before the original method. Prefixes can optionally return a boolean: if prefix returns true, the original method code is executed; if prefix returns false, the original method code is skipped.

Difference between Harmony and HarmonyX: HarmonyX will run all prefixes even if one of them returns false. Refer to differences from Harmony for more info.

Prefixes can accept various parameters. Refer to Patch parameters for a list of all supported patch parameters.

Postfix

// Normal prefix (behaves like prefix)
static void Postfix([...])
// or
[HarmonyPostfix]
static void MyPostfix([...])

// Passthrough prefix (receives return value as first parameter, returns new return value)
static T Postfix([T result, ...])
// or
[HarmonyPostfix]
static T MyPostfix([T result, ...])

This method defines the code that is executed after the original method. All postfixes always get executed even if the original method code was skipped.

There are two types of postfixes: normal and passthrough. Normal postfixes have return type void and behave like prefixes.
Passthrough postfixes have a return value and an optional return argument the types of which are the same target method return value. Passthrough postfixes receive the return value of the original method and must return either the same value or a modified one.

Postfixes can accept various parameters. Refer to Patch parameters for a list of all supported patch parameters.

Finalizer

// Rethrowing finalizer
static void Finalizer([...])
// or
[HarmonyFinalizer]
static void MyFinalizer([...])

// Throwing finalizer
static Exception Finalizer([...])
// or
[HarmonyFinalizer]
static Exception MyFinalizer([...])

Finalizers allow you to catch, throw and suppress exceptions in a method. Finalizers run before postfixes and only if original method wasn't skipped. Finalizers also run immediately if an exception occurs inside the original method.

Finalizers can accept various parameters. Refer to Patch parameters for a list of all supported patch parameters.

Finalizers are an advanced topic. Refer to Finalizers guide for more info.

Transpiler

static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instr[, ...])
// or
[HarmonyTranspiler]
static IEnumerable<CodeInstruction> MyTranspiler(IEnumerable<CodeInstruction> instr[, ...])

This method defines the transpiler that modifies the code of the original method. Use this in the advanced case where you want to modify the original methods IL codes.

This method must always have at least one parameter of type IEnumerable<CodeInstruction> that will receive a list of IL instructions of the original methods. The transpiler must return a new IEnumerable<CodeInstruction> that will be used as the modified code of the original method.

Transpilers are an advanced topic. Refer to Transpilers guide for more info.

ReversePatch

[HarmonyReversePatch]
T OriginalMethod(...)

Reverse patches allow you to copy any target method into the annotated one. This allows you to call private methods or unpatched methods without the penalty of reflection.

Reverse patchers are an advanced topic. Refer to Reverse patcher guide for more info.