diff --git a/README.md b/README.md index 2cc3cd9..e67144f 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Limitations ----------- - Integers are assumed to be (u)int32/64 as CIL doesn't differentiate between them and sint32/64 and (s)fixed32/64. - Package names are not preserved in protobuf compilation so naturally we cannot recover them during decompilation, which may result in naming conflicts. -- Due to the development branch of Cpp2Il not yet recovering method bodies +- Due to the development branch of Cpp2Il not yet recovering method bodies, when parsing an Il2Cpp assembly older than metadata version 29 - The `Name` parameter of `OriginalNameAttribute` is not parsed. In this case, the CIL enum field names are used after conforming them to protobuf conventions. - The `Tool` parameter of `GeneratedCodeAttribute` is not compared against when parsing gRPC service methods, which may cause false positives in the event that another tool has generated methods in the service class. diff --git a/src/LibProtodec/Models/Cil/Clr/ClrAttribute.cs b/src/LibProtodec/Models/Cil/Clr/ClrAttribute.cs index 3b3ef1a..36c8728 100644 --- a/src/LibProtodec/Models/Cil/Clr/ClrAttribute.cs +++ b/src/LibProtodec/Models/Cil/Clr/ClrAttribute.cs @@ -6,37 +6,36 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Reflection; namespace LibProtodec.Models.Cil.Clr; public sealed class ClrAttribute(CustomAttributeData clrAttribute) : ICilAttribute { - private IList? _constructorArguments; + private object?[]? _constructorArgumentValues; public ICilType Type => ClrType.GetOrCreate(clrAttribute.AttributeType); - public IList ConstructorArguments + public IList ConstructorArgumentValues { get { - if (_constructorArguments is null) + if (_constructorArgumentValues is null) { IList args = clrAttribute.ConstructorArguments; - if (args.Count < 1) + _constructorArgumentValues = args.Count < 1 + ? Array.Empty() + : new object[args.Count]; + + for (int i = 0; i < args.Count; i++) { - _constructorArguments = Array.Empty(); - } - else - { - _constructorArguments = args.Select(static arg => arg.Value).ToList(); + _constructorArgumentValues[i] = args[i].Value; } } - return _constructorArguments; + return _constructorArgumentValues; } } } \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Clr/ClrMember.cs b/src/LibProtodec/Models/Cil/Clr/ClrMember.cs index 289e01b..7c5cc31 100644 --- a/src/LibProtodec/Models/Cil/Clr/ClrMember.cs +++ b/src/LibProtodec/Models/Cil/Clr/ClrMember.cs @@ -4,6 +4,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. +using System; using System.Collections.Generic; using System.Reflection; @@ -11,6 +12,8 @@ namespace LibProtodec.Models.Cil.Clr; public abstract class ClrMember(MemberInfo clrMember) { + private ICilAttribute[]? _customAttributes; + public string Name => clrMember.Name; @@ -23,11 +26,25 @@ public abstract class ClrMember(MemberInfo clrMember) : ClrType.GetOrCreate( clrMember.DeclaringType); - public IEnumerable GetCustomAttributes() + public IList CustomAttributes { - foreach (CustomAttributeData attribute in clrMember.GetCustomAttributesData()) + get { - yield return new ClrAttribute(attribute); + if (_customAttributes is null) + { + IList attributes = clrMember.GetCustomAttributesData(); + + _customAttributes = attributes.Count < 1 + ? Array.Empty() + : new ICilAttribute[attributes.Count]; + + for (int i = 0; i < attributes.Count; i++) + { + _customAttributes[i] = new ClrAttribute(attributes[i]); + } + } + + return _customAttributes; } } } \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Clr/ClrType.cs b/src/LibProtodec/Models/Cil/Clr/ClrType.cs index 53dac65..862ac80 100644 --- a/src/LibProtodec/Models/Cil/Clr/ClrType.cs +++ b/src/LibProtodec/Models/Cil/Clr/ClrType.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Reflection; using CommunityToolkit.Diagnostics; @@ -18,7 +17,7 @@ public sealed class ClrType : ClrMember, ICilType private const BindingFlags Everything = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance; private readonly Type _clrType; - private IList? _genericTypeArguments; + private ICilType[]? _genericTypeArguments; private ClrType(Type clrType) : base(clrType) => _clrType = clrType; @@ -62,13 +61,13 @@ public sealed class ClrType : ClrMember, ICilType { Type[] args = _clrType.GenericTypeArguments; - if (args.Length < 1) + _genericTypeArguments = args.Length < 1 + ? Array.Empty() + : new ICilType[args.Length]; + + for (int i = 0; i < args.Length; i++) { - _genericTypeArguments = Array.Empty(); - } - else - { - _genericTypeArguments = args.Select(GetOrCreate).ToList(); + _genericTypeArguments[i] = GetOrCreate(args[i]); } } diff --git a/src/LibProtodec/Models/Cil/ICilAttribute.cs b/src/LibProtodec/Models/Cil/ICilAttribute.cs index 76d17e1..4bbdaac 100644 --- a/src/LibProtodec/Models/Cil/ICilAttribute.cs +++ b/src/LibProtodec/Models/Cil/ICilAttribute.cs @@ -12,5 +12,5 @@ public interface ICilAttribute { ICilType Type { get; } - IList ConstructorArguments { get; } + IList ConstructorArgumentValues { get; } } \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/ICilField.cs b/src/LibProtodec/Models/Cil/ICilField.cs index 5b511e7..061ab2f 100644 --- a/src/LibProtodec/Models/Cil/ICilField.cs +++ b/src/LibProtodec/Models/Cil/ICilField.cs @@ -14,9 +14,9 @@ public interface ICilField object? ConstantValue { get; } + IList CustomAttributes { get; } + bool IsLiteral { get; } bool IsPublic { get; } bool IsStatic { get; } - - IEnumerable GetCustomAttributes(); } \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/ICilMethod.cs b/src/LibProtodec/Models/Cil/ICilMethod.cs index 7c4454c..b4a1e5c 100644 --- a/src/LibProtodec/Models/Cil/ICilMethod.cs +++ b/src/LibProtodec/Models/Cil/ICilMethod.cs @@ -19,7 +19,7 @@ public interface ICilMethod ICilType ReturnType { get; } - IEnumerable GetCustomAttributes(); + IList CustomAttributes { get; } IEnumerable GetParameterTypes(); } \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/ICilProperty.cs b/src/LibProtodec/Models/Cil/ICilProperty.cs index 15b8a20..f3fb079 100644 --- a/src/LibProtodec/Models/Cil/ICilProperty.cs +++ b/src/LibProtodec/Models/Cil/ICilProperty.cs @@ -20,5 +20,5 @@ public interface ICilProperty ICilMethod? Getter { get; } ICilMethod? Setter { get; } - IEnumerable GetCustomAttributes(); + IList CustomAttributes { get; } } \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/ICilType.cs b/src/LibProtodec/Models/Cil/ICilType.cs index 8c94bfb..a2a490b 100644 --- a/src/LibProtodec/Models/Cil/ICilType.cs +++ b/src/LibProtodec/Models/Cil/ICilType.cs @@ -26,7 +26,7 @@ public interface ICilType IList GenericTypeArguments { get; } - IEnumerable GetCustomAttributes(); + IList CustomAttributes { get; } IEnumerable GetFields(); diff --git a/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppAttribute.cs b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppAttribute.cs index eea764e..6a63f2f 100644 --- a/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppAttribute.cs +++ b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppAttribute.cs @@ -4,17 +4,19 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -using System; using System.Collections.Generic; -using LibCpp2IL.Reflection; +using CommunityToolkit.Diagnostics; +using LibCpp2IL.Metadata; namespace LibProtodec.Models.Cil.Il2Cpp; -public sealed class Il2CppAttribute(Il2CppTypeReflectionData il2CppAttrType) : ICilAttribute +public sealed class Il2CppAttribute(Il2CppTypeDefinition il2CppAttrType, object?[]? ctorArgValues) : ICilAttribute { public ICilType Type => Il2CppType.GetOrCreate(il2CppAttrType); - public IList ConstructorArguments => - throw new NotImplementedException(); + public IList ConstructorArgumentValues => + ctorArgValues + ?? ThrowHelper.ThrowNotSupportedException>( + "Attribute constructor argument parsing is only available on metadata version 29 or greater."); } \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppMember.cs b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppMember.cs index b7ec9cb..aee5dca 100644 --- a/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppMember.cs +++ b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppMember.cs @@ -6,36 +6,112 @@ using System; using System.Collections.Generic; +using System.Text; +using SystemEx.Memory; +using CommunityToolkit.Diagnostics; using LibCpp2IL; +using LibCpp2IL.BinaryStructures; using LibCpp2IL.Metadata; namespace LibProtodec.Models.Cil.Il2Cpp; public abstract class Il2CppMember { - public IEnumerable GetCustomAttributes() + private ICilAttribute[]? _customAttributes; + + public IList CustomAttributes { - if (LibCpp2IlMain.MetadataVersion >= 29) + get { - throw new NotImplementedException(); - } + if (_customAttributes is null) + { + if (LibCpp2IlMain.MetadataVersion < 29f) + { + int attrTypeRngIdx = LibCpp2IlMain.MetadataVersion <= 24f + ? CustomAttributeIndex + : BinarySearchToken( + LibCpp2IlMain.TheMetadata!.attributeTypeRanges, + Token, + DeclaringAssembly.customAttributeStart, + (int)DeclaringAssembly.customAttributeCount); - Il2CppCustomAttributeTypeRange? attributeTypeRange = LibCpp2IlMain.TheMetadata!.GetCustomAttributeData( - DeclaringAssembly, CustomAttributeIndex, Token, out _); + if (attrTypeRngIdx < 0) + return _customAttributes = Array.Empty(); - if (attributeTypeRange is null || attributeTypeRange.count == 0) - yield break; + Il2CppCustomAttributeTypeRange attrTypeRng = LibCpp2IlMain.TheMetadata!.attributeTypeRanges[attrTypeRngIdx]; - for (int attrIndex = attributeTypeRange.start, - end = attributeTypeRange.start + attributeTypeRange.count; - attrIndex < end; - attrIndex++) - { - int typeIndex = LibCpp2IlMain.TheMetadata.attributeTypes[attrIndex]; + _customAttributes = new ICilAttribute[attrTypeRng.count]; + for (int attrTypeIdx = 0; attrTypeIdx < attrTypeRng.count; attrTypeIdx++) + { + int typeIndex = LibCpp2IlMain.TheMetadata.attributeTypes[attrTypeRng.start + attrTypeIdx]; + var type = LibCpp2IlMain.Binary!.GetType(typeIndex); + var typeDef = LibCpp2IlMain.TheMetadata.typeDefs[type.Data.ClassIndex]; - yield return new Il2CppAttribute( - LibCpp2ILUtils.GetTypeReflectionData( - LibCpp2IlMain.Binary!.GetType(typeIndex))); + _customAttributes[attrTypeIdx] = new Il2CppAttribute(typeDef, null); + } + } + else + { + int attrDataRngIdx = BinarySearchToken( + LibCpp2IlMain.TheMetadata!.AttributeDataRanges, + Token, + DeclaringAssembly.customAttributeStart, + (int)DeclaringAssembly.customAttributeCount); + + if (attrDataRngIdx < 0) + return _customAttributes = Array.Empty(); + + Il2CppCustomAttributeDataRange attrDataRange = LibCpp2IlMain.TheMetadata.AttributeDataRanges[attrDataRngIdx]; + Il2CppCustomAttributeDataRange attrDataRngNext = LibCpp2IlMain.TheMetadata.AttributeDataRanges[attrDataRngIdx + 1]; + + long attrDataStart = LibCpp2IlMain.TheMetadata.metadataHeader.attributeDataOffset + attrDataRange.startOffset; + long attrDataEnd = LibCpp2IlMain.TheMetadata.metadataHeader.attributeDataOffset + attrDataRngNext.startOffset; + byte[] attrData = LibCpp2IlMain.TheMetadata.ReadByteArrayAtRawAddress(attrDataStart, (int)(attrDataEnd - attrDataStart)); + + MemoryReader reader = new(attrData); + int attributeCount = (int)ReadUnityCompressedUInt32(ref reader); + + Span ctorIndices = stackalloc uint[attributeCount]; + for (int i = 0; i < attributeCount; i++) + ctorIndices[i] = reader.ReadUInt32LittleEndian(); + + _customAttributes = new ICilAttribute[attributeCount]; + for (int i = 0; i < attributeCount; i++) + { + uint ctorArgCount = ReadUnityCompressedUInt32(ref reader); + uint fieldCount = ReadUnityCompressedUInt32(ref reader); + uint propCount = ReadUnityCompressedUInt32(ref reader); + + object?[] ctorArgValues = ctorArgCount > 0 + ? new object[ctorArgCount] + : Array.Empty(); + + for (int j = 0; j < ctorArgCount; j++) + { + ctorArgValues[j] = ReadValue(ref reader); + } + + for (uint j = 0; j < fieldCount; j++) + { + ReadValue(ref reader); + ResolveMember(ref reader); + } + + for (uint j = 0; j < propCount; j++) + { + ReadValue(ref reader); + ResolveMember(ref reader); + } + + Il2CppMethodDefinition attrCtor = LibCpp2IlMain.TheMetadata.methodDefs[ctorIndices[i]]; + Il2CppTypeDefinition attrType = LibCpp2IlMain.TheMetadata.typeDefs[attrCtor.declaringTypeIdx]; + + _customAttributes[i] = new Il2CppAttribute(attrType, ctorArgValues); + } + } + } + + return _customAttributes; } } @@ -44,4 +120,187 @@ public abstract class Il2CppMember protected abstract int CustomAttributeIndex { get; } protected abstract uint Token { get; } + + private static int BinarySearchToken(IReadOnlyList source, uint token, int start, int count) + { + int lo = start; + int hi = start + count - 1; + while (lo <= hi) + { + int i = lo + ((hi - lo) >> 1); + + switch (source[i].Token.CompareTo(token)) + { + case 0: + return i; + case < 0: + lo = i + 1; + break; + default: + hi = i - 1; + break; + } + } + + return ~lo; + } + + private static uint ReadUnityCompressedUInt32(ref MemoryReader reader) + { + byte byt = reader.ReadByte(); + + switch (byt) + { + case < 128: + return byt; + case 240: + return reader.ReadUInt32LittleEndian(); + case 254: + return uint.MaxValue - 1; + case byte.MaxValue: + return uint.MaxValue; + } + + if ((byt & 192) == 192) + { + return (byt & ~192U) << 24 + | ((uint)reader.ReadByte() << 16) + | ((uint)reader.ReadByte() << 8) + | reader.ReadByte(); + } + + if ((byt & 128) == 128) + { + return (byt & ~128U) << 8 + | reader.ReadByte(); + } + + return ThrowHelper.ThrowInvalidDataException(); + } + + private static int ReadUnityCompressedInt32(ref MemoryReader reader) + { + uint unsigned = ReadUnityCompressedUInt32(ref reader); + if (unsigned == uint.MaxValue) + return int.MinValue; + + bool isNegative = (unsigned & 1) == 1; + unsigned >>= 1; + + return isNegative + ? -(int)(unsigned + 1) + : (int)unsigned; + } + + private static object? ReadValue(ref MemoryReader reader) + { + Il2CppTypeEnum type = (Il2CppTypeEnum)reader.ReadByte(); + return ReadValue(ref reader, type); + } + + private static object? ReadValue(ref MemoryReader reader, Il2CppTypeEnum type) + { + switch (type) + { + case Il2CppTypeEnum.IL2CPP_TYPE_ENUM: + Il2CppTypeEnum underlyingType = ReadEnumUnderlyingType(ref reader); + return ReadValue(ref reader, underlyingType); + case Il2CppTypeEnum.IL2CPP_TYPE_SZARRAY: + return ReadSzArray(ref reader); + case Il2CppTypeEnum.IL2CPP_TYPE_IL2CPP_TYPE_INDEX: + int typeIndex = ReadUnityCompressedInt32(ref reader); + return LibCpp2IlMain.Binary!.GetType(typeIndex); + case Il2CppTypeEnum.IL2CPP_TYPE_BOOLEAN: + return reader.ReadBoolean(); + case Il2CppTypeEnum.IL2CPP_TYPE_CHAR: + return (char)reader.ReadInt16LittleEndian(); + case Il2CppTypeEnum.IL2CPP_TYPE_I1: + return reader.ReadSByte(); + case Il2CppTypeEnum.IL2CPP_TYPE_U1: + return reader.ReadByte(); + case Il2CppTypeEnum.IL2CPP_TYPE_I2: + return reader.ReadInt16LittleEndian(); + case Il2CppTypeEnum.IL2CPP_TYPE_U2: + return reader.ReadUInt16LittleEndian(); + case Il2CppTypeEnum.IL2CPP_TYPE_I4: + return ReadUnityCompressedInt32(ref reader); + case Il2CppTypeEnum.IL2CPP_TYPE_U4: + return ReadUnityCompressedUInt32(ref reader); + case Il2CppTypeEnum.IL2CPP_TYPE_I8: + return reader.ReadInt64LittleEndian(); + case Il2CppTypeEnum.IL2CPP_TYPE_U8: + return reader.ReadUInt64LittleEndian(); + case Il2CppTypeEnum.IL2CPP_TYPE_R4: + return reader.ReadSingleLittleEndian(); + case Il2CppTypeEnum.IL2CPP_TYPE_R8: + return reader.ReadDoubleLittleEndian(); + case Il2CppTypeEnum.IL2CPP_TYPE_STRING: + return ReadString(ref reader); + case Il2CppTypeEnum.IL2CPP_TYPE_CLASS: + case Il2CppTypeEnum.IL2CPP_TYPE_OBJECT: + case Il2CppTypeEnum.IL2CPP_TYPE_GENERICINST: + default: + return ThrowHelper.ThrowNotSupportedException(); + } + } + + private static Il2CppTypeEnum ReadEnumUnderlyingType(ref MemoryReader reader) + { + int typeIdx = ReadUnityCompressedInt32(ref reader); + var enumType = LibCpp2IlMain.Binary!.GetType(typeIdx); + var underlyingType = LibCpp2IlMain.Binary.GetType( + enumType.AsClass().ElementTypeIndex); + + return underlyingType.Type; + } + + private static object?[]? ReadSzArray(ref MemoryReader reader) + { + int arrayLength = ReadUnityCompressedInt32(ref reader); + if (arrayLength == -1) + return null; + + Il2CppTypeEnum arrayType = (Il2CppTypeEnum)reader.ReadByte(); + if (arrayType == Il2CppTypeEnum.IL2CPP_TYPE_ENUM) + arrayType = ReadEnumUnderlyingType(ref reader); + + bool typePrefixed = reader.ReadBoolean(); + if (typePrefixed && arrayType != Il2CppTypeEnum.IL2CPP_TYPE_OBJECT) + ThrowHelper.ThrowInvalidDataException("Array elements are type-prefixed, but the array type is not object"); + + object?[] array = new object?[arrayLength]; + for (int i = 0; i < arrayLength; i++) + { + Il2CppTypeEnum elementType = typePrefixed + ? (Il2CppTypeEnum)reader.ReadByte() + : arrayType; + + array[i] = ReadValue(ref reader, elementType); + } + + return array; + } + + private static string? ReadString(ref MemoryReader reader) + { + int length = ReadUnityCompressedInt32(ref reader); + if (length == -1) + return null; + + ReadOnlySpan bytes = reader.ReadBytes(length); + return Encoding.UTF8.GetString(bytes); + } + + private static void ResolveMember(ref MemoryReader reader) + { + // We don't care about attribute properties or fields, + // so we just read enough to exhaust the stream + + int memberIndex = ReadUnityCompressedInt32(ref reader); + if (memberIndex < 0) + { + uint typeIndex = ReadUnityCompressedUInt32(ref reader); + memberIndex = -(memberIndex + 1); + } + } } \ No newline at end of file diff --git a/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppType.cs b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppType.cs index 3d1a07a..621a630 100644 --- a/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppType.cs +++ b/src/LibProtodec/Models/Cil/Il2Cpp/Il2CppType.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Linq; using System.Reflection; using CommunityToolkit.Diagnostics; using LibCpp2IL; @@ -20,7 +19,7 @@ public sealed class Il2CppType : Il2CppMember, ICilType { private readonly Il2CppTypeDefinition _il2CppType; private readonly Il2CppTypeReflectionData[] _genericArgs; - private IList? _genericTypeArguments; + private ICilType[]? _genericTypeArguments; private Il2CppType(Il2CppTypeDefinition il2CppType, Il2CppTypeReflectionData[] genericArgs) => (_il2CppType, _genericArgs) = (il2CppType, genericArgs); @@ -76,13 +75,13 @@ public sealed class Il2CppType : Il2CppMember, ICilType { if (_genericTypeArguments is null) { - if (_genericArgs.Length < 1) + _genericTypeArguments = _genericArgs.Length < 1 + ? Array.Empty() + : new ICilType[_genericArgs.Length]; + + for (int i = 0; i < _genericArgs.Length; i++) { - _genericTypeArguments = Array.Empty(); - } - else - { - _genericTypeArguments = _genericArgs.Select(GetOrCreate).ToList(); + _genericTypeArguments[i] = GetOrCreate(_genericArgs[i]); } } @@ -92,38 +91,42 @@ public sealed class Il2CppType : Il2CppMember, ICilType public IEnumerable GetFields() { - for (int idx = _il2CppType.FirstFieldIdx, end = _il2CppType.FirstFieldIdx + _il2CppType.FieldCount; idx < end; idx++) + for (int i = 0; i < _il2CppType.FieldCount; i++) { yield return new Il2CppField( - LibCpp2IlMain.TheMetadata!.fieldDefs[idx]); + LibCpp2IlMain.TheMetadata!.fieldDefs[ + _il2CppType.FirstFieldIdx + i]); } } public IEnumerable GetMethods() { - for (int idx = _il2CppType.FirstMethodIdx, end = _il2CppType.FirstMethodIdx + _il2CppType.MethodCount; idx < end; idx++) + for (int i = 0; i < _il2CppType.MethodCount; i++) { yield return new Il2CppMethod( - LibCpp2IlMain.TheMetadata!.methodDefs[idx]); + LibCpp2IlMain.TheMetadata!.methodDefs[ + _il2CppType.FirstMethodIdx + i]); } } public IEnumerable GetNestedTypes() { - for (int idx = _il2CppType.NestedTypesStart, end = _il2CppType.NestedTypesStart + _il2CppType.NestedTypeCount; idx < end; idx++) + for (int i = 0; i < _il2CppType.NestedTypeCount; i++) { yield return GetOrCreate( LibCpp2IlMain.TheMetadata!.typeDefs[ - LibCpp2IlMain.TheMetadata.nestedTypeIndices[idx]]); + LibCpp2IlMain.TheMetadata.nestedTypeIndices[ + _il2CppType.NestedTypesStart + i]]); } } public IEnumerable GetProperties() { - for (int idx = _il2CppType.FirstPropertyId, end = _il2CppType.FirstPropertyId + _il2CppType.PropertyCount; idx < end; idx++) + for (int i = 0; i < _il2CppType.PropertyCount; i++) { yield return new Il2CppProperty( - LibCpp2IlMain.TheMetadata!.propertyDefs[idx], + LibCpp2IlMain.TheMetadata!.propertyDefs[ + _il2CppType.FirstPropertyId + i], _il2CppType); } } diff --git a/src/LibProtodec/ProtodecContext.cs b/src/LibProtodec/ProtodecContext.cs index 6f3c0d8..594b889 100644 --- a/src/LibProtodec/ProtodecContext.cs +++ b/src/LibProtodec/ProtodecContext.cs @@ -12,6 +12,7 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using SystemEx; using CommunityToolkit.Diagnostics; +using LibCpp2IL; using LibProtodec.Models.Cil; using LibProtodec.Models.Protobuf; using LibProtodec.Models.Protobuf.Fields; @@ -61,7 +62,7 @@ public sealed class ProtodecContext Message message = new() { Name = TranslateTypeName(messageClass), - IsObsolete = HasObsoleteAttribute(messageClass.GetCustomAttributes()) + IsObsolete = HasObsoleteAttribute(messageClass.CustomAttributes) }; _parsed.Add(messageClass.FullName, message); @@ -77,10 +78,8 @@ public sealed class ProtodecContext for (int pi = 0, fi = 0; pi < properties.Count; pi++) { - ICilProperty property = properties[pi]; - List attributes = property.GetCustomAttributes().ToList(); - - if (((options & ParserOptions.IncludePropertiesWithoutNonUserCodeAttribute) == 0 && !HasNonUserCodeAttribute(attributes))) + ICilProperty property = properties[pi]; + if (((options & ParserOptions.IncludePropertiesWithoutNonUserCodeAttribute) == 0 && !HasNonUserCodeAttribute(property.CustomAttributes))) { continue; } @@ -113,7 +112,7 @@ public sealed class ProtodecContext Type = ParseFieldType(propertyType, options, protobuf), Name = TranslateMessageFieldName(property.Name), Id = (int)idFields[fi].ConstantValue!, - IsObsolete = HasObsoleteAttribute(attributes), + IsObsolete = HasObsoleteAttribute(property.CustomAttributes), HasHasProp = msgFieldHasHasProp }; @@ -136,7 +135,7 @@ public sealed class ProtodecContext Enum @enum = new() { Name = TranslateTypeName(enumEnum), - IsObsolete = HasObsoleteAttribute(enumEnum.GetCustomAttributes()) + IsObsolete = HasObsoleteAttribute(enumEnum.CustomAttributes) }; _parsed.Add(enumEnum.FullName, @enum); @@ -144,14 +143,12 @@ public sealed class ProtodecContext foreach (ICilField field in enumEnum.GetFields().Where(static field => field.IsLiteral)) { - List attributes = field.GetCustomAttributes().ToList(); - @enum.Fields.Add( new EnumField { Id = (int)field.ConstantValue!, - Name = TranslateEnumFieldName(attributes, field.Name, @enum.Name), - IsObsolete = HasObsoleteAttribute(attributes) + Name = TranslateEnumFieldName(field.CustomAttributes, field.Name, @enum.Name), + IsObsolete = HasObsoleteAttribute(field.CustomAttributes) }); } @@ -199,7 +196,7 @@ public sealed class ProtodecContext Service service = new() { Name = TranslateTypeName(serviceClass.DeclaringType), - IsObsolete = HasObsoleteAttribute(serviceClass.GetCustomAttributes()) + IsObsolete = HasObsoleteAttribute(serviceClass.CustomAttributes) }; _parsed.Add(serviceClass.DeclaringType!.FullName, service); @@ -207,9 +204,8 @@ public sealed class ProtodecContext foreach (ICilMethod method in serviceClass.GetMethods().Where(static method => method is { IsInherited: false, IsPublic: true, IsStatic: false })) { - List attributes = method.GetCustomAttributes().ToList(); if ((options & ParserOptions.IncludeServiceMethodsWithoutGeneratedCodeAttribute) == 0 - && !HasGeneratedCodeAttribute(attributes, "grpc_csharp_plugin")) + && !HasGeneratedCodeAttribute(method.CustomAttributes, "grpc_csharp_plugin")) { continue; } @@ -284,7 +280,7 @@ public sealed class ProtodecContext new ServiceMethod { Name = TranslateMethodName(method.Name), - IsObsolete = HasObsoleteAttribute(attributes), + IsObsolete = HasObsoleteAttribute(method.CustomAttributes), RequestType = ParseFieldType(requestType, options, protobuf), ResponseType = ParseFieldType(responseType, options, protobuf), IsRequestStreamed = streamReq, @@ -420,7 +416,12 @@ public sealed class ProtodecContext private string TranslateEnumFieldName(IEnumerable attributes, string fieldName, string enumName) { - //TODO: parse original name from first parameter of OriginalNameAttribute constructor + if (LibCpp2IlMain.MetadataVersion >= 29f //TODO: do not merge into master until il2cpp-specific global is removed + && attributes.SingleOrDefault(static attr => attr.Type.Name == "OriginalNameAttribute") + ?.ConstructorArgumentValues[0] is string originalName) + { + return originalName; + } if (NameLookup?.Invoke(fieldName, out string? translatedName) == true) { @@ -591,11 +592,10 @@ public sealed class ProtodecContext private static bool IsBeebyted(string name) => name.Length == 11 && name.CountUpper() == 11; - private static bool HasGeneratedCodeAttribute(IEnumerable attributes, string tool) - { - return attributes.Any(attr => attr.Type.Name == nameof(GeneratedCodeAttribute)); - //TODO: ensure the first argument of the GeneratedCodeAttribute constructor == tool parameter - } + private static bool HasGeneratedCodeAttribute(IEnumerable attributes, string tool) => + attributes.Any(attr => attr.Type.Name == nameof(GeneratedCodeAttribute) + && (LibCpp2IlMain.MetadataVersion < 29f //TODO: do not merge into master until il2cpp-specific global is removed + || attr.ConstructorArgumentValues[0] as string == tool)); private static bool HasNonUserCodeAttribute(IEnumerable attributes) => attributes.Any(static attr => attr.Type.Name == nameof(DebuggerNonUserCodeAttribute)); diff --git a/src/protodec/Program.cs b/src/protodec/Program.cs index 7011dc8..66ad21a 100644 --- a/src/protodec/Program.cs +++ b/src/protodec/Program.cs @@ -130,4 +130,4 @@ IEnumerable GetProtobufServiceClientTypes() => IEnumerable GetProtobufServiceServerTypes() => loader.LoadedTypes.Where( type => type is { IsNested: true, IsAbstract: true, DeclaringType: { IsNested: false, IsSealed: true, IsAbstract: true } } - && type.GetCustomAttributes().Any(attribute => attribute.Type == loader.BindServiceMethodAttribute)); \ No newline at end of file + && type.CustomAttributes.Any(attribute => attribute.Type == loader.BindServiceMethodAttribute)); \ No newline at end of file