Harmony Patching Guide

Harmony is a tool that allows to alter the functionality in C# applications. This allows to modify the application without needing to recompile it.

Note: This guide breaks things down in a simpler way. For more detailed info, check out the official documentation.

Basics

To make the rest of this guide easier to follow, let's go over a few basics first.

Note: If you'd like to dive deeper into the fundamentals of Harmony, check out the official basics article.

Creating patches

To create a patch, you need to write code similar to the following:

[HarmonyPatch(typeof(OriginalClass))]
internal class OriginalClass_Patches
{
    // Content to be followed
}

Grouping patches by type improves readability and maintainability by keeping the code organized and structured.

Warning: The following example demonstrates one approach to creating patches based on my experience. Other valid methods exist as well.

Applying patches

For patches to work correctly, they need to be applied first. There are several ways to do this, but the example below is the cleanest and most reliable approach I've found:

var harmony = new HarmonyLib.Harmony("com.company.project.product");

harmony.PatchAll(typeof(Patches.OriginalClass_Patches));
harmony.PatchAll(typeof(Patches.OriginalClass2_Patches));
harmony.PatchAll(typeof(Patches.OriginalClass3_Patches));

If your project doesn't involve patching dependencies, you can simplify things by using harmony.PatchAll() without any arguments. It will automatically detect and apply all available patches in the project.

While applying all patches at once is convenient, I find that applying them by type gives more control — especially when dealing with dependencies:

var harmony = new HarmonyLib.Harmony("com.company.project.product");

harmony.PatchAll(typeof(Patches.OriginalClass_Patches));
harmony.PatchAll(typeof(Patches.OriginalClass2_Patches));

if (Dependency.ModTest.Enabled)
{
    harmony.PatchAll(typeof(Patches.ModdedClass_Patches));
    harmony.PatchAll(typeof(Patches.ModdedClass2_Patches));
}

Types of Patching

Harmony has a lot of different tools to modify the application. However, I will be focusing on 3 types:

  • Prefix
  • Postfix
  • Transpiler

In my opinion, they are the most important ones that you will be using while you mod any C# application.

Note: If you'd like to dive deeper into either types, check out the official article about prefixes, postfixes and transpilers.
Note: In the examples, the original method is: int OriginalMethod(int number, string format)

Prefix

A prefix is a method that runs before the original method. It's great for running early logic or skipping the original method entirely. Here's a basic example:

[HarmonyPatch(nameof(OriginalClass.OriginalMethod)), HarmonyPrefix]
private static void OriginalMethod_Prefix()
{
    // Custom logic before the original method runs
}

If you need access to the object instance or the method's arguments, just include them in the prefix method's parameters:

[HarmonyPatch(nameof(OriginalClass.OriginalMethod)), HarmonyPrefix]
private static void OriginalMethod_Prefix(OriginalClass __instance, int number, string format)
{
    // Use __instance or modify arguments before the original method
}

You can also control whether the original method runs at all. By returning false, the original method will be skipped entirely:

[HarmonyPatch(nameof(OriginalClass.OriginalMethod)), HarmonyPrefix]
private static bool OriginalMethod_Prefix()
{
    // Skip the original method
    return false;
}

Postfix

A postfix is a method that runs after the original method has completed. It's great for running follow-up logic or modifying the method's result. Here's a basic example:

[HarmonyPatch(nameof(OriginalClass.OriginalMethod)), HarmonyPostfix]
private static void OriginalMethod_Postfix()
{
    // Custom logic after the original method runs
}

You can also access the return value using the ref keyword with __result to modify what the original method returns:

[HarmonyPatch(nameof(OriginalClass.OriginalMethod)), HarmonyPostfix]
private static void OriginalMethod_Postfix(ref int __result)
{
    __result += 10; // Modify the return value
}

Transpiler

Warning: Use a transpiler only when simpler patching methods are insufficient. This technique requires a good understanding of IL (Intermediate Language) code.

