RogueLibs' logo

Patching Utilities

RogueLibs provides several utilities to help you with patching. Whether you use Harmony's attributes, Harmony instances directly, RogueLibs' stuff or something else, is your choice. All of them have their own pros and cons. You can learn more about Harmony here.

RoguePatcher

RoguePatcher is a small helper class that makes writing patches a little bit faster and easier. If you need more control (patch order, priority, etc.), then you should use the original Harmony methods.

/MyAwesomeProject/MyAwesomePlugin.cs
RoguePatcher patcher = new RoguePatcher(this);patcher.Postfix(typeof(StatusEffects), nameof(StatusEffects.hasStatusEffect));patcher.Postfix(typeof(InvDatabase), nameof(InvDatabase.ChooseArmor), new Type[1] { typeof(string) });
Pro-tip: Use nameof keyword

Instead of specifying method names using strings, you should specify them using the nameof keyword. Use string names only if the method you're trying to patch is not public.

Patch methods should have the following name: <TargetType>_<TargetMethod>. In the example above, RoguePatcher will search for patch methods called StatusEffects_hasStatusEffect and InvDatabase_ChooseArmor in your plugin's class.

Pro-tip: Parameter types are optional

If you have several methods with the same name, but different parameter types, RoguePatcher will choose the one with the most parameters. That would usually be the correct way of patching the methods in the game.

You can change the type to search patch methods in. Specify it in the constructor or set the property between patches:

/MyAwesomeProject/MyAwesomePlugin.cs
public class MyAwesomePlugin : BaseUnityPlugin{    public void Awake()    {        RoguePatcher patcher = new RoguePatcher(this, typeof(MyAwesomePatches));        patcher.Postfix(typeof(StatusEffects), nameof(StatusEffects.hasStatusEffect));        patcher.TypeWithPatches = typeof(MyEvenMoreAwesomePatches);        patcher.Postfix(typeof(InvDatabase), nameof(InvDatabase.ChooseArmor), new Type[1] { typeof(string) });    }}public class MyAwesomePatches{    public static void StatusEffects_hasStatusEffect(StatusEffects __instance)    {        /* ... */    }}public class MyEvenMoreAwesomePatches{    public static void InvDatabase_ChooseArmor(InvDatabase __instance, string previousArmorName)    {        /* ... */    }}

Transpiler helper methods

Transpilers are kind of complicated, since they use a low-level Intermediate Language (IL) instead of C# (branches, loops and conditions are the hardest part in here). As an example, here's one of the simplest transpilers from RogueLibs:

/RogueLibsCore/Patches/Patches_TraitsStatusEffects.cs
public static IEnumerable<CodeInstruction> StatusEffects_AddStatusEffect(IEnumerable<CodeInstruction> codeEnumerable)    => codeEnumerable.AddRegionAfter(        new Func<CodeInstruction, bool>[]        {            i => i.IsLdloc(),            i => i.opcode == OpCodes.Ldarg_3,            i => i.opcode == OpCodes.Stfld && i.StoresField(causingAgentField),        },        new Func<CodeInstruction[], CodeInstruction>[]        {            a => a[0],            _ => new CodeInstruction(OpCodes.Ldarg_0),            _ => new CodeInstruction(OpCodes.Call, typeof(RogueLibsPlugin).GetMethod(nameof(SetupEffectHook))),        });private static readonly FieldInfo causingAgentField = typeof(StatusEffect).GetField(nameof(StatusEffect.causingAgent));
Avoid heavy calculations

When writing predicates, keep in mind that they might get called hundreds or thousands of times. For example, you can pre-calculate the FieldInfo value, used by your predicate, just put it in a static readonly field, like in the example above.

Heavy calculations like that can cost you hundreds of milliseconds of start-up time (or even entire seconds, if you're working on a big project).

Here's another example from RogueLibs:

/RogueLibsCore/Patches/Patches_Unlocks.cs
public static IEnumerable<CodeInstruction> Unlocks_LoadInitialUnlocks(IEnumerable<CodeInstruction> codeEnumerable)    => codeEnumerable.ReplaceRegion(        new Func<CodeInstruction, bool>[]        {            i => i.opcode == OpCodes.Callvirt && i.Calls(List_Unlock_GetEnumerator),            i => i.IsStloc(),        },        new Func<CodeInstruction, bool>[]        {            i => i.opcode == OpCodes.Callvirt,            i => i.opcode == OpCodes.Endfinally,            i => i.opcode == OpCodes.Ldarg_0,        },        new CodeInstruction[]        {            new CodeInstruction(OpCodes.Pop),            new CodeInstruction(OpCodes.Pop),            new CodeInstruction(OpCodes.Call, typeof(RogueLibsPlugin).GetMethod(nameof(LoadUnlockWrappersAndCategorize))),        });private static readonly MethodInfo List_Unlock_GetEnumerator = typeof(List<Unlock>).GetMethod("GetEnumerator");
BTHarmonyUtils

You might also want to consider using BlazingTwist's BTHarmonyUtils. It provides several useful transpiler-patching utilities, similar to the ones in RogueLibs. It is easier to use, but I don't think there's any documentation on it.