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

This commit is contained in:
LukeFZ
2023-12-06 20:09:35 +01:00
parent e9434f4cad
commit 5b1d9c67d1
11 changed files with 335 additions and 119 deletions

View File

@@ -487,7 +487,7 @@ namespace Il2CppInspector.Cpp
} else { } else {
if (declaringType.IsValueType) { if (declaringType.IsValueType) {
// Methods for structs take the boxed object as the this param // 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 { } else {
paramList.Add(("this", AsCType(declaringType))); paramList.Add(("this", AsCType(declaringType)));
} }

View File

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

View File

@@ -273,7 +273,8 @@ namespace Il2CppInspector
attsByToken.Add(token, index); attsByToken.Add(token, index);
} }
AttributeIndicesByToken.Add(image.customAttributeStart, attsByToken); if (attsByToken.Count > 0)
AttributeIndicesByToken.Add(image.customAttributeStart, attsByToken);
} }
} }

View File

@@ -1,45 +1,50 @@
/* /*
Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
Copyright 2023 LukeFZ - https://github.com/LukeFZ
All rights reserved. All rights reserved.
*/ */
using Il2CppInspector.Cpp; using System.Diagnostics;
using Il2CppInspector.Reflection; using Il2CppInspector.Cpp;
using Il2CppInspector.Reflection;
namespace Il2CppInspector.Model using System.Text;
{
// Class that represents a composite IL/C++ method namespace Il2CppInspector.Model
public class AppMethod {
{ // Class that represents a composite IL/C++ method
// The logical group this method is part of public class AppMethod
// This is purely for querying methods in related groups and has no bearing on the code {
public string Group { get; set; } // The logical group this method is part of
// This is purely for querying methods in related groups and has no bearing on the code
// The corresponding C++ function pointer type public string Group { get; set; }
public CppFnPtrType CppFnPtrType { get; internal set; }
// The corresponding C++ function pointer type
// The corresponding .NET method public CppFnPtrType CppFnPtrType { get; internal set; }
public MethodBase Method { get; internal set; }
// The corresponding .NET method
// The VA of the MethodInfo* (VA of the pointer to the MethodInfo) object which defines this method public MethodBase Method { get; internal set; }
// Methods not referenced by the binary will be 0xffffffff_ffffffff
public ulong MethodInfoPtrAddress { 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
// The VA of the method code itself public ulong MethodInfoPtrAddress { get; internal set; }
// Generic method definitions do not have a code address but may have a reference above
public ulong MethodCodeAddress => Method.VirtualAddress?.Start ?? 0xffffffff_ffffffff; // The VA of the method code itself
// Generic method definitions do not have a code address but may have a reference above
// Helpers public ulong MethodCodeAddress => Method.VirtualAddress?.Start ?? 0xffffffff_ffffffff;
public bool HasMethodInfo => MethodInfoPtrAddress != 0xffffffff_ffffffff;
public bool HasCompiledCode => Method.VirtualAddress.HasValue && Method.VirtualAddress.Value.Start != 0; // Helpers
public bool HasMethodInfo => MethodInfoPtrAddress != 0xffffffff_ffffffff;
public AppMethod(MethodBase method, CppFnPtrType cppMethod, ulong methodInfoPtr = 0xffffffff_ffffffff) { public bool HasCompiledCode => Method.VirtualAddress.HasValue && Method.VirtualAddress.Value.Start != 0;
Method = method;
CppFnPtrType = cppMethod; public AppMethod(MethodBase method, CppFnPtrType cppMethod, ulong methodInfoPtr = 0xffffffff_ffffffff) {
MethodInfoPtrAddress = methodInfoPtr; Method = method;
} CppFnPtrType = cppMethod;
MethodInfoPtrAddress = methodInfoPtr;
public override string ToString() => CppFnPtrType.ToSignatureString(); }
}
} public override string ToString() => CppFnPtrType.ToSignatureString();
public string ToMangledString() => MangledNameBuilder.Method(Method);
public string ToMangledMethodInfoString() => MangledNameBuilder.MethodInfo(Method);
}
}

View File

