This repository has been unmaintained for a while and will be archived soon. Consider using BepInEx/Il2CppInterop as a replacement.
A tool to generate Managed->IL2CPP proxy assemblies from Il2CppDumper's output.
This allows the use of IL2CPP domain and objects in it from a managed domain. This includes generic types and methods, arrays, and new object creation. Some things may be horribly broken.
Run AssemblyUnhollower --input=<path to Il2CppDumper's dummy dll dir> --output=<output directory> --mscorlib=<path to target mscorlib>
Resulting assemblies may be used with your favorite loader that offers a Mono domain in the IL2CPP game process, such as MelonLoader.
This appears to be working reasonably well for Unity 2018.4.x games, but more extensive testing is required.
Generated assemblies appear to be invalid according to .NET Core/.NET Framework, but run fine on Mono.
Usage: AssemblyUnhollower [parameters]
Possible parameters:
--help, -h, /? - Optional. Show this help
--verbose - Optional. Produce more console output
--input=<directory path> - Required. Directory with Il2CppDumper's dummy assemblies
--output=<directory path> - Required. Directory to put results into
--mscorlib=<file path> - Required. mscorlib.dll of target runtime system (typically loader's)
--unity=<directory path> - Optional. Directory with original Unity assemblies for unstripping
--gameassembly=<file path> - Optional. Path to GameAssembly.dll. Used for certain analyses
--deobf-uniq-chars=<number> - Optional. How many characters per unique token to use during deobfuscation
--deobf-uniq-max=<number> - Optional. How many maximum unique tokens per type are allowed during deobfuscation
--deobf-analyze - Optional. Analyze deobfuscation performance with different parameter values. Will not generate assemblies.
--blacklist-assembly=<assembly name> - Optional. Don't write specified assembly to output. Can be used multiple times
--add-prefix-to=<assembly name/namespace> - Optional. Assemblies and namespaces starting with these will get an Il2Cpp prefix in generated assemblies. Can be used multiple times.
--no-xref-cache - Optional. Don't generate xref scanning cache. All scanning will be done at runtime.
--no-copy-unhollower-libs - Optional. Don't copy unhollower libraries to output directory
--obf-regex=<regex> - Optional. Specifies a regex for obfuscated names. All types and members matching will be renamed
--rename-map=<file path> - Optional. Specifies a file specifying rename map for obfuscated types and members
--passthrough-names - Optional. If specified, names will be copied from input assemblies as-is without renaming or deobfuscation
Deobfuscation map generation mode:
--deobf-generate - Generate a deobfuscation map for input files. Will not generate assemblies.
--deobf-generate-asm=<assembly name> - Optional. Include this assembly for deobfuscation map generation. If none are specified, all assemblies will be included.
--deobf-generate-new=<directory path> - Required. Specifies the directory with new (obfuscated) assemblies. The --input parameter specifies old (unobfuscated) assemblies.
Before certain features can be used (namely class injection and delegate conversion), some external setup is required.
ClassInjector.Detour
to an implementation of a managed detour with semantics as described in the interface ClassInjector.DoHook
to an Action with same semantics as DetourAttach
(signature void**, void*
, first is a pointer to a variable containing pointer to hooked code start, second is a pointer to patch code start, a pointer to call-original code start is written to the first parameter)UnityVersionHandler.Initialize
with appropriate Unity version (default is 2018.4.20)Cast<T>
, see below), leading to exceptions. Use var
in foreach
or use for
instead of foreach
when possible as a workaround, or cast them to the specific interface you want to use.out T
in Dictionary.TryGetValue
) are currently brokenNotSupportedException
in cases where rewrite failed.Cast<T>
or .TryCast<T>
methods instead of C-style casts or as
.System.Type
, use Il2CppType.Of<T>()
instead of typeof(T)
System.Action
or System.Func
, like this: UnityAction a = new Action(() => {})
or var x = (UnityAction) new Action(() => {})
Starting with version 0.4.0.0, managed classes can be injected into IL2CPP domain. Currently this is fairly limited, but functional enough for GC integration and implementing custom MonoBehaviors.
How-to:
ClassInjector.DerivedConstructorPointer<T>()
, where T is your class type, and call ClassInjector.DerivedConstructorBody(this)
in constructor body.Il2CppToMonoDelegateReference
in DelegateSupport.csClassInjector.RegisterTypeInIl2Cpp<T>()
before first use of class to be injectedAddComponent<T>
Fine-tuning:
[HideFromIl2Cpp]
can be used to prevent a method from being exposed to il2cppCaveats:
ObjectCollectedException
. Conversely, managed representation of injected object will not be garbage collected as long as it's referenced from IL2CPP domain.class Injected: Il2CppSystem.Object {
Il2CppSystem.Collections.Generic.List<Il2CppSystem.Object> list = new ...;
public Injected() {
list.Add(this); // reference to itself through an IL2CPP list. This will prevent both this and list from being garbage collected, ever.
}
}
Limitations:
Starting with version 0.4.15.0, injected components can be used in asset bundles. Currently, deserialization for component fields is not supported. Any fields on the component will initially have their default value as defined in the mono assembly.
How-to:
RegisterTypeInIl2Cpp
before loading any objects from the asset bundle.Starting with 0.4.16.0, injected types can implement IL2CPP interfaces.
Just like previously, your type can't implement the interface directly, as it's still generated as a class.
However, you can pass additional interface types to RegisterTypeInIl2CppWithInterfaces
, and they will be implemented as interfaces on the IL2CPP version of your type.
Interface methods are matched to methods in your class by name, parameter count and genericness.
Known caveats:
obj.Cast<InterfaceType>()
will fail if you try to cast an object of your injected type to an interface. You can work around that with new InterfaceType(obj.Pointer)
if you're absolutely sure it implements that interface.UnhollowerPdbGen builds an executable that can be ran to generate a Microsoft PDB file (debug symbols) for GameAssembly.dll based on unhollower-generated names.
This can be useful for analyzing code of obfuscated games. For unobfuscated games, using Il2CppInspector would provide way better results for code analysis.
Generated PDBs were tested with windbg, lldb, WPA viewer/ETL performance analysis and IDA.
Generated PDBs only include generated methods, and don't include type info, generic method info and IL2CPP internals.
You need to manually copy the following Microsoft-provided libraries from Visual Studio (or other build tools) for this to work - I'm not redistributing them as license on them is not clear.
mspdbcore.dll
msobj140.dll
tbbmalloc.dll
These need to be placed next to the built .exe file. Use file search to find mspdbcore
in VS install.
Bundled into output files:
Used by generator itself:
Parts of source used: