Legacy MonoMod - Documentation
Official Documentation
DANGER
This page is about legacy MonoMod—the version shipped by BepInEx, which is what we use.
The documentation on https://monomod.dev/ is for “reorganize”—the next version of MonoMod, with breaking changes, which this page doesn't touch.
Legacy MonoMod has official documentation here: https://monomod.github.io/api/index.html
And while it contains useful information, not everything is well documented. This is where this page comes in; we have documented and linked some of the most relevant pieces of information for modders.
MonoMod.RuntimeDetour
Official Documentation: MonoMod.RuntimeDetour
Official Usage Example: RuntimeDetour Usage
Hook
Official Documentation: MonoMod.RuntimeDetour.Hook
Hooks are used to detour from the original method to run your method instead. They allow receiving a trampoline as the first parameter, which when called will call the previous detour in the chain if it exists, or the original method if none exist.
You will probably most often use the following constructors:Hook(Delegate from, Delegate to)
Hook(Delegate from, Delegate to, HookConfig config)
For the full list, see the official documentation.
Hook Example
Note: For more Hook Examples, see Hook Examples.
private static Hook isEditorHook = new Hook
(
AccessTools.DeclaredPropertyGetter(typeof(UnityEngine.Application), nameof(UnityEngine.Application.isEditor)),
Hook_Application_get_isEditor,
new HookConfig(){ Priority = 100 }
);
// The original method get_isEditor is a static boolean,
// so the trampoline must be of type Func<bool>
private static bool Hook_Application_get_isEditor(Func<bool> orig)
{
Plugin.Logger.LogInfo("Detoured UnityEngine.Application.isEditor!");
Plugin.Logger.LogInfo("Calling the trampoline...");
return orig();
}
Methods
For all methods, see the Official Documentation: MonoMod.RuntimeDetour.Hook - Methods
Apply
void Apply()
Applies the patch. This is normally done automatically.
Undo
void Undo()
Unapplies the patch.
ILHook
Official Documentation: MonoMod.RuntimeDetour.ILHook
ILHooks are a different type of detour. Instead of detouring from the original code to your method and calling the trampoline, your method runs once to do the modifications to the IL code, when applied.
You will probably most often use the following constructors:ILHook(MethodBase from, ILContext.Manipulator manipulator)
ILHook(MethodBase from, ILContext.Manipulator manipulator, ILHookConfig config)
For the full list, see the official documentation.
Our ILContext.Manipulator manipulator
method takes in an ILContext il
as the only argument. We can use ILCursor
to modify that IL code.
ILHook Example
Note: For more ILHook Examples, see ILHook Examples.
private static ILHook blobCollideHook = new ILHook
(
AccessTools.DeclaredMethod(typeof(BlobAI), nameof(BlobAI.OnCollideWithPlayer)),
ILHook_BlobAI_OnCollideWithPlayer
);
private static void ILHook_BlobAI_OnCollideWithPlayer(ILContext il)
{
ILCursor c = new(il);
c.GotoNext(
MoveType.After, // position our cursor after the last match
// IL_0022: ldarg.0 // load argument 0 'this' onto stack
// IL_0023: ldfld float32 BlobAI::angeredTimer // push the value of 'angeredTimer' onto stack
// IL_0028: ldc.r4 0.0 // push 0 onto the stack as float32
// IL_002d: bge.un.s IL_0030 // Branch to IL_0030 if angeredTimer >= 0
x => x.MatchLdarg(0),
x => x.MatchLdfld<BlobAI>(nameof(BlobAI.angeredTimer)),
x => x.MatchLdcR4(0.0f),
// we can match instructions without specifying the value by using the 'out' keyword
x => x.MatchBgeUn(out _)
);
// Note the difference: we are changing a bge.un.s for bgt.un.s
c.Previous.OpCode = OpCodes.Bgt_Un_S;
Plugin.Logger.LogInfo("IL modifications done!");
// Plugin.Logger.LogInfo(il.ToString()); // uncomment to print the modified IL code to console
}
Methods
For all methods, see the Official Documentation: MonoMod.RuntimeDetour.ILHook - Methods
Apply
void Apply()
Applies the patch. This is normally done automatically.
Undo
void Undo()
Unapplies the patch.
HookConfig / ILHookConfig
Official Documentation:
MonoMod.RuntimeDetour.HookConfig
MonoMod.RuntimeDetour.ILHookConfig
A configuration object for Hooks/ILHooks. Can be fed as the final argument when creating a Hook/ILHook.
Constructors
HookConfig()
/ ILHookConfig()
Fields
The fields of HookConfig
/ ILHookConfig
.
After
IEnumerable<string> After
Before
IEnumerable<string> Before
ID
string ID
ManualApply
bool ManualApply
Determines whether or not the Hook/ILHook should be applied manually or not. Default: false
.
Priority
int Priority
The priority for this hook. Range: int.MinValue
to int.MaxValue
. Default value: 0
. Hooks/ILHooks with a higher priority will run earlier than other Hooks/ILHooks for the same method they are patching.
DetourContext
Official Documentation: MonoMod.RuntimeDetour.DetourContext
DetourContext is used for ambiently setting a DetourConfig, which is basically the same thing as HookConfig/ILHookConfig. This allows us to use things like priority while using MonoMod.RuntimeDetour.HookGen's MMHOOK
helper assemblies.
DetourContext Example
We will want to wrap the DetourContext in a using
statement so it disposes of itself after running out of scope.
using(new DetourContext(priority: 100))
{
// The DetourContext is active inside this scope
On.StartOfRound.Awake += StartOfRound_Awake;
}
IMPORTANT
You will need to install DetourContext.Dispose Fix to fix a bug which causes the DetourContext to never dispose of itself. This is because the version of MonoMod.RuntimeDetour shipped by BepInEx is too old to have that bug fix included.
Constructors
DetourContext()
DetourContext(int priority)
DetourContext(int priority, string id)
DetourContext(string id)
Fields
The fields of DetourContext
.
After
List<string> After
Before
List<string> Before
Priority
int Priority
The priority for hooks set when the DetourContext is active. Range: int.MinValue
to int.MaxValue
. Default value: 0
. Hooks/ILHooks with a higher priority will run earlier than other Hooks/ILHooks for the same method they are patching.
MonoMod.Cil
Official Documentation: MonoMod.Cil
ILContext
Official Documentation: MonoMod.Cil.ILContext
An IL manipulation "context" with various helpers and direct access to the MethodBody. Passed as an argument to ILHook patch methods.
Properties
See: MonoMod.Cil.ILContext - Properties
Methods
For all methods, see the Official Documentation: MonoMod.Cil.ILCursor - Methods
The most relevant methods ILContext provides are listed here.
DefineLabel
ILLabel DefineLabel()
Create a new label without target, to be marked with a cursor. See: ILCursor::MarkLabel.
ILLabel DefineLabel(Instruction target)
Define a new label pointing at a given instruction.
ILCursor
Official Documentation: MonoMod.Cil.ILCursor
A cursor used to manipulate a method body in an ILContext. Similar to a text cursor, the ILCursor is between instructions. ILCursors are used by making an instance of one from an ILContext, and using methods such as Emit, EmitDelegate and Remove to modify the ILContext directly.
Constructors
ILCursor(ILContext context)
ILCursor(ILCursor c)
Properties
For all properties, see the Official Documentation: MonoMod.Cil.ILCursor - Properties
Index
int Index { get; set; }
The index of the instruction immediately following the cursor position. Range: 0 to Instrs.Count
. Setter accepts negative indexing by adding Instrs.Count
to the operand.
Next
Instruction Next { get; set; }
The instruction immediately following the cursor position or null if the cursor is at the end of the instruction list.
Prev/Previous
Instruction Prev { get; set; }
Instruction Previous { get; set; }
The instruction immediately preceding the cursor position or null if the cursor is at the start of the instruction list.
Methods
For all methods, see the Official Documentation: MonoMod.Cil.ILCursor - Methods
The most relevant methods ILCursor provides are listed here.
DefineLabel
ILLabel DefineLabel()
Create a new label without target, for use with MarkLabel.
MarkLabel
void MarkLabel(ILLabel label)
Set the target of a label to the current position (label.Target = Next)
and moves after it. Also see: ILContext::DefineLabel(Instruction target).
Remove
ILCursor Remove()
Remove the Next instruction.
RemoveRange
ILCursor RemoveRange(int num)
Remove several instructions.
Emit
ILCursor Emit(OpCode opcode)
ILCursor Emit(OpCode opcode, CallSite site)
ILCursor Emit(OpCode opcode, Instruction target)
ILCursor Emit(OpCode opcode, Instruction[] targets)
ILCursor Emit(OpCode opcode, VariableDefinition variable)
ILCursor Emit(OpCode opcode, FieldReference field)
ILCursor Emit(OpCode opcode, MethodBase method)
ILCursor Emit(OpCode opcode, ParameterDefinition parameter)
... And more
Emit a new instruction at this cursor's current position.
EmitDelegate
int EmitDelegate<T>(T cb) where T : Delegate
Emit the IL to invoke a delegate as if it were a method. Stack behaviour matches OpCodes.Call
.
GotoNext
ILCursor GotoNext(params Func<Instruction, bool>[] predicates)
ILCursor GotoNext(MoveType moveType, params Func<Instruction, bool>[] predicates)
Search forward and moves the cursor to the next sequence of instructions matching the corresponding predicates. Throws KeyNotFoundException
if sequence is not found. Use TryGotoNext if this is not desirable.
TryGotoNext
ILCursor TryGotoNext(params Func<Instruction, bool>[] predicates)
ILCursor TryGotoNext(MoveType moveType, params Func<Instruction, bool>[] predicates)
Search forward and moves the cursor to the next sequence of instructions matching the corresponding predicates. Returns a boolean which is true if a match was found.
GotoPrev
ILCursor GotoPrev(params Func<Instruction, bool>[] predicates)
ILCursor GotoPrev(MoveType moveType, params Func<Instruction, bool>[] predicates)
Search backward and moves the cursor to the next sequence of instructions matching the corresponding predicates. Throws KeyNotFoundException
if sequence is not found. Use TryGotoPrev if this is not desirable.
TryGotoPrev
ILCursor TryGotoPrev(params Func<Instruction, bool>[] predicates)
ILCursor TryGotoPrev(MoveType moveType, params Func<Instruction, bool>[] predicates)
Search backward and moves the cursor to the next sequence of instructions matching the corresponding predicates. Returns a boolean which is true if a match was found.