Introduction
Harmony changes what .NET methods do while an application is running. Add code before or after a method, change its instructions, or patch a selected operation inside it. The original files stay unchanged.
Prerequisites
Harmony patches CIL, the intermediate language used by .NET and Mono. Your source language does not have to be C#.
The runtime must allow Harmony to generate and execute replacement methods. In Unity, check the scripting backend and target platform. IL2CPP uses ahead-of-time compilation and does not support System.Reflection.Emit; the runtime patching described here requires a compatible managed runtime. See Unity's scripting restrictions.
Bootstrapping and Injection
Harmony does not load your code into an application. You need mod support or a loader to run the few lines that apply your patches. Some loaders include:
Games with built-in mod support, such as RimWorld, can load your assembly themselves.
Dependencies
The standard Lib.Harmony package merges its dependencies into one DLL. Lib.Harmony.Thin keeps them separate. Choose a target framework supported by your application; see Getting Started.
Altering functionality (Patching)
There are two common ways to change an application without its source code:
- Alter DLL files on disk
- Re-point method implementations (hooking)
Editing DLL files has drawbacks:
- it might be blocked by an anti-cheat system
- separate edits can overwrite each other
- it has to be done before and outside the original application
Harmony uses a variation of hooking and focuses only on runtime changes that don't affect files on disk:
- patches from multiple mods can share a method
- supports existing mod loaders
- changes can be made dynamically/conditionally
- the patch order can be flexible
- other mods can be patched too
How Harmony works
Harmony lets you:
- Keep the original method's code
- Execute your code before and/or after it
- Modify its IL instructions
- Combine patches from multiple authors on the same method
See how the patch types fit together before choosing one.
Limits of runtime patching
Harmony can't do everything. Make sure you understand the following:
Harmony rewrites method bodies, including constructors and getters/setters.
Most patches need an IL body. Native methods need a replacement implementation.
Methods that are too small might get inlined and your patches will not run.
You cannot add fields to classes and you cannot extend enums (they get compiled into ints).
Patching generic methods or methods in generic classes is tricky and might not work as expected.
Hello World Example
Original game code:
public class SomeGameClass
{
public bool isRunning;
public int counter;
private int DoSomething()
{
if (isRunning)
{
counter++;
}
return counter * 10;
}
}
Patching with Harmony annotations:
// your code, most likely in your own dll
using HarmonyLib;
using Intro_SomeGame;
public class MyPatcher
{
// make sure DoPatching() is called at start either by
// the mod loader or by your injector
public static void DoPatching()
{
var harmony = new Harmony("com.example.patch");
harmony.PatchAll();
}
}
[HarmonyPatch(typeof(SomeGameClass))]
[HarmonyPatch("DoSomething")] // if possible use nameof() here
class Patch01
{
static AccessTools.FieldRef<SomeGameClass, bool> isRunningRef =
AccessTools.FieldRefAccess<SomeGameClass, bool>("isRunning");
static bool Prefix(SomeGameClass __instance, ref int ___counter)
{
isRunningRef(__instance) = true;
if (___counter > 100)
return false;
___counter = 0;
return true;
}
static void Postfix(ref int __result) => __result *= 2;
}
Alternatively, manual patching with reflection:
// your code, most likely in your own dll
using HarmonyLib;
using Intro_SomeGame;
public class MyPatcher
{
// make sure DoPatching() is called at start either by
// the mod loader or by your injector
public static void DoPatching()
{
var harmony = new Harmony("com.example.patch");
var mOriginal = AccessTools.Method(typeof(SomeGameClass), "DoSomething"); // if possible use nameof() here
var mPrefix = SymbolExtensions.GetMethodInfo(() => MyPrefix());
var mPostfix = SymbolExtensions.GetMethodInfo(() => MyPostfix());
// in general, add null checks here (new HarmonyMethod() does it for you too)
harmony.Patch(mOriginal, new HarmonyMethod(mPrefix), new HarmonyMethod(mPostfix));
}
public static void MyPrefix()
{
// ...
}
public static void MyPostfix()
{
// ...
}
}