Skip to content

Patching with Harmony

Geoffrey Horsington edited this page Jan 27, 2021 · 8 revisions

Attribute-based patching for types

By default, Harmony provides two ways to apply a patch: attribute-based targeting an assembly via PatchAll(Assembly) overloads and manual via Patch overloads. In some cases these options are not enough because

  • the assembly is too big to be iterated at an acceptable speed with PatchAll;
  • PatchAll triggers type resolving for all types inside an assembly, which can in turn trigger unnecessary assembly resolving. This is especially unwanted in cases where you want to control when assemblies get loaded (for example in case of BepInEx preloader patchers);
  • Patch is too verbose.

In these cases you can make use of attribute-based patching targeting a type. This is a new HarmonyX addition which allows you to instead selectively apply all patches defined in a single type. Call Harmony.PatchAll and pass a type that contains methods annotated with HarmonyPatch:

harmony.PatchAll(typeof(Patches));

This will search a type for all methods annotated with HarmonyPatch and other Harmony annotations and will apply patches to the targeted methods accordingly. This method is recommended for small patches or in cases where you need to selectively apply a large number of patches.

Creating an instance and patching methods

Most of the cases you'll want to simply create an instance, call PatchAll and be done with patching. For that, HarmonyX provides helper methods to both create an instance and apply a patch:

Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly());
// or
Harmony.CreateAndPatchAll(typeof(Patch));

Note that you don't have to specify an ID: if you specify none, HarmonyX generates a unique one for you.

Note: Having HarmonyX generate an ID means that other patches can't easily unpatch or reorder your patches.
On the other hand, our experience with BepInEx showed that relying on patch ordering is unreliable at best and at worst inducing almost undebuggable errors.

Helper methods all return the created harmony instance so you can further apply additional patches.

Unpatching all methods of an instance

In many cases you may want to unpatch all methods you patch with your Harmony instance. However, using Unpatch to unpatch methods one-by-one is slow and UnpatchAll has an often-forgotten default behaviour of unpatching EVERYTHING. We found over time that many developers forget to specify the ID of the instance to UnpatchAll and instead end up unpatching all instances.

To unpatch just the current instance, you can use UnpatchSelf:

// equivalent to instance.UnpatchAll(instance.Id);
instance.UnpatchSelf();

This method eliminates all possible mistakes of accidentally (and understandably) forgetting the parameter.