Skip to content

Why MonoDetour

Harmony and MonoMod.RuntimeDetour are completely fine and you may not see any problems with them. But I, Hamunii, was never fully satisfied with either. Not all of that dissatisfaction is actually coming from my experience, but comes from seeing other people struggle with both Harmony and MonoMod.

Existing Solutions

Issues With Harmony

  • Patch never runs/fails to be applied
    • Missing HarmonyPatch attribute on the class
    • Missing patch target type
    • Missing patch method name
    • Target method has overloads
  • How to get/set parameters in patch method
  • How to get/set return value in patch method

These also include preference, such as that I don’t like how much is “magic” — stuff you need to look up the documentation for to figure out, such as the special argument names.

What I Like About Harmony

  • Convenient without any setup—just start patching
  • Almost every hook can be performed the same familiar way
  • Prefixes and Postfixes don’t wrap the whole target method

Originally I liked MonoMod Hooks more because they wrap the whole target method so there is no need for Prefixes and Postfixes and therefore there are no workarounds required for keeping state between them.

In my experience though, you actually almost never need to keep state between prefix and postfix, and you rarely need to do something both before and after the target method. Not to mention that you also need to remember to call the trampoline (commonly called orig) or you accidentally skip the “original” method from running.

Issues With MonoMod

  • Manual Hooking with MonoMod.RuntimeDetour.Hook is tedious
  • HookGen (v1) requires external hook helper assemblies to be present, which usually means something like (Auto)HookGenPatcher needs to be there to generate those files at runtime
    • You may also want to hook something which hasn’t already been generated a hook helper assembly
  • HookGen (v1) doesn’t generate hooks for properties (getters and setters)
  • Hooks wrap the whole target method, requiring calling the trampoline orig with its arguments

What I Like About MonoMod

  • Hooks wrap the whole target method, removing the need for a lot of Harmony’s “magic”
    • Accessing and changing parameter values and overriding return value becomes obvious
  • HookGen is still incredibly convenient despite what I listed earlier
  • ILHook manipulators are great as they get passed an ILContext which holds a lot of information

MonoDetour Design

While I am the main target user of MonoDetour, I have designed the API so that it should as easy as possible for anyone to use. I like to help people, which is why I’m writing this documentation in the first place.

Taking everything I like from Harmony and MonoMod, we end up with MonoDetour.

Convenience Is Priority #1

If MonoDetour is not any more convenient than Harmony or MonoMod with HookGen in all cases, then it has failed. This ties in closely with being easy and intuitive to use. MonoDetour also borrows Prefix and Postfix patches from Harmony, which means less code to write.

Avoiding Common Pitfalls

MonoDetour solves most of the Harmony problems I listed with HookGen. This means that MonoDetour can generate hook helpers to easily hook methods and figure out what you can do in the hook, right in your IDE! Gone are the days of failing to hook a method due to a small mistake.

MonoDetour also differentiates methods with overloads right in HookGen just like in MonoMod so they are just as easy to hook as methods without overloads.

HookGen With Source Generators

MonoMod’s HookGen v1 works with Mono.Cecil and generates assemblies directly. These are external assemblies which are large as they contain all hooks for all types, except some hooks are missing, such as for properties.

MonoDetour only generates hooks for types you tell it about, and the generated hooks stay in your assembly for maximum portability.

Minimizing Damages

MonoDetour is strict by default about target methods for hooks. If the targe method signature for a hook changes, MonoDetour throws as soon as the target method is attempted to be retrieved via reflection if it isn’t found with the exact signature MonoDetour expects.

While this may appear as a downside compared to Harmony which is much less strict, your hook not breaking when a signature changes for a method you are hooking can result in missing new possible issues. Throwing will help catch changes you would otherwise have missed.

Additionally, Prefix and Postfix hooks which throw are caught by MonoDetour in an attempt to recover the target method from throwing and preventing as much damage as possible, and MonoDetour will immediately dispose of all hooks attached to the MonoDetourManager the hook is attached to. This is done to try prevent mods that are outdated or broken from an incompatibility from breaking a game for example.

Comparing Solutions

Let’s perform the same simple hook on the following method with MonoDetour, Harmony, MonoMod, and MonoMod with HookGen. The target type and method will be:

namespace Lib;
public class TargetClass
{
// We want to hook this.
public int TakeAndReturnInt(int number)
{
// Call this.PrintFoo() here
// and increment 'number' argument by one.
return number;
}
public void PrintFoo()
{
Console.WriteLine("Foo");
}
}
// Generate hooks for the target class.
[MonoDetourTargets(typeof(TargetClass))]
static class TargetClassHook
{
// This attribute is not required, but will use this as hook init method.
[MonoDetourHookInit]
static void Init()
{
// These hooks are generated, so they are quick to write
// and nearly impossible to get wrong!
On.Lib.TargetClass.TakeAndReturnInt.Prefix(Prefix_TakeAndReturnInt);
}
// This method signature can be generated by your IDE from the PrefixSignature
// delegate argument in the On.Lib.TargetClass.TakeAndReturnInt.Prefix method!
private static void Prefix_TakeAndReturnInt(TargetClass self, ref int number)
{
self.PrintFoo();
number += 1;
}
}

Conclusion

It is up to you to decide if MonoDetour makes sense for you, as it is about preference in the end. Nevertheless MonoDetour tries its best to be as convenient and easy as possible.

However if you are sold on MonoDetour, you can include MonoDetour into your existing projects that are using Harmony or MonoMod, as they are compatible with each other.

Though, note that if you are using HookGen with MonoMod you may need to configure MonoDetour’s Hook generation namespace to something other than On to avoid collisions with generated hooks.