Skip to main content
The project's repository is archived as part of the GitHub Archive Program. RogueLibs' code and the documentation will no longer be updated.

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.

RLSetup attribute

Since RogueLibs handles custom stuff as classes, you might forget to initialize a new class in your plugin's Awake. That's why RLSetup attribute is here. You can just add it to a static method, and initialize your custom thing in there.

MyCustomItem.cs
public class MyCustomItem : CustomItem
{
[RLSetup]
public static void Setup()
{
RogueLibs.CreateCustomItem<MyCustomItem>()
.WithName(new CustomNameInfo("Name"))
.WithDescription(new CustomNameInfo("Description"))
.WithSprite(Properties.Resources.Sprite)
.WithUnlock(new ItemUnlock());

RogueLibs.CreateCustomName("SomeName", "Dialogue", new CustomNameInfo("Text"));
}
}

You'll just have to call the following method in your plugin's Awake:

MyCoolPlugin.cs
    public void Awake()
{
RogueLibs.LoadFromAssembly();
/* ... */
}
Pro-tip

Seriously, you should use it. It helps with versioning too. All of the logic in one place.

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) });

Pro-tip

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.

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

public class MyCoolPlugin : BaseUnityPlugin
{
public void Awake()
{
RoguePatcher patcher = new RoguePatcher(this, typeof(MyCoolPatches));

patcher.Postfix(typeof(StatusEffects), nameof(StatusEffects.hasStatusEffect));

patcher.TypeWithPatches = typeof(MyEvenCoolerPatches);

patcher.Postfix(typeof(InvDatabase), nameof(InvDatabase.ChooseArmor), new Type[1] { typeof(string) });
}
}
public class MyCoolPatches
{
public static void StatusEffects_hasStatusEffect(StatusEffects __instance)
{
/* ... */
}
}
public class MyEvenCoolerPatches
{
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:

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:

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.

The project's repository is archived as part of the GitHub Archive Program. RogueLibs' code and the documentation will no longer be updated.