Model: Ensure all TypeRefs are initialized with the model

Ensures that all types are output in the IDA Python script
Includes TypeRefs that aren't used in any type definition or member
This commit is contained in:
Katy Coe
2020-01-30 07:54:45 +01:00
parent a1e332620c
commit 37f1c49828
7 changed files with 96 additions and 92 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -17,20 +17,20 @@ namespace Il2CppInspector.Reflection
public Il2CppInspector Package { get; }
public List<Assembly> Assemblies { get; } = new List<Assembly>();
// 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<string, TypeInfo> TypesByFullName { get; } = new Dictionary<string, TypeInfo>();
// 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<ulong, TypeInfo> TypesByVirtualAddress { get; } = new ConcurrentDictionary<ulong, TypeInfo>();
// Every type
public IEnumerable<TypeInfo> Types => new IEnumerable<TypeInfo>[] {TypesByDefinitionIndex, TypeReferences, TypesByVirtualAddress.Values}.SelectMany(t => t);
public IEnumerable<TypeInfo> Types => new IEnumerable<TypeInfo>[] {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<int, CustomAttributeData> AttributesByIndices { get; } = new ConcurrentDictionary<int, CustomAttributeData>();
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<Il2CppType>(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}";

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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<TypeInfo> ImplementedInterfaces => implementedInterfaceUsages.Select(x => Assembly.Model.GetTypeFromUsage(x, MemberTypes.TypeInfo));
private readonly int[] implementedInterfaceReferences;
public IEnumerable<TypeInfo> 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<Il2CppGenericClass>(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)