diff --git a/Il2CppInspector/Reflection/CustomAttributeData.cs b/Il2CppInspector/Reflection/CustomAttributeData.cs index e94d337..6ed663b 100644 --- a/Il2CppInspector/Reflection/CustomAttributeData.cs +++ b/Il2CppInspector/Reflection/CustomAttributeData.cs @@ -47,7 +47,7 @@ namespace Il2CppInspector.Reflection continue; } - attribute = new CustomAttributeData { Index = customAttributeIndex, AttributeType = asm.Model.GetTypeFromUsage(typeIndex) }; + attribute = new CustomAttributeData { Index = customAttributeIndex, AttributeType = asm.Model.TypesByReferenceIndex[typeIndex] }; asm.Model.AttributesByIndices.TryAdd(i, attribute); yield return attribute; diff --git a/Il2CppInspector/Reflection/EventInfo.cs b/Il2CppInspector/Reflection/EventInfo.cs index d933d29..ff90d22 100644 --- a/Il2CppInspector/Reflection/EventInfo.cs +++ b/Il2CppInspector/Reflection/EventInfo.cs @@ -28,8 +28,8 @@ namespace Il2CppInspector.Reflection public MethodInfo RaiseMethod { get; } // Event handler delegate type - private int eventTypeUsage; - public TypeInfo EventHandlerType => Assembly.Model.GetTypeFromUsage(eventTypeUsage, MemberTypes.TypeInfo); + private int eventTypeReference; + public TypeInfo EventHandlerType => Assembly.Model.TypesByReferenceIndex[eventTypeReference]; // True if the event has a special name public bool IsSpecialName => (Attributes & EventAttributes.SpecialName) == EventAttributes.SpecialName; @@ -42,8 +42,8 @@ namespace Il2CppInspector.Reflection Index = eventIndex; Name = pkg.Strings[Definition.nameIndex]; - eventTypeUsage = Definition.typeIndex; - var eventType = pkg.TypeReferences[eventTypeUsage]; + eventTypeReference = Definition.typeIndex; + var eventType = pkg.TypeReferences[eventTypeReference]; if ((eventType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_SPECIAL_NAME) == Il2CppConstants.FIELD_ATTRIBUTE_SPECIAL_NAME) Attributes |= EventAttributes.SpecialName; diff --git a/Il2CppInspector/Reflection/FieldInfo.cs b/Il2CppInspector/Reflection/FieldInfo.cs index 03f06f9..af74b11 100644 --- a/Il2CppInspector/Reflection/FieldInfo.cs +++ b/Il2CppInspector/Reflection/FieldInfo.cs @@ -30,8 +30,8 @@ namespace Il2CppInspector.Reflection { public FieldAttributes Attributes { get; } // Type of field - private readonly int fieldTypeUsage; - public TypeInfo FieldType => Assembly.Model.GetTypeFromUsage(fieldTypeUsage, MemberTypes.Field); + private readonly int fieldTypeReference; + public TypeInfo FieldType => Assembly.Model.TypesByReferenceIndex[fieldTypeReference]; // For the Is* definitions below, see: // https://docs.microsoft.com/en-us/dotnet/api/system.reflection.fieldinfo.isfamilyandassembly?view=netframework-4.7.1#System_Reflection_FieldInfo_IsFamilyAndAssembly @@ -81,8 +81,8 @@ namespace Il2CppInspector.Reflection { Offset = pkg.FieldOffsets[fieldIndex]; Name = pkg.Strings[Definition.nameIndex]; - fieldTypeUsage = Definition.typeIndex; - var fieldType = pkg.TypeReferences[fieldTypeUsage]; + fieldTypeReference = Definition.typeIndex; + var fieldType = pkg.TypeReferences[fieldTypeReference]; if ((fieldType.attrs & Il2CppConstants.FIELD_ATTRIBUTE_FIELD_ACCESS_MASK) == Il2CppConstants.FIELD_ATTRIBUTE_PRIVATE) Attributes |= FieldAttributes.Private; diff --git a/Il2CppInspector/Reflection/Il2CppModel.cs b/Il2CppInspector/Reflection/Il2CppModel.cs index c6d085b..579a4ff 100644 --- a/Il2CppInspector/Reflection/Il2CppModel.cs +++ b/Il2CppInspector/Reflection/Il2CppModel.cs @@ -17,20 +17,20 @@ namespace Il2CppInspector.Reflection public Il2CppInspector Package { get; } public List Assemblies { get; } = new List(); - // List of all types ordered by their TypeDefinitionIndex + // List of all types from TypeDefs ordered by their TypeDefinitionIndex public TypeInfo[] TypesByDefinitionIndex { get; } - // List of all type definitions by fully qualified name + // List of all types from TypeRefs ordered by index + public TypeInfo[] TypesByReferenceIndex { get; } + + // List of all type definitions by fully qualified name (TypeDefs only) public Dictionary TypesByFullName { get; } = new Dictionary(); - // List of all type references ordered by index - public TypeInfo[] TypeReferences { get; } - - // List of type usages that are initialized via pointers in the image + // List of type references that are initialized via pointers in the image public ConcurrentDictionary TypesByVirtualAddress { get; } = new ConcurrentDictionary(); // Every type - public IEnumerable Types => new IEnumerable[] {TypesByDefinitionIndex, TypeReferences, TypesByVirtualAddress.Values}.SelectMany(t => t); + public IEnumerable Types => new IEnumerable[] {TypesByDefinitionIndex, TypesByReferenceIndex, TypesByVirtualAddress.Values}.SelectMany(t => t); // List of all methods ordered by their MethodDefinitionIndex public MethodBase[] MethodsByDefinitionIndex { get; } @@ -38,67 +38,66 @@ namespace Il2CppInspector.Reflection // List of all generated CustomAttributeData objects by their index into AttributeTypeIndices public ConcurrentDictionary AttributesByIndices { get; } = new ConcurrentDictionary(); - public Il2CppModel(Il2CppInspector package) { - Package = package; - TypesByDefinitionIndex = new TypeInfo[package.TypeDefinitions.Length]; - TypeReferences = new TypeInfo[package.TypeReferences.Count]; - MethodsByDefinitionIndex = new MethodBase[package.Methods.Length]; - - // Create Assembly objects from Il2Cpp package - for (var image = 0; image < package.Images.Length; image++) - Assemblies.Add(new Assembly(this, image)); - } - // Get an assembly by its image name public Assembly GetAssembly(string name) => Assemblies.FirstOrDefault(a => a.ShortName == name); - private TypeInfo getNewTypeUsage(Il2CppType usage, MemberTypes memberType) { + // Create type model + public Il2CppModel(Il2CppInspector package) { + Package = package; + TypesByDefinitionIndex = new TypeInfo[package.TypeDefinitions.Length]; + TypesByReferenceIndex = new TypeInfo[package.TypeReferences.Count]; + MethodsByDefinitionIndex = new MethodBase[package.Methods.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++) { + var typeRef = Package.TypeReferences[typeRefIndex]; + var referencedType = resolveTypeReference(typeRef); + + TypesByReferenceIndex[typeRefIndex] = referencedType; + } + } + + private TypeInfo resolveTypeReference(Il2CppType typeRef) { TypeInfo underlyingType; - switch (usage.type) { + switch (typeRef.type) { + // Classes defined in the metadata (reference to a TypeDef) case Il2CppTypeEnum.IL2CPP_TYPE_CLASS: case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE: - // Classes defined in the metadata - underlyingType = TypesByDefinitionIndex[usage.datapoint]; // klassIndex + underlyingType = TypesByDefinitionIndex[typeRef.datapoint]; // klassIndex break; + // Constructed types case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST: case Il2CppTypeEnum.IL2CPP_TYPE_ARRAY: case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY: case Il2CppTypeEnum.IL2CPP_TYPE_PTR: + + // Generic type and generic method parameters case Il2CppTypeEnum.IL2CPP_TYPE_VAR: case Il2CppTypeEnum.IL2CPP_TYPE_MVAR: - // Everything that requires special handling - underlyingType = new TypeInfo(this, usage, memberType); + + underlyingType = new TypeInfo(this, typeRef); break; + // Primitive types default: - // Primitive types - underlyingType = GetTypeFromTypeEnum(usage.type); + underlyingType = getTypeDefinitionFromTypeEnum(typeRef.type); break; } // Create a reference type if necessary - return usage.byref ? underlyingType.MakeByRefType() : underlyingType; + return typeRef.byref ? underlyingType.MakeByRefType() : underlyingType; } - // Get or generate a type from its IL2CPP binary type usage reference - // (field, return type, generic type parameter etc.) - public TypeInfo GetTypeFromUsage(int typeUsageIndex, MemberTypes memberType = MemberTypes.All) { - - // Already generated type previously? - if (TypeReferences[typeUsageIndex] != null) - return TypeReferences[typeUsageIndex]; - - var usage = Package.TypeReferences[typeUsageIndex]; - var newUsage = getNewTypeUsage(usage, memberType); - - TypeReferences[typeUsageIndex] = newUsage; - return newUsage; - } - - // Basic primitive types - public TypeInfo GetTypeFromTypeEnum(Il2CppTypeEnum t) { + // Basic primitive types are specified via a flag value + private TypeInfo getTypeDefinitionFromTypeEnum(Il2CppTypeEnum t) { if ((int) t >= Il2CppConstants.FullNameTypeString.Count) return null; @@ -107,16 +106,17 @@ namespace Il2CppInspector.Reflection } // Type from a virtual address pointer - // These are always nested types frorm usages within another type + // These are always nested types from references within another TypeRef + // TODO: Eliminate GetTypeFromVirtualAddress() - use base and offset from MetadataRegistration.ptypes (Package.TypeReferences) instead public TypeInfo GetTypeFromVirtualAddress(ulong ptr) { if (TypesByVirtualAddress.ContainsKey(ptr)) return TypesByVirtualAddress[ptr]; var type = Package.BinaryImage.ReadMappedObject(ptr); - var newUsage = getNewTypeUsage(type, MemberTypes.NestedType); + var referencedType = resolveTypeReference(type); - TypesByVirtualAddress.TryAdd(ptr, newUsage); - return newUsage; + TypesByVirtualAddress.TryAdd(ptr, referencedType); + return referencedType; } // 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 @@ -131,12 +131,12 @@ namespace Il2CppInspector.Reflection return index; } - // Get the name of a metadata usage + // Get the name of a metadata typeRef public string GetMetadataUsageName(MetadataUsage usage) { switch (usage.Type) { case MetadataUsageType.TypeInfo: case MetadataUsageType.Type: - var type = GetTypeFromUsage(usage.SourceIndex); + var type = TypesByReferenceIndex[usage.SourceIndex]; return type.Name; case MetadataUsageType.MethodDef: @@ -145,7 +145,7 @@ namespace Il2CppInspector.Reflection case MetadataUsageType.FieldInfo: var fieldRef = Package.FieldRefs[usage.SourceIndex]; - type = GetTypeFromUsage(fieldRef.typeIndex); + type = TypesByReferenceIndex[fieldRef.typeIndex]; var field = type.DeclaredFields.First(f => f.Index == type.Definition.fieldStart + fieldRef.fieldIndex); return $"{type.Name}.{field.Name}"; diff --git a/Il2CppInspector/Reflection/MethodInfo.cs b/Il2CppInspector/Reflection/MethodInfo.cs index 1f3cd9d..6331139 100644 --- a/Il2CppInspector/Reflection/MethodInfo.cs +++ b/Il2CppInspector/Reflection/MethodInfo.cs @@ -1,5 +1,5 @@ /* - Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + Copyright 2017-2020 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com All rights reserved. */ @@ -17,8 +17,8 @@ namespace Il2CppInspector.Reflection public ParameterInfo ReturnParameter { get; } // Return type of the method - private readonly int returnTypeUsage; - public TypeInfo ReturnType => Assembly.Model.GetTypeFromUsage(returnTypeUsage, MemberTypes.TypeInfo); + private readonly int returnTypeReference; + public TypeInfo ReturnType => Assembly.Model.TypesByReferenceIndex[returnTypeReference]; public override bool RequiresUnsafeContext => base.RequiresUnsafeContext || ReturnType.RequiresUnsafeContext; @@ -26,7 +26,7 @@ namespace Il2CppInspector.Reflection public MethodInfo(Il2CppInspector pkg, int methodIndex, TypeInfo declaringType) : base(pkg, methodIndex, declaringType) { // Add return parameter - returnTypeUsage = Definition.returnType; + returnTypeReference = Definition.returnType; ReturnParameter = new ParameterInfo(pkg, -1, this); } diff --git a/Il2CppInspector/Reflection/ParameterInfo.cs b/Il2CppInspector/Reflection/ParameterInfo.cs index 04db528..70ba153 100644 --- a/Il2CppInspector/Reflection/ParameterInfo.cs +++ b/Il2CppInspector/Reflection/ParameterInfo.cs @@ -43,8 +43,8 @@ namespace Il2CppInspector.Reflection public string CSharpSafeName => Constants.Keywords.Contains(Name) ? "@" + Name : Name; // Type of this parameter - private readonly int paramTypeUsage; - public TypeInfo ParameterType => DeclaringMethod.Assembly.Model.GetTypeFromUsage(paramTypeUsage, MemberTypes.TypeInfo); + private readonly int paramTypeReference; + public TypeInfo ParameterType => DeclaringMethod.Assembly.Model.TypesByReferenceIndex[paramTypeReference]; // Zero-indexed position of the parameter in parameter list public int Position { get; } @@ -56,7 +56,7 @@ namespace Il2CppInspector.Reflection if (paramIndex == -1) { Position = -1; - paramTypeUsage = declaringMethod.Definition.returnType; + paramTypeReference = declaringMethod.Definition.returnType; Attributes |= ParameterAttributes.Retval; return; } @@ -69,8 +69,8 @@ namespace Il2CppInspector.Reflection Name = string.Format($"param_{Index:x8}"); Position = paramIndex - declaringMethod.Definition.parameterStart; - paramTypeUsage = Definition.typeIndex; - var paramType = pkg.TypeReferences[paramTypeUsage]; + paramTypeReference = Definition.typeIndex; + var paramType = pkg.TypeReferences[paramTypeReference]; if ((paramType.attrs & Il2CppConstants.PARAM_ATTRIBUTE_HAS_DEFAULT) != 0) Attributes |= ParameterAttributes.HasDefault; diff --git a/Il2CppInspector/Reflection/TypeInfo.cs b/Il2CppInspector/Reflection/TypeInfo.cs index f3e58c5..8168b74 100644 --- a/Il2CppInspector/Reflection/TypeInfo.cs +++ b/Il2CppInspector/Reflection/TypeInfo.cs @@ -22,11 +22,11 @@ namespace Il2CppInspector.Reflection { public TypeAttributes Attributes { get; } // Type that this type inherits from - private readonly int baseTypeUsage = -1; + private readonly int baseTypeReference = -1; public TypeInfo BaseType => IsPointer? null : - baseTypeUsage != -1? - Assembly.Model.GetTypeFromUsage(baseTypeUsage, MemberTypes.TypeInfo) + baseTypeReference != -1? + Assembly.Model.TypesByReferenceIndex[baseTypeReference] : IsArray? Assembly.Model.TypesByFullName["System.Array"] : Namespace != "System" || BaseName != "Object" ? Assembly.Model.TypesByFullName["System.Object"] : null; @@ -109,7 +109,7 @@ namespace Il2CppInspector.Reflection { public TypeInfo[] GetGenericParameterConstraints() { var types = new TypeInfo[genericConstraintCount]; for (int c = 0; c < genericConstraintCount; c++) - types[c] = Assembly.Model.GetTypeFromUsage(Assembly.Model.Package.GenericConstraintIndices[genericConstraintIndex + c], MemberTypes.TypeInfo); + types[c] = Assembly.Model.TypesByReferenceIndex[Assembly.Model.Package.GenericConstraintIndices[genericConstraintIndex + c]]; return types; } @@ -373,14 +373,14 @@ namespace Il2CppInspector.Reflection { // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.haselementtype?view=netframework-4.8 public bool HasElementType => ElementType != null; - private readonly int[] implementedInterfaceUsages; - public IEnumerable ImplementedInterfaces => implementedInterfaceUsages.Select(x => Assembly.Model.GetTypeFromUsage(x, MemberTypes.TypeInfo)); + private readonly int[] implementedInterfaceReferences; + public IEnumerable ImplementedInterfaces => implementedInterfaceReferences.Select(x => Assembly.Model.TypesByReferenceIndex[x]); public bool IsAbstract => (Attributes & TypeAttributes.Abstract) == TypeAttributes.Abstract; public bool IsArray { get; } public bool IsByRef { get; } public bool IsClass => (Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Class; - public bool IsEnum => enumUnderlyingTypeUsage != -1; + public bool IsEnum => enumUnderlyingTypeReference != -1; public bool IsGenericParameter { get; } public bool IsGenericType { get; } public bool IsGenericTypeDefinition => genericArguments.Any() && genericArguments.All(a => a.IsGenericTypeParameter); @@ -422,21 +422,19 @@ namespace Il2CppInspector.Reflection { public string[] GetEnumNames() => IsEnum? DeclaredFields.Where(x => x.Name != "value__").Select(x => x.Name).ToArray() : throw new InvalidOperationException("Type is not an enumeration"); // The underlying type of an enumeration (int by default) - private readonly int enumUnderlyingTypeUsage = -1; + private readonly int enumUnderlyingTypeReference = -1; private TypeInfo enumUnderlyingType; public TypeInfo GetEnumUnderlyingType() { if (!IsEnum) return null; - enumUnderlyingType ??= Assembly.Model.GetTypeFromUsage(enumUnderlyingTypeUsage, MemberTypes.TypeInfo); + enumUnderlyingType ??= Assembly.Model.TypesByReferenceIndex[enumUnderlyingTypeReference]; return enumUnderlyingType; } public Array GetEnumValues() => IsEnum? DeclaredFields.Where(x => x.Name != "value__").Select(x => x.DefaultValue).ToArray() : throw new InvalidOperationException("Type is not an enumeration"); - // Initialize from specified type index in metadata - - // Top-level types + // Initialize type from TypeDef using specified index in metadata public TypeInfo(int typeIndex, Assembly owner) : base(owner) { var pkg = Assembly.Model.Package; @@ -447,7 +445,7 @@ namespace Il2CppInspector.Reflection { // Derived type? if (Definition.parentIndex >= 0) - baseTypeUsage = Definition.parentIndex; + baseTypeReference = Definition.parentIndex; // Nested type? if (Definition.declaringTypeIndex >= 0) { @@ -501,18 +499,18 @@ namespace Il2CppInspector.Reflection { if ((Definition.flags & Il2CppConstants.TYPE_ATTRIBUTE_INTERFACE) != 0) Attributes |= TypeAttributes.Interface; - // Enumerations - bit 1 of bitfield indicates this (also the baseTypeUsage will be System.Enum) + // Enumerations - bit 1 of bitfield indicates this (also the baseTypeReference will be System.Enum) if (((Definition.bitfield >> 1) & 1) == 1) - enumUnderlyingTypeUsage = Definition.elementTypeIndex; + enumUnderlyingTypeReference = Definition.elementTypeIndex; // Pass-by-reference type // NOTE: This should actually always evaluate to false in the current implementation IsByRef = Index == Definition.byrefTypeIndex; // Add all implemented interfaces - implementedInterfaceUsages = new int[Definition.interfaces_count]; + implementedInterfaceReferences = new int[Definition.interfaces_count]; for (var i = 0; i < Definition.interfaces_count; i++) - implementedInterfaceUsages[i] = pkg.InterfaceUsageIndices[Definition.interfacesStart + i]; + implementedInterfaceReferences[i] = pkg.InterfaceUsageIndices[Definition.interfacesStart + i]; // Add all nested types declaredNestedTypes = new int[Definition.nested_type_count]; @@ -575,14 +573,20 @@ namespace Il2CppInspector.Reflection { DeclaredEvents.Add(new EventInfo(pkg, e, this)); } - // Initialize type from type reference + // Initialize type from type reference (TypeRef) // Much of the following is adapted from il2cpp::vm::Class::FromIl2CppType - public TypeInfo(Il2CppModel model, Il2CppType pType, MemberTypes memberType) { + public TypeInfo(Il2CppModel model, Il2CppType pType) { var image = model.Package.BinaryImage; // Open and closed generic types if (pType.type == Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST) { var generic = image.ReadMappedObject(pType.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; + var genericTypeDef = model.TypesByDefinitionIndex[generic.typeDefinitionIndex]; Assembly = genericTypeDef.Assembly; @@ -592,12 +596,12 @@ namespace Il2CppInspector.Reflection { // Derived type? if (genericTypeDef.Definition.parentIndex >= 0) - baseTypeUsage = genericTypeDef.Definition.parentIndex; + baseTypeReference = genericTypeDef.Definition.parentIndex; // Nested type? if (genericTypeDef.Definition.declaringTypeIndex >= 0) { declaringTypeDefinitionIndex = (int)model.Package.TypeReferences[genericTypeDef.Definition.declaringTypeIndex].datapoint; - MemberType = memberType | MemberTypes.NestedType; + MemberType |= MemberTypes.NestedType; } IsGenericType = true; @@ -665,11 +669,11 @@ namespace Il2CppInspector.Reflection { // Derived type? if (ownerType.Definition.parentIndex >= 0) - baseTypeUsage = ownerType.Definition.parentIndex; + baseTypeReference = ownerType.Definition.parentIndex; // Nested type always - sets DeclaringType used below declaringTypeDefinitionIndex = ownerType.Index; - MemberType = memberType | MemberTypes.NestedType; + MemberType |= MemberTypes.NestedType; // All generic method type parameters have a declared method if (container.is_method == 1)