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.
Basics
To make the rest of this guide easier to follow, let's go over a few basics first.
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.
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.
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
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;
}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
retThe 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;
}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 // formattedIncorporating 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;
}