Skip to content

Valid patch targets

Geoffrey Horsington edited this page Jan 26, 2020 · 2 revisions

Harmony is able to patch different kinds of methods, managed or native. However, there are some important notes on what can and cannot be patched with Harmony. This page outlines some certain method types you need to be aware of.

Managed methods

In general, Harmony is able to patch all managed methods -- that is, methods that are defined within .NET itself.

To patch methods you simply use the HarmonyPatch attribute normally:

// Example of a normal method in a class
class OriginalClass 
{
    void OriginalMethod()
    {
        // Some managed code
    }
}

// Basic Harmony prefix
[HarmonyPatch(typeof(OriginalClass), "OriginalMethod")]
[HarmonyPrefix]
static void Prefix();

Inlined methods

In some cases, the just-in-time (JIT) compiler of the Common Language Runtime (CLR) can replace calls to small methods with the contents of it. As a simple example, consider this code:

int _myInt = 0;
int MyInt 
{
    get // Small method, compiled to `int get_MyInt()`
    {
        return _myInt;
    }
}

void MyMethod()
{
    Console.WriteLine(MyInt); // Compiled to Console.WriteLine(get_MyInt());
}

The getter for MyInt is very small: it essentially simply contains a return of _myInt. Thus, when the JIT tries to compile MyMethod, it will replace the expensive call to get_MyInt directly with the contents of the getter:

// NOTE: This is pseudocode of how inlining works; the actual process is a bit more tricky than that
void MyMethod()
{
    Console.WriteLine(_myInt); // get_MyInt() got inlined and replaced with `_myInt` field directly.
}

As a result, if you were to patch get_MyInt(), your patch would not work because all calls to the method would get inlined.

If your code runs on Mono (e.g. any Unity game at the moment of writing), Harmony automatically disables inlining, provided you patch the short method before it is called anywhere.
On .NET Framework or .NET Core there is no a concrete fix at the moment.

Native methods marked extern

Sometimes you may want to patch extern methods like these:

// Native method from kernel32.dll
[DllImport("kernel32.dll")]
static extern void Sleep(int seconds);

// Internall call (e.g. in Unity)
[MethodImpl(MethodImplOptions.InternalCall)]
extern AsyncResult AsyncLoad();

At the time of writing, Harmony 2 supports only transpiling extern methods without ability to call back the original code.
HarmonyX extends this and allows you to apply prefixes, postfixes, transpilers and finalizers to any method marked with extern as if it was a normal managed method. The syntax for patching an extern is the same as patching a normal managed method: specify the target with HarmonyPatch attribute and the patch type. A simple example:

class MyNativeClass
{
    // A simple internal call
    // Can also be DllImport for a proper extern call
    [MethodImpl(MethodImplOptions.InternalCall)]
    extern SomeSpecialType MyICall();
}

// Prefixes work
[HarmonyPatch(typeof(MyNativeClass), "MyICall")]
[HarmonyPrefix]
static void Prefix();

// Transpilers also work
// Note: because externs have no body, `instrs` will get body of a
// special wrapper code that calls the original native method
[HarmonyPatch(typeof(MyNativeClass), "MyICall")]
[HarmonyTranspiler]
static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instrs);

For a full example, check out the unit tests.

Inlining of externs

At the time of writing this only applies to .NET Core

Just like managed methods, calls to externs can be inlined as well. Because of that, HarmonyX cannot reliably patch externs on code running in .NET Core. However, patching is still possible by using MonoMod's NativeDetour directly. For an example of that refer to MonoMod's NativeDetour unit test.