Open a Unity game APK in JADX and you will find almost no game code. Unity’s IL2CPP scripting backend compiles all C# into native machine code, so the DEX files contain only a thin loader while the real logic lives in a native library. Recovering that logic requires a different tool: Il2CppDumper, which rebuilds the full C# API surface from the metadata the build ships with.
TL;DR: Unity IL2CPP apps hide their code in native libraries, not DEX bytecode. Extract two files from the APK — the native library (
libil2cpp.so) andglobal-metadata.dat— feed them to Il2CppDumper, and you get back adump.cswith every class and method name, plus DLLs you can open in dnSpy or ILSpy. This post walks through the whole workflow, with what to do when the metadata is encrypted.
A note on scope: reverse engineering is a legitimate learning and research practice. Only analyze apps you own or have permission to analyze, and respect the licenses of the tools and software involved.
Why JADX comes back empty on Unity games
Most Android apps ship their logic as DEX bytecode, which tools like JADX turn back into readable Java. Unity games using the IL2CPP scripting backend are different: the C# game code is compiled ahead-of-time into native C++, packaged as libil2cpp.so inside the APK’s lib/ directory. JADX will decompile the APK fine — it just won’t find the game logic, because there is no DEX bytecode to decompile. (This is exactly the “when it won’t work” case from the JADX guide.)
The good news is that IL2CPP builds ship a second file, global-metadata.dat, which still contains every class name, method name, and field name from the original C# code. Il2CppDumper reads that metadata and reconstructs the full managed type system — turning anonymous native functions back into named methods like PlayerController.TakeDamage.
The two files you need
Unzip the APK (an APK is just a ZIP archive) and locate:
- The native library:
lib/arm64-v8a/libil2cpp.so— the compiled game code. On 32-bit builds it lives underlib/armeabi-v7a/instead. This is the “executable file” Il2CppDumper asks for. - The metadata:
assets/bin/Data/Managed/Metadata/global-metadata.dat— the class and method names. The exact path can vary slightly between Unity versions, but it is always underassets/.
You can pull an installed APK off a device with adb shell pm path <package> followed by adb pull, or work from an APK file you already have.
Step-by-step: dumping an IL2CPP APK
- Get Il2CppDumper. Download a release build; it supports ELF, ELF64, Mach-O, PE, NSO, and WASM formats, and Unity versions from 5.3 through 2022.2. There are also maintained forks that extend support to newer Unity releases.
- Run the dump. Point it at the two files and an output directory:
Il2CppDumper.exe libil2cpp.so global-metadata.dat ./dump-outputThe tool may ask for the exact Unity version the game was built with. If you don’t know it, check the APK for version strings — many builds include a
unity_builtin_extrafile or embed the version in the library itself. - Choose an extraction mode. The interactive prompt offers several modes; Auto(Plus) is the recommended starting point. If the output looks wrong, try a different mode — results vary by Unity version.
- Inspect the output directory. A successful run produces the artifacts in the table below.
What Il2CppDumper gives you
| Output | What it is | What to do with it |
|---|---|---|
dump.cs |
The full recovered C# API surface: every class, method signature, and field | Search it with grep or a text editor to find interesting classes and method names |
DummyDll/ |
Restored .NET DLLs built from the metadata | Open in dnSpy or ILSpy to browse the API like normal .NET assemblies |
ida.py / ida_with_struct.py |
IDA Pro scripts that rename functions and apply structure info | Run in IDA after loading libil2cpp.so to turn sub_XXXX into real method names |
ghidra.py |
Ghidra equivalent of the IDA script | Run in Ghidra with the native library loaded |
il2cpp.h |
C header with recovered structure definitions | Feed to ida_with_struct.py or use as a reference for native analysis |
script.json |
Machine-readable dump data | Drive the IDA/Ghidra scripts, or parse it with your own tooling |
stringliteral.json |
Every string literal in the binary | Grep for URLs, API endpoints, and hardcoded secrets |
Reading the recovered code
Start with dump.cs. It is a large file, but it is searchable — look for class names related to what you want to understand (networking, purchases, save data). Note that Il2CppDumper restores declarations, not method bodies; the actual logic still lives in the native code. The dump tells you what exists and how it connects, which is usually enough to navigate.
For a browsable view, open the DummyDll folder in ILSpy or dnSpy. These are stub assemblies — method bodies are empty — but the type hierarchy, inheritance, and cross-references are intact, which makes exploring far easier than grepping a flat file.
To read actual logic, load libil2cpp.so into Ghidra or IDA, run the generated script, and the disassembly gets annotated with real method and class names. This is where reverse engineering proper begins: you go from staring at anonymous subroutines to reading named game code in assembly.
Don’t overlook stringliteral.json. It contains every string the binary references — endpoints, error messages, configuration keys. A quick search there often answers “where does this app talk to?” faster than any disassembly.
When the metadata is encrypted
Some builds — particularly games with anti-tamper protection — encrypt or obfuscate global-metadata.dat. Il2CppDumper will fail on these files, and no extraction mode will fix it because the input itself is unreadable.
The standard workaround is to dump the metadata from memory at runtime: launch the app (on a rooted device or emulator), wait until the loader has decrypted the metadata into memory, then dump the process memory and carve out the decrypted global-metadata.dat. Frida is the usual tool for this — attach to the process, locate the decrypted metadata region after the metadata-load completes, and write it to disk. Only then will Il2CppDumper accept it as input.
This is the point where static analysis hands off to dynamic analysis. If you are not set up for that yet, treat encrypted metadata as a hard stop rather than guessing at decryption.
Il2CppDumper vs. Cpp2IL
| Il2CppDumper | Cpp2IL | |
|---|---|---|
| Primary output | dump.cs declarations, stub DLLs, IDA/Ghidra scripts |
Decompiled DLLs you can open directly |
| Best for | Navigating the full API surface and annotating native disassembly | Quickly getting DLLs into dnSpy with a one-liner (--just-give-me-dlls-asap-dammit) |
| Native analysis support | Yes — generates scripts and headers for IDA, Ghidra, Binary Ninja | Limited — focused on the managed side |
| Encrypted metadata | Requires a decrypted global-metadata.dat (dump from memory first) |
Same requirement |
They are complementary, not competing: use Cpp2IL when you want DLLs fast, Il2CppDumper when you want the full picture including native-side annotation.
Key takeaways
- JADX can’t help with IL2CPP games — the code is native, not DEX. Recognize this early by checking for
libil2cpp.soin the APK’slib/directory. - You only need two files:
libil2cpp.soandglobal-metadata.dat. Everything else is derived. - The dump restores names, not logic.
dump.csand the stub DLLs map the territory; reading behavior still means native disassembly with the generated scripts applied. stringliteral.jsonis the fastest win for finding endpoints and hardcoded values without touching a disassembler.- Encrypted metadata means going dynamic. Dump it from memory at runtime with Frida rather than fighting the encryption statically.
References
- Modding Unity IL2CPP games — jakzo’s blog: Cpp2IL and Il2CppDumper workflow with Ghidra
- Unity / IL2CPP — IDA reverse engineering skill reference: metadata recovery and encrypted-metadata handling
- How to extract C# Code from Unity APK — dev.to: the classic Il2CppDumper APK workflow
- Il2CppDumper — usage, supported formats, and output reference
Was this useful?
Thanks for the feedback.