/* Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com All rights reserved. */ using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; namespace Il2CppInspector.Reflection { public class Il2CppModel { public Il2CppInspector Package { get; } public List Assemblies { get; } = new List(); // List of all types from TypeDefs ordered by their TypeDefinitionIndex public TypeInfo[] TypesByDefinitionIndex { get; } // List of all types from TypeRefs ordered by instanceIndex public TypeInfo[] TypesByReferenceIndex { get; } // List of all types from GenericParameters public TypeInfo[] GenericParameterTypes { get; } // List of all methods from MethodSpecs (closed generic methods that can be called; does not need to be in a generic class) public Dictionary GenericMethods { get; } = new Dictionary(); // List of all type definitions by fully qualified name (TypeDefs only) public Dictionary TypesByFullName { get; } = new Dictionary(); // Every type public IEnumerable Types => TypesByDefinitionIndex.Concat(TypesByReferenceIndex) .Concat(GenericMethods.Values.Select(m => m.DeclaringType)).Distinct(); // List of all methods ordered by their MethodDefinitionIndex public MethodBase[] MethodsByDefinitionIndex { get; } // List of all Method.Invoke functions by invoker index public MethodInvoker[] MethodInvokers { get; } // List of all generated CustomAttributeData objects by their instanceIndex into AttributeTypeIndices public ConcurrentDictionary AttributesByIndices { get; } = new ConcurrentDictionary(); // Get an assembly by its image name public Assembly GetAssembly(string name) => Assemblies.FirstOrDefault(a => a.ShortName == name); // Get a type by its fully qualified name including generic type arguments, array brackets etc. // In other words, rather than only being able to fetch a type definition such as in Assembly.GetType(), // this method can also find reference types, types created from TypeRefs and constructed types from MethodSpecs public TypeInfo GetType(string fullName) => Types.FirstOrDefault(t => fullName == t.Namespace + "." + t.Name); // Get a concrete instantiation of a generic method from its fully qualified name and type arguments public MethodBase GetGenericMethod(string fullName, params TypeInfo[] typeArguments) => GenericMethods.Values.First(m => fullName == m.DeclaringType.Namespace + "." + m.DeclaringType.Name + "." + m.Name && m.GetGenericArguments().SequenceEqual(typeArguments)); // Create type model public Il2CppModel(Il2CppInspector package) { Package = package; TypesByDefinitionIndex = new TypeInfo[package.TypeDefinitions.Length]; TypesByReferenceIndex = new TypeInfo[package.TypeReferences.Count]; GenericParameterTypes = new TypeInfo[package.GenericParameters.Length]; MethodsByDefinitionIndex = new MethodBase[package.Methods.Length]; MethodInvokers = new MethodInvoker[package.MethodInvokePointers.Length]; // Recursively create hierarchy of assemblies and types from TypeDefs // No code that executes here can access any type through a TypeRef (ie. via TypesByReferenceIndex) for (var image = 0; image < package.Images.Length; image++) Assemblies.Add(new Assembly(this, image)); // Create and reference types from TypeRefs // Note that you can't resolve any TypeRefs until all the TypeDefs have been processed for (int typeRefIndex = 0; typeRefIndex < package.TypeReferences.Count; typeRefIndex++) { if(TypesByReferenceIndex[typeRefIndex] != null) { /* type already generated - probably by forward reference through GetTypeFromVirtualAddress */ continue; } var typeRef = Package.TypeReferences[typeRefIndex]; var referencedType = resolveTypeReference(typeRef); TypesByReferenceIndex[typeRefIndex] = referencedType; } // Create types and methods from MethodSpec (which incorporates TypeSpec in IL2CPP) foreach (var spec in Package.MethodSpecs) { var methodDefinition = MethodsByDefinitionIndex[spec.methodDefinitionIndex]; var declaringType = methodDefinition.DeclaringType; // Concrete instance of a generic class // If the class index is not specified, we will later create a generic method in a non-generic class if (spec.classIndexIndex != -1) { var genericInstance = Package.GenericInstances[spec.classIndexIndex]; var genericArguments = ResolveGenericArguments(genericInstance); declaringType = declaringType.MakeGenericType(genericArguments); } // Concrete instance of a generic method if (methodDefinition is MethodInfo) GenericMethods[spec] = new MethodInfo(this, spec, declaringType); else GenericMethods[spec] = new ConstructorInfo(this, spec, declaringType); } // Find all custom attribute generators (populate AttributesByIndices) (use ToList() to force evaluation) var allAssemblyAttributes = Assemblies.Select(a => a.CustomAttributes).ToList(); var allTypeAttributes = TypesByDefinitionIndex.Select(t => t.CustomAttributes).ToList(); var allEventAttributes = TypesByDefinitionIndex.SelectMany(t => t.DeclaredEvents).Select(e => e.CustomAttributes).ToList(); var allFieldAttributes = TypesByDefinitionIndex.SelectMany(t => t.DeclaredFields).Select(f => f.CustomAttributes).ToList(); var allPropertyAttributes = TypesByDefinitionIndex.SelectMany(t => t.DeclaredProperties).Select(p => p.CustomAttributes).ToList(); var allMethodAttributes = MethodsByDefinitionIndex.Select(m => m.CustomAttributes).ToList(); var allParameterAttributes = MethodsByDefinitionIndex.SelectMany(m => m.DeclaredParameters).Select(p => p.CustomAttributes).ToList(); // Create method invokers (one per signature, in invoker index order) foreach (var method in MethodsByDefinitionIndex) { var index = package.GetInvokerIndex(method.DeclaringType.Assembly.ModuleDefinition, method.Definition); if (index != -1) { if (MethodInvokers[index] == null) MethodInvokers[index] = new MethodInvoker(method, index); method.Invoker = MethodInvokers[index]; } } // TODO: Some invokers are not initialized or missing, need to find out why // Create method invokers sourced from generic method invoker indices foreach (var spec in GenericMethods.Keys) { if (package.GenericMethodInvokerIndices.TryGetValue(spec, out var index)) { if (MethodInvokers[index] == null) MethodInvokers[index] = new MethodInvoker(GenericMethods[spec], index); GenericMethods[spec].Invoker = MethodInvokers[index]; } } } // Get generic arguments from either a type or method instanceIndex from a MethodSpec public TypeInfo[] ResolveGenericArguments(Il2CppGenericInst inst) { // Get list of pointers to type parameters (both unresolved and concrete) var genericTypeArguments = Package.BinaryImage.ReadMappedWordArray(inst.type_argv, (int)inst.type_argc); return genericTypeArguments.Select(a => GetTypeFromVirtualAddress((ulong) a)).ToArray(); } // Initialize type from type reference (TypeRef) // Much of the following is adapted from il2cpp::vm::Class::FromIl2CppType private TypeInfo resolveTypeReference(Il2CppType typeRef) { var image = Package.BinaryImage; TypeInfo underlyingType; switch (typeRef.type) { // Classes defined in the metadata (reference to a TypeDef) case Il2CppTypeEnum.IL2CPP_TYPE_CLASS: case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE: underlyingType = TypesByDefinitionIndex[typeRef.datapoint]; // klassIndex break; // Constructed types case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST: // TODO: Replace with array load from Il2CppMetadataRegistration.genericClasses var generic = image.ReadMappedObject(typeRef.datapoint); // Il2CppGenericClass * // We have seen one test case where the TypeRef can point to no generic instance // This is going to leave the TypeInfo in an undefined state if (generic.typeDefinitionIndex == 0x0000_0000_ffff_ffff) return null; var genericTypeDef = TypesByDefinitionIndex[generic.typeDefinitionIndex]; // Get the instantiation // TODO: Replace with array load from Il2CppMetadataRegistration.genericInsts var genericInstance = image.ReadMappedObject(generic.context.class_inst); var genericArguments = ResolveGenericArguments(genericInstance); underlyingType = genericTypeDef.MakeGenericType(genericArguments); break; case Il2CppTypeEnum.IL2CPP_TYPE_ARRAY: var descriptor = image.ReadMappedObject(typeRef.datapoint); var elementType = GetTypeFromVirtualAddress(descriptor.etype); underlyingType = elementType.MakeArrayType(descriptor.rank); break; case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY: elementType = GetTypeFromVirtualAddress(typeRef.datapoint); underlyingType = elementType.MakeArrayType(1); break; case Il2CppTypeEnum.IL2CPP_TYPE_PTR: elementType = GetTypeFromVirtualAddress(typeRef.datapoint); underlyingType = elementType.MakePointerType(); break; // Generic type and generic method parameters case Il2CppTypeEnum.IL2CPP_TYPE_VAR: case Il2CppTypeEnum.IL2CPP_TYPE_MVAR: underlyingType = GetGenericParameterType((int)typeRef.datapoint); break; // Primitive types default: underlyingType = getTypeDefinitionFromTypeEnum(typeRef.type); break; } // Create a reference type if necessary return typeRef.byref ? underlyingType.MakeByRefType() : underlyingType; } // Basic primitive types are specified via a flag value private TypeInfo getTypeDefinitionFromTypeEnum(Il2CppTypeEnum t) { if ((int)t >= Il2CppConstants.FullNameTypeString.Count) return null; var fqn = Il2CppConstants.FullNameTypeString[(int)t]; return TypesByFullName[fqn]; } // Get a TypeRef by its virtual address // These are always nested types from references within another TypeRef public TypeInfo GetTypeFromVirtualAddress(ulong ptr) { var typeRefIndex = Package.TypeReferenceIndicesByAddress[ptr]; if (TypesByReferenceIndex[typeRefIndex] != null) return TypesByReferenceIndex[typeRefIndex]; var type = Package.TypeReferences[typeRefIndex]; var referencedType = resolveTypeReference(type); TypesByReferenceIndex[typeRefIndex] = referencedType; return referencedType; } public TypeInfo GetGenericParameterType(int index) { if (GenericParameterTypes[index] != null) return GenericParameterTypes[index]; var paramType = Package.GenericParameters[index]; // genericParameterIndex var container = Package.GenericContainers[paramType.ownerIndex]; TypeInfo result; if (container.is_method == 1) { var owner = MethodsByDefinitionIndex[container.ownerIndex]; result = new TypeInfo(owner, paramType); } else { var owner = TypesByDefinitionIndex[container.ownerIndex]; result = new TypeInfo(owner, paramType); } GenericParameterTypes[index] = result; return result; } // The attribute index is an index into AttributeTypeRanges, each of which is a start-end range index into AttributeTypeIndices, each of which is a TypeIndex public int GetCustomAttributeIndex(Assembly asm, uint token, int customAttributeIndex) { // Prior to v24.1, Type, Field, Parameter, Method, Event, Property, Assembly definitions had their own customAttributeIndex field if (Package.Version <= 24.0) return customAttributeIndex; // From v24.1 onwards, token was added to Il2CppCustomAttributeTypeRange and each Il2CppImageDefinition noted the CustomAttributeTypeRanges for the image if (!Package.AttributeIndicesByToken[asm.ImageDefinition.customAttributeStart].TryGetValue(token, out var index)) return -1; return index; } // Get the name of a metadata typeRef public string GetMetadataUsageName(MetadataUsage usage) { switch (usage.Type) { case MetadataUsageType.TypeInfo: case MetadataUsageType.Type: return GetMetadataUsageType(usage).Name; case MetadataUsageType.MethodDef: var method = GetMetadataUsageMethod(usage); return $"{method.DeclaringType.Name}.{method.Name}"; case MetadataUsageType.FieldInfo: var fieldRef = Package.FieldRefs[usage.SourceIndex]; var type = GetMetadataUsageType(usage); var field = type.DeclaredFields.First(f => f.Index == type.Definition.fieldStart + fieldRef.fieldIndex); return $"{type.Name}.{field.Name}"; case MetadataUsageType.StringLiteral: return Package.StringLiterals[usage.SourceIndex]; case MetadataUsageType.MethodRef: type = GetMetadataUsageType(usage); method = GetMetadataUsageMethod(usage); return $"{type.Name}.{method.Name}"; } throw new NotImplementedException("Unknown metadata usage type: " + usage.Type); } // Get the type used in a metadata usage public TypeInfo GetMetadataUsageType(MetadataUsage usage) => usage.Type switch { MetadataUsageType.Type => TypesByReferenceIndex[usage.SourceIndex], MetadataUsageType.TypeInfo => TypesByReferenceIndex[usage.SourceIndex], MetadataUsageType.MethodDef => GetMetadataUsageMethod(usage).DeclaringType, MetadataUsageType.FieldInfo => TypesByReferenceIndex[Package.FieldRefs[usage.SourceIndex].typeIndex], MetadataUsageType.MethodRef => GetMetadataUsageMethod(usage).DeclaringType, _ => throw new InvalidOperationException("Incorrect metadata usage type to retrieve referenced type") }; // Get the method used in a metadata usage public MethodBase GetMetadataUsageMethod(MetadataUsage usage) => usage.Type switch { MetadataUsageType.MethodDef => MethodsByDefinitionIndex[usage.SourceIndex], MetadataUsageType.MethodRef => GenericMethods[Package.MethodSpecs[usage.SourceIndex]], _ => throw new InvalidOperationException("Incorrect metadata usage type to retrieve referenced type") }; } }