Harmony Patching
A common method of altering the behavior of the game is through the Harmony API, and every modder should know how to use it.
Summary
Harmony patching is a way of hooking the games methods and pointing them to different implementations. By writing harmony patches, you are essentially adding code to methods, changing parts of them, or entirely rewriting them.
Harmony patches are quite powerful and are used in a great amount of different mods. There is a lot of detail about patching and you should read the documentation if you ever need to do something specific.
Harmony Setup
There are different methods of setting up your patches as stated here in the documentation. We are simply going to patch all methods marked with the HarmonyPatch
attributes using PatchAll()
:
internal class Plugin
{
private Harmony harmony;
private Assembly executingAssembly = Assembly.GetExecutingAssembly();
[Init]
public Plugin(PluginMetadata pluginMetadata)
{
harmony = new Harmony(pluginMetadata.Id);
}
[OnStart]
public void OnApplicationStart() => harmony.PatchAll(executingAssembly);
[OnExit]
public void OnApplicationQuit() => harmony.UnpatchSelf();
}
Now, any classes and methods with the HarmonyPatch
attribute will be registered as a patch. Once again, if you want more control, there are different methods to do this as stated in the documentation.
Examples
To make understanding harmony patches easier, we will provide some simple examples of the different things you can do.
Postfix
The simplest method of patching involves adding code at the end of a method - a postfix.
- This is very commonly seen and can be used as events to execute code when the game does certain events
- It can be used to reliably get references to objects without having to use expensive methods like
Resources.FindObjectsOfTypeAll
. - They can also change the return result of methods as mentioned in the documentation.
The following patch patches the Init()
method in any NoteController
. The patch gets a reference to the instance of the object by injecting the __instance
variable in the patch params.
Since NoteController
is a type that has many inheritors, we can get what type of note controller it is. If you run this in a map that also has bombs and chains, you will see their types get listed in the logs too.
[HarmonyPatch(typeof(NoteController), "Init")]
public class ExamplePatch
{
public static void Postfix(NoteController __instance)
{
Plugin.Log.Info($"A {__instance.GetType().Name} has been initialized.");
}
}
Prefix
Another common patch, this is very similar to a postfix except it runs before the original method.
- This allows you to decide dynamically decide whether the original implementation should run or not
- Like with a postfix, you can also decide the result yourself
- You can create a state variable that can be passed to a postfix of the same method
The following example patches the RefreshScore()
method in the FlyingScoreEffect
, which is the MonoBehaviour attached to the text that displays your score when you cut a note. We get a reference to the instance, and also the original method params: score
and maxPossibleCutScore
.
The patch is pretty self explanatory, but when you score the max possible score for a note - which is 115 for a normal note - the text will be replaced with Hello World!
, and the original method will be ignored. If the score does not reach the max possible score, then the original method will be called instead.
[HarmonyPatch(typeof(FlyingScoreEffect), "RefreshScore")]
public class ExamplePatch
{
public static bool Prefix(FlyingScoreEffect __instance, int score, int maxPossibleCutScore)
{
if (score >= maxPossibleCutScore)
{
__instance._text.text = "Hello World!";
__instance._colorAMultiplier = 1f;
// Cancel the original method
return false;
}
// Run the original implementation
return true;
}
}
Transpiler
The last commonly used patch we will mention is the transpiler. These are used to modify the CIL code of the game directly. With these, you can make changes in the middle of methods.
This type of patch is much more complicated, and we won't provide an example here (for now), but we can recommend checking out transpilers from other mods. As always, if you want to learn more about transpilers, check the documentation.
Accessing Private Code
When making mods you often will need to alter private
fields, or call private
methods. Thankfully, in C# there are some methods that allow us to do this.
Publicizing Assemblies
The easiest and recommended way to access private
members is by utilizing the BepInEx.AssemblyPublicizer.MSBuild
NuGet package. To add this to your project, do one of the following:
- Navigate to your project dependencies in the assembly explorer, right click it, and select
Manage NuGet Packages
. Then, search for "BepInEx.AssemblyPublicizer.MSBuild", right click it, and select install; - or navigate to the top bar and look for
Tools | NuGet | Manage NuGet Packages for Solution
and search for it there.
Alternatively, you can add it manually in the .csproj
project file manually by adding a PackageReference
:
<PackageReference Include="BepInEx.AssemblyPublicizer.MSBuild" Version="0.4.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Once installed, all you have to do is add the Publicize
property to an assembly reference like this:
<Reference Include="Main" Publicize="true">
<HintPath>$(BeatSaberDir)\Beat Saber_Data\Managed\Main.dll</HintPath>
<Private>false</Private>
</Reference>
Now, anything that was private
or protected
will be seen as public
by the compiler, allowing you to bypass this restriction.
The only restriction you will run in to now is with readonly
fields and auto-computed properties (see below). If you want to set the value of these, you will have to use reflection.
public readonly float _field;
public float Property { get; }
IMPORTANT
Do not use the assembly publicizer to publicize other mods. This can cause some problems with the mod loader. Instead, use reflection or make a request to the mod's maintainer to add a change if you need it.
Reflection
Reflection is a special feature of C# that lets you read code at runtime by making types into objects which you can access members from. There is a lot you can do with reflection, and something that it is commonly used for is checking if certain parts of another mod's code are running without actually having to reference that mod's assembly.
If you want to read more about reflection you can check Microsoft's docs.
IPA provides some utilities to use reflection to get and set values of members, and invoke methods, even if they are private.
using IPA.Utilities;
Now we can use the ReflectionUtil
class, which provides a couple extension methods to pretty easily access private members of an object.
public class SomeClass
{
private float someValue = 0.25f;
public float SomeValue => someValue;
}
Let's say we had a reference to an object of type SomeClass
, we can access and set the private field by using SetField
, this works even when the field is readonly
.
var someClass = new SomeClass();
someClass.SetField("someValue", 0.5f);
NOTE
If you are just reading the values of members, accessing methods, or setting the values of non-readonly fields and properties, you should use the Assembly Publicizer, because it is easier to read, is faster, and creates less garbage.
If you must set a field often or repetitively with reflection, you should use the FieldAccessor
to reduce the performance cost. You create the accessor by providing the type of the object the field is on, and the backing type of the field, as well as the name of the field itself.
private static FieldAccessor<SomeClass, float>.Accessor SomeValueAccessor { get; } =
FieldAccessor<SomeClass, float>.GetAccessor("someValue");
private SomeClass someClass = new();
public void SomeMethod()
{
SomeValueAccessor(ref someClass) = 1f;
}