Skip to content

Prefix changes

Geoffrey Horsington edited this page Sep 30, 2020 · 1 revision

HarmonyX prefix behaviour

In HarmonyX, prefixes have a slightly different behaviour from Harmony in how they are run.

In Harmony, prefixes can be skipped:

Note: The first prefix that returns false will skip all remaining prefixes unless they have no side effects (no return value, no ref arguments) and will skip the original too. Postfixes and Finalizers are not affected.

This is not the case in HarmonyX: by default, all prefixes are run to allow symmetrical patches (e.g. for profiling) without the chicken-egg problem that can come with patch ordering.

Example

Consider the following example:

class SomeTargetClass 
{
    void SomeImportantMethod() { /* Does some important stuff */ }
}


class Patch1
{
    [HarmonyPatch(typeof(SomeTargetClass), "SomeImportantMethod")]
    [HarmonyPrefix]
    static bool Prefix()
    {
        // Skip original
        return false;
    }
}

class Patch2
{
    private static Stopwatch sw;    

    [HarmonyPatch(typeof(SomeTargetClass), "SomeImportantMethod")]
    [HarmonyPrefix]
    static void SomeImportantPrefix()
    {
         sw = new Stopwatch();
         sw.Start();
    }

    [HarmonyPatch(typeof(SomeTargetClass), "SomeImportantMethod")]
    [HarmonyPostfix]
    static void SomeImportantPostfix()
    {
         sw.Stop();
         Console.WriteLine(sw.ElapsedMilliseconds);
    }
}

// In your main code
var instance = new Harmony("tester");
instance.PatchAll(typeof(Patch1)); // First Patch1 which will skip original
instance.PatchAll(typeof(Patch2)); // Patch1 comes before Patch2

Calling SomeImportantMethod will HarmonyX print the execution time of the method (even though it was skipped). In Harmony 2 this would've caused a NullReferenceException as Patch2's prefix would be never run (skipped by Patch1) but postix would be.

What if I want to skip my prefix?

If you need to mimick Harmony 2 behaviour and skip a prefix when another prefix wants to skip the original method, you can add ref bool __runOriginal parameter to your prefix and check that:

static void Prefix(ref bool __runOriginal)
{
    // Skip this prefix if some other prefix wants to skip the original method
    if (!__runOriginal)
        return;
}

You can also use __runOriginal as an alternative to bool return value:

static void Prefix(ref bool __runOriginal)
{
    // Skip running the original method
    __runOriginal = false;
}