Harmony patching basics
Harmony lets a mod run code before or after an existing method without replacing the game's assembly. BepInEx includes HarmonyX, which uses the same patch attributes shown in most Harmony documentation.
This example changes the popup shown when the player receives an item or upgrade. The popup title gets a [MODDED] prefix and turns magenta, so the patch is easy to spot in-game.
The game method
The decompiled UpgradePopup class contains this overload of Setup:
public void Setup(PickupInfo info, bool start = true)
{
Setup(
info.reference,
info.name,
info.subtitle,
info.parentName,
info.icon,
info.color,
info.rarityColor,
info.iconColor,
info.colorIcon,
info.rarity,
info.playSounds,
info.isNew,
!info.disableEffects,
start
);
}The method copies values from PickupInfo into the popup. A prefix can change that argument before the original method reads it.
Apply the patches
Add the Harmony import to Plugin.cs:
using HarmonyLib;Create a Harmony instance in Awake, then ask it to find the patch classes in your mod:
private Harmony _harmony;
private void Awake()
{
Logger = base.Logger;
_harmony = new Harmony(MyPluginInfo.PLUGIN_GUID);
_harmony.PatchAll();
Logger.LogInfo("Harmony patches applied");
}
private void OnDestroy()
{
_harmony?.UnpatchSelf();
}The Harmony ID should be unique, so the plugin GUID is a good choice. PatchAll searches the mod assembly for classes marked with HarmonyPatch.
Change the pickup popup
Create UpgradePopupPatch.cs next to Plugin.cs:
using System;
using HarmonyLib;
using UnityEngine;
namespace MycopunkModTemplate;
[HarmonyPatch(
typeof(UpgradePopup),
nameof(UpgradePopup.Setup),
new Type[] { typeof(PickupInfo), typeof(bool) }
)]
internal static class UpgradePopupPatch
{
[HarmonyPrefix]
private static void Prefix(ref PickupInfo info)
{
info.name = $"[MODDED] {info.name}";
info.color = Color.magenta;
}
}Change the namespace if you renamed it during project setup. Build the plugin, replace the installed DLL, and launch the game. The next item or upgrade popup should have a magenta title beginning with [MODDED].
The ref keyword matters because PickupInfo is a struct. Without it, the prefix would only edit its own copy and the game would receive the original values.
The parameter types in HarmonyPatch select the correct Setup overload. UpgradePopup has another method with the same name, so specifying the types prevents Harmony from patching the wrong one.
Prefixes and postfixes
A prefix runs before the original method. It can inspect arguments or change arguments passed by reference, as the popup example does.
A postfix runs after the original method:
[HarmonyPostfix]
private static void Postfix()
{
Plugin.Logger.LogDebug("UpgradePopup.Setup finished");
}Patch methods can receive several useful values:
__instanceis the object whose method was called.- Parameters with the same names as the original method receive its arguments.
ref __resultcan read or replace a method's return value in a postfix.
Only request values you need. If a patch has the wrong parameter type or name, Harmony reports an error in LogOutput.log.
Avoiding compatibility problems
Prefer a postfix or a small prefix over replacing the whole method. A prefix that returns false skips the original method, which can break the game or conflict with other mods.
Keep each patch focused on one method, and do not patch a frequently called method just to write logs. After a game update, check the target method again in the decompiler and test the mod without other plugins installed.
For more detail, see the BepInEx runtime patching guide and the Harmony documentation.