@@ -51,8 +51,8 @@ namespace Il2CppInspector.Model
// For il2cpp < 19, the key is the string literal ordinal instead of the address // For il2cpp < 19, the key is the string literal ordinal instead of the address
public Dictionary<ulong, string> Strings { get; } = []; public Dictionary<ulong, string> Strings { get; } = [];
public Dictionary<ulong, (string Name, string Value)> Fields { get; } = []; public Dictionary<ulong, (FieldInfo Field, string Value)> Fields { get; } = [];
public Dictionary<ulong, (string Name, string Value)> FieldRvas { get; } = []; public Dictionary<ulong, (FieldInfo Field, string Value)> FieldRvas { get; } = [];
public bool StringIndexesAreOrdinals => Package.Version < 19; public bool StringIndexesAreOrdinals => Package.Version < 19;
@@ -254,10 +254,6 @@ namespace Il2CppInspector.Model
var fieldType = TypeModel.GetMetadataUsageType(usage); var fieldType = TypeModel.GetMetadataUsageType(usage);
var field = fieldType.DeclaredFields.First(f => f.Index == fieldType.Definition.fieldStart + fieldRef.fieldIndex); 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 var value = field.HasFieldRVA
? Convert.ToHexString(Package.Metadata.ReadBytes( ? Convert.ToHexString(Package.Metadata.ReadBytes(
(long) field.DefaultValueMetadataAddress, field.FieldType.Sizes.nativeSize)) (long) field.DefaultValueMetadataAddress, field.FieldType.Sizes.nativeSize))
@@ -265,9 +261,9 @@ namespace Il2CppInspector.Model
if (usage.Type == MetadataUsageType.FieldInfo) if (usage.Type == MetadataUsageType.FieldInfo)
Fields[usage.VirtualAddress] = (name, value); Fields[usage.VirtualAddress] = (field, value);
else else
FieldRvas[usage.VirtualAddress] = (name, value); FieldRvas[usage.VirtualAddress] = (field, value);
break; break;
} }
@@ -345,11 +341,8 @@ namespace Il2CppInspector.Model
// Get the address map for the model // Get the address map for the model
// This takes a while to construct so we only build it if requested // This takes a while to construct so we only build it if requested
private AddressMap addressMap; private AddressMap addressMap;
public AddressMap GetAddressMap() { public AddressMap GetAddressMap()
if (addressMap == null) => addressMap ??= new AddressMap(this);
addressMap = new AddressMap(this);
return addressMap;
}
// Get the byte offset in Il2CppClass for this app's Unity version to the vtable // Get the byte offset in Il2CppClass for this app's Unity version to the vtable
public int GetVTableOffset() => CppTypeCollection.GetComplexType("Il2CppClass")["vtable"].OffsetBytes; public int GetVTableOffset() => CppTypeCollection.GetComplexType("Il2CppClass")["vtable"].OffsetBytes;

View File

