Patching
Patching allows you to run code before and after base-game methods. It also allows you to completely replace base-game methods.
Decompiling The Game
To examine base-game methods and figure out what you need to patch, we recommend you use DnSpy, a tool for decompiling and viewing .NET assemblies.
Once installed, select File -> Open. Then, navigate to the game's folder and open the Assembly-CSharp.dll
file located in OuterWilds_Data/Managed
.
You should now see the assembly loaded on the left, most classes in Outer Wilds' assembly do not have a namespace, so they'll be located under a -
Here are some tips for viewing the base game code:
- If you ever need to find out where a method, class, or variable is used, you can right-click it and select "Analyze".
- You can jump to the definition of a method, class, or variable by clicking on it, you can also control-click to open it in a new tab.
- Sometimes the decompiler treats switch statements strangely, it may look like they're simply chaining else ifs when in reality they're using a switch
Preparing For Patches
To prepare for adding patches to our mod we'll do two things:
- Use Harmony.CreateAndPatchAll to make the patches actually do something
- Implement the singleton pattern on our ModBehaviour to make accessing ModHelper outside of it easier.
CreateAndPatchAll
To make our patches run, we need to execute Harmony.CreateAndPatchAll
in our Awake
method (technically it doesn't need to be in Awake
but you should try to run it as early as possible)
public class MyCoolMod : ModBehaviour {
public void Awake() {
Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly());
}
}
And that's it! Now our patches will be applied
Using A Singleton
Throughout your patches you'll likely want to access ModHelper
; however, since this is a member of your mod's class and not the patches class you won't be able to.
To remedy this, you can create a public field on your mod class called Instance
and assign that field inside of Awake
.
public class MyCoolMod : ModBehaviour {
public static MyCoolMod Instance;
public void Awake() {
Instance = this;
}
}
Now, whenever you want to access your ModHelper you can simply use MyCoolMod.Instance.ModHelper
.
Patch Classes
For organization purposes, it's recommended to separate your patches into another class, this class will also need the HarmonyPatch
attribute on it.
[HarmonyPatch]
public class MyPatchClass {
}
Prefixes
A prefix is code that runs before another method.
To create a prefix, add a new method with the HarmonyPrefix
attribute.
Then, add the HarmonyPatch
attribute as well, this attribute takes at least two arguments, the first is the type you want to patch, and the second is the name of the method you wish to patch.
Let's say I want to log to the console every time the player dies, to do so I need to prefix DeathManager.KillPlayer
, so my patch might look something like this:
[HarmonyPatch]
public class MyPatchClass {
[HarmonyPrefix]
[HarmonyPatch(typeof(DeathManager), nameof(DeathManager.KillPlayer))]
public static void DeathManager_KillPlayer_Prefix() {
MyCoolMod.Instance.ModHelper.Console.WriteLine("The player has died! oh no!");
}
}
Warning
All patch methods must be static!
Overwriting Methods With Prefixes
Prefixes also allow you to completely stop the original method from running, to do so, make your method return a bool.
- If the boolean returned from the method is
true
the original method is still run - If the boolean returned from the method is
false
the original method will be skipped
Let's say instead of simply logging when the player dies, we want to prevent it from happening entirely.
[HarmonyPatch]
public class MyPatchClass {
[HarmonyPrefix]
[HarmonyPatch(typeof(DeathManager), nameof(DeathManager.KillPlayer))]
public static bool DeathManager_KillPlayer_Prefix() {
MyCoolMod.Instance.ModHelper.Console.WriteLine("The hatchling would have died, but my cool mod saved them!");
return false;
}
}
Postfixes
A postfix is code that runs after another method.
To create a postfix, add a new method with the HarmonyPostfix
attribute.
Also add the HarmonyPatch
attribute, it functions the same as it does with prefixes, you need to provide the type to patch and the name of the method to patch.
We'll use DeathManager.KillPlayer
as an example again:
[HarmonyPatch]
public class MyPatchClass {
[HarmonyPostfix]
[HarmonyPatch(typeof(DeathManager), nameof(DeathManager.KillPlayer))]
public static void DeathManager_KillPlayer_Postfix() {
MyCoolMod.Instance.ModHelper.Console.WriteLine("The player has died! oh no!");
}
}
Advanced Patching
These next few sections will apply to both Prefixes and Postfixes, so even if the examples use one, know that they will work for the other as well.
Getting The Object You're Patching
If you want to be able to access the actual object you're patching you can make your patch take an argument named __instance
.
Let's say I want to log to the console where the Quantum Moon goes to every time it's observed:
[HarmonyPatch]
public class MyPatchClass {
[HarmonyPostfix]
[HarmonyPatch(typeof(QuantumMoon), nameof(QuantumMoon.ChangeQuantumState))]
public static void QuantumMoon_ChangeQuantumState_Postfix(QuantumMoon __instance) {
MyCoolMod.Instance.ModHelper.Console.WriteLine($"The quantum moon is now at state index: {__instance._stateIndex}!");
}
}
How can we access that private field?
We publicize all base-game assemblies for you, meaning you can get and set private fields and call private methods on base-game classes. This is done in the Outer Wilds game libs package
Getting The Arguments Passed
If you need to get the arguments that were passed to a method inside your patch, you can simply have your method take arguments of the same name.
Going back to our DeathManager example, let's say I want to say the reason the player died as well:
[HarmonyPatch]
public class MyPatchClass {
[HarmonyPrefix]
[HarmonyPatch(typeof(DeathManager), nameof(DeathManager.KillPlayer))]
public static void DeathManager_KillPlayer_Prefix(DeathType deathType) {
MyCoolMod.Instance.ModHelper.Console.WriteLine($"The player died because of: {deathType}!");
}
}
Overriding The Return Value
If you need to override what value the method returns, you can make your method accept an argument named __result
by ref.
So if I wanted to make all cards in the ship log green I would do
[HarmonyPatch]
public class MyPatchClass {
[HarmonyPrefix]
[HarmonyPatch(typeof(UIStyleManager), nameof(UIStyleManager.GetCuriosityColor))]
public static bool UIStyleManager_GetCuriosityColor(ref Color __result) {
__result = Color.green;
return false;
}
}
Patching Properties
You can patch the getters and setters of properties by passing in a third argument to the HarmonyPatch
attribute of MethodType.Getter
and MethodType.Setter
respectively.
For example, if I wanted to log the repair fraction every time ShipComponent.repairFraction is gotten, I would do:
[HarmonyPatch]
public class MyPatchClass {
[HarmonyPostfix]
[HarmonyPatch(typeof(ShipComponent), nameof(ShipComponent.repairFraction), MethodType.Getter)]
public static void ShipComponent_RepairFraction_Get(ref float __result) {
MyCoolMod.Instance.ModHelper.Console.WriteLine($"This component is at {__result * 100}%!");
}
}
Patching Overloads
When you want to patch a specific overload of a method, you need to pass the types of the parameters that overload takes as a Type[]
as the third argument to HarmonyPatch
.
For example, if I wanted to log ReferenceFrameTracker.UntargetReferenceFrame
but only the overload that takes a single bool
, I would do:
[HarmonyPatch]
public class MyPatchClass {
[HarmonyPrefix]
[HarmonyPatch(typeof(ReferenceFrameTracker), nameof(ReferenceFrameTracker.UntargetReferenceFrame), new System.Type[] { typeof(bool) })]
public static void ReferenceFrameTracker_UntargetReferenceFrame(bool playAudio)
{
MyCoolMod.Instance.ModHelper.Console.WriteLine($"playAudio is {playAudio}!");
}
}
Further Reading
This about covers all the basics of patching. However, this is just the tip of the iceberg, to learn more, check out both the Harmony and HarmonyX docs.