A transpiler modifies a method by injecting instructions directly into its compiled IL code. This offers precise control over how and where logic is inserted. This is used in scenarios that cannot be handled with other patch types.

Here's a basic template for a transpiler:

[HarmonyPatch(nameof(OriginalClass.OriginalMethod)), HarmonyTranspiler]
private static IEnumerable<CodeInstruction> OriginalMethod_Transpiler(IEnumerable<CodeInstruction> instructions)
{
    var code = new List<CodeInstruction>(instructions);

    var insertionIndex = -1;

    for (var i = 0; i < code.Count; i++)
    {
        var instruction = code[i];

        // TODO: Identify the appropriate insertion point

        insertionIndex = i;
        break;
    }

    if (insertionIndex == -1)
        return code;

    var injected = new CodeInstruction[]
    {
        // TODO: IL instructions to insert
    };

    code.InsertRange(insertionIndex, injected);

    return code;
}

Let's use the following method as a reference:

public int OriginalMethod(int number, string format)
{
    var formatted = number.ToString(format);

    // Line to add:
    // formatted = "|" + formatted + "|";

    System.Console.WriteLine(formatted);
    return number;
}
Note: To analyze IL code, tools like Rider's IL Viewer or dnSpy are highly recommended. These tools help visualize compiled IL instructions, making it easier to pinpoint insertion points.

To determine where to inject your code, you will need to inspect the IL output of the method and identify a unique, reliable pattern of opcodes that appear at the desired location.

For our example, the IL looks like this:

nop
ldarga.s     number
ldarg.2      // format
call         instance string [mscorlib]System.Int32::ToString(string)
stloc.0      // formatted
ldloc.0      // formatted
call         void [mscorlib]System.Console::WriteLine(string)
nop
ldarg.1      // number
stloc.1      // V_1
br.s         IL_0015
ldloc.1      // V_1
ret

The instruction ldloc.0 is a good marker. It appears just before the call to Console.WriteLine, which is where we want to inject our logic. We can then go through every instruction, looking for the desired pattern.

var insertionIndex = -1;

for (var i = 0; i < code.Count - 1; i++)
{
    var instruction = code[i + 1];

    if (instruction.opcode != OpCodes.Ldloc_0)
        continue;

    insertionIndex = i;
    break;
}
Note: For more precise targeting, consider matching a sequence of instructions—for example, ldloc.0 followed by call. This reduces the risk of injecting at the wrong place if similar opcodes appear elsewhere in the method.

To determine the exact IL instructions to inject, you can write the desired logic in a separate dummy method, then examine its IL output. For our target modification, the IL looks like this:

ldstr        "|"
ldloc.0      // formatted
ldstr        "|"
call         string [mscorlib]System.String::Concat(string, string, string)
stloc.0      // formatted

Incorporating these instructions into the transpiler gives us the final implementation:

[HarmonyPatch(nameof(OriginalClass.OriginalMethod)), HarmonyTranspiler]
private static IEnumerable<CodeInstruction> OriginalMethod_Transpiler(IEnumerable<CodeInstruction> instructions)
{
    var code = new List<CodeInstruction>(instructions);

    var insertionIndex = -1;

    for (var i = 0; i < code.Count - 1; i++)
    {
        var instruction = code[i + 1];

        if (instruction.opcode != OpCodes.Ldloc_0)
            continue;

        insertionIndex = i;
        break;
    }

    if (insertionIndex == -1)
        return code;

    var injected = new CodeInstruction[]
    {
        new(OpCodes.Ldstr, "|"),
        new(OpCodes.Ldloc_0),
        new(OpCodes.Ldstr, "|"),
        new(OpCodes.Call, AccessTools.Method(
            typeof(string),
            nameof(string.Concat),
            new[] { typeof(string), typeof(string), typeof(string) }
        )),
        new(OpCodes.Stloc_0)
    };

    code.InsertRange(insertionIndex, injected);

    return code;
}
Note: For more complex logic, it is recommended to inject instructions that call a separate method containing your code. This approach is simpler and more maintainable than injecting each instruction individually.