Harmony FAQ - PULSAR-Modders/pulsar-mod-loader GitHub Wiki
Harmony
is the recommended library for modding the game's code. With a few well placed "patches", a plugin can add or change just about anything.
Harmony provides a clean API for using, replacing, or adding onto PULSAR's existing C# via non-destructive hooking. It even helps prioritize or override patches of the same methods, just in case two mods don't play well by default.
Basic Features
Read the official documentation for more, but here are features most commonly used in plugins:
Patching
See also: patching; transpiler tutorial
Patching is modifying the original code to do extra things, like calling a plugin's code from the main game loop to add new things, or changing the outcome of some method.
Prefix
(run before original) and Postfix
(run after original) are often enough, but there are more options like Prepare
(setup before patching) and Transpiler
(modify original method's bytecode/change the middle of a method; see tutorial). These Harmony methods can take many optional parameters that grant access to things like the current instance (think this
), return value, original arguments, and even (private) fields using triple underscore notation. ref
can also be used to write back to those variables (e.g., ref int count
).
Private members are not immediately accessible in these methods' scopes, but can use the ___tripleUnderscore
notation from above or reflection to gain access.
Reflection
See also: utilities
Reflection essentially allows looking into and modify types at runtime, regardless of access modifiers. This means plugins can use private fields and methods when extending existing types, which wouldn't be possible normally.
Harmony provides a few utilities to simplify reflections, namely AccessTools
and Traverse
. These take care of boilerplate and help keep plugin code readable when reflecting on types.
Prioritization
See also: priority annotations
Patches can be prioritized to ensure multiple patches of the same method run in a certain order, even between two "competing" plugins. Prioritization can be done numerically, or using another plugin's HarmonyIdentifier
to guarantee code always runs before/after/other relation to that plugin's code.
Prioritization can be very useful when depending on another plugin's code to run first, such as library-like plugins.
Usage Examples
Prefixing and Postfixing Methods
// Patch SampleType.SomeMethodName()
[HarmonyPatch(typeof(SampleType))]
[HarmonyPatch("SomeMethodName")]
class ExamplePatches
{
static void Prefix()
{
Console.WriteLine("This line appears BEFORE SomeMethodName() runs.")
}
static void Postfix()
{
Console.WriteLine("This line appears AFTER SomeMethodName() runs.")
}
}
Patching Constructors
Note that the official documentation is out of date when it comes to constructors. Instead of just specifying the target class and argument types, MethodType.Constructor
is also necessary:
[HarmonyPatch(typeof(SampleType), MethodType.Constructor)]
class MyPatchClass
{
static void Prefix(...) { ... }
}
Additionally, Harmony 1.2+ won't patch inherited constructors since they technically don't belong to the target class. For example, almost all Unity game object scripts inherit from MonoBehaviour
and make use of Awake()
and Start()
instead of a traditional constructor. Therefore, these classes' constructors can't be easily patched.
Parameterizing Patches
Given a class and method like this:
class SampleType
{
private string SecretName = "Dog";
public void SomeMethodName(int count)
{
// Some Method Body
}
}
The patch's parameter list can be filled out to access various things:
[HarmonyPatch(typeof(SampleType))]
[HarmonyPatch("SomeMethodName")]
class ExamplePatches
{
static void Prefix(SampleType __instance, ref string ___SecretName, int count)
{
// `__instance` is the actual `SampleType` object we're working with,
// but from a public perspective so we can't just do `__instance.SecretName`.
// `___SecretName` refers to this instance's `SecretName` field despite being
// private due to some reflection magic. We can even change the value and
// have it propagate back into the object thanks to using `ref`.
// `count` is the same value passed to `SomeMethodName()` above.
}
}
Intercepting and Changing Return Values
Given a method with a return value:
public int SomeMethodName() // returns `int`
We can modify the return value by parameterizing a variable named __result
that matches the return type (here, int
). This example uses a postfix, so __result
will be the original method's actual return value. Prefixes can also get __result
, but it will contain the type's default value since the original method hasn't run at that point.
Modifying the return value during a postfix is probably most common, but setting it in a prefix can be useful when preventing the original method from running (read: replacing original method entirely).
[HarmonyPatch(typeof(SampleType))]
[HarmonyPatch("SomeMethodName")]
class ExamplePatches
{
static void Postfix(ref int __result)
{
__result = 10;
}
}