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.
RoguePatcher patcher = new RoguePatcher(this);patcher.Postfix(typeof(StatusEffects), nameof(StatusEffects.hasStatusEffect));patcher.Postfix(typeof(InvDatabase), nameof(InvDatabase.ChooseArmor), new Type[1] { typeof(string) });
Harmony harmony = new Harmony(pluginGUID);MethodInfo original = AccessTools.Method(typeof(StatusEffects), nameof(StatusEffects.hasStatusEffect));MethodInfo patch = AccessTools.Method(GetType(), nameof(MyAwesomePatchMethod));harmony.Patch(original, new HarmonyMethod(patch));original = AccessTools.Method(typeof(InvDatabase), nameof(InvDatabase.ChooseArmor), new Type[1] { typeof(string) });patch = AccessTools.Method(GetType(), nameof(MyAwesomePatchMethod2));harmony.Patch(original, new HarmonyMethod(patch));
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.
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:
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) { /* ... */ }}
public class MyAwesomePlugin : BaseUnityPlugin{ public void Awake() { Harmony harmony = new Harmony(pluginGUID); MethodInfo original = AccessTools.Method(typeof(StatusEffects), nameof(StatusEffects.hasStatusEffect)); MethodInfo patch = AccessTools.Method(typeof(MyAwesomePatches), nameof(MyAwesomePatchMethod)); harmony.Patch(original, new HarmonyMethod(patch)); original = AccessTools.Method(typeof(InvDatabase), nameof(InvDatabase.ChooseArmor), new Type[1] { typeof(string) }); patch = AccessTools.Method(typeof(MyEvenMoreAwesomePatches), nameof(MyAwesomePatchMethod2)); harmony.Patch(original, new HarmonyMethod(patch)); }}public class MyAwesomePatches{ public static void MyAwesomePatchMethod(StatusEffects __instance) { /* ... */ }}public class MyEvenMoreAwesomePatches{ public static void MyAwesomePatchMethod2(InvDatabase __instance, string previousArmorName) { /* ... */ }}
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:
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));
public static IEnumerable<CodeInstruction> StatusEffects_AddStatusEffect(IEnumerable<CodeInstruction> code){ bool searching = true; int current = 0; CodeInstruction[] matches = new CodeInstruction[after.Length]; foreach (CodeInstruction instr in code) { yield return instr; if (searching) { if (current is 0 ? instr.IsLdloc() : current is 1 ? instr.opcode == OpCodes.Ldarg_3 : instr.opcode == OpCodes.Stfld && instr.StoresField(causingAgentField)) { matches[current] = instr; if (++current is 3) { searching = false; yield return matches[0]; yield return new CodeInstruction(OpCodes.Ldarg_0); yield return new CodeInstruction(OpCodes.Call, typeof(RogueLibsPlugin).GetMethod(nameof(SetupEffectHook))); } } else current = 0; } }}private static readonly FieldInfo causingAgentField = typeof(StatusEffect).GetField(nameof(StatusEffect.causingAgent));
Yeah, it looks easy. But that's only because it's a very simple example.
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:
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");
public static IEnumerable<CodeInstruction> Unlocks_LoadInitialUnlocks(IEnumerable<CodeInstruction> code){ int state = 0; int current = 0; CodeInstruction[] beginCache = new CodeInstruction[2]; foreach (CodeInstruction instr in code) { if (state is 2) yield return instr; else if (state is 0) { if (current is 0 ? instr.opcode == OpCodes.Callvirt && instr.Calls(List_Unlock_GetEnumerator) : instr.IsStloc()) { beginCache[current] = instr; if (++current == 2) { state = 1; current = 0; } } else { if (current > 0) { for (int i = 0; i < current; i++) yield return beginCache[i]; current = 0; } yield return instr; } } else { if (current is 0 ? instr.opcode == OpCodes.Callvirt : current is 1 ? instr.opcode == OpCodes.Endfinally : instr.opcode == OpCodes.Ldarg_0) { if (++current == 3) { yield return new CodeInstruction(OpCodes.Pop); yield return new CodeInstruction(OpCodes.Pop); yield return new CodeInstruction(OpCodes.Call, typeof(RogueLibsPlugin).GetMethod(nameof(LoadUnlockWrappersAndCategorize))); } } else current = 0; } }}private static readonly MethodInfo List_Unlock_GetEnumerator = typeof(List<Unlock>).GetMethod("GetEnumerator");
Still relatively simple. I just don't want to spend my time writing a really complicated example.
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.