What MonoMod is and how Unity developers can detect it

Learn what MonoMod is, how BepInEx, MelonLoader, and HarmonyX use it to detour Unity methods, and how to detect it.

By Tim UhlottFounder|Last updated: August 12, 2026|14 minutes read
unityanti cheatmonomod
What MonoMod is and how Unity developers can detect it
If you have read about BepInEx, MelonLoader, or HarmonyX, you have probably also seen MonoMod mentioned underneath them. MonoMod is not a player facing mod manager. It is a lower level .NET modding toolkit. Loaders and libraries use MonoMod pieces to hook and rewrite methods at runtime. You will learn what MonoMod is, how frameworks use it, what it does inside a Unity process, and how to spot its traces.

What MonoMod is

MonoMod is a general purpose .NET modding "basework." That word is intentional. It is a set of base tools and libraries, not one single app. Depending on which package is involved, it can help with runtime method detours, IL rewriting, hook helper generation, and patching support across Mono and related runtimes. In Unity modding and cheating talks, people usually mean the runtime pieces, especially MonoMod.RuntimeDetour. Those let foreign code redirect your methods while the game is running. A useful way to place it in the stack:
  • BepInEx or MelonLoader get into the process and load mods.
  • The mod or plugin is the actual cheat or feature code.
  • HarmonyX is often the friendly patch API the mod calls.
  • MonoMod is one of the deeper engines that carries out detours and IL work.
Players rarely install "MonoMod" as the main product. They install a loader, and MonoMod arrives with that toolchain.

How mod loaders and frameworks use MonoMod

Mod frameworks need a way to change compiled methods without shipping a cracked game build. MonoMod helps with that. HarmonyX is the API many plugin authors write against. Underneath, HarmonyX is built on MonoMod.RuntimeDetour. BepInEx docs list both as runtime patching options, and patches from both can coexist. Some ecosystems also use MonoMod directly. RuntimeDetour attaches hooks and builds detour chains. HookGen can generate helper assemblies so hooks feel like C# events, for example On.SomeType.SomeMethod += MyHook;. When a Unity PC game runs with BepInEx or MelonLoader, you will often find MonoMod assemblies loaded with the loader core. The usual flow looks like this:
  1. The loader boots into the Unity process.
  2. It loads plugin DLLs plus support libraries such as MonoMod.RuntimeDetour.
  3. A plugin creates a Hook, an ILHook, or goes through HarmonyX.
  4. The hook redirects the target method into a detour chain.
  5. From that moment, selected game methods run through the hooked path.
For legitimate mods, that might mean better UI or content hooks. For cheats, that might mean free purchases, skipped cooldowns, or rewritten damage and inventory logic.

How MonoMod works technically

MonoMod's runtime model is easiest to understand as layered detours. At the bottom, a detour redirects execution from one method entry point to another. Higher level hooks accept delegates and pass a trampoline so your code can call the next handler or the original method. That detour can change arguments, skip the original behavior, call through, or change the return value. MonoMod can also work at the IL level through ILHook. An IL hook receives the method body as instructions and rewrites them. This is similar in spirit to a Harmony transpiler. HookGen does not turn your game assembly into a cracked copy for distribution. It generates a helper DLL, often named like MMHOOK_Assembly-CSharp.dll, with events that use RuntimeDetour behind the scenes. The game EXE may still match Steam. The change happens live in memory. Unity gameplay code is full of normal methods: take damage, spend currency, unlock item, check entitlement, start cooldown. If those methods exist on the client and the client decides the result, MonoMod gives a mod a clean way to intercept them. On Mono games this is especially direct. On IL2CPP games, loaders add interop so many mods can still reach equivalent targets. A cheat does not need MonoMod's full toolkit. It only needs one reliable hook on a valuable method.

MonoMod vs HarmonyX

These two are related, but they are different tools. HarmonyX is the higher level patch API. Authors write prefixes, postfixes, and transpilers. MonoMod is the lower level detour and IL toolkit. It can be used directly, and HarmonyX can use it underneath. BepInEx and MelonLoader often ship both. Think of it this way: the loader is the workshop, the mod is the worker, HarmonyX is the standard wrench, and MonoMod is part of the machine shop that makes those wrenches work. Many Unity cheat plugins stay on Harmony attributes and never mention MonoMod by name, even when MonoMod assemblies are present.

