This guide will walk through how to decompile/reverse engineer IL2CPP games for modding usage.
Note
This is a fork of a gist by BadMagic100. I modified it to use Il2CppInspectorRedux instead of Il2CppDumper since newer Unity versions use newer IL2CPP metadata versions that are not supported by Il2CppDumper.
Note: expect this entire process to take upwards of an hour. Have something ready to do on the side while waiting for processing to finish.
- Il2CppInspectorRedux
You can use the GUI version (Redux or non-Redux), but this tutorial will use the Redux CLI version. - Ghidra
This tutorial will use version 12, older versions might or might not work. - Any Python version >= 3.9
Decompiling the assembly with Ghidra will lose most symbol information by default. Fortunately, IL2CPP preserves symbol information for us as global metadata. We will use Il2CppInspectorRedux to extract the relevant metadata.
- Open up a command line in your game folder
- Create a directory which will hold your outputs
- Run the following:
Path/To/Il2CppInspector.Redux.CLI process "GameName_Data/il2cpp_data/Metadata/global-metadata.dat" "GameAssembly.dll" -o Path/To/Output/Folder --output-disassembler-metadata --disassembler ghidra
- Il2CppInspectorRedux will generate a variety of files for you:
il2cpp.h: contains C header files with type informationil2cpp.json: provides IL2CPP metadata (methods, types, strings, pointers, etc.) for the Ghidra scriptil2cpp.py: PyGhidra script to apply the metadata to your Ghidra project
- Open Ghidra and create a new project (
File->New Project) - Set the project name as desired (I like the format
GameName_X_Y_Zwhere X, Y, and Z make up the version number). - Click
Finish - Click the Code Browser (dragon head) icon.
- Click
File->Import File. SelectGameAssembly.dll->OK- If Ghidra says:
GameAssembly.dll has not been analyzed. Would you like to analyze it now?, press No!
- If Ghidra says:
- Import type data
- Click
File->Parse C Source... - Under
Parse Configuration, selectVisualStudio22_64.prf - Click
Save profile to new name(2nd icon in the top bar) - Remove all entries from
Source files to parse,Include paths, andParse Options - Add the generated
il2cpp.hto theSource files to parsesection - Add
-D_GHIDRA_to theParse Optionssection - Click
Parse to Program->Continue. If prompted, selectUse Open Archives. This may take a while.- If the parser encounters syntax errors, open the file in a text editor like Notepad++ and navigate to the line in question. Note down the line number for later and comment out the entire body of the struct where the syntax error appears. Often, the syntax error Ghidra is complaining about is not a real error so we'll just work around it until Ghidra is happy.
- If needed, we can later use the Structure Editor in Ghidra to re-populate the struct
- Repeat as needed until Ghidra successfully parses the header
- You can view the imported types in the
Data Type Managerwindow
- Click
- Import function data. Open the script manager (green play icon) and run
il2cpp.py. If prompted, select theil2cpp.jsonthat Il2CppInspectorRedux generated earlier.- If this is your first time, you'll have to add the Il2CppInspectorRedux scripts to Ghidra by clicking
Manage Script Directories(third-to-last icon in the script manager top bar) and adding your Il2CppInspectorRedux install directory to the list
- If this is your first time, you'll have to add the Il2CppInspectorRedux scripts to Ghidra by clicking
- Wait patiently while Ghidra decompiles the code. You can watch the progress in the lower right corner. This will take a while.
- Use Window -> Functions to open the function display. Here we can search for the methods we want. Start with
ClassName$$MethodNameorClassName$$if you don't know the specific method you want yet. - Double-clicking a function will take you to that function in the listing view. Highlight the function, right click, and click "Disassemble" to generate a decompilation.
- Dealing with async functions and coroutines - these get generated as anonymous state machine classes. For example,
they might appear in
ClassName.<MethodName>d__44$$MoveNext(the numbers are compiler generated). When you decompile the original method, you should easily be able to identify the calls to these state machines, and can track them down in functions window as you normally would. Again, the MoveNext method is where the state machine is actually implemented. - You can right click variables to rename them for easier readability.