The Definitive Il2CppInspector C++ Scaffold Masterclass: Developing High-Performance Native DLLs, MinHook Detours, and Direct Memory Manipulation for Unity IL2CPP Games
Executive Overview & Architectural Roadmap
In the realm of modern Unity game reverse engineering and modification, developers are faced with two fundamentally distinct architectural paths:
- Managed Runtime Interop: Utilizing frameworks like BepInEx 6 or MelonLoader to host a .NET runtime inside the game process and interact with unhollowed C# wrapper types.
- Pure Native C++ Scaffolding: Reverse engineering the native
GameAssembly.dll binary and global-metadata.dat to generate static C++ type definitions, header files, and function pointer tables, allowing developers to author 100% native C++ dynamic link libraries (DLLs) compiled directly with MSVC or Clang.
While managed toolchains offer great convenience, native C++ modding remains the gold standard for high-performance applications, low-level memory introspection, hardware-accelerated overlay rendering (Dear ImGui via DirectX 11/12 hook), anti-cheat bypasses, and direct hardware manipulation. In native C++, there is zero Garbage Collector overhead, zero managed-to-unmanaged marshaling latency, and direct access to CPU registers, memory offsets, and virtual method tables.
The pinnacle tool for generating native C++ development environments from IL2CPP titles is Il2CppInspector, created by reverse engineer Katy Coe (djkaty). Il2CppInspector analyzes the game's executable binary (GameAssembly.dll / libil2cpp.so) and its companion metadata file (global-metadata.dat), reconstructing a complete, compile-ready C++ Visual Studio solution known as the C++ Scaffold.
This masterclass is a complete, deep-dive technical guide to mastering Il2CppInspector and C++ Scaffolding. You will learn the mathematics of RVA offset mapping, explore the internal anatomy of generated scaffold headers (il2cpp-types.h, il2cpp-functions.h, il2cpp-api-functions.h), initialize native IL2CPP threads, master MinHook function detours, manipulate native strings and arrays, integrate hardware-rendered Dear ImGui menus, and compile high-performance native DLLs.
IL2CPP C++ SCAFFOLD REVERSE ENGINEERING ARCHITECTURE
======================================================================================================
[ Target Game Files: GameAssembly.dll + global-metadata.dat ]
|
v (Static Inspection & Type Reconstruction)
[ Il2CppInspector Engine (CLI / GUI) ]
|
v (Generates Native C++ Solution)
[ C++ Visual Studio Scaffold Project ]
|
+-> [ appdata/il2cpp-types.h ] <-- C++ Structs, Fields, Memory Offsets, Unions
|
+-> [ appdata/il2cpp-functions.h ] <-- Function Pointer Typedefs & Calling Conventions
|
+-> [ appdata/il2cpp-api-functions.h ] <-- Raw Unity IL2CPP Runtime APIs (il2cpp_*)
|
+-> [ appdata/il2cpp-init.cpp ] <-- Dynamic Base Address & RVA Resolution Engine
|
v
[ Your Custom Native C++ DLL (MyMod.dll) ]
<ul>
<li>Injected via DLL Injector / Sideload Proxy (version.dll)</li>
<li>DllMain & Dedicated Worker Thread</li>
<li>Attached to IL2CPP Domain via il2cpp_thread_attach()</li>
<li>Native Function Detours via MinHook / Detours</li>
<li>Direct GameObject & Transform Memory Manipulation</li>
<li>Hardware-Accelerated Dear ImGui Overlay (DirectX 11 / 12 Present Hook)</li>
</ul>
======================================================================================================
Module 1: The Native C++ Modding Paradigm vs. Managed Interop
Before delving into code generation, it is essential to understand why and when native C++ scaffolding is superior to managed C# toolchains.
1.1 Performance & Memory Footprint
In a managed C# modding framework (like BepInEx or MelonLoader), every interaction with an IL2CPP object passes through a managed proxy wrapper (Il2CppObjectBase). Calling a method or accessing a property requires marshaling arguments across the runtime boundary, allocating temporary memory buffers on the .NET Garbage Collector heap, and executing wrapper subroutines.
In a pure native C++ DLL built with an Il2CppInspector scaffold:
- Direct Pointer Arithmetic: An object instance is literally a raw C++ pointer (
app::PlayerHealth* player). Accessing a field like player->fields.currentHealth compiles to a single native assembly instruction: mov eax, [rcx + 0x18].
- Zero Garbage Collection Overhead: Native C++ does not run a garbage collector. Memory is allocated on the stack or via native heap allocators (
malloc / new), guaranteeing deterministic execution with zero frame-time stutter.
- Seamless Graphics Hooking: Native C++ allows seamless hooking of DirectX 11 (D3D11), DirectX 12 (D3D12), Vulkan, or OpenGL swap chains using kiero or MinHook, enabling high-performance Dear ImGui overlays running at hundreds of frames per second.
1.2 Comparison Matrix: Native C++ Scaffold vs Managed Interop
| Feature |
Native C++ Scaffold (Il2CppInspector) |
Managed Interop (BepInEx / MelonLoader) |
| Primary Language |
Modern C++ (C++17 / C++20) |
C# (.NET 6.0 / .NET Standard 2.1) |
| Execution Speed |
Maximum (Direct CPU Instructions) |
High (Thin wrapper marshaling) |
| Memory Overhead |
Extremely Low (~2-5 MB DLL footprint) |
Moderate (Hosts complete .NET runtime) |
| Compilation Target |
Native Machine Code (.dll / .so) |
Managed CIL Bytecode (.dll) |
| Hooking Engine |
MinHook / Microsoft Detours / Dobby |
HarmonyX / MonoMod |
| UI Rendering |
Dear ImGui / Direct3D / OpenGL Hooks |
Unity IMGUI (OnGUI) / Canvas UI |
| Anti-Tamper Resilience |
High (Native obfuscation, no CLR footprint) |
Moderate (CLR hosting artifacts visible) |
Module 2: What is Il2CppInspector and How It Reconstructs Native Types
2.1 The Internal Mechanics of Il2CppInspector
When Unity compiles a project with IL2CPP, it generates two critical data structures inside GameAssembly.dll:
- Il2CppCodeRegistration: Contains pointers to function pointer arrays, generic method pointers, delegate wrappers, and reverse P/Invoke stubs.
- Il2CppMetadataRegistration: Contains type definitions, field offsets, generic type signatures, and interface offsets.
Il2CppInspector executes the following reverse engineering pipeline:
- Binary Parsing: Parses the PE (Portable Executable), ELF, or Mach-O binary to locate section headers (
.text, .data, .rdata).
- Heuristic Search: Locates the addresses of
s_Il2CppCodeRegistration and s_Il2CppMetadataRegistration by scanning for signature patterns or xrefs to il2cpp_init.
- Metadata Corroboration: Reads
global-metadata.dat and cross-references string literal indices, type definitions, method signatures, and class hierarchies with the binary structures.
- C++ Header Synthesis: Translates native metadata into valid, syntactically correct C++ header files with exact memory alignments, padding, and function declarations.
Module 3: Installing & Executing Il2CppInspector (CLI & GUI)
3.1 Downloading the Toolchain
- Download the latest release of Il2CppInspector from the official GitHub repository (created by djkaty).
- Extract the archive to a working directory (e.g.,
C:\Tools\Il2CppInspector).
3.2 Command-Line Generation of C++ Scaffolding
To generate a complete, compilable Visual Studio C++ solution, open a command prompt or terminal and execute:
Il2CppInspector.exe -b "C:\Games\TargetGame\GameAssembly.dll" -m "C:\Games\TargetGame\TargetGame_Data\il2cpp_data\Metadata\global-metadata.dat" -c "C:\ModProjects\TargetGameScaffold"
Key command-line parameters:
-b <path>: Path to the game's executable binary (GameAssembly.dll on Windows, libil2cpp.so on Android).
-m <path>: Path to the metadata file (global-metadata.dat).
-c <path>: Generate a full C++ scaffolding project at the specified directory.
-p <path>: Generate Python scripts for Ghidra / IDA Pro.
-d <path>: Generate standard C# dump.cs.
3.3 The Generated File Hierarchy
Once Il2CppInspector completes its analysis, the output directory contains the following project layout:
TargetGameScaffold/
├── TargetGameScaffold.sln <-- Visual Studio Solution File
├── TargetGameScaffold.vcxproj <-- Visual Studio Project Configuration
├── appdata/
│ ├── il2cpp-api-functions.h <-- Exported Unity IL2CPP runtime APIs
│ ├── il2cpp-functions.h <-- Function pointer typedefs for all game methods
│ ├── il2cpp-types.h <-- C++ Structs, Classes, Enums, and Offsets
│ ├── il2cpp-types-ptr.h <-- Pointer typedefs for complex structures
│ ├── il2cpp-init.h <-- Scaffolding initialization header
│ └── il2cpp-init.cpp <-- Dynamic base address & RVA resolution logic
├── framework/
│ ├── helpers.h <-- Helper macros and string conversion routines
│ ├── il2cpp-appdata.h <-- Master umbrella include header
│ └── il2cpp-init.h <-- Framework bootstrapping stubs
└── user/
└── main.cpp <-- Your C++ DLL entrypoint and custom mod code!
Module 4: Deep Dive into the Generated C++ Scaffold Files
Understanding how Il2CppInspector organizes generated code is essential for navigating classes and writing hooks.
4.1 Analyzing appdata/il2cpp-types.h
il2cpp-types.h contains the C++ struct representation of every class in the game.
Let us inspect how a sample PlayerHealth class is structured:
// Defined inside namespace app
namespace app {
// Struct field definitions
struct PlayerHealth__Fields {
struct MonoBehaviour__Fields _; // Base class fields inheritance
float currentHealth; // Field offset: 0x18
float maxHealth; // Field offset: 0x1C
bool isInvulnerable; // Field offset: 0x20
struct Transform* playerTransform;// Field offset: 0x28 (Pointer to native Transform)
};
// Full object layout
struct PlayerHealth {
struct PlayerHealth__Class* klass; // Pointer to Il2CppClass runtime descriptor
MonitorData* monitor; // Synchronization monitor
PlayerHealth__Fields fields; // Actual data fields
};
// Class metadata and VTable layout
struct PlayerHealth__Class {
Il2CppClass_0 _0;
Il2CppRuntimeInterfaceOffsetPair* interfaceOffsets;
struct PlayerHealth__StaticFields* static_fields;
const Il2CppRGCTXData* rgctx_data;
Il2CppClass_1 _1;
struct PlayerHealth__VTable vtable; // Virtual method table
};
}
Notice how cleanly the memory layout is mirrored:
- Base class data is embedded at the top of
PlayerHealth__Fields, guaranteeing exact byte-for-byte binary alignment.
- When you have a pointer
PlayerHealth* player, accessing player->fields.currentHealth accesses the exact memory offset 0x18.
4.2 Analyzing appdata/il2cpp-functions.h
il2cpp-functions.h defines macro declarations for every compiled function in the game:
// Format: DO_APP_FUNC(RVA, ReturnType, FunctionName, (ParameterList))
DO_APP_FUNC(0x004A8B20, void, PlayerHealth_TakeDamage, (app::PlayerHealth __this, float amount, const MethodInfo method));
DO_APP_FUNC(0x004A9100, bool, PlayerHealth_CanHeal, (app::PlayerHealth __this, const MethodInfo method));
DO_APP_FUNC(0x004A9450, void, PlayerHealth_Die, (app::PlayerHealth __this, const MethodInfo method));
DO_APP_FUNC(0x005B1200, app::GameObject, GameObject_Find, (app::String name, const MethodInfo method));
DO_APP_FUNC(0x005B1450, app::Transform, GameObject_get_transform, (app::GameObject __this, const MethodInfo method));
What does DO_APP_FUNC do?
At compile time, it expands into a function pointer typedef:
typedef void (PlayerHealth_TakeDamage_t)(app::PlayerHealth __this, float amount, const MethodInfo* method);
extern PlayerHealth_TakeDamage_t PlayerHealth_TakeDamage;
4.3 Analyzing appdata/il2cpp-init.cpp
il2cpp-init.cpp contains the runtime loader that locates GameAssembly.dll in memory, iterates all DO_APP_FUNC declarations, adds the base address to each RVA, and binds the function pointer:
include "il2cpp-appdata.h"
include <windows.h></windows.h>
namespace app {
// Function pointer definitions
define DO_APP_FUNC(a, r, n, p) n ## _t n = nullptr
#include "il2cpp-functions.h"
#undef DO_APP_FUNC
void Init() {
// 1. Fetch base address of GameAssembly.dll in current process
uintptr_t baseAddress = reinterpret_cast<uintptr_t>(GetModuleHandleA("GameAssembly.dll"));
// 2. Resolve every function pointer by adding RVA to base address
#define DO_APP_FUNC(a, r, n, p) n = reinterpret_cast<n ##="" _t="">(baseAddress + a)
#include "il2cpp-functions.h"
#undef DO_APP_FUNC
}</n></uintptr_t>
}
When you call app::Init() inside your DLL startup, every function pointer—such as app::PlayerHealth_TakeDamage—becomes immediately callable!
Module 5: Setting Up Visual Studio 2022 for Native C++ IL2CPP Development
Let us configure the Visual Studio 2022 development environment for building native DLLs.
5.1 Project Properties Configuration
Open TargetGameScaffold.sln in Visual Studio 2022. Verify and configure the following settings:
- Configuration: Set to Release and x64 (or x86 for 32-bit games).
- C++ Language Standard: Navigate to Configuration Properties -> C/C++ -> Language -> C++ Language Standard and select ISO C++17 Standard (/std:c++17) or ISO C++20 Standard (/std:c++20).
- Runtime Library: Navigate to C/C++ -> Code Generation -> Runtime Library and select Multi-threaded DLL (/MD) for Release builds.
- Precompiled Headers: Set C/C++ -> Precompiled Headers -> Precompiled Header to Not Using Precompiled Headers.
- Character Set: Set General -> Character Set to Use Multi-Byte Character Set or Use Unicode Character Set.
5.2 Integrating MinHook for Native Function Detouring
MinHook is the industry-standard, lightweight x86/x64 hooking library for Windows.
To integrate MinHook into your Visual Studio project:
- Install MinHook via NuGet: Right-click project -> Manage NuGet Packages -> Search minhook -> Install.
- Alternatively, clone the MinHook GitHub repository and add
MinHook.h and libMinHook.x64.lib to your project include/linker paths.
Module 6: Initializing the IL2CPP Domain and Worker Threads
When your native DLL is injected into the game, Windows executes DllMain.
CRITICAL ARCHITECTURAL RULE: If you create a new Win32 background thread (CreateThread) and attempt to invoke IL2CPP engine functions without attaching to the IL2CPP domain, the process will instantly crash with a fatal 0xC0000005: Access Violation!
To safely call Unity APIs from your background thread, you must call il2cpp_thread_attach():
include <windows.h></windows.h>
include <iostream></iostream>
include "il2cpp-appdata.h"
void WorkerThread(HMODULE hModule) {
// 1. Allocate a debug console for output
AllocConsole();
FILE* f;
freopen_s(&f, "CONOUT$", "w", stdout);
freopen_s(&f, "CONOUT$", "w", stderr);
std::cout << "[UnreliableCode] Native DLL injected successfully!\n";
// 2. Wait until GameAssembly.dll is fully loaded and initialized
while (GetModuleHandleA("GameAssembly.dll") == nullptr) {
Sleep(100);
}
// 3. Initialize Il2CppInspector function pointers
app::Init();
std::cout << "[UnreliableCode] Il2Cpp function pointers bound.\n";
// 4. CRITICAL: Attach current Win32 thread to IL2CPP Domain
Il2CppDomain* domain = app::il2cpp_domain_get();
if (domain != nullptr) {
app::il2cpp_thread_attach(domain);
std::cout << "[UnreliableCode] Worker thread attached to IL2CPP domain!\n";
}
// Now it is 100% safe to call any Unity API or manipulate objects!
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
if (ul_reason_for_call == DLL_PROCESS_ATTACH) {
DisableThreadLibraryCalls(hModule);
CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)WorkerThread, hModule, 0, nullptr);
}
return TRUE;
}
Module 7: Working with IL2CPP Types and Objects in Native C++
7.1 Creating and Converting Strings
In IL2CPP, strings are managed objects of type app::String (wrapping a 16-bit UTF-16 wchar array).
Converting std::string / C-String to Il2Cpp String
// Creates a new Il2Cpp String object in the engine
app::String CreateIl2CppString(const char text) {
return app::il2cpp_string_new(text);
}
Reading an Il2Cpp String to std::string
include <string></string>
include <locale></locale>
include
std::string Il2CppStringToStdString(app::String* il2cppStr) {
if (il2cppStr == nullptr || il2cppStr->fields.m_firstChar == 0) {
return "";
}
// il2cpp string characters are wchar_t (UTF-16)
const wchar_t* chars = reinterpret_cast<const wchar_t*="">(&il2cppStr->fields.m_firstChar);
int length = il2cppStr->fields.m_stringLength;
std::wstring wstr(chars, length);
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
return converter.to_bytes(wstr);</std::codecvt_utf8_utf16<wchar_t></const>
}
7.2 Working with Native Arrays
In IL2CPP, an array is represented by app::Array (or typed variations like app::GameObject__Array).
The array struct contains:
max_length: Number of elements.
vector: Contiguous array of pointers or values.
void IterateAllEnemies(app::GameObject__Array* enemyArray) {
if (enemyArray == nullptr) return;
// Number of elements in array
il2cpp_array_size_t count = enemyArray->max_length;
std::cout << "[NativeMod] Found " << count << " enemies in array.\n";
for (il2cpp_array_size_t i = 0; i < count; ++i) {
app::GameObject* enemy = enemyArray->vector[i];
if (enemy != nullptr) {
std::string name = Il2CppStringToStdString(enemy->fields.name);
std::cout << " Enemy [" << i << "]: " << name << "\n";
}
}
}
Module 8: Native Function Hooking with MinHook in the Scaffold
Let us now implement native detours using MinHook.
8.1 Step-by-Step Detour Pattern
- Declare a typed storage pointer for the Original (Trampoline) function.
- Write your Detour Function matching the exact signature.
- Call
MH_Initialize().
- Call
MH_CreateHook() passing the target function pointer, detour function pointer, and trampoline storage address.
- Call
MH_EnableHook().
8.2 Practical Example: God Mode & Damage Multiplier Detour
include "il2cpp-appdata.h"
include <minhook.h></minhook.h>
include <iostream></iostream>
// 1. Storage for original trampoline function pointer
app::PlayerHealth_TakeDamage_t Original_PlayerHealth_TakeDamage = nullptr;
// Cheat Toggles
bool g_GodModeEnabled = true;
float g_DamageMultiplier = 2.5f;
// 2. Custom Detour Function
void Hooked_PlayerHealth_TakeDamage(app::PlayerHealth __this, float amount, const MethodInfo method) {
if (__this == nullptr) return;
std::cout << "[Hook] TakeDamage called! Original amount: " << amount << "\n";
// If God Mode is active on the local player, nullify damage
if (g_GodModeEnabled) {
std::cout << "[GodMode] Blocked incoming damage of " << amount << "!\n";
return; // Skip original method entirely!
}
// Otherwise, apply custom multiplier and forward to original function
float modifiedAmount = amount * g_DamageMultiplier;
Original_PlayerHealth_TakeDamage(__this, modifiedAmount, method);
}
// 3. Hook Initialization
void InstallCombatHooks() {
// Initialize MinHook engine
if (MH_Initialize() != MH_OK) {
std::cout << "[Error] Failed to initialize MinHook!\n";
return;
}
// Create detour on PlayerHealth_TakeDamage
void* targetFunction = reinterpret_cast<void*>(app::PlayerHealth_TakeDamage);
if (MH_CreateHook(targetFunction, &Hooked_PlayerHealth_TakeDamage, reinterpret_cast<void**>(&Original_PlayerHealth_TakeDamage)) == MH_OK) {
MH_EnableHook(targetFunction);
std::cout << "[MinHook] Successfully detoured PlayerHealth_TakeDamage!\n";
} else {
std::cout << "[Error] Failed to hook PlayerHealth_TakeDamage!\n";
}</void**></void*>
}
Module 9: Complete Hands-On Native C++ Projects
Let us build five complete, production-grade native mod projects in C++.
Project 1: Direct Native Unity Transform & Speedhack Controller
This module demonstrates finding the player GameObject in native memory, extracting its Transform, and modifying position/velocity in real-time.
include "il2cpp-appdata.h"
include <iostream></iostream>
namespace Features {
void ApplySpeedhack(float multiplier) {
// 1. Find local player GameObject via native engine API
app::String* playerName = app::il2cpp_string_new("LocalPlayer");
app::GameObject* playerObj = app::GameObject_Find(playerName, nullptr);
if (playerObj == nullptr) {
return;
}
// 2. Extract Transform component
app::Transform* playerTransform = app::GameObject_get_transform(playerObj, nullptr);
if (playerTransform == nullptr) {
return;
}
// 3. Read current 3D position struct
app::Vector3 currentPos = app::Transform_get_position(playerTransform, nullptr);
// 4. Check keyboard input via native Unity Input API
// KeyCode::W = 119, S = 115, A = 97, D = 100
if (app::Input_GetKey(app::KeyCode__Enum::W, nullptr)) {
app::Vector3 forward = app::Transform_get_forward(playerTransform, nullptr);
currentPos.x += forward.x * multiplier * app::Time_get_deltaTime(nullptr);
currentPos.y += forward.y * multiplier * app::Time_get_deltaTime(nullptr);
currentPos.z += forward.z * multiplier * app::Time_get_deltaTime(nullptr);
// Write back modified position struct
app::Transform_set_position(playerTransform, currentPos, nullptr);
}
}
void TeleportForward(float distance) {
app::GameObject* playerObj = app::GameObject_Find(app::il2cpp_string_new("LocalPlayer"), nullptr);
if (playerObj == nullptr) return;
app::Transform* transform = app::GameObject_get_transform(playerObj, nullptr);
if (transform == nullptr) return;
app::Vector3 pos = app::Transform_get_position(transform, nullptr);
app::Vector3 forward = app::Transform_get_forward(transform, nullptr);
pos.x += forward.x * distance;
pos.y += forward.y * distance;
pos.z += forward.z * distance;
app::Transform_set_position(transform, pos, nullptr);
std::cout << "[Teleport] Teleported player forward by " << distance << " meters.\n";
}
}
Project 2: Native Weapon Controller Detours (Infinite Ammo & No Recoil)
include "il2cpp-appdata.h"
include <minhook.h></minhook.h>
include <iostream></iostream>
namespace WeaponMods {
app::WeaponController_Fire_t Original_WeaponController_Fire = nullptr;
app::WeaponController_ApplyRecoil_t Original_WeaponController_ApplyRecoil = nullptr;
bool g_InfiniteAmmo = true;
bool g_NoRecoil = true;
// Detour for WeaponController.Fire
void Hooked_WeaponController_Fire(app::WeaponController* __this, const MethodInfo* method) {
if (__this != nullptr && g_InfiniteAmmo) {
// Keep current ammo pinned to max capacity
__this->fields.currentAmmo = __this->fields.maxAmmo;
}
// Execute original weapon firing logic
Original_WeaponController_Fire(__this, method);
}
// Detour for WeaponController.ApplyRecoil
void Hooked_WeaponController_ApplyRecoil(app::WeaponController* __this, const MethodInfo* method) {
if (g_NoRecoil) {
// Block recoil animation and muzzle climb entirely!
return;
}
Original_WeaponController_ApplyRecoil(__this, method);
}
void InstallWeaponHooks() {
void* fireAddr = reinterpret_cast<void*>(app::WeaponController_Fire);
void* recoilAddr = reinterpret_cast<void*>(app::WeaponController_ApplyRecoil);
MH_CreateHook(fireAddr, &Hooked_WeaponController_Fire, reinterpret_cast<void**>(&Original_WeaponController_Fire));
MH_CreateHook(recoilAddr, &Hooked_WeaponController_ApplyRecoil, reinterpret_cast<void**>(&Original_WeaponController_ApplyRecoil));
MH_EnableHook(fireAddr);
MH_EnableHook(recoilAddr);
std::cout << "[WeaponMods] Weapon detours activated.\n";
}</void**></void**></void*></void*>
}
Project 3: Hardware-Accelerated Dear ImGui Overlay (DirectX 11 Present Hook)
By hooking IDXGISwapChain::Present, we can render a high-performance, dark-neon Dear ImGui interface directly inside the game window.
include <windows.h></windows.h>
include <d3d11.h></d3d11.h>
include <dxgi.h></dxgi.h>
include <iostream></iostream>
include "il2cpp-appdata.h"
include <minhook.h></minhook.h>
// Dear ImGui Headers
include <imgui.h></imgui.h>
include <imgui_impl_win32.h></imgui_impl_win32.h>
include <imgui_impl_dx11.h></imgui_impl_dx11.h>
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
typedef HRESULT(__stdcall Present_t)(IDXGISwapChain pSwapChain, UINT SyncInterval, UINT Flags);
Present_t Original_Present = nullptr;
ID3D11Device g_pd3dDevice = nullptr;
ID3D11DeviceContext g_pd3dContext = nullptr;
ID3D11RenderTargetView* g_mainRenderTargetView = nullptr;
HWND g_hwnd = nullptr;
WNDPROC g_originalWndProc = nullptr;
bool g_imguiInitialized = false;
bool g_showMenu = true;
// Window message handler hook
LRESULT CALLBACK Hooked_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
if (uMsg == WM_KEYDOWN && wParam == VK_INSERT) {
g_showMenu = !g_showMenu;
return 0;
}
if (g_showMenu && ImGui_ImplWin32_WndProcHandler(hWnd, uMsg, wParam, lParam)) {
return true;
}
return CallWindowProc(g_originalWndProc, hWnd, uMsg, wParam, lParam);
}
// Hooked Present Function (Called on every rendered frame)
HRESULT stdcall Hooked_Present(IDXGISwapChain* pSwapChain, UINT SyncInterval, UINT Flags) {
if (!g_imguiInitialized) {
if (SUCCEEDED(pSwapChain->GetDevice(uuidof(ID3D11Device), (void**)&g_pd3dDevice))) {
g_pd3dDevice->GetImmediateContext(&g_pd3dContext);
DXGI_SWAP_CHAIN_DESC desc;
pSwapChain->GetDesc(&desc);
g_hwnd = desc.OutputWindow;
ID3D11Texture2D* pBackBuffer = nullptr;
pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);
if (pBackBuffer != nullptr) {
g_pd3dDevice->CreateRenderTargetView(pBackBuffer, nullptr, &g_mainRenderTargetView);
pBackBuffer->Release();
}
g_originalWndProc = (WNDPROC)SetWindowLongPtr(g_hwnd, GWLP_WNDPROC, (LONG_PTR)Hooked_WndProc);
// Initialize Dear ImGui
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO();
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
// Apply custom dark neon cyber style
ImGui::StyleColorsDark();
ImGuiStyle& style = ImGui::GetStyle();
style.WindowRounding = 8.0f;
style.FrameRounding = 5.0f;
style.Colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.08f, 0.12f, 0.94f);
style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.0f, 0.5f, 0.8f, 1.0f);
style.Colors[ImGuiCol_Button] = ImVec4(0.1f, 0.3f, 0.5f, 0.8f);
ImGui_ImplWin32_Init(g_hwnd);
ImGui_ImplDX11_Init(g_pd3dDevice, g_pd3dContext);
g_imguiInitialized = true;
}
}
if (g_imguiInitialized) {
ImGui_ImplDX11_NewFrame();
ImGui_ImplWin32_NewFrame();
ImGui::NewFrame();
if (g_showMenu) {
ImGui::SetNextWindowSize(ImVec2(480, 400), ImGuiCond_FirstUseEver);
ImGui::Begin("UNRELIABLECODE // Native C++ IL2CPP Master Menu", &g_showMenu);
ImGui::TextColored(ImVec4(0.0f, 0.8f, 1.0f, 1.0f), "Runtime Architecture: Pure Native C++ x64");
ImGui::Separator();
static bool godMode = true;
static bool infAmmo = true;
static float speedMult = 2.0f;
ImGui::Checkbox("Invulnerability (God Mode)", &godMode);
ImGui::Checkbox("Infinite Weapon Ammo", &infAmmo);
ImGui::SliderFloat("Speed Multiplier", &speedMult, 1.0f, 10.0f);
if (ImGui::Button("Teleport 10m Forward", ImVec2(200, 30))) {
Features::TeleportForward(10.0f);
}
ImGui::End();
}
ImGui::Render();
g_pd3dContext->OMSetRenderTargets(1, &g_mainRenderTargetView, nullptr);
ImGui_ImplDX11_RenderDrawData(ImGui::GetDrawData());
}
return Original_Present(pSwapChain, SyncInterval, Flags);
}
Module 10: Handling Stripped Metadata, Obfuscation & Dynamic Anti-Tamper
In commercial Unity titles, game developers frequently deploy anti-tamper mechanisms or metadata encryption to impede reverse engineering.
10.1 Memory Dumping Encrypted Metadata
If global-metadata.dat is encrypted on disk (yielding errors in Il2CppInspector):
- The game must decrypt the metadata in RAM before passing it to
il2cpp_init.
- Set a hardware breakpoint on
il2cpp::vm::MetadataCache::Initialize() or il2cpp_init inside x64dbg / Cheat Engine.
- Once the breakpoint hits, inspect the pointer argument pointing to the decrypted metadata buffer in memory.
- Dump the memory buffer to disk as
decrypted_metadata.dat and feed it to Il2CppInspector!
10.2 Inline Assembly Patching & Code Caves
If a function cannot be hooked via standard trampolines (e.g. anti-hook integrity checks scan function prologues), use Windows VirtualProtect to patch opcodes directly or create a mid-function code cave:
void PatchMemoryNop(uintptr_t targetAddress, size_t byteCount) {
DWORD oldProtect;
VirtualProtect(reinterpret_cast<void*>(targetAddress), byteCount, PAGE_EXECUTE_READWRITE, &oldProtect);</void*>
// Fill with NOP opcodes (0x90)
memset(reinterpret_cast<void*>(targetAddress), 0x90, byteCount);
VirtualProtect(reinterpret_cast<void*>(targetAddress), byteCount, oldProtect, &oldProtect);
std::cout << "[Memory] Patched " << byteCount << " bytes at 0x" << std::hex << targetAddress << "\n";</void*></void*>
}
Module 11: Compilation, Injection, and Deployment
11.1 Building the DLL in Visual Studio
- In Visual Studio, select Release | x64.
- Press Build Solution (Ctrl + Shift + B).
- The compiled binary will be generated at
TargetGameScaffold\x64\Release\TargetGameScaffold.dll.
11.2 Injection Methods
Method 1: Sideloading via Proxy DLL (Stealth & Automatic)
Rename your compiled DLL to a known Windows dependency loaded by the game:
version.dll
winhttp.dll
dxgi.dll
Place the file next to Game.exe. When the game launches, Windows automatically injects your DLL!
Method 2: Manual Injection
Use an injector tool (e.g., Xenos, Cheat Engine Injector, or Process Hacker) to inject TargetGameScaffold.dll into the active game process.
Module 12: Debugging, Troubleshooting & Native Exception Handling
12.1 Common Native Crash Codes & Root Causes
| Exception Code |
Error Name |
Root Cause & Resolution |
| 0xC0000005 |
STATUS_ACCESS_VIOLATION |
Calling IL2CPP functions from a Win32 thread without calling il2cpp_thread_attach(), or dereferencing a null object pointer. Guard with if (ptr != nullptr). |
| 0xC000001D |
STATUS_ILLEGAL_INSTRUCTION |
MinHook trampoline overwritten partially or executing non-aligned instruction bytes. Ensure target function is at least 5 bytes long. |
| 0x80070057 |
E_INVALIDARG in DirectX |
Present hook executed before swapchain backbuffer is fully initialized. Guard with null checks on g_mainRenderTargetView. |
12.2 Protecting Mod Logic with Structured Exception Handling (try / except)
Wrap experimental pointer dereferences inside Windows SEH to prevent game crashes:
bool SafeReadPlayerHealth(app::PlayerHealth player, float outHealth) {
try {
if (player != nullptr) {
*outHealth = player->fields.currentHealth;
return true;
}
}
except (EXCEPTION_EXECUTE_HANDLER) {
std::cout << "[Warning] Caught native access violation in SafeReadPlayerHealth!\n";
}
return false;
}
Quick Reference API Cheat Sheet
// 1. RESOLVE BASE ADDRESS & INIT
app::Init();
// 2. ATTACH THREAD TO DOMAIN
Il2CppDomain* domain = app::il2cpp_domain_get();
app::il2cpp_thread_attach(domain);
// 3. CREATE & READ STRINGS
app::String myStr = app::il2cpp_string_new("PlayerEntity");
const wchar_t rawChars = reinterpret_cast<const wchar_t*="">(&myStr->fields.m_firstChar);</const>
// 4. FIND GAMEOBJECT
app::GameObject* go = app::GameObject_Find(myStr, nullptr);
// 5. GET TRANSFORM & POSITION
app::Transform* transform = app::GameObject_get_transform(go, nullptr);
app::Vector3 pos = app::Transform_get_position(transform, nullptr);
// 6. MINHOOK CREATION
MH_Initialize();
MH_CreateHook((void*)app::PlayerHealth_TakeDamage, &Hooked_TakeDamage, (void**)&Original_TakeDamage);
MH_EnableHook(MH_ALL_HOOKS);
Conclusion & Next Steps
Developing native C++ mods for Unity IL2CPP games using Il2CppInspector and C++ Scaffolding provides unmatched execution performance, low-level memory control, and seamless integration with hardware-accelerated overlays like Dear ImGui. By mastering binary RVA resolution, MinHook detours, and thread domain attachment, you are equipped to build professional-grade reverse engineering tools and native modifications for any modern Unity game.
Happy coding, and reverse engineer responsibly!
Authored by the UnreliableCode Engineering Team for unreliablecode.net