A tiny example

using MonoMod.RuntimeDetour; using System.Reflection; // Skip SpendCoins completely var spendCoins = typeof(PlayerWallet).GetMethod(nameof(PlayerWallet.SpendCoins)); var hook = new Hook(spendCoins, (Action<PlayerWallet, int> orig, PlayerWallet self, int amount) => { // Do not call orig. The wallet never spends. });
In a real cheat, that kind of hook can stop coin spending or force free unlocks. Many cheats never write this MonoMod code by hand. They write a HarmonyX prefix instead, and MonoMod still ends up in the process underneath. The important part for developers is the model: once MonoMod style hooking is active, your methods are no longer guaranteed to run as compiled.

Why Unity developers should care

If you develop a single player game, you will mostly not care about mods or cheats. Maybe you even appreciate the extra content. A user bought the game and can modify it. No one gets hurt. But what if someone does get hurt? That usually means financial loss for you, or a broken experience for other players. No one wants a wallhacking opponent in multiplayer. And no one wants a player on top of a competitive leaderboard without earning it. Financially, that can mean skipping licence checks, skipping ads, or unlocking items they should not have. The attacker only needs the right method name and a host that can apply the hook. So how can you protect your game? Let's look at the clues MonoMod leaves.

Traces MonoMod leaves

MonoMod is quieter than a full mod loader tree. Its fingerprints are the toolkit itself. Useful MonoMod specific clues include:
  • Assemblies such as MonoMod.RuntimeDetour, MonoMod.Utils, and often MonoMod.Core
  • HookGen helpers named MMHOOK_..., commonly MMHOOK_Assembly-CSharp
  • RuntimeDetour types such as Hook, ILHook, and DetourConfig
  • HookGen namespaces such as On. and IL. generated against your game types
  • MonoMod or MMHOOK_*.dll files on disk that are not part of your shipped build
  • Method entry points rewritten by RuntimeDetour after JIT
Those signals point at MonoMod style hooking even when the cheat author only writes Harmony attributes on top.

Detecting MonoMod traces

You can check for these signals in your game and then warn, restrict online features, or block startup.

MonoMod assemblies loaded into the process

A normal Unity player does not ship MonoMod. When RuntimeDetour is active, you usually see several MonoMod assemblies in the current AppDomain. Newer stacks often include MonoMod.Core under RuntimeDetour.
void Awake() { bool monoModLoaded = AppDomain.CurrentDomain.GetAssemblies() .Any(a => { string name = a.GetName().Name; return name == "MonoMod.RuntimeDetour" || name == "MonoMod.Utils" || name == "MonoMod.Core" || name == "MonoMod.RuntimeDetour.HookGen"; }); if (monoModLoaded) { Debug.Log("MonoMod assembly detected. Blocking game from starting."); } }

HookGen helpers and RuntimeDetour types

HookGen generates a helper DLL whose name starts with MMHOOK_. That helper does not contain your original game code. It exposes event style hooks under namespaces like On.YourNamespace and IL.YourNamespace. An MMHOOK_ assembly in your process is a strong MonoMod signal. Inside MonoMod.RuntimeDetour, mods construct Hook for delegate based detours and ILHook for IL rewrites. Finding those types means the MonoMod hooking API is available.
void Awake() { var assemblies = AppDomain.CurrentDomain.GetAssemblies(); bool mmhookLoaded = assemblies.Any(a => a.GetName().Name.StartsWith("MMHOOK_")); string[] markers = { "MonoMod.RuntimeDetour.Hook", "MonoMod.RuntimeDetour.ILHook", "MonoMod.RuntimeDetour.DetourConfig" }; var typeNames = assemblies .SelectMany(a => { try { return a.GetTypes(); } catch { return Type.EmptyTypes; } }) .Select(t => t.FullName); bool runtimeDetourApi = markers.Any(marker => typeNames.Contains(marker)); if (mmhookLoaded || runtimeDetourApi) { Debug.Log("MonoMod HookGen or RuntimeDetour API detected."); } }

MonoMod files on disk

