IL2CPP Compatibility
Some protections can’t survive a Unity IL2CPP build. Mark those with [IL2CPPIncompatible] and
BitMono skips them automatically whenever it runs in IL2CPP mode, while still running them normally on
Mono and standalone .NET.
[IL2CPPIncompatible("Emits calli, which IL2CPP's AOT compiler does not support")]
public class CallToCalli : Protection
The reason you pass is shown to the user, so they understand why it was skipped:
[IL2CPP] CallToCalli - Emits calli, which IL2CPP's AOT compiler does not support
Like the runtime moniker, the attribute is read by reflection, you
don’t register anything. Add it on top of the protection (built-in or a plugin) and
you’re done. IL2CPP mode is set automatically by the Unity integration when the scripting backend is
IL2CPP, or by hand with the CLI --il2cpp flag / "IL2CPP": true in obfuscation.json.
Should I mark my protection?
On an IL2CPP build the managed Assembly-CSharp.dll isn’t shipped, il2cpp.exe consumes it and
converts it to C++ (GameAssembly.dll). BitMono obfuscates before that conversion, so where your
protection’s output ends up decides this:
Don’t mark it if it only does managed metadata/IL edits
il2cpp.execan parse, renaming, clearing namespaces, swappingldstrfor a managed decryptor call. That effect carries through intoglobal-metadata.dat, which is exactly what you want.Mark it if it emits native code,
calli, packs the PE, or otherwise produces outputil2cpp.execan’t handle. It would either break the conversion or only touch the managed PE that IL2CPP throws away.
Note
Protections marked with Native Code ([ConfigureForNativeCode]) are treated as
IL2CPP-incompatible automatically, native method bodies can never become C++, so you don’t need to add
[IL2CPPIncompatible] on top of them as well.
See Unity Protections for which built-in protections run on IL2CPP and which are skipped.
Encrypting the IL2CPP output (global-metadata.dat)
Everything above is about the managed side, renaming and string encryption that land inside
global-metadata.dat because BitMono runs before il2cpp.exe. That already cloaks your names, but the
global-metadata.dat file itself is still a normal, parseable file, so tools like Il2CppDumper and Cpp2IL
happily read its structure straight off disk.
The IL2CPP metadata encryption protection (issue #276) closes that. When you turn it on, BitMono encrypts
global-metadata.dat in the built player so static dumpers can’t parse it at all, and a tiny native
decryptor that BitMono compiles into the IL2CPP binary restores it in memory at startup. The game boots
exactly as before; the dumpers just see noise.
It’s a separate, independent layer from the managed obfuscation, you can use either or both. Turn it on in Unity from the BitMonoConfig asset: tick Encrypt IL2CPP Metadata. Works on Windows x64 and Android (arm64/x86_64).
BitMonoConfig
[x] Enable Obfuscation (managed renaming/strings, runs before il2cpp.exe)
[x] Encrypt IL2CPP Metadata (encrypts global-metadata.dat, decrypts in GameAssembly.dll)
Under the hood it’s two halves that share a key BitMono generates fresh for every build:
Offline: after the player is built, BitMono runs
BitMono.CLI --encrypt-metadata global-metadata.dat --metadata-key <hex>. That XXTEA-encrypts the whole file behind a small header and self-checks the round-trip. You can run it by hand for CI builds too (omit--metadata-keyto use the fixed dev key).Runtime: the source plugin
global_metadata_decrypt.cpp(shipped in the Unity package) is compiled into the IL2CPP binary -GameAssembly.dllon Windows,libil2cpp.soon Android - and hooks the file read ofglobal-metadata.dat(CreateFileWon Windows,openon Android) to hand IL2CPP the decrypted bytes. It’s only compiled in when you turn the feature on, so a plain build ships no hook at all.
Note
This stops static dumping, the shipped global-metadata.dat is unreadable, so anything that parses
the file off disk is dead in the water. It does not stop a memory dumper that reads the already
decrypted bytes out of the running process; nothing that ships the key in the binary can. Treat it as one
more wall on top of the managed renaming, not a magic bullet. The key ships inside the IL2CPP binary so
it’s obfuscation strength, not a secret - but it’s random per build, so cracking one game’s key doesn’t
unlock every other BitMono game.
Validated end to end on real Unity 6000.2 IL2CPP builds (metadata version 31): Windows x64, and Android x86_64 on an emulator (arm64 shares the same 64-bit code path). The encryption is whole-file, so it doesn’t care about the per-version metadata layout; only the decryptor’s file hook is platform-specific.
Renaming the IL2CPP exports
Metadata encryption blocks static dumpers, but a runtime dumper (Zygisk-Il2CppDumper, frida-il2cpp-bridge)
attaches to the live game and walks GameAssembly.dll’s exported il2cpp_* functions to drive the
runtime and reconstruct everything from memory. The Rename IL2CPP Exports toggle takes those away: after
the build, BitMono mangles every il2cpp_* export (il2cpp_init -> Z6f8fdaf3 and friends), so a
dumper scanning the export table finds nothing it recognises.
The game still needs them, though - UnityPlayer resolves the IL2CPP API by name with GetProcAddress. So
the same decryptor plugin installs a GetProcAddress hook: when UnityPlayer asks for il2cpp_init it
computes the same mangled name (same per-build key) and hands back the renamed export. The mangling is a
keyed hash, so it’s per-build like the metadata key.
Windows x64 only for now (it rewrites the PE export table via AsmResolver; Android’s ELF version is a
follow-up). It’s a separate toggle from metadata encryption - use either or both. Validated end to end on
Unity 6000.2: after renaming, il2cpp_init is gone from the export table but the game boots fine.
Note
Same honest caveat as the metadata encryption: this raises the bar against runtime dumpers a lot (they can’t find the API by name), but a determined attacker can still hook the plugin’s resolver or signature-scan for the functions. It’s another wall, not a magic bullet.