cpp2il

repository·development·Indexed 25 days ago

https://github.com/samboycoding/cpp2il

A C# library and toolset for inspecting IL2CPP-generated metadata and game assemblies from Unity games. It provides LibCpp2IL for reflection-like capabilities on compiled binaries, Cpp2IL.Core for generating dummy Cecil assemblies via MakeDummyDLLs, and a Call Analyzer to identify method relationships and inject analysis attributes into the resulting assembly.

Tokens
4.1K
Snippets
7
Records
11
Agent score
83%

What's inside cpp2il

  1. Understand Call Analyzer injected attributes

    development
    The Call Analyzer is a processing layer in Cpp2IL that analyzes method instructions to identify calls to other methods. It injects 9 specific attributes into the resulting assembly (under the Cpp2ILInjected.CallAnalysis namespace) to provide metadata about method relationships, instruction validity, and compiler optimizations. These attributes allow developers to programmatically inspect call graphs and instruction quality via reflection.
  2. Handle Generic Method implementations

    development

    In IL2CPP, generic methods are specialized into concrete implementations. Because IL2CPP strips implementations that the game does not use, you may need to resolve specific variants.

    Find existing variants for a method

    Given an Il2CppMethodDefinition, you can find which concrete generic versions exist in the assembly using LibCpp2IlMain.ThePe.ConcreteGenericMethods.

    Resolve a method from a raw address

    If you have a call to a specific address (e.g., 0x123456789) and it is not a standard method, use LibCpp2IlMain.ThePe.ConcreteGenericImplementationsByAddress to find the base definition.

    // Find variants for a specific method
    var listType = LibCpp2IlReflection.GetType("List`1", "System");
    var addMethod = listType.Methods.First(m => m.Name == "Add");
    var variants = LibCpp2IlMain.ThePe.ConcreteGenericMethods[addMethod];
    
    // Resolve base method from a raw address
    var genericImplementations = LibCpp2IlMain.ThePe.ConcreteGenericImplementationsByAddress[0x123456789];
    Console.WriteLine(genericImplementations[0].BaseMethod.HumanReadableSignature);
  3. Initialize the Cpp2IL.Core library

    development

    Before invoking most APIs, you must initialize the library using Cpp2IlApi.InitializeLibCpp2Il. This method links the core logic to the underlying metadata and binary files via LibCpp2IL.

    There are two ways to initialize:

    1. From disk: Provide file paths to the binary and metadata files.
    2. From memory: Provide byte arrays of the binary and metadata files (useful if you need to decrypt files before processing).

    Required Parameters:

    • Binary/Metadata: Paths or byte arrays for the files.
    • Unity Version: An integer array containing at least three elements [major, minor, patch] (e.g., [2020, 3, 1]). This determines the supported il2cpp metadata version (supports Unity 2018 and up).
    • Verbose Logging: A boolean flag to enable detailed logs. It is advised to set this to false during normal runtime.
    • Manual Registration Addresses: A boolean flag to allow manual specification of CodeRegistration and MetadataRegistration struct addresses if they cannot be automatically located. It is advised to set this to false during normal runtime.

    Unity Version Utility: If you are unsure of the Unity version, use Cpp2IlApi.DetermineUnityVersion. This method takes two paths:

    • The path to the Game's main executable (used on Windows to parse the file version).
    • The path to the GameName_Data folder (used on non-Windows platforms to read the version from the globalgamemanagers file).
  4. Initialize LibCpp2IL from files or byte arrays

    development

    LibCpp2IL can be initialized using two methods. Note that debug logging is enabled by default, which will output extensive timing and loading data to the Console. You must provide the Unity version as an integer array (e.g., new [] {2019, 2, 0}).

    From the hard drive

    Use LibCpp2IlMain.LoadFromFile by providing the paths to the game assembly and the global metadata.

    From a byte array

    Use LibCpp2IlMain.Initialize if you have already loaded the file contents into memory.

    // From the hard drive
    var unityVersion = new [] {2019, 2, 0};
    if (!LibCpp2IlMain.LoadFromFile(gameAssemblyPath, globalMetadataPath, unityVersion)) {
        Console.WriteLine("initialization failed!");
        return;
    }
    
    // From a byte array
    var unityVersion = new [] {2019, 2, 0};
    if (!LibCpp2IlMain.Initialize(gameAssemblyBytes, globalMetadataBytes, unityVersion)) {
        Console.WriteLine("initialization failed!");
        return;
    }
  5. Inspect Methods, Fields, Properties, and Events

    development

    Use the following properties on an Il2CppTypeDefinition to inspect members:

    • Methods: Access .Methods. Each Il2CppMethodDefinition provides .Name, .MethodPointer, .ReturnType (as Il2CppTypeReflectionData), and .Parameters (as Il2CppParameterReflectionData).
    • Fields: Access .Fields. Each Il2CppFieldDefinition provides .Name and .FieldType (as Il2CppTypeReflectionData).
    • Properties: Access .Properties. Each Il2CppPropertyDefinition provides .Name, .Getter, .Setter, and .PropertyType.
    • Events: Access .Events. Each Il2CppEventDefinition provides .Name, .EventType, .Adder, .Remover, and .Invoker.
    // Methods
    var join = type.Methods[0];
    Console.Log(join.Name);
    Console.Log($"0x{join.MethodPointer:X}");
    Console.Log(join.ReturnType);
    
    // Fields
    var lengthField = type.Fields[0];
    Console.WriteLine(lengthField.Name);
    Console.WriteLine(lengthField.FieldType);
    
    // Properties
    var lengthProperty = type.Properties[1];
    Console.WriteLine(lengthProperty.Name);
    Console.WriteLine(lengthProperty.Getter.Name);
    
    // Events
    var appDomain = LibCpp2IlReflection.GetType("AppDomain", "System");
    Console.Log(appDomain.Events[0].Name);
    Console.Log(appDomain.Events[0].Adder.Name);
  6. Generate dummy assemblies with MakeDummyDLLs

    development

    Once the library is initialized, you can call Cpp2IlApi.MakeDummyDLLs to generate a representation of the il2cpp application's type model.

    This function returns a List<AssemblyDefinition> (Cecil assemblies). These assemblies include the full type model, including:

    • Types
    • Fields
    • Properties
    • Events
    • Methods

    Note: The generated methods do not contain actual implementation bodies; they only contain stubs. These assemblies are intended for use as metadata references in your own tools or analysis.

  7. Use the Reflection API to find types

    development

    Use LibCpp2IlReflection.GetType to retrieve Il2CppTypeDefinition objects by their name and an optional namespace.

    Signature: Il2CppTypeDefinition GetType(string typeName, string? optionalNamespaceName)

    // Examples
    Il2CppTypeDefinition type = LibCpp2IlReflection.GetType("String");
    type = LibCpp2IlReflection.GetType("List`1");
    type = LibCpp2IlReflection.GetType("Object", "UnityEngine");
  8. Resolve IL2CPP global references by address

    development

    IL2CPP stores type, field, string literal, and method references as globals. If you have a virtual address (for example, from an il2cpp_codegen_object_new call), you can resolve it to its corresponding object using the following methods:

    • Type References: Use LibCpp2IlMain.GetTypeGlobalByAddress(address) to get an Il2CppTypeReflectionData.
    • Method References:
      • For basic details: LibCpp2IlMain.GetMethodDefinitionByGlobalAddress(address) returns an Il2CppMethodDefinition.
      • For complex/generic data: LibCpp2IlMain.GetMethodGlobalByAddress(address) returns a MetadataUsage?. If the usage is a MethodRef, you can use .AsGenericMethodRef() to access generic parameters.
    • Field References: Use LibCpp2IlMain.GetFieldGlobalByAddress(address) to get an Il2CppFieldDefinition.
    • String Literals: Use LibCpp2IlMain.GetLiteralByAddress(address) to get the string value.
    // Type
    Il2CppTypeReflectionData type = LibCpp2IlMain.GetTypeGlobalByAddress(0x180623548);
    
    // Method (Basic)
    Il2CppMethodDefinition method = LibCpp2IlMain.GetMethodDefinitionByGlobalAddress(0x182938239);
    
    // Method (Generic/Complex)
    MetadataUsage? usage = LibCpp2IlMain.GetMethodGlobalByAddress(0x182938239);
    if(usage != null && usage.Type == MetadataUsageType.MethodRef) {
        var genericMethodRef = usage.AsGenericMethodRef();
        Console.WriteLine(genericMethodRef.declaringType);
        Console.WriteLine(genericMethodRef.baseMethod);
        Console.WriteLine(genericMethodRef.typeGenericParams);
        Console.WriteLine(genericMethodRef.methodGenericParams);
    }
    
    // Field
    Il2CppFieldDefinition fieldDef = LibCpp2IlMain.GetFieldGlobalByAddress(0x182933215);
    
    // String
    string literal = LibCpp2IlMain.GetLiteralByAddress(0x182197654);
  9. Access type properties and hierarchy

    development

    Once you have an Il2CppTypeDefinition, you can access its metadata and hierarchy:

    • Basic Info: Access .Namespace, .Name, and .FullName.
    • Inheritance: Use .BaseType for the parent class. Use .Interfaces to get an array of Il2CppTypeReflectionData representing implemented interfaces.
    • Nested Types: Use .NestedTypes to find types declared within the current type.
    • Tokens: Every field, type, method, property, and event stores its metadata token in the .token field.
    Console.WriteLine(type.Namespace);
    Console.WriteLine(type.Name);
    Console.WriteLine(type.FullName);
    
    // Inheritance
    Console.WriteLine(type.BaseType.FullName);
    Il2CppTypeReflectionData[] interfaces = type.Interfaces;
    
    // Nested Types
    var transform = LibCpp2IlReflection.GetType("Transform", "UnityEngine");
    Console.Log(transform.NestedTypes[0].Name);
  10. Reference Call Analyzer injected attributes

    development

    The following attributes are injected into methods by the Call Analyzer. They are located in the Cpp2ILInjected.CallAnalysis namespace.

    Method Relationship Attributes

    • DeduplicatedMethodAttribute: Indicates the method's instructions were deduplicated by the compiler and it shares an address with other methods.
    • CallAnalysisNotSupportedAttribute: Indicates the method has no instructions to analyze (and is not abstract or an interface).
    • CalledByAttribute: Indicates this method is called from a specific method. Note: This is not emitted if there are a large number of callers. Contains fields for Type, Member, MemberTypeParameters, MemberParameters, and ReturnType.
    • CallsAttribute: Indicates this method calls a specific method. Contains fields for Type, Member, MemberTypeParameters, MemberParameters, and ReturnType.
    • CallerCountAttribute: Provides the number of direct calls to this method (similar to Unhollower's implementation). Contains an int Count field.

    Instruction Analysis Attributes

    • CallsDeduplicatedMethodsAttribute: Indicates the method calls at least one deduplicated method. Contains an int Count field.
    • CallsUnknownMethodsAttribute: Indicates the method calls at least one unknown method. Contains an int Count field.
    • ContainsInvalidInstructionsAttribute: Indicates the method contains at least one invalid instruction.
    • ContainsUnimplementedInstructionsAttribute: Indicates the method contains at least one instruction that is not implemented.
    using System;
    namespace Cpp2ILInjected.CallAnalysis;
    
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public sealed class DeduplicatedMethodAttribute : Attribute {}
    
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public sealed class CallAnalysisNotSupportedAttribute : Attribute {}
    
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public sealed class CalledByAttribute : Attribute
    {
    	public object Type;
    	public string Member;
    	public object[] MemberTypeParameters;
    	public object[] MemberParameters;
    	public object ReturnType;
    }
    
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
    public sealed class CallsAttribute : Attribute
    {
    	public object Type;
    	public string Member;
    	public object[] MemberTypeParameters;
    	public object[] MemberParameters;
    	public object ReturnType;
    }
    
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public sealed class CallerCountAttribute : Attribute
    {
    	public int Count;
    }
    
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public sealed class CallsDeduplicatedMethodsAttribute : Attribute
    {
    	public int Count;
    }
    
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public sealed class CallsUnknownMethodsAttribute : Attribute
    {
    	public int Count;
    }
    
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public sealed class ContainsInvalidInstructionsAttribute : Attribute {}
    
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public sealed class ContainsUnimplementedInstructionsAttribute : Attribute {}