Skip to content

Instantly share code, notes, and snippets.

@toasterparty
Last active July 14, 2025 17:38
Show Gist options
  • Save toasterparty/57a50eddc2203fc6ca24cf96789f5dd2 to your computer and use it in GitHub Desktop.
Save toasterparty/57a50eddc2203fc6ca24cf96789f5dd2 to your computer and use it in GitHub Desktop.

Unity IL2CPP Reverse Engineering

Unity games are fun.

Modding them is more fun.

Most unity games ship as "Mono". This means .NET IL code is right there for your viewing pleasure and makes for one of the easiest modding experience you can have (outside of official mod support).

Recently, many Unity games have been shipping with "IL2CPP" which optimizes out the "just in time" compilation with actual compilation. This process makes the game perform better for the player, but it also obfuscates game code meaning we have less information to work with when attempting to reverse engineer. This document describes the process of reverse engineering a Unity game built with IL2CPP despite the limitations. Keep in mind the best process for reverse engineering will change as Unity Engine evolves and the tools available change, and so this may become outdated.

Pre-Requisites

Is your game really IL2CPP?

If your game contains the folder \<game-name>_Data\il2cpp_data\, then yes. Otherwise your game is Mono and you should not be reading this guide.

What .NET version is your game built with?

A quick google search will reveal there are many ways to answer this, but it's must-have information before we can start. You also need to figure out if it's 32-bit (x86) or 64-bit (x64).

Unhollow Assemblies

Despite the obfuscation from IL2CPP, the symbols for public class methods are still plainly stored in the game's files. There are many tools to help with this, but my favorite is installing BepInEx and checking /BepInEx/interop after running the game once.

For IL2CPP support, you must use a Bleeding Edge Build of BepInEx. Always use the latest version unless you have specific needs.

You want to download one of the following depending on your game's architecture:

image

DnSpyEx

Once the assemblies are unhollowed, you can use this program to explore the signatures of public methods. Make sure you use the revived DnSpyEx and not the original abandoned DnSpy. You now know what public methods do, but not how they do it.

Harmony

With the assemblies unhollowed, you also have everything you need to inject prefix/suffix code into these public methods using harmony. I reccomend BepInEx as the way to bootstrap harmony. There are many guides on this which are beyond the scope of this guide.

IL2CPP Dumper

If want more insight into how the public methods are doing what they do, we'll need to decompile. The first step is to unhollow again, but this time using a tool called IL2CPP Dumper which has Ghidra in mind as the final destination.

First, download and extract the Latest Release of the tool. Make sure to pick the right .NET version when you pick the download.

The, run the exe which matches the 32-bit/64-bit architecture of the game.

The first file it wants is GameAssembly.dll. It should be at the top level of your game directory.

The second file it wants is global-metadata.dat. It should be at /<game-name>_Data/il2cpp_data/Metadata/.

Now make sure you have Python installed so you can run python il2cpp_header_to_ghidra.py from the Il2CppDumper directory. If all goes well it should create il2cpp_ghidra.h.

Ghidra

Now that we dumped with Il2CPP, we can use Ghidra to reconstruct as much of original source code as possible via deduction. The success of this operation varies from game-to-game.

Install Ghidra.

Copy ghidra_with_struct.py to /path/to/ghidra/Ghidra/Features/Base/ghidra_scripts/.

Run Ghidra and create a new project by clicking File > New Project > Non-Shared Project and then supply the project and project directory.

Copy il2cpp_ghidra.h, script.json and GameAssembly.dll into the freshly created project directory.

Import GameAssembly.dll into the Ghidra project window (drag and drop works fine). The default import settings should be fine.

Open GameAssembly.dll in the Ghidra code editor and select No if tells you it has not yet been analyzed yet.

Open the Script Manager by clicking Window > Script Manager. Search for ghidra_with_struct.py and run it. When it prompts you for a file, give it script.json.

Save. Now run the analyzer with Analysis > Auto Analyze 'GameAssembly.dll'. Continue with the default anylizer settings.

Wait a up to several hours for anlysis to finish.

Now you can browse an estimation of what the source code would look like if it were written in C.

@Fatih120
Copy link

Is it considered SOL if a game seems resistant to either BepInEx or Perfare's tools? I've been trying to take a crack at a game but no tools seem to work out of the box. I'm only guessing that's because of its Unity version that's unsupported or there's some other encryption.

@toasterparty
Copy link
Author

toasterparty commented Jul 1, 2025

@Fatih120

Some things to try/double-check:

  1. Make sure you're using the correct BepInEx build:
    • mono vs IL2CPP
    • x86 vs x64
  2. Try v6.0.0-pre.2 instead of bleeding edge
  3. Try toggling ignoreDisableSwitch in doorstop_config.ini
  4. Try turning off antivirus
  5. Ask in the BepInEx discord

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment