Table of Contents
Custom UI (ModHelperComponents)
Advanced users only.
The Mod Hook system enables interception and modification of game methods without extensive boilerplate or conflicts with other modifications. This system combines native hooking and managed delegates (Harmony‑style patching) to inject custom logic before or after the original method invocation.
At its core, the abstract class ModHook<TNative, TManaged>
(where TNative
is the unmanaged delegate type and TManaged
is the managed delegate type) encapsulates:
Native Hook Attachment
Utilizes MelonLoader.NativeUtils.NativeHook<TNative>
to bind your hook to the target method.
Hook Management
Maintains two priority‑sorted dictionaries for prefix and postfix hooks to control execution order.
Original Method Invocation
Employs the TrampolineInvoker
(constructed via Expression Trees) to invoke the original method accurately after hook execution.
Before any hook is created, the system verifies that TNative
is decorated with
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
If this requirement is not met, an error is logged and the hook setup is aborted.
When AddPrefix(TManaged method)
or AddPostfix(TManaged method)
is called:
Check Existing Hook
Determines whether a native hook is already attached. If so, adds the prefix/postfix to the dictionary.
Create and Attach Hook
Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod
.The CallOriginalMethod
function uses a precompiled lambda in TrampolineInvoker
to invoke the original method. This supports:
Prefix and postfix hooks are stored in separate dictionaries keyed by priority. Hooks with higher priority values execute first, providing precise control over invocation order.
As of Bloons TD6 v48, the following hook implementations are provided:
BloonDamageHook
Hooks into the Bloon.Damage method.
BloonDegradeHook
Hooks into the Bloon.Degrade method.
Both implementations follow this pattern:
false
.CallOriginalMethod
.false
.Hook methods may declare any parameters from the original signature:
By Value
Non‑Il2CppObjectBase
types may be passed by value.
By Reference
Use the ref
keyword to modify parameter values; changes propagate through the hook chain.
Parameters inheriting from Il2CppObjectBase
are implicitly passed by reference.
Prefix hooks can inspect or adjust incoming parameters. Returning false
prevents execution of subsequent hooks and the original method.
Postfix hooks execute after the original method. Modifications to ref
parameters affect only subsequent postfix hooks, not the outcome of the original method.
[HookTarget(typeof(BloonDamageHook), HookTargetAttribute.EHookType.Prefix)]
[HookPriority(HookPriorityAttribute.HIGHER)]
public static bool DamageHook(Bloon @this, float totalAmount) {
if (@this != null) {
MelonLogger.Msg($"Bloon ID {@this.Id} would receive {totalAmount:N0} damage.");
}
return false; // Abort further execution, including the original method.
}
[HookTarget(typeof(BloonDamageHook), HookTargetAttribute.EHookType.Postfix)]
public static bool PostDamageHook(Bloon @this, ref float totalAmount, Projectile projectile, ref bool distributeToChildren,
ref bool overrideDistributeBlocker, ref bool createEffect, Tower tower, BloonProperties immuneBloonProperties,
BloonProperties originalImmuneBloonProperties, ref bool canDestroyProjectile, ref bool ignoreNonTargetable, ref bool blockSpawnChildren,
ref bool ignoreInvulnerable, HookNullable<int> powerActivatedByPlayerId) {
if (@this != null) {
MelonLogger.Msg($"Bloon ID {@this.Id} received {totalAmount:N0} damage.");
}
return true; // Continue execution of remaining hooks.
}