@@ -1,52 +1,56 @@
/* /*
Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty Copyright 2020-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty
All rights reserved. All rights reserved.
*/ */
using Il2CppInspector.Cpp; using Il2CppInspector.Cpp;
using Il2CppInspector.Reflection; using Il2CppInspector.Reflection;
namespace Il2CppInspector.Model namespace Il2CppInspector.Model
{ {
// Class that represents a composite IL/C++ type // Class that represents a composite IL/C++ type
public class AppType public class AppType
{ {
// The logical group this type is part of // The logical group this type is part of
// This is purely for querying types in related groups and has no bearing on the code // This is purely for querying types in related groups and has no bearing on the code
public string Group { get; set; } public string Group { get; set; }
// The corresponding C++ type definition which represents an instance of the object // The corresponding C++ type definition which represents an instance of the object
// This is derived from Il2CppObject // This is derived from Il2CppObject
// If the underlying .NET type is a struct (value type), this will return the boxed version // If the underlying .NET type is a struct (value type), this will return the boxed version
public CppComplexType CppType { get; internal set; } public CppComplexType CppType { get; internal set; }
// For an underlying .NET type which is a struct (value type), the unboxed type, otherwise null // For an underlying .NET type which is a struct (value type), the unboxed type, otherwise null
public CppComplexType CppValueType { get; internal set; } public CppComplexType CppValueType { get; internal set; }
// The type in the .NET type model this object maps to // The type in the .NET type model this object maps to
public TypeInfo Type { get; internal set; } public TypeInfo Type { get; internal set; }
// The VA of the Il2CppClass object which defines this type (ClassName__TypeInfo) // The VA of the Il2CppClass object which defines this type (ClassName__TypeInfo)
public ulong TypeClassAddress { get; internal set; } 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) // 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 ulong TypeRefPtrAddress { get; internal set; }
public AppType(TypeInfo ilType, CppComplexType cppType, CppComplexType valueType = null, public AppType(TypeInfo ilType, CppComplexType cppType, CppComplexType valueType = null,
ulong cppClassPtr = 0xffffffff_ffffffff, ulong cppTypeRefPtr = 0xffffffff_ffffffff) { ulong cppClassPtr = 0xffffffff_ffffffff, ulong cppTypeRefPtr = 0xffffffff_ffffffff) {
CppType = cppType; CppType = cppType;
Type = ilType; Type = ilType;
CppValueType = valueType; CppValueType = valueType;
TypeClassAddress = cppClassPtr; TypeClassAddress = cppClassPtr;
TypeRefPtrAddress = cppTypeRefPtr; TypeRefPtrAddress = cppTypeRefPtr;
} }
// The C++ name of the type // The C++ name of the type
// TODO: Known issue here where we should be using CppDeclarationGenerator.TypeNamer to ensure uniqueness // 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 // 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 string Name => CppValueType?.Name ?? CppType?.Name ?? Type.Name.ToCIdentifier();
public override string ToString() => Type.FullName + " -> " + CppType.Name; public override string ToString() => Type.FullName + " -> " + CppType.Name;
}
public string ToMangledTypeInfoString() => MangledNameBuilder.TypeInfo(Type);
public string ToMangledTypeRefString() => MangledNameBuilder.TypeRef(Type);
}
} }

View File

@@ -67,7 +67,7 @@ namespace Il2CppInspector.Outputs
"""); """);
if (_useBetterArraySize) 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"); writeSectionHeader("IL2CPP internal types");
writeCode(_model.UnityHeaders.GetTypeHeaderText(_model.WordSizeBits)); writeCode(_model.UnityHeaders.GetTypeHeaderText(_model.WordSizeBits));
@@ -82,7 +82,7 @@ namespace Il2CppInspector.Outputs
actual_il2cpp_array_size_t value; actual_il2cpp_array_size_t value;
} better_il2cpp_array_size_t; } 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) if (_model.TargetCompiler == CppCompilerType.MSVC)

View File

