AD SPACE AVAILABLE · BRAND PARTNERS WELCOME

Unity Game Modding Architecture: BepInEx Mono vs. IL2CPP, Harmony Hooks, and Assembly Patching

365Finds Editorial
Indexed 11,430 Wiki articles

Put 365 Wiki to work with your AI

Copy the prompt below, paste it into the AI you already use, then ask your question.

Download full Skill

Try it with the prompt first. If you use 365 Wiki often, install the full 365Wiki Skill so you do not have to paste it again. No regular AI? Gemini or Microsoft Copilot on the web will work.

365Wiki Skill

Introduction: The Mechanics of Unity Engine Modding

The Unity game engine powers an enormous portion of modern independent and mid-tier PC gaming, from multiplayer survival titles like Valheim and Lethal Company to complex simulation titles like Cities: Skylines II and Dyson Sphere Program. Unlike proprietary game engines that rely on monolithic binary blobs or Bethesda-style declarative plugin records, Unity projects are structured around the .NET and C# software ecosystems.

Modifying a Unity title typically does not involve replacing raw asset archives on disk. Instead, modern Unity modding operates via dynamic runtime assembly patching and in-memory method interception. At the center of this ecosystem sits BepInEx (Bepis Injector Extensible), an open-source modding framework and plugin bootstrapper, paired with Andreas Pardeike's Harmony library.

However, modders and players frequently encounter severe compatibility failures when attempting to install mods across different games. The root cause lies in Unity's underlying compilation model: the profound architectural divide between the traditional Mono scripting backend and the ahead-of-time compiled IL2CPP (Intermediate Language to C++) backend. Understanding how BepInEx and Harmony navigate this divide is essential for diagnosing crashes, broken hooks, and load-order conflicts.

The Scripting Backend Divide: Mono vs. IL2CPP Architecture

When a game developer packages a Unity title, they select one of two fundamentally distinct scripting compilation targets:

[Source C# Scripts: Game Code]
              │
              ▼
[Roslyn / Unity C# Compiler] ---> Common Intermediate Language (CIL Bytecode)
              │
              ├─────────────────────────────────────────┐
              ▼                                         ▼
     [Mono Backend Path]                      [IL2CPP Backend Path]
- Bundles CIL in Managed Assemblies        - IL2CPP Transpiler converts CIL to C++
- Packaged as Assembly-CSharp.dll          - Native C++ Compiler outputs GameAssembly.dll
- Executed by Mono JIT Virtual Machine     - Metadata stripped into global-metadata.dat
- Full Reflection, Easy Decompilation      - Pure Machine Code, No Runtime CLR/JIT VM

The Mono Scripting Backend

In a Mono-compiled game, all gameplay logic, AI scripts, and character behaviors are compiled into managed .NET assemblies—most notably Managed/Assembly-CSharp.dll.

The IL2CPP Scripting Backend

In an IL2CPP-compiled game, the compilation pipeline takes an entirely different trajectory:

The BepInEx Bootstrapper Architecture

BepInEx resolves the challenge of executing external code inside a closed Unity binary through dynamic link library (DLL) proxy hijacking.

The Doorstop / Proxy Hijack Chain

Windows applications search for dynamic link libraries in their local root directory before querying system directories (C:\Windows\System32). BepInEx takes advantage of this operating system search order:

  1. The Proxy DLL: BepInEx installs a proxy DLL—typically winhttp.dll, version.dll, or doorstop.dll—into the game's executable folder.
  2. Execution Interception: When the Windows loader launches the game executable, it loads the local winhttp.dll proxy instead of the legitimate operating system system library.
  3. Bootstrapping the Runtime: The proxy initializes Unity Doorstop, which immediately locates and loads BepInEx.Preloader.dll and BepInEx.Core.dll into the application's memory space before Unity's primary initialization routines take control.
  4. Plugin Discovery: Once the runtime environment is established, BepInEx creates a console window (if enabled), initializes logging sinks, and scans the BepInEx/plugins/ directory for compiled mod assemblies.

BepInEx 5 vs. BepInEx 6: The IL2CPP Interop Bridge

Harmony: Dynamic In-Memory Method Patching

Once BepInEx bootstraps mod code into memory, mods rely on Harmony to alter game functionality without modifying original files on disk. Harmony operates by dynamically rewriting the method prologues in JIT-compiled memory.

The Four Core Harmony Patch Types

[Calling Method: e.g. PlayerController.TakeDamage(float amount)]
           │
           ▼
[Harmony Prefix Patch] ---> Return true (continue) OR Return false (skip original)
           │
           ▼
[Original Game Method Execution: Calculates HP deduction]
           │
           ▼
[Harmony Postfix Patch] ---> Reads/Modifies return value (__result) or state
           │
           ▼
[Harmony Finalizer] ---> Catches unhandled exceptions, suppresses game crashes
  1. Prefix ([HarmonyPrefix]):
    • Executes immediately before the original game method begins.
    • Can inspect and alter incoming arguments (e.g., modifying float amount before damage is applied).
    • Can short-circuit execution: if a Prefix returns false, the original game method is completely bypassed.
  2. Postfix ([HarmonyPostfix]):
    • Executes immediately after the original game method finishes.
    • Can read and rewrite the method's return value using the special ref __result parameter.
    • Ideal for adding secondary effects (e.g., granting an achievement whenever an item is crafted).
  3. Transpiler ([HarmonyTranspiler]):
    • The most powerful and advanced patch type. A Transpiler intercepts the raw CIL bytecode of a method as an IEnumerable sequence of CodeInstruction tokens before JIT compilation occurs.
    • Allows modders to insert, remove, or modify individual CIL opcodes (e.g., changing an ldc.i4.0 constant to ldc.i4.1 or swapping a jump target).
  4. Finalizer ([HarmonyFinalizer]):
    • Acts as an in-memory try/finally wrapper around the patched method. It intercepts and suppresses unexpected exceptions, preventing unhandled mod errors from crashing the game to desktop.

Troubleshooting Matrix: Unity Modding Failure Modes

When Unity mods fail to load or trigger runtime crashes, evaluate your installation against the following architectural diagnostic matrix:

Failure SymptomRoot Architectural CauseDiagnostic IndicatorCorrective Action
No Console / Mods Completely IgnoredProxy DLL blocked or ignored by OS loaderNo BepInEx/LogOutput.log file createdVerify winhttp.dll presence in root; check folder write permissions
Instant Crash at Launch (IL2CPP)Attempting to run BepInEx 5 on an IL2CPP gameGameAssembly.dll exists in root, but BepInEx 5 installedWipe BepInEx/ and install BepInEx 6 x64 IL2CPP build
Outdated Interop CacheGame updated but interop assemblies not regeneratedTypeLoadException or MissingMethodException in logDelete BepInEx/interop/ folder to force a fresh proxy rebuild
Prefix Hook CollisionMultiple mods hooking the same method returning falseMod A works, Mod B functionality completely brokenReorder mod plugins; avoid destructive prefix overrides
Anti-Cheat Bootstrapper RejectionEAC or BattlEye blocking proxy DLL injectionGame closes immediately or shows "Untrusted system file"Run in official offline mode or use dedicated modded game launch flags

When building custom mod load orders, debugging assembly conflicts, or developing custom C# extensions across diverse game engines, consulting technical breakdowns on 365Finds ensures your pipeline adheres to verified runtime architecture. Approaching Unity modding through the lens of memory residency, proxy injection, and non-destructive bytecode patching safeguards your game installations against cascading version breakage.

References