Skip to content
Geoffrey Horsington edited this page Oct 10, 2021 · 39 revisions

NOTE: This documentation refers to HarmonyX 2.1 and newer.
You can still use the information for Harmony 2 as core API is the same, but note that some of API might be missing.

HarmonyX

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


Where should I start?

This wiki contains information related to HarmonyX -- that is, the extensions we added to Harmony 2. When it comes to base Harmony features, we attempt to keep our API consistent with latest Harmony code. Because of that, you can use official Harmony docs.

As such:

  • If you're looking for base Harmony guide, use official Harmony 2 documentation
  • Use this wiki to check the additions and extensions introduced by HarmonyX. Check the links in the sidebar for all topics.

About HarmonyX

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
  • Have 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

  • Better logging via streamlined API
  • Helper methods for creating patches
  • Easy extendibility to support patching special methods (like IL2CPP methods)
  • Reduced overall complexity and code duplication by using helpers from MonoMod instead of reimplementing them

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

  • Ability to target 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.0

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.

Why another fork of Harmony? What did you change?

NOTE: HarmonyX design is strongly motivated by our needs that arose over time working with BepInEx. While Harmony aims at general runtime patching, our target is to make Harmony "modding-framework-friendly". You might not agree with all our changes, but they proved to be useful enough to warrant a custom fork.

Without a long essay on the history of BepInEx Harmony forks and our needs to integrate it with MonoMod, here are some points:

  • Logging: current Harmony 2 logging system is primitive and not enough for modding frameworks. We added a custom extendable logging system to facilitate custom log levels, lazy logging and multiple log targets.
  • Remove "global patch state": in modding frameworks there is only one source of Harmony. Instead of slowdown of serializing patch state and handle version incompatibilities we prefer assembly shimming.
  • Interop with MonoMod.RuntimeDetours to support modding communities that use it primarily.
  • Remove duplicated code between MonoMod and Harmony where possible: IL parsing, delegate generation should be done by only one library. We use MonoMod.Common to its full capabilities to remove duplicated code from Harmony.
  • Don't hide useful code, allow extendability: most of internal patching code is made public as helpers. Custom patching backends can be implemented to support patching new methods (e.g. native methods and il2cpp methods)
  • Helper methods to make life easier: unpatch current instance, patch specified type, emit call to a delegate, code matching. We've seen too many plugin makers call instance.UnpatchAll thinking it will unpatch just the current instance.

For a more specific explanation of more technical changes, refer to implementation difference outline.