From 5b1d9c67d1df6efcc33e893995c84c23fb9d3bd4 Mon Sep 17 00:00:00 2001 From: LukeFZ <17146677+LukeFZ@users.noreply.github.com> Date: Wed, 6 Dec 2023 20:09:35 +0100 Subject: [PATCH] Add name mangling to Methods/MethodInfo/TypeInfo/TypeRef, remove Boxing from ValueTypes when used as the this parameter, fix crashes when a module has no attributes --- .../Cpp/CppDeclarationGenerator.cs | 2 +- .../Cpp/MangledNameBuilder.cs | 204 ++++++++++++++++++ .../IL2CPP/Il2CppInspector.cs | 3 +- Il2CppInspector.Common/Model/AppMethod.cs | 95 ++++---- Il2CppInspector.Common/Model/AppModel.cs | 19 +- Il2CppInspector.Common/Model/AppType.cs | 106 ++++----- .../Outputs/CppScaffolding.cs | 4 +- .../Outputs/JSONMetadata.cs | 12 +- .../Reflection/FieldInfo.cs | 3 + Il2CppInspector.Common/Reflection/TypeInfo.cs | 2 + README.md | 4 + 11 files changed, 335 insertions(+), 119 deletions(-) create mode 100644 Il2CppInspector.Common/Cpp/MangledNameBuilder.cs diff --git a/Il2CppInspector.Common/Cpp/CppDeclarationGenerator.cs b/Il2CppInspector.Common/Cpp/CppDeclarationGenerator.cs index eaf5d17..a6729d2 100644 --- a/Il2CppInspector.Common/Cpp/CppDeclarationGenerator.cs +++ b/Il2CppInspector.Common/Cpp/CppDeclarationGenerator.cs @@ -487,7 +487,7 @@ namespace Il2CppInspector.Cpp } else { if (declaringType.IsValueType) { // Methods for structs take the boxed object as the this param - paramList.Add(("this", types.GetType(TypeNamer.GetName(declaringType) + "__Boxed *"))); + paramList.Add(("this", types.GetType(TypeNamer.GetName(declaringType) + " *"))); // + "__Boxed *"))); } else { paramList.Add(("this", AsCType(declaringType))); } diff --git a/Il2CppInspector.Common/Cpp/MangledNameBuilder.cs b/Il2CppInspector.Common/Cpp/MangledNameBuilder.cs new file mode 100644 index 0000000..0a3e87b --- /dev/null +++ b/Il2CppInspector.Common/Cpp/MangledNameBuilder.cs @@ -0,0 +1,204 @@ +using System.Diagnostics; +using System.Text; +using Il2CppInspector.Reflection; + +namespace Il2CppInspector.Cpp; + +// This follows Itanium/GCC mangling specifications. +public class MangledNameBuilder +{ + private readonly StringBuilder _sb = new("_Z"); + + public override string ToString() + => _sb.ToString(); + + public static string Method(MethodBase method) + { + var builder = new MangledNameBuilder(); + builder.BuildMethod(method); + return builder.ToString(); + } + + public static string MethodInfo(MethodBase method) + { + var builder = new MangledNameBuilder(); + builder.BuildMethod(method, "MethodInfo"); + return builder.ToString(); + } + + public static string TypeInfo(TypeInfo type) + { + var builder = new MangledNameBuilder(); + builder.BeginName(); + builder.WriteIdentifier("TypeInfo"); + builder.WriteTypeName(type); + builder.WriteEnd(); + return builder.ToString(); + } + + public static string TypeRef(TypeInfo type) + { + var builder = new MangledNameBuilder(); + builder.BeginName(); + builder.WriteIdentifier("TypeRef"); + builder.WriteTypeName(type); + builder.WriteEnd(); + return builder.ToString(); + } + + private void BuildMethod(MethodBase method, string prefix = "") + { + /* + * We do not have any CV-qualifiers nor ref-qualifiers, + * so we immediately write the nested name. + */ + + BeginName(); + + if (prefix.Length > 0) + WriteIdentifier(prefix); + + WriteTypeName(method.DeclaringType); + + switch (method.Name) + { + case ".ctor": + _sb.Append("C1"); // Constructor + break; + case ".cctor": + WriteIdentifier("cctor"); + break; + default: + WriteIdentifier(method.Name); + break; + } + + var genericParams = method.GetGenericArguments(); + + WriteGenericParams(genericParams); + + WriteEnd(); // End nested name + + // Now write the method parameters + + if (genericParams.Length > 0 && method is MethodInfo mInfo) + { + // If this is a generic method, the first parameter needs to be the return type + WriteType(mInfo.ReturnType); + } + + if (method.DeclaredParameters.Count == 0) + _sb.Append('v'); + else + { + foreach (var param in method.DeclaredParameters) + WriteType(param.ParameterType); + } + } + + private void WriteTypeName(TypeInfo type) + { + if (type.HasElementType) + type = type.ElementType; + + WriteName(type.Namespace); + + if (type.DeclaringType != null) + WriteIdentifier(type.DeclaringType.Name); + + WriteIdentifier(type.CSharpBaseName); + WriteGenericParams(type.GenericTypeArguments); + } + + private void WriteType(TypeInfo type) + { + if (type.FullName == "System.Void") + { + _sb.Append('v'); + return; + } + + if (type.IsByRef) + _sb.Append('R'); + + if (type.IsPointer) + _sb.Append('P'); + + if (type.IsArray) + _sb.Append("A_"); + + if (type.IsPrimitive && type.Name != "Decimal") + { + if (type.Name is "IntPtr" or "UIntPtr") + _sb.Append("Pv"); // void* + else + { + _sb.Append(type.Name switch + { + "Boolean" => 'b', + "Byte" => 'h', + "SByte" => 'a', + "Int16" => 's', + "UInt16" => 't', + "Int32" => 'i', + "UInt32" => 'j', + "Int64" => 'l', + "UInt64" => 'm', + "Char" => 'w', + "Single" => 'f', + "Double" => 'd', + _ => throw new UnreachableException() + }); + } + } + else + { + BeginName(); + WriteTypeName(type); + WriteEnd(); + } + } + + private void WriteGenericParams(TypeInfo[] generics) + { + if (generics.Length > 0) + { + BeginGenerics(); + + foreach (var arg in generics) + WriteType(arg); + + WriteEnd(); + } + } + + private void WriteIdentifier(string identifier) + { + _sb.Append(identifier.Length); + _sb.Append(identifier); + } + + private void WriteName(string name) + { + foreach (var part in name.Split(".")) + { + if (part.Length > 0) + WriteIdentifier(part); + } + } + + private void BeginName() + { + _sb.Append('N'); + } + + private void BeginGenerics() + { + _sb.Append('I'); + } + + private void WriteEnd() + { + _sb.Append('E'); + } +} \ No newline at end of file diff --git a/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs b/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs index d3d4160..e332342 100644 --- a/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs +++ b/Il2CppInspector.Common/IL2CPP/Il2CppInspector.cs @@ -273,7 +273,8 @@ namespace Il2CppInspector attsByToken.Add(token, index); } - AttributeIndicesByToken.Add(image.customAttributeStart, attsByToken); + if (attsByToken.Count > 0) + AttributeIndicesByToken.Add(image.customAttributeStart, attsByToken); } } diff --git a/Il2CppInspector.Common/Model/AppMethod.cs b/Il2CppInspector.Common/Model/AppMethod.cs index 814281a..afa311d 100644 --- a/Il2CppInspector.Common/Model/AppMethod.cs +++ b/Il2CppInspector.Common/Model/AppMethod.cs @@ -1,45 +1,50 @@ -/* - Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty - - All rights reserved. -*/ - -using Il2CppInspector.Cpp; -using Il2CppInspector.Reflection; - -namespace Il2CppInspector.Model -{ - // Class that represents a composite IL/C++ method - public class AppMethod - { - // The logical group this method is part of - // This is purely for querying methods in related groups and has no bearing on the code - public string Group { get; set; } - - // The corresponding C++ function pointer type - public CppFnPtrType CppFnPtrType { get; internal set; } - - // The corresponding .NET method - public MethodBase Method { get; internal set; } - - // The VA of the MethodInfo* (VA of the pointer to the MethodInfo) object which defines this method - // Methods not referenced by the binary will be 0xffffffff_ffffffff - public ulong MethodInfoPtrAddress { get; internal set; } - - // The VA of the method code itself - // Generic method definitions do not have a code address but may have a reference above - public ulong MethodCodeAddress => Method.VirtualAddress?.Start ?? 0xffffffff_ffffffff; - - // Helpers - public bool HasMethodInfo => MethodInfoPtrAddress != 0xffffffff_ffffffff; - public bool HasCompiledCode => Method.VirtualAddress.HasValue && Method.VirtualAddress.Value.Start != 0; - - public AppMethod(MethodBase method, CppFnPtrType cppMethod, ulong methodInfoPtr = 0xffffffff_ffffffff) { - Method = method; - CppFnPtrType = cppMethod; - MethodInfoPtrAddress = methodInfoPtr; - } - - public override string ToString() => CppFnPtrType.ToSignatureString(); - } -} +/* + Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + Copyright 2023 LukeFZ - https://github.com/LukeFZ + All rights reserved. +*/ + +using System.Diagnostics; +using Il2CppInspector.Cpp; +using Il2CppInspector.Reflection; +using System.Text; + +namespace Il2CppInspector.Model +{ + // Class that represents a composite IL/C++ method + public class AppMethod + { + // The logical group this method is part of + // This is purely for querying methods in related groups and has no bearing on the code + public string Group { get; set; } + + // The corresponding C++ function pointer type + public CppFnPtrType CppFnPtrType { get; internal set; } + + // The corresponding .NET method + public MethodBase Method { get; internal set; } + + // The VA of the MethodInfo* (VA of the pointer to the MethodInfo) object which defines this method + // Methods not referenced by the binary will be 0xffffffff_ffffffff + public ulong MethodInfoPtrAddress { get; internal set; } + + // The VA of the method code itself + // Generic method definitions do not have a code address but may have a reference above + public ulong MethodCodeAddress => Method.VirtualAddress?.Start ?? 0xffffffff_ffffffff; + + // Helpers + public bool HasMethodInfo => MethodInfoPtrAddress != 0xffffffff_ffffffff; + public bool HasCompiledCode => Method.VirtualAddress.HasValue && Method.VirtualAddress.Value.Start != 0; + + public AppMethod(MethodBase method, CppFnPtrType cppMethod, ulong methodInfoPtr = 0xffffffff_ffffffff) { + Method = method; + CppFnPtrType = cppMethod; + MethodInfoPtrAddress = methodInfoPtr; + } + + public override string ToString() => CppFnPtrType.ToSignatureString(); + + public string ToMangledString() => MangledNameBuilder.Method(Method); + public string ToMangledMethodInfoString() => MangledNameBuilder.MethodInfo(Method); + } +} diff --git a/Il2CppInspector.Common/Model/AppModel.cs b/Il2CppInspector.Common/Model/AppModel.cs index 8ddd9c0..e16c0c7 100644 --- a/Il2CppInspector.Common/Model/AppModel.cs +++ b/Il2CppInspector.Common/Model/AppModel.cs @@ -51,8 +51,8 @@ namespace Il2CppInspector.Model // For il2cpp < 19, the key is the string literal ordinal instead of the address public Dictionary Strings { get; } = []; - public Dictionary Fields { get; } = []; - public Dictionary FieldRvas { get; } = []; + public Dictionary Fields { get; } = []; + public Dictionary FieldRvas { get; } = []; public bool StringIndexesAreOrdinals => Package.Version < 19; @@ -254,10 +254,6 @@ namespace Il2CppInspector.Model var fieldType = TypeModel.GetMetadataUsageType(usage); var field = fieldType.DeclaredFields.First(f => f.Index == fieldType.Definition.fieldStart + fieldRef.fieldIndex); - var name = usage.Type == MetadataUsageType.FieldInfo - ? $"{fieldType.Name}.{field.Name}".ToCIdentifier() - : $"{fieldType.Name}.{field.Name}_FieldRva".ToCIdentifier(); - var value = field.HasFieldRVA ? Convert.ToHexString(Package.Metadata.ReadBytes( (long) field.DefaultValueMetadataAddress, field.FieldType.Sizes.nativeSize)) @@ -265,9 +261,9 @@ namespace Il2CppInspector.Model if (usage.Type == MetadataUsageType.FieldInfo) - Fields[usage.VirtualAddress] = (name, value); + Fields[usage.VirtualAddress] = (field, value); else - FieldRvas[usage.VirtualAddress] = (name, value); + FieldRvas[usage.VirtualAddress] = (field, value); break; } @@ -345,11 +341,8 @@ namespace Il2CppInspector.Model // Get the address map for the model // This takes a while to construct so we only build it if requested private AddressMap addressMap; - public AddressMap GetAddressMap() { - if (addressMap == null) - addressMap = new AddressMap(this); - return addressMap; - } + public AddressMap GetAddressMap() + => addressMap ??= new AddressMap(this); // Get the byte offset in Il2CppClass for this app's Unity version to the vtable public int GetVTableOffset() => CppTypeCollection.GetComplexType("Il2CppClass")["vtable"].OffsetBytes; diff --git a/Il2CppInspector.Common/Model/AppType.cs b/Il2CppInspector.Common/Model/AppType.cs index b3514d5..20aff10 100644 --- a/Il2CppInspector.Common/Model/AppType.cs +++ b/Il2CppInspector.Common/Model/AppType.cs @@ -1,52 +1,56 @@ -/* - Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty - - All rights reserved. -*/ - -using Il2CppInspector.Cpp; -using Il2CppInspector.Reflection; - -namespace Il2CppInspector.Model -{ - // Class that represents a composite IL/C++ type - public class AppType - { - // The logical group this type is part of - // This is purely for querying types in related groups and has no bearing on the code - public string Group { get; set; } - - // The corresponding C++ type definition which represents an instance of the object - // This is derived from Il2CppObject - // If the underlying .NET type is a struct (value type), this will return the boxed version - public CppComplexType CppType { get; internal set; } - - // For an underlying .NET type which is a struct (value type), the unboxed type, otherwise null - public CppComplexType CppValueType { get; internal set; } - - // The type in the .NET type model this object maps to - public TypeInfo Type { get; internal set; } - - // The VA of the Il2CppClass object which defines this type (ClassName__TypeInfo) - public ulong TypeClassAddress { get; internal set; } - - // The VA of the Il2CppType* (VA of the pointer to the Il2CppType) object which references this type (ClassName__TypeRef) - public ulong TypeRefPtrAddress { get; internal set; } - - public AppType(TypeInfo ilType, CppComplexType cppType, CppComplexType valueType = null, - ulong cppClassPtr = 0xffffffff_ffffffff, ulong cppTypeRefPtr = 0xffffffff_ffffffff) { - CppType = cppType; - Type = ilType; - CppValueType = valueType; - TypeClassAddress = cppClassPtr; - TypeRefPtrAddress = cppTypeRefPtr; - } - - // The C++ name of the type - // TODO: Known issue here where we should be using CppDeclarationGenerator.TypeNamer to ensure uniqueness - // Prefer Foo over Foo__Boxed; if there is no C++ type defined, just convert the IL type to a C identifier - public string Name => CppValueType?.Name ?? CppType?.Name ?? Type.Name.ToCIdentifier(); - - public override string ToString() => Type.FullName + " -> " + CppType.Name; - } +/* + Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + + All rights reserved. +*/ + +using Il2CppInspector.Cpp; +using Il2CppInspector.Reflection; + +namespace Il2CppInspector.Model +{ + // Class that represents a composite IL/C++ type + public class AppType + { + // The logical group this type is part of + // This is purely for querying types in related groups and has no bearing on the code + public string Group { get; set; } + + // The corresponding C++ type definition which represents an instance of the object + // This is derived from Il2CppObject + // If the underlying .NET type is a struct (value type), this will return the boxed version + public CppComplexType CppType { get; internal set; } + + // For an underlying .NET type which is a struct (value type), the unboxed type, otherwise null + public CppComplexType CppValueType { get; internal set; } + + // The type in the .NET type model this object maps to + public TypeInfo Type { get; internal set; } + + // The VA of the Il2CppClass object which defines this type (ClassName__TypeInfo) + public ulong TypeClassAddress { get; internal set; } + + // The VA of the Il2CppType* (VA of the pointer to the Il2CppType) object which references this type (ClassName__TypeRef) + public ulong TypeRefPtrAddress { get; internal set; } + + public AppType(TypeInfo ilType, CppComplexType cppType, CppComplexType valueType = null, + ulong cppClassPtr = 0xffffffff_ffffffff, ulong cppTypeRefPtr = 0xffffffff_ffffffff) { + CppType = cppType; + Type = ilType; + CppValueType = valueType; + TypeClassAddress = cppClassPtr; + TypeRefPtrAddress = cppTypeRefPtr; + } + + // The C++ name of the type + // TODO: Known issue here where we should be using CppDeclarationGenerator.TypeNamer to ensure uniqueness + // Prefer Foo over Foo__Boxed; if there is no C++ type defined, just convert the IL type to a C identifier + public string Name => CppValueType?.Name ?? CppType?.Name ?? Type.Name.ToCIdentifier(); + + public override string ToString() => Type.FullName + " -> " + CppType.Name; + + public string ToMangledTypeInfoString() => MangledNameBuilder.TypeInfo(Type); + + public string ToMangledTypeRefString() => MangledNameBuilder.TypeRef(Type); + } } \ No newline at end of file diff --git a/Il2CppInspector.Common/Outputs/CppScaffolding.cs b/Il2CppInspector.Common/Outputs/CppScaffolding.cs index 7f4b1d2..6dfbccc 100644 --- a/Il2CppInspector.Common/Outputs/CppScaffolding.cs +++ b/Il2CppInspector.Common/Outputs/CppScaffolding.cs @@ -67,7 +67,7 @@ namespace Il2CppInspector.Outputs """); if (_useBetterArraySize) - writeCode("#define il2cpp_array_size_t actual_il2cpp_array_size_t"); + writeCode("#define actual_il2cpp_array_size_t il2cpp_array_size_t"); writeSectionHeader("IL2CPP internal types"); writeCode(_model.UnityHeaders.GetTypeHeaderText(_model.WordSizeBits)); @@ -82,7 +82,7 @@ namespace Il2CppInspector.Outputs actual_il2cpp_array_size_t value; } better_il2cpp_array_size_t; - #define il2cpp_array_size_t better_il2cpp_array_size_t + #define better_il2cpp_array_size_t il2cpp_array_size_t """); if (_model.TargetCompiler == CppCompilerType.MSVC) diff --git a/Il2CppInspector.Common/Outputs/JSONMetadata.cs b/Il2CppInspector.Common/Outputs/JSONMetadata.cs index 24856e7..89f4ddc 100644 --- a/Il2CppInspector.Common/Outputs/JSONMetadata.cs +++ b/Il2CppInspector.Common/Outputs/JSONMetadata.cs @@ -72,7 +72,7 @@ namespace Il2CppInspector.Outputs private void writeMethods(IEnumerable methods) { foreach (var method in methods.Where(m => m.HasCompiledCode)) { writeObject(() => { - writeTypedFunctionName(method.MethodCodeAddress, method.CppFnPtrType.ToSignatureString(), method.CppFnPtrType.Name); + writeTypedFunctionName(method.MethodCodeAddress, method.CppFnPtrType.ToSignatureString(), method.ToMangledString()); writeDotNetSignature(method.Method); }); } @@ -105,7 +105,7 @@ namespace Il2CppInspector.Outputs if (type.TypeClassAddress != 0xffffffff_ffffffff) { writeObject(() => { - writeTypedName(type.TypeClassAddress, $"struct {type.Name}__Class *", $"{type.Name}__TypeInfo"); + writeTypedName(type.TypeClassAddress, $"struct {type.Name}__Class *", type.ToMangledTypeInfoString()); writeDotNetTypeName(type.Type); }); } @@ -119,7 +119,7 @@ namespace Il2CppInspector.Outputs if (type.TypeRefPtrAddress != 0xffffffff_ffffffff) { writeObject(() => { // A generic type definition does not have any direct C++ types, but may have a reference - writeName(type.TypeRefPtrAddress, $"{type.Name}__TypeRef"); + writeName(type.TypeRefPtrAddress, type.ToMangledTypeRefString()); writeDotNetTypeName(type.Type); }); } @@ -131,7 +131,7 @@ namespace Il2CppInspector.Outputs () => { foreach (var method in model.Methods.Values.Where(m => m.HasMethodInfo)) { writeObject(() => { - writeName(method.MethodInfoPtrAddress, $"{method.CppFnPtrType.Name}__MethodInfo"); + writeName(method.MethodInfoPtrAddress, method.ToMangledMethodInfoString()); writeDotNetSignature(method.Method); }); } @@ -221,13 +221,13 @@ namespace Il2CppInspector.Outputs writeArray("fields", () => { foreach (var (addr, field) in model.Fields) - writeFieldObject(addr, field.Name, field.Value); + writeFieldObject(addr, (field.Field + "_Field").ToCIdentifier(), field.Value); }); writeArray("fieldRvas", () => { foreach (var (addr, rva) in model.FieldRvas) - writeFieldObject(addr, rva.Name, rva.Value); + writeFieldObject(addr, (rva.Field + "_FieldRva").ToCIdentifier(), rva.Value); }); } diff --git a/Il2CppInspector.Common/Reflection/FieldInfo.cs b/Il2CppInspector.Common/Reflection/FieldInfo.cs index c493831..0b76299 100644 --- a/Il2CppInspector.Common/Reflection/FieldInfo.cs +++ b/Il2CppInspector.Common/Reflection/FieldInfo.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Reflection; using System.Text; @@ -68,7 +69,9 @@ namespace Il2CppInspector.Reflection { public bool IsLiteral => (Attributes & FieldAttributes.Literal) == FieldAttributes.Literal; // True if the field has the NonSerialized attribute +#pragma warning disable SYSLIB0050 public bool IsNotSerialized => (Attributes & FieldAttributes.NotSerialized) == FieldAttributes.NotSerialized; +#pragma warning restore SYSLIB0050 // True if the field is extern public bool IsPinvokeImpl => (Attributes & FieldAttributes.PinvokeImpl) == FieldAttributes.PinvokeImpl; diff --git a/Il2CppInspector.Common/Reflection/TypeInfo.cs b/Il2CppInspector.Common/Reflection/TypeInfo.cs index b3d66ef..e12eec0 100644 --- a/Il2CppInspector.Common/Reflection/TypeInfo.cs +++ b/Il2CppInspector.Common/Reflection/TypeInfo.cs @@ -702,7 +702,9 @@ namespace Il2CppInspector.Reflection public bool IsPrimitive => Namespace == "System" && new[] { "Boolean", "Byte", "SByte", "Int16", "UInt16", "Int32", "UInt32", "Int64", "UInt64", "IntPtr", "UIntPtr", "Char", "Decimal", "Double", "Single" }.Contains(Name); public bool IsPublic => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.Public; public bool IsSealed => (Attributes & TypeAttributes.Sealed) == TypeAttributes.Sealed; +#pragma warning disable SYSLIB0050 public bool IsSerializable => (Attributes & TypeAttributes.Serializable) == TypeAttributes.Serializable; +#pragma warning restore SYSLIB0050 public bool IsSpecialName => (Attributes & TypeAttributes.SpecialName) == TypeAttributes.SpecialName; public bool IsValueType => BaseType?.FullName == "System.ValueType"; diff --git a/README.md b/README.md index 9c9c41c..983623c 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ This is a continuation of [Il2CppInspector, by djkaty](https://github.com/djkaty * Support for [ThreadStatic] static fields * Better heuristic for detecting metadata usages * Performance improvements +* Slight IDA/Ghidra script improvements: + - Made ValueTypes use their non-boxed variants when used as the this parameter + - Added labeling of FieldInfo/FieldRva MetadataUsages and their respective values as comments + - Implemented name mangling to properly display generics and other normally-replaced characters ### Main features