Skip to content
Geoffrey Horsington edited this page Jan 14, 2020 · 39 revisions

HarmonyX

A library for patching, replacing and decorating .NET methods during runtime powered by MonoMod.

If you develop in C# and your code is loaded as a module/plugin into a host application, you can use HarmonyX (later referred to as Harmony) to alter the functionality of all the available assemblies of that application. Where other patch libraries simply allow you to replace the original method, Harmony goes one step further and gives you:

  • A way to keep the original method intact
  • Execute your code before and/or after the original method
  • Modify the original with IL code processors
  • Multiple Harmony patches co-exist and don't conflict with each other

HarmonyX builds on top the original Harmony library, extending and streamlining its features. Most importantly, you get

  • Streamlined exception messages
  • Better logging
  • New Harmony 2 features while keeping compatibility with Harmony 1

At the same time, HarmonyX is based on MonoMod, a robust tool for patching .NET assemblies. With it, you get additional additional features such as

  • A way to patch methods marked with extern
  • Ability to use HarmonyX in environments where dynamic code generation is not supported (e.g. Unity's .NET Standard 2.0 API)
  • Interop with MonoMod patches

Prerequisites

HarmonyX works on any games that support running code in .NET. Currently HarmonyX is built against the following targets:

  • .NET Framework 3.5
  • .NET Framework 4.0
  • .NET Standard 2.1

HarmonyX has been tested mainly on Unity games and has been confirmed to work on all Unity versions (even ones with dynamic code generation disabled).
HarmonyX supports patching on all architectures that MonoMod supports. At the time of the writing, that is x86, x64, ARM, ARM64.

Quick example

This is a quick example of the most basic patch, where Original.RollDice() is patched to simulate the behaviour of the well-known random number generator.

using System;
using HarmonyLib;
using System.Reflection;

namespace HarmonyTest
{
    class Original
    {
        public static int RollDice()
        {
            var random = new Random();
            return random.Next(1, 7); // Roll dice from 1 to 6
        }
    }

    class Main
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"Random roll: {Original.RollDice()}"); // Prints: "Random roll: <some number between 1 and 6>"

            var harmony = new Harmony("io.bepis.harmonyx.example1");
            harmony.PatchAll(typeof(Main));

            Console.WriteLine($"Random roll: {Original.RollDice()}"); // Will always print "Random roll: 4"
        }

        [HarmonyPatch(typeof(Original), "RollDice")] // Specify target method with HarmonyPatch attribute
        [HarmonyPrefix]                              // There are different patch types. Prefix code runs before original code
        static bool RollRealDice(ref int __result)
        {
            // https://xkcd.com/221/
            __result = 4; // The special __result variable allows you to read or change the return value
            return false; // Returning false in prefix patches skips running the original code
        }
    }
}

To start learning how to use Harmony, refer to the next section:

Next: Bootstrapping