-
Notifications
You must be signed in to change notification settings - Fork 8
Mod Creation_C# Programming_Hooking
In the C# world, hooking methods is easy thanks to libraries like MonoMod or Harmony.
For Risk of Rain 2, most of the modders in the community use MonoMod and the MMHOOK_X.dll
files (To be more precise MMHOOK_RoR2.dll
is the one that get generated by HookGenPatcher and that you will most likely see be used, even though a MMHOOK
could be generated for other C# assemblies!)
Those MMHOOK
files are generated through one of the MonoMod tool called MonoMod.RuntimeDetour.HookGen.
Or, if you have R2Modman installed with HookGenPatcher, you can have it generated:
-
Launch the game using R2Modman ("Start modded");
-
Find the file at
%appdata%\\r2modmanPlus-local\\RiskOfRain2\\profiles\\<YOUR_PROFILE>\\BepInEx\\plugins\\MMHOOK\\MMHOOK_RoR2.dll
.
(If you use the default profile, simply replace <YOUR_PROFILE>
with Default
.)
Having an assembly reference to one of these MMHOOK
files will add the On
and IL
namespaces into your C# project for you to use.
There are two main type of hooks:
-
On Hooks means hooks which allow for adding code before and after the hooked method runs.
-
IL Hooks means hooks that modify the original method CIL
A hook is defined once and most of the time is:
- Enabled inside your
BaseUnityPlugin
OnEnable
method. - Disabled inside your
BaseUnityPlugin
OnDisable
method.
On hooks will be executed every time the original method is called by the game.
This means that this hook is not bound to a specific instance of an object, but rather to all objects of that type.
A very basic On hook goes as follows:
// Somewhere in your BaseUnityPlugin class
private void OnEnable()
{
On.RoR2.RoR2Application.Awake += OnRoR2ApplicationAwake;
}
private void OnDisable()
{
On.RoR2.RoR2Application.Awake -= OnRoR2ApplicationAwake;
}
...
private static void OnRoR2ApplicationAwake(On.RoR2.RoR2Application.orig_Awake orig, RoR2Application self)
{
// code that will run before the original method
orig(self);
// code that will run after the original method
}
orig
is the original function in the game, if you don't want to outright replace the function, you need to call the orig
at the end of your function, or at the start if you want to run after it!
self
allows you to access the instance member variables, if the method you are hooking is a member method !
If not, it means its a static method, and that there is no self
parameter which represent the instance of the class.
If we run our own code before the original, we can perform logic prior to calling the original method, and even modify the parameters of the method before it is executed. Similarly, if we run our code after the original, the original will still perform its intended task, but will also execute our code right after. One problem that many people will find with this method, is that we are unable to modify the logic that exists within the method, without replacing it entirely.
Failing to call orig()
will result in the original/vanilla method never being executed and other mods also hooking that methow will not be called!
For a beginner you basically never want to do this unless you have very good reasons. If what you want instead is modifiying some parts of the original method, you can do so using an IL Hook, a tutorial goes over them here
Parameters can be intercepted prior to being passed to the games vanilla methods, for example, we could intercept the arguments passed to the PickupController, and instead of dropping lunar coins, we can now drop goat hoofs.
private static void PickupDropletController_CreatePickupDroplet(On.RoR2.PickupDropletController.orig_CreatePickupDroplet orig, PickupIndex pickupIndex, UnityEngine.Vector3 position, UnityEngine.Vector3 velocity)
{
if (pickupIndex == PickupCatalog.FindPickupIndex("LunarCoin.Coin0"))
{
pickupIndex = PickupCatalog.FindPickupIndex(ItemIndex.Hoof);
}
orig(pickupIndex, position, velocity);
}
Replacing a large method in its entirety in order to modify a single line of logic would not be advised as it will create interoperability issues with other mods, for such scenario's something like an IL Hook is better.
IL Hook are more complicated, since you'll have to deal with the CIL.
A full guide can be found here
Assuming that multiple mods hook a method, load order follows LIFO (Last In First Out) priorities.
Vanilla -> Mod A -> Mod B -> Mod C
Becomes
C() -> B() -> A() -> Vanilla
If B does not orig(self), but A does, A will never execute, and neither will vanilla.