MonoMod often leaves files under the install tree. Typical names include MonoMod.RuntimeDetour.dll, MonoMod.Utils.dll, MonoMod.Core.dll, and MMHOOK_*.dll helpers. Scan next to your executable for those names.
void Awake() { string[] monoModFiles = { "MonoMod.RuntimeDetour.dll", "MonoMod.Utils.dll", "MonoMod.Core.dll", "MMHOOK_Assembly-CSharp.dll" }; bool found = Directory.EnumerateFiles(".", "*.dll", SearchOption.AllDirectories) .Select(Path.GetFileName) .Any(name => monoModFiles.Contains(name) || name.StartsWith("MMHOOK_", StringComparison.OrdinalIgnoreCase)); if (found) { Debug.Log("MonoMod files found on disk."); } }

Method entry points rewritten by RuntimeDetour

MonoMod does not need to permanently rewrite your shipped DLLs on disk. A file hash can still match. What RuntimeDetour changes is the compiled method itself. After Unity has JIT compiled a method, MonoMod's native detour layer can overwrite the entry point so every call jumps into the detour chain. A simple approach is to force a method to compile, read the first bytes of its entry point, and keep that as a baseline early at startup. Later, read those bytes again. If they changed, a detour library rewrote the method.
using System.Runtime.CompilerServices; void Awake() { MethodInfo method = typeof(PlayerWallet).GetMethod(nameof(PlayerWallet.SpendCoins)); RuntimeHelpers.PrepareMethod(method.MethodHandle); // force JIT compile IntPtr entry = method.MethodHandle.GetFunctionPointer(); // Read the first bytes at 'entry' and compare them // with a clean baseline taken at startup. }
This works well on Mono desktop builds. A changed entry point proves a detour, not the name "MonoMod" by itself. Pair it with the assembly and type checks above when you want the signal to stay MonoMod specific. You do not want to create all those checks by yourself? There are tools that can help you. Check out my Anti-Cheat solution that can help you detect MonoMod based hooking stacks and other cheating or modding tools. Plus a tone of more features to help you with your anti-cheat needs.

There is still one problem

Do you remember the example from earlier?
var spendCoins = typeof(PlayerWallet).GetMethod(nameof(PlayerWallet.SpendCoins)); var hook = new Hook(spendCoins, (Action<PlayerWallet, int> orig, PlayerWallet self, int amount) => { // Do not call orig. The wallet never spends. });
Especially the nameof(PlayerWallet.SpendCoins) part? All those checks you implemented to detect MonoMod are useless if an attacker can bypass them. An attacker could look for something like My.Amazing.MonoMod.Detector.Detect and hook the body of the Detect method. There are two main ways to address this:
  1. Outsource critical code to an external source
You can move part of your important code to an external source, such as your server, and load it at runtime. This makes it much harder for attackers to analyze your code in advance. Your detection logic can live in this external code and run before attackers have a chance to patch it. However, this approach can be quite complicated to implement and maintain.
  1. Use an obfuscation solution
Obfuscation can make your source code much harder to understand. That makes it harder for attackers to find and patch your detection logic. For example, a name like My.Amazing.MonoMod.Detector.Detect would become something like IUQWEWQ.XACBSCA. That is still possible to reverse, but it takes more effort. You might consider using my Obfuscator to obfuscate your source code. It is a powerful build-time tool that can make your code much harder to understand and analyze.

Your takeaway

MonoMod is a low level .NET modding toolkit, not a loader. BepInEx and MelonLoader bring it in so mods can detour methods and rewrite IL at runtime. HarmonyX is often the friendlier API above it. Once a hook is applied, your method no longer runs as compiled. That is fine in many single player games, but it becomes a real problem when cheats hurt your revenue or other players. The good news is that MonoMod leaves its own traces: RuntimeDetour and Core assemblies, HookGen MMHOOK_ helpers, known Hook / ILHook types, toolkit DLLs on disk, and method entry points that suddenly jump into a detour chain. You can check for those signals yourself, or use existing tools to help you.

Share this article

Frequently asked questions

Newsletter

Stay in the Loop.

Subscribe to our newsletter to receive the latest news, updates, and special offers directly in your inbox. Don't miss out!