Using IL2CPP on the command-line

The second option is more interesting: IL2CPP does not actually require Unity at all. It can work as a completely standalone tool, which also means it can produce things like executable binaries for regular .NET console applications that don’t have any connection with Unity. This is the technique I used to create the Hello World example above.

My preferred technique is to place the IL2CPP toolchain and all of its dependencies into their own folder. The folders you need are all in the Editor\Data folder of Unity and they are:

il2cpp
Mono or MonoBleedingEdge (depending on version; new versions of Unity use the latter)
PlaybackEngines\AndroidPlayer (if building for Android)
PlaybackEngines\windowsstandalonesupport (if building for Win32; not needed for UWP)

If you are building for Android, you will also need to download the Android NDK, which you can place in a sibling folder.

Copy these folders into a folder of your choice – maintaining the directory structure. This is your IL2CPP toolchain. Go ahead and compile a C# file (for example, the Hello World example above) in Visual Studio or with the Roslyn compiler csc.exe, so that you have a test assembly to work with.

The actual work is done by il2cpp.exe which can be found in il2cpp\build, il2cppbuild\deploy\net471 or similar depending on your Unity version.

Important: il2cpp.exe takes many arguments and some of them vary from version to version. Crucially, this wide variety of options can have a significant impact on the generated code, which means the disassembly of games you are reverse engineering may vary drastically depending on which options the developers used. You can experiment by building IL2CPP projects as described above in different versions of Unity and using a process snooping tool such as the excellent Process Hacker 2 to ascertain the command-line arguments used to il2cpp.exe during the build.

Some of the most important options are:

  • --convert-to-cpp converts the input assemblies to C++
  • --compile-cpp compiles the C++ to executable machine code
  • --libil2cpp-static bundles libil2cpp in with the executable. You should always specify this option as it is how software is shipped
  • --platform is the target build platform, eg. WindowsDesktop, Android etc.
  • --architecture is the target architecture, eg. x86, x64, ARMv7
  • --configuration is the build configuration to use. Normally you will want to use Release to produce code most similar to that shipped with games
  • --dotnetprofile="unityaot" sets the .NET profile (later versions of IL2CPP require this to avoid errors)
  • --forcerebuild will force the C++ to be re-generated even if it already exists
  • --assembly is a comma-separated list of assemblies to compile
OR
  • --directory is a comma-separated list of directories containing assemblies to compile
  • --outputpath specifies where to save the compiled executable binary
  • --generatedcppdir specifies where to save the generated C++ code
  • --verbose enables verbose output
  • When building for Android, also include:
  • --additional-include-directories=<path to your AndroidPlayer folder>/Tools/bdwgc/include
  • --additional-include-directories=<path to your AndroidPlayer folder>/Tools/libil2cpp/include
  • --tool-chain-path=<path to your AndroidPlayer folder>

If you chose to let Unity download the Android NDK for you, it will be located in AndroidPlayer\NDK.

Note: IL2CPP in Unity 2017 onwards does not automatically find mscorlib.dll and requires you to supply it explicitly; specifically, the version found at Editor\Data\MonoBleedingEdge\lib\mono\unityaot\mscorlib.dll. You should place this in the same folder as your test assemblies so that IL2CPP can find it. If you try to use any other version of mscorlib.dll, you will get a fatal error when running il2cpp.exe.

Example usages

Here is what a minimal build command for our Hello World example would look like:

il2cpp.exe ^
  --assembly=HelloWorld.exe ^
  --outputpath=HelloIl2Cpp.exe ^
  --libil2cpp-static ^
  --convert-to-cpp ^
  --compile-cpp ^
  --generatedcppdir=Cpp ^
  --verbose

To build an executable binary for a Windows 32-bit standalone application with a similar composition to Unity’s default settings for shipping games (replace x86 with x64 for 64-bit):

il2cpp.exe ^
  --convert-to-cpp ^
  --emit-null-checks ^
  --enable-array-bounds-check ^
  --dotnetprofile="unityaot" ^
  --compile-cpp ^
  --libil2cpp-static ^
  --platform="WindowsDesktop" ^
  --architecture="x86" ^
  --configuration="Release" ^
  --outputpath=Output ^
  --map-file-parser="il2cpp\MapFileParser\MapFileParser.exe" ^
  --directory=InputAssemblies ^
  --generatedcppdir=Cpp ^
  --verbose ^

To build an executable binary for Android with a similar composition to Unity’s default settings for shipping games (replace ARMv7 with ARM64 for 64-bit):

il2cpp.exe ^
  --convert-to-cpp ^
  --emit-null-checks ^
  --enable-array-bounds-check ^
  --dotnetprofile="unityaot" ^
  --compile-cpp ^
  --libil2cpp-static ^
  --platform="Android" ^
  --architecture="ARMv7" ^
  --configuration="Release" ^
  --outputpath=Output ^
  --additional-include-directories="PlaybackEngines/AndroidPlayer/Tools\bdwgc/include" ^
  --additional-include-directories="PlaybackEngines/AndroidPlayer/Tools\libil2cpp/include" ^
  --tool-chain-path="PlaybackEngines/AndroidPlayer/NDK" ^
  --map-file-parser="il2cpp\MapFileParser\MapFileParser.exe" ^
  --directory=InputAssemblies ^
  --generatedcppdir=Cpp ^
  --verbose ^

Note: From Unity 2019 onwards you can only create a DLL with il2cpp.exe. Attempting to create an EXE will fail.