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:
@@ -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)));
|
||||
}
|
||||
|
||||
204
Il2CppInspector.Common/Cpp/MangledNameBuilder.cs
Normal file
204
Il2CppInspector.Common/Cpp/MangledNameBuilder.cs
Normal 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');
|
||||
}
|
||||
}
|
||||
@@ -273,7 +273,8 @@ namespace Il2CppInspector
|
||||
attsByToken.Add(token, index);
|
||||
}
|
||||
|
||||
AttributeIndicesByToken.Add(image.customAttributeStart, attsByToken);
|
||||
if (attsByToken.Count > 0)
|
||||
AttributeIndicesByToken.Add(image.customAttributeStart, attsByToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
/*
|
||||
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
|
||||
{
|
||||
@@ -41,5 +43,8 @@ namespace Il2CppInspector.Model
|
||||
}
|
||||
|
||||
public override string ToString() => CppFnPtrType.ToSignatureString();
|
||||
|
||||
public string ToMangledString() => MangledNameBuilder.Method(Method);
|
||||
public string ToMangledMethodInfoString() => MangledNameBuilder.MethodInfo(Method);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ namespace Il2CppInspector.Model
|
||||
// For il2cpp < 19, the key is the string literal ordinal instead of the address
|
||||
public Dictionary<ulong, string> Strings { get; } = [];
|
||||
|
||||
public Dictionary<ulong, (string Name, string Value)> Fields { get; } = [];
|
||||
public Dictionary<ulong, (string Name, string Value)> FieldRvas { get; } = [];
|
||||
public Dictionary<ulong, (FieldInfo Field, string Value)> Fields { get; } = [];
|
||||
public Dictionary<ulong, (FieldInfo Field, string Value)> 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;
|
||||
|
||||
@@ -48,5 +48,9 @@ namespace Il2CppInspector.Model
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Il2CppInspector.Outputs
|
||||
private void writeMethods(IEnumerable<AppMethod> 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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user