Coder Social home page Coder Social logo

knah / il2cppassemblyunhollower Goto Github PK

View Code? Open in Web Editor NEW
490.0 12.0 88.0 1.56 MB

A tool to generate Managed->IL2CPP proxy assemblies

License: GNU Lesser General Public License v3.0

C# 100.00%
il2cpp unity3d il2cppdumper modding-tools modding-games

il2cppassemblyunhollower's Introduction

Archival note

This repository has been unmaintained for a while and will be archived soon. Consider using BepInEx/Il2CppInterop as a replacement.

Il2CppAssemblyUnhollower

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.

Usage

  1. Build or get a release
  2. Obtain dummy assemblies using Il2CppDumper
  3. 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.

Command-line parameter reference

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. 

Required external setup

Before certain features can be used (namely class injection and delegate conversion), some external setup is required.

  • Set ClassInjector.Detour to an implementation of a managed detour with semantics as described in the interface
  • Alternatively, set 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)
  • Call UnityVersionHandler.Initialize with appropriate Unity version (default is 2018.4.20)

Known Issues

  • Non-blittable structs can't be used in delegates
  • Types implementing interfaces, particularly IEnumerable, may be arbitrarily janky with interface methods. Additionally, using them in foreach may result in implicit casts on managed side (instead of 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.
  • in/out/ref parameters on generic parameter types (like out T in Dictionary.TryGetValue) are currently broken
  • Unity unstripping only partially restores types, and certain methods can't be unstripped still; some calls to unstripped methods might result in crashes
  • Unstripped methods with array operations inside contain invalid bytecode
  • Unstripped methods with casts inside will likely throw invalid cast exceptions or produce nulls
  • Some unstripped methods are stubbed with NotSupportedException in cases where rewrite failed
  • Nullables have issues when returned from field/property getters and methods

Generated assemblies caveats

  • IL2CPP types must be cast using .Cast<T> or .TryCast<T> methods instead of C-style casts or as.
  • When IL2CPP code requires a System.Type, use Il2CppType.Of<T>() instead of typeof(T)
  • For IL2CPP delegate types, use the implicit conversion from System.Action or System.Func, like this: UnityAction a = new Action(() => {}) or var x = (UnityAction) new Action(() => {})
  • IL2CPP assemblies are stripped, so some methods or even classes could be missing compared to pre-IL2CPP assemblies. This is mostly applicable to Unity assemblies.
  • Using generics with value types may lead to exceptions or crashes because of missing method bodies. If a specific value-typed generic signature was not used in original game code, it can't be used externally either.

Class injection

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:

  • Your class must inherit from a non-abstract IL2CPP class.
  • You must include a constructor that takes IntPtr and passes it to base class constructor. It will be called when objects of your class are created from IL2CPP side.
  • To create your object from managed side, call base class IntPtr constructor with result of ClassInjector.DerivedConstructorPointer<T>(), where T is your class type, and call ClassInjector.DerivedConstructorBody(this) in constructor body.
  • An example of injected class is Il2CppToMonoDelegateReference in DelegateSupport.cs
  • Call ClassInjector.RegisterTypeInIl2Cpp<T>() before first use of class to be injected
  • The injected class can be used normally afterwards, for example a custom MonoBehavior implementation would work with AddComponent<T>

Fine-tuning:

  • [HideFromIl2Cpp] can be used to prevent a method from being exposed to il2cpp

Caveats:

  • Injected class instances are handled by IL2CPP garbage collection. This means that an object may be collected even if it's referenced from managed domain. Attempting to use that object afterwards will result in ObjectCollectedException. Conversely, managed representation of injected object will not be garbage collected as long as it's referenced from IL2CPP domain.
  • It might be possible to create a cross-domain reference loop that will prevent objects from being garbage collected. Avoid doing anything that will result in injected class instances (indirectly) storing references to itself. The simplest example of how to leak memory is this:
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:

  • Interfaces can't be implemented
  • Virtual methods can't be overridden
  • Only instance methods are exposed to IL2CPP side - no fields, properties, events or static methods will be visible to IL2CPP reflection
  • Only a limited set of types is supported for method signatures

Injected components in asset bundles

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:

  • Your class must meet the above critereon mentioned in Class Injection.
  • Add a dummy script for your component into Unity. Remove any methods, constructors, and properties. Fields can optionally be left in for future deserialization support.
  • Apply the component to your intended objects in Unity and build the asset bundle.
  • At runtime, register your component with RegisterTypeInIl2Cpp before loading any objects from the asset bundle.

Implementing interfaces with injected types

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.
  • Limited method matching might result in some interfaces being trickier or impossible to implement, namely those with overloads differing only in parameter types.

PDB generator

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.

Upcoming features (aka TODO list)

  • Unstripping engine code - fix current issues with unstripping failing or generating invalid bytecode
  • Proper interface support - IL2CPP interfaces will be generated as interfaces and properly implemented by IL2CPP types
  • Improve class injection to support virtual methods and interfaces
  • Improve class injection to support deserializing fields

Used libraries

Bundled into output files:

  • iced by 0xd4d, an x86 disassembler used for xref scanning and possibly more in the future

Used by generator itself:

  • Mono.Cecil by jbevain, the main tool to produce assemblies

Parts of source used:

il2cppassemblyunhollower's People

Contributors

bbepis avatar ds5678 avatar ghorsington avatar hookedbehemoth avatar js6pak avatar kasuromi avatar knah avatar zeobviouslyfakeacc avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

il2cppassemblyunhollower's Issues

Random garbage in IL2CPPStructArray<T>

  • Game has PlayerCharacter with property myUtilities[] (as Il2CppReferenceArray) of base type BaseUtilityComponent. Said class exposes base property myInputs which is "unhollowed" as Il2CppStructArray<INPUT> where INPUT is plain enum.
  • Original types from metadata are plain arrays.
  • I have created my custom class MyUtility and inherited from BaseUtilityComponent on mono side (bepinex plugin)
  • Upon initialization of PlayerCharacter I instantiate my custom component on mono side, add it to GameObject of PlayerCharacter and add it to myUtilities[] as well
  • Finally, I populate myInputs with custom array of INPUT enum values.
  • All these objects are created on a stack in Postfix method so they are collected eventually on mono side and I'm assuming they are copied to il2cpp domain through its boxing/unboxing.

For the first time (first scene load) everything works fine. When I change the scene, thing become weird, myInputs on my custom Component now has some weird garbage data, like Count/Length being a few million or throwing nullref when trying to request any value by index. The odd part is that this component isnt even transitioned through scene as DDOL or HideandDontSave, it is literally recreated as
PlayerCharacter is being remade. WasCollected returns false for the given IL2cppStructArray (sine it is referenced on il2cpp side)
Let me know if more information is needed.

Error Unhandled exception: System.Collections.Generic.KeyNotFoundException:

Unhandled exception: System.Collections.Generic.KeyNotFoundException: This key is missing from the dictionary.
in System.ThrowHelper.ThrowKeyNotFoundException()
in System.Collections.Generic.Dictionary`2.get_Item(TKey key)
in AssemblyUnhollower.Contexts.RewriteGlobalContext.GetNewAssemblyForOriginal(AssemblyDefinition oldAssembly)
in AssemblyUnhollower.Contexts.RewriteGlobalContext.GetNewTypeForOriginal(TypeDefinition originalType)
in AssemblyUnhollower.Passes.Pass11ComputeTypeSpecifics.ComputeSpecifics(TypeRewriteContext typeContext)
in AssemblyUnhollower.Passes.Pass11ComputeTypeSpecifics.DoPass(RewriteGlobalContext context)
in AssemblyUnhollower.Program.Main(UnhollowerOptions options)
in AssemblyUnhollower.Program.Main(String[] args)
How do I fix this error?

Implement support for Unity (de)serializing fields of injected MonoBehaviors

Right now injected classes have no (extra) fields. This is largely due to the fact that field storage is allocated in the managed-side object, with the il2cpp side object only holding a gchandle to the managed one.
This presents an issue with cloning gameobjects with components of injected types, and loading them from assetbundles - Unity can't access managed fields, and, as such, they are not copied during instantiation/bundle load.

Preliminary research indicates that Unity uses field offsets and (optionally) il2cpp_gc_wbarrier_set_field to directly access fields on il2cpp objects - this means that the usual approach of "hook set_field so it redirects to the managed field" is likely non-viable.

This means that an alternative approach for handling fields on injected types is necessary. I have a few approaches that I'd consider viable:

  1. Create an annotation for auto-properties alongside the lines of RedirectToNativeField. When performing class injection, a field in il2cpp class would be created for every property marked with that attribute, and its getter and setter would be patched to access the field in native object.
    • This might take some effort to interop nicely with defining those components in the editor. SerializeField and FormerlySerializedAsAttribute attributes can be applied to auto-property backing fields to make them serialized and have the appropriate name. Alternatively, injected fields can be made to match property backing field name.
    • Alternatively, the separate editor version of the component can have plain fields with correct names - removing the { get; set; } block is relatively easy.
    • Patching property getter/setter might require special magic tricks on certain runtimes (see Harmony/netcore - I believe those issues are resolved currently).
  2. Create a wrapper type for native fields, such as NativeFieldWrapper<T>. Fields of this type can be declared in the injected class, and class injector would generate a matching field in the il2cpp class for every field of that type. The wrapper would provide access to those il2cpp fields.
    • It would have been nice if C# had property delegation like Kotlin - this would look way cleaner.
    • Interop with defining those fields in the editor would be even more challenging, unless the "separate script code" approach is used.
  3. Use ISerializationCallbackReceiver to manually serialize/deserialize state to a single hardcoded string/byte[] field. This would likely work fairly well with editor interop, but might not be compatible with older unity versions if they're missing this interface. Additionally, this would require interface implementation to be supported by class injection.
  4. Maybe something else I didn't think about?

A thing to consider for injected fields would be GC integration - IL2CPP can use either boehm (which requires no special GC magic), or something sgen-like which would likely require correct GC descriptors on classes to tell it where reference-typed fields are located in the object.

Mono.Cecil Error

Not matter what Unity Game I try to use the program on, I always get the same error message:

Generating implicit conversions...

Unhandled Exception: System.ArgumentNullException: Value cannot be null.
Parameter name: type
at Mono.Cecil.Mixin.CheckType(Object type)
at Mono.Cecil.ModuleDefinition.ImportReference(TypeReference type, IGenericParameterProvider context)
at AssemblyUnhollower.Passes.Pass60AddImplicitConversions.AddDelegateConversions(RewriteGlobalContext context)
at AssemblyUnhollower.Passes.Pass60AddImplicitConversions.DoPass(RewriteGlobalContext context)
at AssemblyUnhollower.Program.Main(UnhollowerOptions options)
at AssemblyUnhollower.Program.Main(String[] args)

I have tried using different versions of Mono.Cecil; nothig worked.
Games I've tried: Redmatch 2, Pistol Whip, Pixel Strike 3D.

Allow specifying target assembly for injected classes

Right now classes are injected into effectively all assemblies - Unity will be able to find it in any assembly it runs class_from_name for.

This might be undesireable in some cases, so it might be nice to add an annotation that would allow restricting injected classes to one specific assembly.

Version not supported

I use this (https://github.com/LavaGang/MelonLoader/blob/master/BaseLibs/Managed/mscorlib.dll) mscorlib. Unity version is 2020.3.14f1

System.NotSupportedException: Version not supported: 3.7.1.6
   in Mono.Cecil.BaseAssemblyResolver.GetCorlib(AssemblyNameReference reference, ReaderParameters parameters)
   in Mono.Cecil.BaseAssemblyResolver.Resolve(AssemblyNameReference name, ReaderParameters parameters)
   in Mono.Cecil.DefaultAssemblyResolver.Resolve(AssemblyNameReference name)
   in Mono.Cecil.MetadataResolver.Resolve(TypeReference type)
   in Mono.Cecil.TypeReference.Resolve()
   in AssemblyUnhollower.Passes.Pass05CreateRenameGroups.NameOrRename(TypeReference typeRef, RewriteGlobalContext context) in C:\Game\1\AssemblyUnhollower\Passes\Pass05CreateRenameGroups.cs:line 148
   in AssemblyUnhollower.Passes.Pass05CreateRenameGroups.GenericNameToStrings(TypeReference typeRef, RewriteGlobalContext context) in C:\Game\1\AssemblyUnhollower\Passes\Pass05CreateRenameGroups.cs:line 176
   in AssemblyUnhollower.Passes.Pass05CreateRenameGroups.GetUnobfuscatedNameBase(RewriteGlobalContext context, TypeDefinition typeDefinition, Boolean allowExtraHeuristics) in C:\Game\1\AssemblyUnhollower\Passes\Pass05CreateRenameGroups.cs:line 74
   in AssemblyUnhollower.Passes.Pass05CreateRenameGroups.ProcessType(RewriteGlobalContext context, TypeDefinition originalType, Boolean allowExtraHeuristics) in C:\Game\1\AssemblyUnhollower\Passes\Pass05CreateRenameGroups.cs:line 43
   in AssemblyUnhollower.Passes.Pass05CreateRenameGroups.ProcessType(RewriteGlobalContext context, TypeDefinition originalType, Boolean allowExtraHeuristics) in C:\Game\1\AssemblyUnhollower\Passes\Pass05CreateRenameGroups.cs:line 39
   in AssemblyUnhollower.Passes.Pass05CreateRenameGroups.DoPass(RewriteGlobalContext context) in C:\Game\1\AssemblyUnhollower\Passes\Pass05CreateRenameGroups.cs:line 18
   in AssemblyUnhollower.Program.Main(UnhollowerOptions options) in C:\Game\1\AssemblyUnhollower\Program.cs:line 229
   in AssemblyUnhollower.Program.Main(String[] args) in C:\Game\1\AssemblyUnhollower\Program.cs:line 170
Done in 00:00:03.3977315

C:\Game\1\AssemblyUnhollower\bin\Debug\net4.7.2\AssemblyUnhollower.exe (process 15016) exited with code -532462766.

Implement support for injecting properties

I have no idea why this would be useful, but some reflection-based libraries might make use of this.
Properties, being a pair of get/set methods, should be relatively straightforward to implement.

System.NullReferenceException

C:\Users\admin\Desktop\adv\unhollower>AssemblyUnhollower --input=DummyDLL --output=out --mscorlib=mscorlib.dll
Reading assemblies... Done in 00:00:01.7637685
Computing renames... Done in 00:00:00.1016879
Creating typedefs... Done in 00:00:00.0469580
Computing struct blittability... Done in 00:00:00.0051524
Filling typedefs... Done in 00:00:00.0128215
Filling generic constraints... Done in 00:00:00.0027324
Creating members... Done in 00:00:00.4199734
Scanning method cross-references... Done in 00:00:00.0033256
Finalizing method declarations...
ะะตะพะฑั€ะฐะฑะพั‚ะฐะฝะฝะพะต ะธัะบะปัŽั‡ะตะฝะธะต: System.NullReferenceException: ะกัั‹ะปะบะฐ ะฝะฐ ะพะฑัŠะตะบั‚ ะฝะต ัƒะบะฐะทั‹ะฒะฐะตั‚ ะฝะฐ ัะบะทะตะผะฟะปัั€ ะพะฑัŠะตะบั‚ะฐ.
ะฒ AssemblyUnhollower.Passes.Pass18FinalizeMethodContexts.DoPass(RewriteGlobalContext context)
ะฒ AssemblyUnhollower.Program.Main(UnhollowerOptions options)
ะฒ AssemblyUnhollower.Program.Main(String[] args)
Done in 00:00:04.5023735

Access to the path is denied when executing the command

Access Denied

When I try to use the AssemblyUnhollower command I get access denied. I've searched everywhere online and wasn't able to resolve this issue. I tried running the command line as administrator, I tried to change the directory permission to "administrator". I tried using the "runas" command giving me "error 5: access denied". No matter what I try the result is the same. Could you help resolve this issue? I think it's probably a problem with my OS and not with the repo itself. I'm using the latest release of the repo.

Investigate compatibility with 5.x Unity versions

IL2CPP is not new. It would be nice to check:

  • Are there any commercial games using IL2CPP with pre-2017 Unity versions.
  • If there are, check native struct/API differences.
  • Add additional native struct handlers for those versions

Check this table for Unity/IL2CPP versions, and this repo for native headers.

ๆœช็ปๅค„็†็š„ๅผ‚ๅธธ: System.ArgumentNullException: ๅ€ผไธ่ƒฝไธบ nullใ€‚

C:\Users\Administrator\Downloads\Il2CppAssemblyUnhollower.0.4.15.1>AssemblyUnhollower --input=C:\Users\Administrator\Downloads\Il2CppDumper-v6.6.2\DummyDll --output=C:\Users\Administrator\Desktop\ttt111 --mscorlib=C:\Users\Administrator\Downloads\Il2CppDumper-v6.6.2\DummyDll\mscorlib.dll
Reading assemblies...
Done in 00:00:00.1612040
Reading system assemblies...
Done in 00:00:00.0047727
Creating rewrite assemblies...
Done in 00:00:00.0163156
Computing renames...
Done in 00:00:00.0525007
Creating typedefs...
Done in 00:00:00.0412334
Computing struct blittability...
Done in 00:00:00.0077098
Filling typedefs...
Done in 00:00:00.0090349
Filling generic constraints...
Done in 00:00:00.0040606
Creating members...
Done in 00:00:00.5208839
Scanning method cross-references...
Done in 00:00:00.0053586
Finalizing method declarations...
Done in 00:00:00.4741855
0 total potentially dead methods
Filling method parameters...
Done in 00:00:00.0639566
Creating static constructors...
Done in 00:00:00.3964237
Creating value type fields...
Done in 00:00:00.0964819
Creating enums...
Done in 00:00:00.0510904
Creating IntPtr constructors...
Done in 00:00:00.0514518
Creating type getters...
Done in 00:00:00.0231261
Creating non-blittable struct constructors...
Done in 00:00:00.0217440
Creating generic method static constructors...
Done in 00:00:00.0091972
Creating field accessors...
Done in 00:00:00.5083132
Filling methods...
Done in 00:00:00.4963606
Generating implicit conversions...

ๆœช็ปๅค„็†็š„ๅผ‚ๅธธ: System.ArgumentNullException: ๅ€ผไธ่ƒฝไธบ nullใ€‚
ๅ‚ๆ•ฐๅ: type
ๅœจ Mono.Cecil.Mixin.CheckType(Object type)
ๅœจ Mono.Cecil.ModuleDefinition.ImportReference(TypeReference type, IGenericParameterProvider context)
ๅœจ AssemblyUnhollower.Passes.Pass60AddImplicitConversions.AddDelegateConversions(RewriteGlobalContext context)
ๅœจ AssemblyUnhollower.Passes.Pass60AddImplicitConversions.DoPass(RewriteGlobalContext context)
ๅœจ AssemblyUnhollower.Program.Main(UnhollowerOptions options)
ๅœจ AssemblyUnhollower.Program.Main(String[] args)
Done in 00:01:31.9494710

่ฏท้—ฎ่ฟ™ๆ˜ฏไป€ไนˆๅŽŸๅ› ๅผ•่ตท็š„๏ผŸ

Unstripped methods with casts to array types fail at runtime

[14:57:07.459] [MelonLoader] [MyMod] [Error] System.ArgumentException: UnhollowerBaseLib.Il2CppReferenceArray`1[UnityEngine.Component] is not al Il2Cpp reference type
  at UnhollowerBaseLib.Il2CppObjectBase.TryCast[T] () [0x0002a] in <56ec66a01d45498298ce9793c60195c6>:0
  at UnhollowerBaseLib.Il2CppObjectBase.Cast[T] () [0x00001] in <56ec66a01d45498298ce9793c60195c6>:0
  at UnityEngine.GameObject.GetComponents (Il2CppSystem.Type type) [0x0000d] in <e55c8968e207437b8f754f5fff33d365>:0

Workaround: try using a different version (such as generic/non-generic one), or recreated the problematic method via base icalls they use.

Struct values randomly corrupted when returned back from il2cpp domain

Recently released Oddworld: Soulstorm uses custom Unity build of 2019.4.12.15033 base, however it is still compiled with known IL2CPP version: 24.3
Generated by Unhollower assemblies (as a part of BepinEX 6.x BE build) run and work fine, however, I'm getting weird issue when some integer values in a returned struct are corrupted.

public static LevelStats GetCurrentLevelStats() => SavableManager<LevelStatsManager>.instance.stats;
// LevelStatsManager
// Token: 0x06000B01 RID: 2817 RVA: 0x0002E6A4 File Offset: 0x0002C8A4
[CallerCount(29)]
[CachedScanResults(RefRangeStart = 171928, RefRangeEnd = 171957, XrefRangeStart = 171912, XrefRangeEnd = 171928, MetadataInitTokenRva = 0L, MetadataInitFlagRva = 0L)]
public unsafe static LevelStats GetCurrentLevelStats()
{
    IntPtr* ptr = null;
    IntPtr intPtr2;
    IntPtr intPtr = IL2CPP.il2cpp_runtime_invoke(LevelStatsManager.NativeMethodInfoPtr_GetCurrentLevelStats_Public_Static_LevelStats_0, 0, (void**)ptr, ref intPtr2);
    Il2CppException.RaiseExceptionIfNecessary(intPtr2);
    return *IL2CPP.il2cpp_object_unbox(intPtr);
}

It looks like fairly blittable sequential struct data. However, at runtime, when I call properties, they sometimes return random integer data, unrelated to the actual state of the value on il2cpp domain side.

Total: 25 :: Unsaved: 0 :: Killed: 0
 Total: 25 :: Unsaved: 0 :: Killed: 0
 Total: 25 :: Unsaved: 0 :: Killed: 0
<snip>
 Total: 25 :: Unsaved: 0 :: Killed: 0
 Total: 25 :: Unsaved: 492 :: Killed: 240058848  //????????????????
 Total: 25 :: Unsaved: 0 :: Killed: 0
 Total: 25 :: Unsaved: 0 :: Killed: 0

I've confirmed this by injecting custom DLL from the scaffolding project generated by Il2CppInspector:

auto levelStats = LevelStatsManager_GetCurrentLevelStats(nullptr);
auto rescued = levelStats._RescuedMudokonCount_k__BackingField;
std::cout << "Rescued: " << std::to_string(rescued)
          << std::endl;

Returns perfectly valid value every frame.

Value cannot be null?

I tried dumping some files with this args in a .bat file
AssemblyUnhollower --input=IN\DummyDLL --output=OUT --mscorlib=IN\DummyDLL\mscorlib.dll
Everythin ran fine, until the end when the dumber gave this error:

Unhandled Exception: System.ArgumentNullException: Value cannot be null.
Parameter name: type
at Mono.Cecil.Mixin.CheckType(Object type)
at Mono.Cecil.ModuleDefinition.ImportReference(TypeReference type, IGenericParameterProvider context)
at AssemblyUnhollower.Passes.Pass60AddImplicitConversions.AddDelegateConversions(RewriteGlobalContext context)
at AssemblyUnhollower.Passes.Pass60AddImplicitConversions.DoPass(RewriteGlobalContext context)
at AssemblyUnhollower.Program.Main(UnhollowerOptions options)
at AssemblyUnhollower.Program.Main(String[] args)
Done in 00:00:00.6069457

How to use.

This may seem like a dumb question but this has me alittile stumped, how do i use this tool with dummy .dll's i extracted with cpp2il. Do i need deobf? where are system libs? do i need to specify mscorlib? where would that be? how do i get wasm framework? so on. It may be a bit dumb but some help would mean alot.

Polymorphism and casting object into actual type

For example,

class A : Il2CppSystem.Object { }
class B : A { }
class C : A { }

var list = new List<A>() { new A(), new B(), new C() };

In this case, when I try to get list[2].GetType(), I get typeof(A).
I was able to get actual type of it using code below

public static Type GetActualType(Il2CppSystem.Object obj)
{
    var cpptype = obj.GetIl2CppType();
    return Type.GetType(cpptype.AssemblyQualifiedName);
}

but I cannot cast object into type with cppobj.Cast<actualType>() because I cannot provide type variable into it. Is there any other way to cast il2cpp object into specific type variable in runtime?

Wrong Unity OnGUI

Hello ! The Unhollower is using the wrong OnGUI function, can you do something for select the OnGUI function of a specific script?

ClassInjector: nested interfaces aren't implemented

e.g. UnityEngine.EventSystems.IPointerDownHandler extends UnityEngine.EventSystems.IEventSystemHandler
The current implementation of RegisterTypeInIl2CppWithInterfaces doesn't implement the latter on il2cpp side when only specifying the former.

The .exe does not run!

I tried to run the .exe on Windows 10.
The CMD appears and then instantly closes in 1 second.
Could you make a tutuorial to show how to use this tool please?

ArgumentException: Base class Il2CppSystem.Object is sealed and can't be inherited from

Originally filed on BepInEx/BepInEx#180.

What I did

Ran BepInEx on unity 2020.2.1 game.

What I got

Full log is available on the BepInEx issue.

[Fatal  :   BepInEx] Unable to execute IL2CPP chainloader
[Error  :   BepInEx] System.ArgumentException: Base class Il2CppSystem.Object is sealed and can't be inherited from
  at UnhollowerRuntimeLib.ClassInjector.RegisterTypeInIl2Cpp[T] () [0x000d9] in <3c9b7d776049470ab76a3adc498166ff>:0 
  at UnhollowerRuntimeLib.DelegateSupport.ConvertDelegate[TIl2Cpp] (System.Delegate delegate) [0x00100] in <3c9b7d776049470ab76a3adc498166ff>:0 
  at UnityEngine.Application+LogCallback.op_Implicit (System.Action`3[T1,T2,T3] ) [0x00000] in <cddda00cb9e046539753e35a234cef04>:0 
  at BepInEx.IL2CPP.Logging.IL2CPPUnityLogSource..ctor () [0x00011] in <fda293712a454bf3b6b2e8d2b45714fe>:0 
  at BepInEx.IL2CPP.IL2CPPChainloader.OnInvokeMethod (System.IntPtr method, System.IntPtr obj, System.IntPtr parameters, System.IntPtr exc) [0x0002a] in <fda293712a454bf3b6b2e8d2b45714fe>:0 

Additional context

BepInEx developer commented this.

This is indeed an issue with Il2CppAssemblyUnhollwer. More precisely, it's the issue of the unhollower not supporting memory layout of Il2Cpp class info in Unity 2020.2.1.

Add API for checking if an object was collected

It would be nice to have a property/method to check if an object was garbage collected in the Il2CPP context. Currently, the only way of checking if an object was collected is to force Unhollower to throw an exception by accessing Il2CppObjectBase.Pointer.

I was thinking something like Il2CppObjectBase.IsCollected { get; }.

TypeLoadException when using classes which inherit from generic types with explicit UnityEngine-namespace struct arguments.

Hey knah, so I found an issue with generic types which I don't think is covered by your known issues.

Consider the following class, assuming it existed in the original game.

public class Test : UnityEvent<Vector2>

This will throw a TypeLoadException if you attempt to access it in any way.

The issue is the explicit generic argument, and the fact that argument type is in the UnityEngine dll. If I used or any System or custom type as the argument it would work fine.

I thought perhaps it was the known blittable struct delegate issue, but this was proven to be unrelated. This does not cause a TypeLoadException:

public struct NonBlittableStruct
{
    public string s;
}
public class Test : UnityEvent<NonBlittableStruct>

Nor does this:

public class Test2<T> : UnityEvent<T>

public Test2<Vector2> test;

The same issue affects InputSystem as well, such as Mouse.current.position, as the value is a Vector2Control : InputControl<Vector2>.

Let me know if I can provide any more details!

And again, these are types which exist in the actual game, not new generic types created in a mod. Have not tested if the same issue affects custom mod classes.

Cannot write to a closed TextWriter

[ERROR]: System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> UnhollowerBaseLib.Il2CppException: System.ObjectDisposedException: Cannot write to a closed TextWriter.

at UnhollowerBaseLib.Il2CppException.RaiseExceptionIfNecessary (System.IntPtr returnedException) [0x00018] in <0d78579fe0744baaa87f6f150cc3197a>:0

at Il2CppSystem.IO.StreamWriter..ctor (Il2CppSystem.IO.Stream stream) [0x00056] in <37184e02f7d2469f94117d48e10de8c6>:0

at (wrapper managed-to-native) System.Reflection.MonoCMethod.InternalInvoke(System.Reflection.MonoCMethod,object,object[],System.Exception&)

at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters) [0x00002] in :0

--- End of inner exception stack trace ---

at System.Reflection.MonoCMethod.InternalInvoke (System.Object obj, System.Object[] parameters) [0x00014] in :0

at System.Reflection.MonoCMethod.DoInvoke (System.Object obj, System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x0007a] in :0

at System.Reflection.MonoCMethod.Invoke (System.Reflection.BindingFlags invokeAttr, System.Reflection.Binder binder, System.Object[] parameters, System.Globalization.CultureInfo culture) [0x00000] in :0

at System.Reflection.ConstructorInfo.Invoke (System.Object[] parameters) [0x00000] in :0

at MelonLoader.Support.Main.ConsoleCleaner () [0x000e9] in <42608154b4be43d399be6120b3678d66>:0

System.ArgumentException: Base class UnityEngine.MonoBehaviour is generic and can't be inherited from

When trying to inherit from MonoBehaviours in Unity v2018.3.7, I get the following exception

[Error  :   BepInEx] Error loading [Fast Mode] : Base class UnityEngine.MonoBehaviour is generic and can't be inherited from
[Debug  :   BepInEx] System.ArgumentException: Base class UnityEngine.MonoBehaviour is generic and can't be inherited from
  at UnhollowerRuntimeLib.ClassInjector.RegisterTypeInIl2Cpp[T] () [0x000c5] in <78b0a74ed8474311860460a2b2541509>:0 
  at FastMode.MyMod.Load () [0x00017] in <c635291483104ba6bcd486771a7e2830>:0 
  at BepInEx.IL2CPP.IL2CPPChainloader.LoadPlugin (BepInEx.PluginInfo pluginInfo, System.Reflection.Assembly pluginAssembly) [0x00019] in <25d17e9887f345fb8db7fda2eb27a374>:0 
  at BepInEx.Bootstrap.BaseChainloader`1[TPlugin].Execute () [0x00223] in <43cd6c7a3ac44c01acb29606479d8d71>:0 

Example code: Main.cs.txt

I believe the issue is caused by incorrect Il2CppClass field alignment, the field naturalAligment does not exist in unity versions 2018.3 or lower, and commenting out the field fixes the issue.

public byte naturalAligment; // Alignment of this type without accounting for packing

Generate TypeForwardedToAttributes in UnityEngine.dll proxy

Greetings,

For each type that were previously located in UnityEngine.dll (5.x) that were moved into separate assemblies from version 2017+, a TypeForwardedToAttribute were added to UnityEngine.dll.

This allows assemblies that were built against the old version of the Unity Engine (5.x and lower) to properly work against newer versions (2017 and newer).

I'm developing a plugin that leverages this in order to work with all versions of Unity ranging from 4.x to 2020.x, and in order to do so, I am targeting an old version of the Unity Engine.

Therefore I would ask if it is possible to add these assembly-level attributes to the proxy version of UnityEngine.dll.

Add unit tests

Unit tests are incredibly valuable when performing refactorings, rewrites, and even general development.
Things to cover:

  • Assembly generation
    • Various OS/Architecture combinations (current targets would be x86/x64/ARM; Windows, Linux, Android)
      • The generation process itself currently is targeting Windows only, optionally Linux - Android modding test approaches currently modify the APK on the PC
    • Various Unity/IL2CPP versions (ideally one sample for each metadata format, see the Il2CppVersions repo)
    • Renaming for deobfuscation
    • Generation-time xref scans
    • General validity of resulting assemblies as far as netcore is concerned (#44)
  • Runtime features
    • Figure out a way to run an IL2CPP GameAssembly without the corresponding Unity game?
    • General functioning of generated assemblies
    • Class injection (on different architectures/OS - does GH Actions have ARM runners?)
    • Assuming any arch is not covered by generation-time xref scanning - that there are any architectures left
    • Delegates conversion

Things to consider:

  • Test data (generated GameAssembly/metadata/generated dummy dlls) can be exceedingly big, so put it into a subrepo
  • Test execution time should be kept low, so include dummy dlls in test data?
  • Include some easy ways to generate new variations of test data for other platforms/unity versions (sample project, sample build scripts? Run IL2CPP converter/compiler without unity editor?)
  • No real games in test data, only hand-built sample projects might be limiting, but also not impossible to expand to cover various versions.

Implement support for injecting events

I have no idea why this would be useful, but some reflection-based libraries might make use of this.
Events might be a bit tricky due to generated delegate types being not-really-delegates, so a managed-side event can't have an IL2CPP delegate type and vice versa.
Due to that, this might be non-implementable in general, unless some additional hoops are jumped, similar to those needed for field injection.

System.ArgumentException: Base class UnityEngine.MonoBehaviour is value type and can't be inherited from

I'm trying class injection but I keep having this error.

System.ArgumentException: Base class UnityEngine.MonoBehaviour is value type and can't be inherited from
  at UnhollowerRuntimeLib.ClassInjector.RegisterTypeInIl2Cpp[T] () [0x000c3] in <e23489bf60bd4989ae101f3462221c4c>:0
  at MyMods0c6c997e473c4c9fa6c7c23c8ef04755.Init.Setup () [0x00071] in <fca399b1c8814998958ecbbee0151af9>:0

What am I doing wrong ?
Class being injected

public class ModMain : UnityEngine.MonoBehaviour
    {
        
        private void OnEnable()
        {
            System.Console.WriteLine("enabled yes");
        }

        public ModMain(IntPtr pointer) : base(pointer)
        {
        }
    }

Injector code

UnityVersionHandler.Initialize(2019, 4, 9);
ClassInjector.RegisterTypeInIl2Cpp<ModMain>();

Patched methods break exception propagation

If a (patched) game method throws and exception, the trampoline code will complain about the exception (thus breaking the exception flow) and won't execute the postfix/prefix.

Example stack trace:

[Error  :    Detour] UnhollowerBaseLib.Il2CppException: System.NullReferenceException: Object reference not set to an instance of an object.

  at UnhollowerBaseLib.Il2CppException.RaiseExceptionIfNecessary (System.IntPtr returnedException) [0x00014] in <777559d3b76a4c3e8ec3d7f2e15ce0fa>:0
  at (wrapper dynamic-method) LevelGeneration.LG_MarkerFactory.DMD<LevelGeneration.LG_MarkerFactory::InstantiateMarkerGameObject>(LG_MarkerSpawner,GameData.ExpeditionFunction,single,single,single,bool&,bool)
  at (wrapper dynamic-method) MonoMod.Utils.DynamicMethodDefinition.(il2cpp -> managed) InstantiateMarkerGameObject(intptr,GameData.ExpeditionFunction,single,single,single,byte&,bool,UnhollowerBaseLib.Runtime.Il2CppMethodInfo*)

Check/fix assemblies being invalid as far as netcore/netfw runtimes are concerned

Last time I checked, netcore/netfw runtimes refused to load generated assemblies. One specific issues was enum types having the Il2CppType static field.
After the field is removed in one of the future updates, we need to investigate if there's anything else invalid in generated assemblies, and fix those issues to make the assemblies compatible with netcore.

Mono somehow cares way less about what's in assemblies it loads, so current assemblies work just fine on mono.

mscorlib

The only mscorlib file i can find is called mscorlib.dll-resources.dat
and when i fill that one in it says access is denied to it

Implement non-boxing invokers for methods returning value types

Currently, Unhollower uses il2cpp_runtime_invoke, which boxes returned values if they have value types. This boxing happens in the invoker function, so a simple replacement for runtime_invoke seems non-viable - instead, custom invokers (plus perhaps a custom runtime_invoke) need to be generated to allow for non-boxing returns of value types. Like all allocations, this boxing can degrade performance quite a lot.

To solve this, custom invokers that don't box the returned value can be generated. Ideally, invokers would be generated at runtime. However, given the difficulty of achieving that for multiple platforms while keeping compatibility with C++ exceptions, a precompiled native library with invokers for most-common method signatures might be a good middle ground.

Naturally, assembly generation must be changed to make use of those non-boxing invoker methods.

Il2CppReferenceArray<Il2CppSystem.Object> usage

hey i started using ur staff through melonloader

i have older binaries from the game and can check what functions used to look like before il2cpp
the rpc arguments used to expect a "params object[]", now after il2cpp it expects an Il2CppReferenceArray<Il2CppSystem.Object>

i tried a few things on how to create an array with this but cant seem to get this to work.

aspecially cuz the arguments arent objects but all kind of types such as floats, booleans, vectors and some other things like enums.

any help would be appreciated :D

NullReferenceException when iterating through IEnumrable<XrefInstance>

Hello,
I am getting a NullReferenceException when iterating through the IEnumerable object that is returned by XrefScanner.XrefScan - I have already looked at the generated IL code, and it seems like it happens when it moves onto the next entry in the IEnumerable.

Exception:

[11:39:18.482] [Arcadius_Mod] [ERROR] System.NullReferenceException: Object reference not set to an instance of an object
  at System.Buffer.memcpy1 (System.Byte* dest, System.Byte* src, System.Int32 size) [0x00002] in <e1319b7195c343e79b385cd3aa43f5dc>:0
  at System.Buffer.Memcpy (System.Byte* dest, System.Byte* src, System.Int32 size) [0x00058] in <e1319b7195c343e79b385cd3aa43f5dc>:0
  at System.Runtime.InteropServices.Marshal.ReadInt64 (System.IntPtr ptr) [0x00015] in <e1319b7195c343e79b385cd3aa43f5dc>:0
  at UnhollowerRuntimeLib.XrefScans.XrefScanner.XrefGlobalClassFilter (System.IntPtr movTarget) [0x0001d] in <8eb030b38f51415daeb459bae49fe0fc>:0
  at UnhollowerRuntimeLib.XrefScans.XrefScanner+<>c.<XrefScan>b__0_0 (UnhollowerRuntimeLib.XrefScans.XrefInstance it) [0x00009] in <8eb030b38f51415daeb459bae49fe0fc>:0
  at System.Linq.Enumerable+WhereEnumerableIterator`1[TSource].MoveNext () [0x00037] in <fbb5ed17eb6e46c680000f8910ebb50c>:0
  at MyMod.Class1.OnApplicationLateStart () [0x000a0] in <6cd357c9270946a7aa332220af7ac4ad>:0
  at MelonLoader.MelonHandler+<>c.<OnApplicationLateStart_Mods>b__31_0 (MelonLoader.MelonMod x) [0x00010] in <239dd078bb4e49c8a1ff8200d76f2e32>:0
  at MelonLoader.MelonHandler.InvokeMelonMethod[T] (System.Collections.Generic.List`1[T]& melons, MelonLoader.MelonHandler+InvokeMelonMethodDelegate`1[T] method, System.Boolean remove_failed) [0x00032] in <239dd078bb4e49c8a1ff8200d76f2e32>:0

Code:

public override void OnApplicationLateStart()
{
    logger = LoggerInstance;

    MethodBase mb = typeof(Menu_Blacksmith).GetMethod("RepairTool");

    if (mb == null)
    {
        LoggerInstance.Error("Unable to find RepairTool for some god damn reason");
    }
    else
    {
        var instances = XrefScanner.XrefScan(mb);

        if (instances == null)
        {
            LoggerInstance.Error("Instances is null");
        }
        else
        {
            foreach (XrefInstance instance in instances)
            {
                MethodBase calledMethod = instance.TryResolve();
                LoggerInstance.Msg(calledMethod?.Name);
            }
        }
    }

    UniverseLib.Universe.Init(OnUniverseLibInit);
}

Fix native exceptions stack traces

il2cpp_format_stack_trace practically always returns an empty string. Using the trace_ips field of Exception class, or calling the GetStackTrace method might provide better results.

Pointer types try to construct pointer(intptr), which does not exist.

At the moment, when trying to access any value that is an unsafe pointer (i.e. int*), unhollower tries to construct the return value as int*(intptr), which fails because there is no constructor for int*

The workaround I have made is to get the raw il2cpp pointer manually and dereference the Intptr as the pointer type, I do not fully comprehend the implications of this as I assume the intptr constructor is used for GC. I am unsure if this is required for a raw intptr value, or if any boxing/unboxing is required.

Implement support for throwing/rethrowing exceptions

Right now, when patching game code, the patch typically can catch exceptions (via runtime_invoke) but can't re-throw them. This is unwelcome in some cases, as execution of patch caller can differ because of that.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.