@@ -72,7 +72,7 @@ namespace Il2CppInspector.Outputs
private void writeMethods(IEnumerable<AppMethod> methods) { private void writeMethods(IEnumerable<AppMethod> methods) {
foreach (var method in methods.Where(m => m.HasCompiledCode)) { foreach (var method in methods.Where(m => m.HasCompiledCode)) {
writeObject(() => { writeObject(() => {
writeTypedFunctionName(method.MethodCodeAddress, method.CppFnPtrType.ToSignatureString(), method.CppFnPtrType.Name); writeTypedFunctionName(method.MethodCodeAddress, method.CppFnPtrType.ToSignatureString(), method.ToMangledString());
writeDotNetSignature(method.Method); writeDotNetSignature(method.Method);
}); });
} }
@@ -105,7 +105,7 @@ namespace Il2CppInspector.Outputs
if (type.TypeClassAddress != 0xffffffff_ffffffff) { if (type.TypeClassAddress != 0xffffffff_ffffffff) {
writeObject(() => { writeObject(() => {
writeTypedName(type.TypeClassAddress, $"struct {type.Name}__Class *", $"{type.Name}__TypeInfo"); writeTypedName(type.TypeClassAddress, $"struct {type.Name}__Class *", type.ToMangledTypeInfoString());
writeDotNetTypeName(type.Type); writeDotNetTypeName(type.Type);
}); });
} }
@@ -119,7 +119,7 @@ namespace Il2CppInspector.Outputs
if (type.TypeRefPtrAddress != 0xffffffff_ffffffff) { if (type.TypeRefPtrAddress != 0xffffffff_ffffffff) {
writeObject(() => { writeObject(() => {
// A generic type definition does not have any direct C++ types, but may have a reference // 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); writeDotNetTypeName(type.Type);
}); });
} }
@@ -131,7 +131,7 @@ namespace Il2CppInspector.Outputs
() => { () => {
foreach (var method in model.Methods.Values.Where(m => m.HasMethodInfo)) { foreach (var method in model.Methods.Values.Where(m => m.HasMethodInfo)) {
writeObject(() => { writeObject(() => {
writeName(method.MethodInfoPtrAddress, $"{method.CppFnPtrType.Name}__MethodInfo"); writeName(method.MethodInfoPtrAddress, method.ToMangledMethodInfoString());
writeDotNetSignature(method.Method); writeDotNetSignature(method.Method);
}); });
} }
@@ -221,13 +221,13 @@ namespace Il2CppInspector.Outputs
writeArray("fields", () => writeArray("fields", () =>
{ {
foreach (var (addr, field) in model.Fields) foreach (var (addr, field) in model.Fields)
writeFieldObject(addr, field.Name, field.Value); writeFieldObject(addr, (field.Field + "_Field").ToCIdentifier(), field.Value);
}); });
writeArray("fieldRvas", () => writeArray("fieldRvas", () =>
{ {
foreach (var (addr, rva) in model.FieldRvas) foreach (var (addr, rva) in model.FieldRvas)
writeFieldObject(addr, rva.Name, rva.Value); writeFieldObject(addr, (rva.Field + "_FieldRva").ToCIdentifier(), rva.Value);
}); });
} }

View File

@@ -6,6 +6,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
@@ -68,7 +69,9 @@ namespace Il2CppInspector.Reflection {
public bool IsLiteral => (Attributes & FieldAttributes.Literal) == FieldAttributes.Literal; public bool IsLiteral => (Attributes & FieldAttributes.Literal) == FieldAttributes.Literal;
// True if the field has the NonSerialized attribute // True if the field has the NonSerialized attribute
#pragma warning disable SYSLIB0050
public bool IsNotSerialized => (Attributes & FieldAttributes.NotSerialized) == FieldAttributes.NotSerialized; public bool IsNotSerialized => (Attributes & FieldAttributes.NotSerialized) == FieldAttributes.NotSerialized;
#pragma warning restore SYSLIB0050
// True if the field is extern // True if the field is extern
public bool IsPinvokeImpl => (Attributes & FieldAttributes.PinvokeImpl) == FieldAttributes.PinvokeImpl; public bool IsPinvokeImpl => (Attributes & FieldAttributes.PinvokeImpl) == FieldAttributes.PinvokeImpl;

View File

@@ -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 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 IsPublic => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.Public;
public bool IsSealed => (Attributes & TypeAttributes.Sealed) == TypeAttributes.Sealed; public bool IsSealed => (Attributes & TypeAttributes.Sealed) == TypeAttributes.Sealed;
#pragma warning disable SYSLIB0050
public bool IsSerializable => (Attributes & TypeAttributes.Serializable) == TypeAttributes.Serializable; public bool IsSerializable => (Attributes & TypeAttributes.Serializable) == TypeAttributes.Serializable;
#pragma warning restore SYSLIB0050
public bool IsSpecialName => (Attributes & TypeAttributes.SpecialName) == TypeAttributes.SpecialName; public bool IsSpecialName => (Attributes & TypeAttributes.SpecialName) == TypeAttributes.SpecialName;
public bool IsValueType => BaseType?.FullName == "System.ValueType"; public bool IsValueType => BaseType?.FullName == "System.ValueType";

View File

@@ -16,6 +16,10 @@ This is a continuation of [Il2CppInspector, by djkaty](https://github.com/djkaty
* Support for [ThreadStatic] static fields * Support for [ThreadStatic] static fields
* Better heuristic for detecting metadata usages * Better heuristic for detecting metadata usages
* Performance improvements * 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 ### Main features