From 5a42afe7f2ad9c72532abab32bae1624c94631ba Mon Sep 17 00:00:00 2001 From: Katy Coe Date: Wed, 11 Dec 2019 09:30:00 +0100 Subject: [PATCH] Model and Output: Dramatically improve handling of byref types --- Il2CppInspector/Il2CppModel.cs | 14 ++++++-- Il2CppInspector/Reflection/MethodInfo.cs | 3 +- Il2CppInspector/Reflection/ParameterInfo.cs | 11 +++--- Il2CppInspector/Reflection/TypeInfo.cs | 39 +++++++++++++++++---- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/Il2CppInspector/Il2CppModel.cs b/Il2CppInspector/Il2CppModel.cs index f285efd..162ff65 100644 --- a/Il2CppInspector/Il2CppModel.cs +++ b/Il2CppInspector/Il2CppModel.cs @@ -52,11 +52,14 @@ namespace Il2CppInspector.Reflection public Assembly GetAssembly(string name) => Assemblies.FirstOrDefault(a => a.ShortName == name); private TypeInfo getNewTypeUsage(Il2CppType usage, MemberTypes memberType) { + TypeInfo underlyingType; + switch (usage.type) { case Il2CppTypeEnum.IL2CPP_TYPE_CLASS: case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE: // Classes defined in the metadata - return TypesByDefinitionIndex[usage.datapoint]; // klassIndex + underlyingType = TypesByDefinitionIndex[usage.datapoint]; // klassIndex + break; case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST: case Il2CppTypeEnum.IL2CPP_TYPE_ARRAY: @@ -65,12 +68,17 @@ namespace Il2CppInspector.Reflection case Il2CppTypeEnum.IL2CPP_TYPE_VAR: case Il2CppTypeEnum.IL2CPP_TYPE_MVAR: // Everything that requires special handling - return new TypeInfo(this, usage, memberType); + underlyingType = new TypeInfo(this, usage, memberType); + break; default: // Primitive types - return GetTypeFromTypeEnum(usage.type); + underlyingType = GetTypeFromTypeEnum(usage.type); + break; } + + // Create a reference type if necessary + return usage.byref? underlyingType.MakeByRefType() : underlyingType; } // Get or generate a type from its IL2CPP binary type usage reference diff --git a/Il2CppInspector/Reflection/MethodInfo.cs b/Il2CppInspector/Reflection/MethodInfo.cs index 4b12241..1f3cd9d 100644 --- a/Il2CppInspector/Reflection/MethodInfo.cs +++ b/Il2CppInspector/Reflection/MethodInfo.cs @@ -31,7 +31,8 @@ namespace Il2CppInspector.Reflection } // TODO: Generic arguments (and on ConstructorInfo) - public override string ToString() => ReturnType.Name + " " + Name + "(" + string.Join(", ", DeclaredParameters.Select(x => x.ParameterType.Name)) + ")"; + public override string ToString() => ReturnType.Name + " " + Name + "(" + string.Join(", ", + DeclaredParameters.Select(x => x.ParameterType.IsByRef? x.ParameterType.Name.TrimEnd('&') + " ByRef" : x.ParameterType.Name)) + ")"; public override string GetSignatureString() => ReturnParameter.GetSignatureString() + " " + Name + GetFullTypeParametersString() + "(" + string.Join(",", DeclaredParameters.Select(x => x.GetSignatureString())) + ")"; diff --git a/Il2CppInspector/Reflection/ParameterInfo.cs b/Il2CppInspector/Reflection/ParameterInfo.cs index eba215e..bc64db9 100644 --- a/Il2CppInspector/Reflection/ParameterInfo.cs +++ b/Il2CppInspector/Reflection/ParameterInfo.cs @@ -30,8 +30,6 @@ namespace Il2CppInspector.Reflection // Default value for the parameter public object DefaultValue { get; } - public bool IsByRef => !ParameterType.ContainsGenericParameters && paramTypeUsage == ParameterType.Definition.byrefTypeIndex; - public bool IsIn => (Attributes & ParameterAttributes.In) != 0; public bool IsOptional => (Attributes & ParameterAttributes.Optional) != 0; public bool IsOut => (Attributes & ParameterAttributes.Out) != 0; @@ -96,12 +94,13 @@ namespace Il2CppInspector.Reflection } } + // ref will be handled as part of the type name public string GetModifierString() => (IsIn ? "in " : "") - + (IsByRef? "ref " : "") - + (IsOut? "out " : ""); + + (IsOut ? "out " : "") + + (!IsIn && !IsOut && ParameterType.IsByRef ? "ref " : ""); - private string getCSharpSignatureString(Scope scope) => $"{GetModifierString()}{ParameterType.GetScopedCSharpName(scope)}"; + private string getCSharpSignatureString(Scope scope) => $"{GetModifierString()}{ParameterType.GetScopedCSharpName(scope, omitRef: true)}"; public string GetSignatureString() => $"{GetModifierString()}{ParameterType.FullName}"; public string GetParameterString(Scope usingScope, bool emitPointer = false, bool compileAttributes = false) => IsRetval? null : @@ -112,5 +111,7 @@ namespace Il2CppInspector.Reflection + (emitPointer && !(DefaultValue is null)? $" /* Metadata: 0x{(uint) DefaultValueMetadataAddress:X8} */" : "") : ""); public string GetReturnParameterString(Scope scope) => !IsRetval? null : getCSharpSignatureString(scope); + + public override string ToString() => ParameterType.Name + " " + Name; } } \ No newline at end of file diff --git a/Il2CppInspector/Reflection/TypeInfo.cs b/Il2CppInspector/Reflection/TypeInfo.cs index 6c07d0a..1b9beee 100644 --- a/Il2CppInspector/Reflection/TypeInfo.cs +++ b/Il2CppInspector/Reflection/TypeInfo.cs @@ -59,13 +59,16 @@ namespace Il2CppInspector.Reflection { n = "out " + n; if ((GenericParameterAttributes & GenericParameterAttributes.Contravariant) == GenericParameterAttributes.Contravariant) n = "in " + n; + if (IsByRef) + n = "ref " + n; return n + (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + (IsPointer ? "*" : ""); } } // C# name as it would be written in a type declaration public string CSharpTypeDeclarationName => - (HasElementType? + (IsByRef? "ref " : "") + + (HasElementType? ElementType.CSharpTypeDeclarationName : ((GenericParameterAttributes & GenericParameterAttributes.Contravariant) == GenericParameterAttributes.Contravariant? "in " : "") + ((GenericParameterAttributes & GenericParameterAttributes.Covariant) == GenericParameterAttributes.Covariant? "out ":"") @@ -144,14 +147,16 @@ namespace Il2CppInspector.Reflection { // Type name including namespace public string FullName => IsGenericParameter? null : + HasElementType && ElementType.IsGenericParameter? null : (HasElementType? ElementType.FullName : (DeclaringType != null? DeclaringType.FullName + "+" : Namespace + (Namespace.Length > 0? "." : "")) + base.Name) + (IsArray? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + + (IsByRef? "&" : "") + (IsPointer? "*" : ""); // Returns the minimally qualified type name required to refer to this type within the specified scope - public string GetScopedFullName(Scope scope) { + private string getScopedFullName(Scope scope) { // This is the type to be used (generic type parameters have a null FullName) var usedType = FullName?.Replace('+', '.') ?? Name; @@ -299,7 +304,7 @@ namespace Il2CppInspector.Reflection { } // C#-friendly type name as it should be used in the scope of a given type - public string GetScopedCSharpName(Scope usingScope = null) { + public string GetScopedCSharpName(Scope usingScope = null, bool omitRef = false) { // Unscoped name if no using scope specified if (usingScope == null) return CSharpName; @@ -308,7 +313,7 @@ namespace Il2CppInspector.Reflection { // Built-in keyword type names do not require a scope var i = Il2CppConstants.FullNameTypeString.IndexOf(s); - var n = i != -1 ? Il2CppConstants.CSharpTypeString[i] : GetScopedFullName(usingScope); + var n = i != -1 ? Il2CppConstants.CSharpTypeString[i] : getScopedFullName(usingScope); // Unmangle generic type names if (n?.IndexOf("`", StringComparison.Ordinal) != -1) @@ -327,7 +332,7 @@ namespace Il2CppInspector.Reflection { if (HasElementType) n = ElementType.GetScopedCSharpName(usingScope); - return n + (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + (IsPointer ? "*" : ""); + return (IsByRef && !omitRef? "ref " : "") + n + (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + (IsPointer ? "*" : ""); } // Get the generic type parameters for a specific usage of this type based on its scope, @@ -375,7 +380,7 @@ namespace Il2CppInspector.Reflection { public bool IsAbstract => (Attributes & TypeAttributes.Abstract) == TypeAttributes.Abstract; public bool IsArray { get; } - public bool IsByRef => throw new NotImplementedException(); + public bool IsByRef { get; } public bool IsClass => (Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Class; public bool IsEnum => enumUnderlyingTypeUsage != -1; public bool IsGenericParameter { get; } @@ -504,6 +509,10 @@ namespace Il2CppInspector.Reflection { if (((Definition.bitfield >> 1) & 1) == 1) enumUnderlyingTypeUsage = 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]; for (var i = 0; i < Definition.interfaces_count; i++) @@ -692,6 +701,23 @@ namespace Il2CppInspector.Reflection { public TypeInfo(MethodBase declaringMethod, Il2CppGenericParameter param) : this(declaringMethod.DeclaringType, param) => DeclaringMethod = declaringMethod; + // Initialize a type that is a reference to the specified type + private TypeInfo(TypeInfo underlyingType) { + ElementType = underlyingType; + IsByRef = true; + + // No base type or declaring type for reference types + Assembly = ElementType.Assembly; + Definition = ElementType.Definition; + Index = ElementType.Index; + Namespace = ElementType.Namespace; + Name = ElementType.Name; + + Attributes = ElementType.Attributes; + } + + public TypeInfo MakeByRefType() => new TypeInfo(this); + // Get all the other types directly referenced by this type (single level depth; no recursion) public List GetAllTypeReferences() { var refs = new HashSet(); @@ -797,6 +823,7 @@ namespace Il2CppInspector.Reflection { + (GenericTypeParameters != null ? "[" + string.Join(",", GenericTypeParameters.Select(x => x.Namespace != Namespace? x.FullName ?? x.Name : x.Name)) + "]" : "") + (GenericTypeArguments != null ? "[" + string.Join(",", GenericTypeArguments.Select(x => x.Namespace != Namespace? x.FullName ?? x.Name : x.Name)) + "]" : "")) + (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + + (IsByRef ? "&" : "") + (IsPointer ? "*" : ""); public string GetAccessModifierString() => this switch {