Skip to content

Hooking Normal Methods

To start hooking, we need a target method we want to hook. So, given the target type and method we want to hook:

namespace Lib;
public class LibraryClass
{
public bool IsTrue { get; set; }
// We want to hook this.
public int TakeAndReturnInt(int number)
{
return number;
}
public void PrintFoo()
{
Console.WriteLine("Foo");
}
}

Let’s generate hooks for the type:

// Tell MonoDetour's HookGen to generate hooks
// for the target type.
[MonoDetourTargets(typeof(LibraryClass))]
static class LibraryClassHooks
{
// ...
}

Now we can get to hooking. A simple hook we can do is:

[MonoDetourTargets(typeof(LibraryClass))]
static class LibraryClassHooks
{
// MonoDetourManager.InvokeHookInitializers will
// call methods marked with this attribute in types
// which have the MonoDetourTargetsAttribute.
[MonoDetourHookInitialize]
static void Init()
{
// Add a prefix hook which runs at the start of the target method.
Md.Lib.LibraryClass.TakeAndReturnInt.Prefix(Prefix_TakeAndReturnInt);
}
static void Prefix_TakeAndReturnInt(LibraryClass self, ref int number)
{
// As soon as LibraryClass.TakeAndReturnInt runs,
// call its method PrintFoo with its instance.
self.PrintFoo();
// Increment the 'number' parameter by one.
number += 1;
}
}

We can either call our LibraryClassHooks.Init method manually, or use the following to call all methods marked with MonoDetourHookInitialize attribute that are in types marked with the MonoDetourTargets attribute:

internal static void InitAllHooks()
{
// Searches for types with MonoDetourTargets to go through and
// finds methods in them marked with MonoDetourHookInitialize and invokes them.
MonoDetourManager.InvokeHookInitializers(Assembly.GetExecutingAssembly());
}

We can also add a hook that runs at the end of a method:

// Add a postfix hook which runs at the end of the target method.
Md.Lib.LibraryClass.TakeAndReturnInt.Postfix(Postfix_TakeAndReturnInt);
// ...
static void Postfix_TakeAndReturnInt(LibraryClass self, ref int number,
ref int returnValue)
{
Console.WriteLine("Hello from postfix hook!");
}

To change a return value, your hook must be either be a Postfix or a ControlFlowPrefix (see Modifying ControlFlow). We can use the returnValue parameter for accessing and setting the return value.

Md.Lib.LibraryClass.TakeAndReturnInt.Postfix(Postfix_TakeAndReturnInt);
// ...
static void Postfix_TakeAndReturnInt(LibraryClass self, ref int number,
ref int returnValue)
{
returnValue += 50;
}

The behavior of returning from a ControlFlowPrefix hook can be one of the following:

  • Continue executing the target method normal
  • Skip the original instructions, but still run all Prefix and Postfix hooks
  • Return immediately from the target method, skipping anything that would have come after your control flow prefix, but while still running all hooks that came before

This behavior is defined by the ReturnFlow enum.

To apply a control flow prefix, one should first instruct MonoDetour.HookGen to generate ControlFlowPrefix hook helpers for the target type:

[MonoDetourTargets(typeof(LibraryClass), GenerateControlFlowVariants = true)]
static class LibraryClassHooks { }

Once this is done, hook methods named ControlFlowPrefix will appear for all methods in the target type:

// 1. MUST SET: GenerateControlFlowVariants = true
[MonoDetourTargets(typeof(LibraryClass), GenerateControlFlowVariants = true)]
static class LibraryClassHooks
{
[MonoDetourHookInitialize]
static void Init()
{
// 2. Now ControlFlowPrefix hooks can be used and applied
Md.Lib.LibraryClass.TakeAndReturnInt.ControlFlowPrefix(
ControlFlowPrefix_TakeAndReturnInt
);
}
static ReturnFlow ControlFlowPrefix_TakeAndReturnInt(
LibraryClass self,
ref int number,
ref int returnValue
)
{
// We can set the to-be return value of the
// target method by assigning to returnValue:
returnValue = 1337;
// We can dynamically choose the return behavior
// of our ControlFlowPrefix hook.
return number switch
{
0 => ReturnFlow.None,
1 => ReturnFlow.SkipOriginal,
_ => ReturnFlow.HardReturn,
};
}
}

MonoDetour.HookGen also generates hooks for properties, so this is the same case as with the basic hook example. One thing to know though is that getter methods have a get_ prefix, and setter methods have a set_ prefix.

// Hook the getter method:
Md.Lib.LibraryClass.get_IsTrue.Prefix(Prefix_get_IsTrue);
// Hook the setter method:
Md.Lib.LibraryClass.set_IsTrue.Prefix(Prefix_set_IsTrue);
// Note that if the property doesn't implement
// a setter, there won't be a hook for it.