From 6aa96b431d6cbd27aeec5e9171688e5f71702f98 Mon Sep 17 00:00:00 2001 From: LukeFZ <17146677+LukeFZ@users.noreply.github.com> Date: Fri, 1 Dec 2023 04:43:27 +0100 Subject: [PATCH] Support reading and writing custom arguments for v29 --- .../IL2CPP/CustomAttributeDataReader.cs | 192 ++ Il2CppInspector.Common/Il2CppInspector.csproj | 2 +- .../Outputs/AssemblyShims.cs | 1195 +++++---- .../Outputs/CSharpCodeStubs.cs | 1450 +++++----- .../Reflection/CustomAttributeData.cs | 247 +- .../Reflection/Extensions.cs | 177 +- .../Reflection/FieldInfo.cs | 314 +-- Il2CppInspector.Common/Reflection/TypeInfo.cs | 2366 ++++++++--------- .../Reflection/TypeModel.cs | 28 +- Il2CppInspector.Common/Utils/BlobReader.cs | 30 +- 10 files changed, 3230 insertions(+), 2771 deletions(-) create mode 100644 Il2CppInspector.Common/IL2CPP/CustomAttributeDataReader.cs diff --git a/Il2CppInspector.Common/IL2CPP/CustomAttributeDataReader.cs b/Il2CppInspector.Common/IL2CPP/CustomAttributeDataReader.cs new file mode 100644 index 0000000..5b8897a --- /dev/null +++ b/Il2CppInspector.Common/IL2CPP/CustomAttributeDataReader.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using dnlib.DotNet; +using Il2CppInspector.Reflection; +using Il2CppInspector.Utils; +using NoisyCowStudios.Bin2Object; + +namespace Il2CppInspector +{ + public class CustomAttributeDataReader + { + private readonly Il2CppInspector _inspector; + private readonly Assembly _assembly; + private readonly BinaryObjectStream _data; + + private readonly uint _start; + private readonly uint _end; + + private readonly long _ctorBufferStart; + private readonly long _dataBufferStart; + + public uint Count { get; } + + public CustomAttributeDataReader(Il2CppInspector inspector, Assembly assembly, BinaryObjectStream data, uint startOffset, uint endOffset) + { + _inspector = inspector; + _assembly = assembly; + _data = data; + + _start = startOffset; + _end = endOffset; + + data.Position = _start; + Count = data.ReadCompressedUInt32(); + + _ctorBufferStart = data.Position; + _dataBufferStart = _ctorBufferStart + Count * sizeof(int); + } + + public IEnumerable Read() + { + _data.Position = _ctorBufferStart; + + var ctors = new CustomAttributeCtor[Count]; + for (int i = 0; i < Count; i++) + { + ctors[i] = new CustomAttributeCtor(); + + var ctorIndex = _data.ReadUInt32(); + ctors[i].Ctor = _assembly.Model.MethodsByDefinitionIndex[ctorIndex]; + } + + _data.Position = _dataBufferStart; + for (int i = 0; i < Count; i++) + { + var ctor = ctors[i]; + var attrClass = ctor.Ctor.DeclaringType; + + var argumentCount = _data.ReadCompressedUInt32(); + var fieldCount = _data.ReadCompressedUInt32(); + var propertyCount = _data.ReadCompressedUInt32(); + + ctor.Arguments = new CustomAttributeArgument[argumentCount]; + for (int j = 0; j < argumentCount; j++) + { + ctor.Arguments[j] = new CustomAttributeArgument(); + + ReadAttributeDataValue(ctor.Arguments[j]); + } + + ctor.Fields = new CustomAttributeFieldArgument[fieldCount]; + for (int j = 0; j < fieldCount; j++) + { + ctor.Fields[j] = new CustomAttributeFieldArgument(); + ReadAttributeDataValue(ctor.Fields[j]); + + var (fieldClass, fieldIndex) = ReadCustomAttributeNamedArgumentClassAndIndex(attrClass); + ctor.Fields[j].Field = fieldClass.DeclaredFields[fieldIndex]; + } + + ctor.Properties = new CustomAttributePropertyArgument[propertyCount]; + for (int j = 0; j < propertyCount; j++) + { + ctor.Properties[j] = new CustomAttributePropertyArgument(); + ReadAttributeDataValue(ctor.Properties[j]); + + var (propertyClass, propertyIndex) = ReadCustomAttributeNamedArgumentClassAndIndex(attrClass); + ctor.Properties[j].Property = propertyClass.DeclaredProperties[propertyIndex]; + } + + yield return ctor; + } + + if (_data.Position != _end) + Debugger.Break(); + } + + private void ReadAttributeDataValue(CustomAttributeArgument arg) + { + var type = BlobReader.ReadEncodedTypeEnum(_inspector, _data, out var typeDef); + var value = BlobReader.GetConstantValueFromBlob(_inspector, type, _data); + + if (value is BlobReader.ConstantBlobArray blobArray) + { + arg.Type = ConvertTypeDef(blobArray.ArrayTypeDef, blobArray.ArrayTypeEnum); + arg.Value = blobArray.Elements.Select(ConvertAttributeValue).ToArray(); + } + else + { + arg.Type = ConvertTypeDef(typeDef, type); + arg.Value = ConvertAttributeValue(value); + } + } + + private object ConvertAttributeValue(object value) + { + switch (value) + { + case Il2CppType type: + return _assembly.Model.TypesByReferenceIndex[_inspector.TypeReferences.IndexOf(type)]; + case BlobReader.ConstantBlobArray blobArray: + { + var arrValue = new CustomAttributeArgument + { + Type = ConvertTypeDef(blobArray.ArrayTypeDef, blobArray.ArrayTypeEnum), + Value = blobArray.Elements.Select(ConvertAttributeValue).ToArray() + }; + + return arrValue; + } + case BlobReader.ConstantBlobArrayElement blobElem: + { + var subArgument = new CustomAttributeArgument + { + Type = ConvertTypeDef(blobElem.TypeDef, blobElem.TypeEnum), + Value = blobElem.Value + }; + + return subArgument; + } + default: + return value; + } + } + + private TypeInfo ConvertTypeDef(Il2CppTypeDefinition typeDef, Il2CppTypeEnum type) + => typeDef == null + ? _assembly.Model.GetTypeDefinitionFromTypeEnum(type) + : _assembly.Model.TypesByDefinitionIndex[Array.IndexOf(_inspector.TypeDefinitions, typeDef)]; + + private (TypeInfo, int) ReadCustomAttributeNamedArgumentClassAndIndex(TypeInfo attrInfo) + { + var memberIndex = _data.ReadCompressedInt32(); + if (memberIndex >= 0) // Negative indices mean that it's a member of a base class + return (attrInfo, memberIndex); + + memberIndex = -(memberIndex + 1); + + var typeDefIndex = _data.ReadCompressedUInt32(); + var typeInfo = _assembly.Model.TypesByDefinitionIndex[typeDefIndex]; + + return (typeInfo, memberIndex); + } + } + + public class CustomAttributeCtor + { + public MethodBase Ctor { get; set; } + public CustomAttributeArgument[] Arguments { get; set; } + public CustomAttributeFieldArgument[] Fields { get; set; } + public CustomAttributePropertyArgument[] Properties { get; set; } + } + + public class CustomAttributeArgument + { + public TypeInfo Type { get; set; } + public object Value { get; set; } + } + + public class CustomAttributeFieldArgument : CustomAttributeArgument + { + public FieldInfo Field { get; set; } + } + + public class CustomAttributePropertyArgument : CustomAttributeArgument + { + public PropertyInfo Property { get; set; } + } +} \ No newline at end of file diff --git a/Il2CppInspector.Common/Il2CppInspector.csproj b/Il2CppInspector.Common/Il2CppInspector.csproj index fe0d6f2..3be1708 100644 --- a/Il2CppInspector.Common/Il2CppInspector.csproj +++ b/Il2CppInspector.Common/Il2CppInspector.csproj @@ -38,7 +38,7 @@ - + NU1605 diff --git a/Il2CppInspector.Common/Outputs/AssemblyShims.cs b/Il2CppInspector.Common/Outputs/AssemblyShims.cs index 5232c2b..192acc7 100644 --- a/Il2CppInspector.Common/Outputs/AssemblyShims.cs +++ b/Il2CppInspector.Common/Outputs/AssemblyShims.cs @@ -1,553 +1,642 @@ -/* - Copyright 2017-2020 Perfare - https://github.com/Perfare/Il2CppDumper - Copyright 2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty - - All rights reserved. -*/ - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using dnlib.DotNet; -using dnlib.DotNet.Emit; -using Il2CppInspector.Reflection; -using BindingFlags = System.Reflection.BindingFlags; - -namespace Il2CppInspector.Outputs -{ - public static class dnlibExtensions - { - // Add a default parameterless constructor that calls a specified base constructor - public static MethodDef AddDefaultConstructor(this TypeDef type, IMethod @base) { - var ctor = new MethodDefUser(".ctor", MethodSig.CreateInstance(type.Module.CorLibTypes.Void), - MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName); - - var ctorBody = new CilBody(); - ctorBody.Instructions.Add(OpCodes.Ldarg_0.ToInstruction()); - ctorBody.Instructions.Add(OpCodes.Call.ToInstruction(@base)); - ctorBody.Instructions.Add(OpCodes.Ret.ToInstruction()); - ctor.Body = ctorBody; - - type.Methods.Add(ctor); - return ctor; - } - - // Add custom attribute to item with named property arguments - // 'module' is the module that owns 'type'; type.Module may still be null when this is called - public static CustomAttribute AddAttribute(this IHasCustomAttribute def, ModuleDef module, TypeDef attrTypeDef, params (string prop, object value)[] args) { - - // If SuppressMetadata is set, our own attributes will never be generated so attrTypeDef will be null - if (attrTypeDef == null) - return null; - - var attRef = module.Import(attrTypeDef); - var attCtorRef = new MemberRefUser(attrTypeDef.Module, ".ctor", MethodSig.CreateInstance(module.CorLibTypes.Void), attRef); - - // Attribute arguments - var attrArgs = args.Select(a => - new CANamedArgument(true, module.CorLibTypes.String, a.prop, new CAArgument(module.CorLibTypes.String, a.value))); - - var attr = new CustomAttribute(attCtorRef, null, attrArgs); - - def.CustomAttributes.Add(attr); - return attr; - } - } - - // Output module to create .NET DLLs containing type definitions - public class AssemblyShims - { - // Suppress informational attributes - public bool SuppressMetadata { get; set; } - - // .NET type model - private readonly TypeModel model; - - // Our custom attributes - private TypeDef addressAttribute; - private TypeDef fieldOffsetAttribute; - private TypeDef staticFieldOffsetAttribute; - private TypeDef attributeAttribute; - private TypeDef metadataOffsetAttribute; - private TypeDef metadataPreviewAttribute; - private TypeDef tokenAttribute; - - // The namespace for our custom types - private const string rootNamespace = "Il2CppInspector.DLL"; - - // All modules (single-module assemblies) - private Dictionary modules = new Dictionary(); - - // Custom attributes we will apply directly instead of with a custom attribute function pointer - private Dictionary directApplyAttributes; - - public AssemblyShims(TypeModel model) => this.model = model; - - // Generate base DLL with our custom types - private ModuleDef CreateBaseAssembly() { - // Create DLL with our custom types - var module = CreateAssembly("Il2CppInspector.dll"); - - // Import our IL2CPP application's copy of System.Attribute - // to avoid introducing a dependency on System.Private.CoreLib (.NET Core) from Il2CppInspector itself - var attributeType = model.TypesByFullName["System.Attribute"]; - var attributeCtor = attributeType.DeclaredConstructors.First(c => !c.IsPublic && !c.IsStatic); - var attributeTypeRef = GetTypeRef(module, attributeType); - var attributeCtorRef = new MemberRefUser(attributeTypeRef.Module, ".ctor", MethodSig.CreateInstance(module.CorLibTypes.Void), attributeTypeRef); - - var stringField = new FieldSig(module.CorLibTypes.String); - - // Create a type deriving from System.Attribute and add it to the assembly - TypeDefUser createAttribute(string name) { - var attribute = new TypeDefUser(rootNamespace, name, attributeTypeRef); - attribute.Attributes = TypeAttributes.Public | TypeAttributes.BeforeFieldInit; - module.Types.Add(attribute); - return attribute; - } - - // Create our custom attributes for compatibility with Il2CppDumper - // TODO: New format with numeric values where applicable - addressAttribute = createAttribute("AddressAttribute"); - addressAttribute.Fields.Add(new FieldDefUser("RVA", stringField, FieldAttributes.Public)); - addressAttribute.Fields.Add(new FieldDefUser("Offset", stringField, FieldAttributes.Public)); - addressAttribute.Fields.Add(new FieldDefUser("VA", stringField, FieldAttributes.Public)); - addressAttribute.Fields.Add(new FieldDefUser("Slot", stringField, FieldAttributes.Public)); - addressAttribute.AddDefaultConstructor(attributeCtorRef); - - fieldOffsetAttribute = createAttribute("FieldOffsetAttribute"); - fieldOffsetAttribute.Fields.Add(new FieldDefUser("Offset", stringField, FieldAttributes.Public)); - fieldOffsetAttribute.AddDefaultConstructor(attributeCtorRef); - - staticFieldOffsetAttribute = createAttribute("StaticFieldOffsetAttribute"); - staticFieldOffsetAttribute.Fields.Add(new FieldDefUser("Offset", stringField, FieldAttributes.Public)); - staticFieldOffsetAttribute.AddDefaultConstructor(attributeCtorRef); - - attributeAttribute = createAttribute("AttributeAttribute"); - attributeAttribute.Fields.Add(new FieldDefUser("Name", stringField, FieldAttributes.Public)); - attributeAttribute.Fields.Add(new FieldDefUser("RVA", stringField, FieldAttributes.Public)); - attributeAttribute.Fields.Add(new FieldDefUser("Offset", stringField, FieldAttributes.Public)); - attributeAttribute.AddDefaultConstructor(attributeCtorRef); - - metadataOffsetAttribute = createAttribute("MetadataOffsetAttribute"); - metadataOffsetAttribute.Fields.Add(new FieldDefUser("Offset", stringField, FieldAttributes.Public)); - metadataOffsetAttribute.AddDefaultConstructor(attributeCtorRef); - - metadataPreviewAttribute = createAttribute("MetadataPreviewAttribute"); - metadataPreviewAttribute.Fields.Add(new FieldDefUser("Data", stringField, FieldAttributes.Public)); - metadataPreviewAttribute.AddDefaultConstructor(attributeCtorRef); - - tokenAttribute = createAttribute("TokenAttribute"); - tokenAttribute.Fields.Add(new FieldDefUser("Token", stringField, FieldAttributes.Public)); - tokenAttribute.AddDefaultConstructor(attributeCtorRef); - - return module; - } - - // Create a new DLL assembly definition - private ModuleDefUser CreateAssembly(string name) { - // Create module - var module = new ModuleDefUser(name) { Kind = ModuleKind.Dll }; - - // Create assembly - var ourVersion = System.Reflection.Assembly.GetAssembly(typeof(Il2CppInspector)).GetName().Version; - var asm = new AssemblyDefUser(name.Replace(".dll", ""), ourVersion); - - // Add module to assembly - asm.Modules.Add(module); - return module; - } - - // Generate type recursively with all nested types - private TypeDefUser CreateType(ModuleDef module, TypeInfo type) { - // Initialize with base class - var mType = new TypeDefUser(type.Namespace, type.BaseName, GetTypeRef(module, type.BaseType)) { - Attributes = (TypeAttributes) type.Attributes - }; - - // Generic parameters - foreach (var gp in type.GenericTypeParameters) { - var p = new GenericParamUser((ushort) gp.GenericParameterPosition, (GenericParamAttributes) gp.GenericParameterAttributes, gp.Name); - - // Generic constraints (types and interfaces) - foreach (var c in gp.GetGenericParameterConstraints()) - p.GenericParamConstraints.Add(new GenericParamConstraintUser(GetTypeRef(module, c))); - - mType.GenericParameters.Add(p); - } - - // Interfaces - foreach (var @interface in type.ImplementedInterfaces) - mType.Interfaces.Add(new InterfaceImplUser(GetTypeRef(module, @interface))); - - // Add nested types - foreach (var nestedType in type.DeclaredNestedTypes) - mType.NestedTypes.Add(CreateType(module, nestedType)); - - // Add fields - foreach (var field in type.DeclaredFields) - AddField(module, mType, field); - - // Add properties - foreach (var prop in type.DeclaredProperties) - AddProperty(module, mType, prop); - - // Add events - foreach (var evt in type.DeclaredEvents) - AddEvent(module, mType, evt); - - // Add methods that aren't properties or events - var props = type.DeclaredProperties.SelectMany(p => new[] { p.GetMethod, p.SetMethod }).Where(m => m != null); - var events = type.DeclaredEvents.SelectMany(p => new[] { p.AddMethod, p.RemoveMethod, p.RaiseMethod }).Where(m => m != null); - - foreach (var method in type.DeclaredConstructors.AsEnumerable().Concat(type.DeclaredMethods).Except(props).Except(events)) - AddMethod(module, mType, method); - - // Add token attribute - if (type.Definition != null) - mType.AddAttribute(module, tokenAttribute, ("Token", $"0x{type.MetadataToken:X8}")); - - // Add custom attribute attributes - foreach (var ca in type.CustomAttributes) - AddCustomAttribute(module, mType, ca); - - return mType; - } - - // Add a field to a type - private FieldDef AddField(ModuleDef module, TypeDef mType, FieldInfo field) { - var s = new FieldSig(GetTypeSig(module, field.FieldType)); - - var mField = new FieldDefUser(field.Name, s, (FieldAttributes) field.Attributes); - - // Default value - if (field.HasDefaultValue) - mField.Constant = new ConstantUser(field.DefaultValue); - - // Add offset attribute if no default value but metadata present - else if (field.HasFieldRVA || field.IsLiteral) - mField.AddAttribute(module, metadataOffsetAttribute, ("Offset", $"0x{field.DefaultValueMetadataAddress:X8}")); - - // Static array initializer preview - if (field.HasFieldRVA) { - var preview = model.Package.Metadata.ReadBytes((long) field.DefaultValueMetadataAddress, 8); - var previewText = string.Join(" ", preview.Select(b => $"{b:x2}")); - - mField.AddAttribute(module, metadataPreviewAttribute, ("Data", previewText)); - } - - // Field offset - if (!field.IsStatic) - mField.AddAttribute(module, fieldOffsetAttribute, ("Offset", $"0x{field.Offset:X2}")); - else if (!field.IsLiteral) - mField.AddAttribute(module, staticFieldOffsetAttribute, ("Offset", $"0x{field.Offset:X2}")); - - // Add token attribute - mField.AddAttribute(module, tokenAttribute, ("Token", $"0x{field.MetadataToken:X8}")); - - // Add custom attribute attributes - foreach (var ca in field.CustomAttributes) - AddCustomAttribute(module, mField, ca); - - mType.Fields.Add(mField); - return mField; - } - - // Add a property to a type - private PropertyDef AddProperty(ModuleDef module, TypeDef mType, PropertyInfo prop) { - PropertySig s; - - // Static or instance - if (prop.GetMethod?.IsStatic ?? prop.SetMethod.IsStatic) - s = PropertySig.CreateStatic(GetTypeSig(module, prop.PropertyType)); - else - s = PropertySig.CreateInstance(GetTypeSig(module, prop.PropertyType)); - - var mProp = new PropertyDefUser(prop.Name, s, (PropertyAttributes) prop.Attributes); - - mProp.GetMethod = AddMethod(module, mType, prop.GetMethod); - mProp.SetMethod = AddMethod(module, mType, prop.SetMethod); - - // Add token attribute - // Generic properties and constructed properties (from disperate get/set methods) have no definition - if (prop.Definition != null) - mProp.AddAttribute(module, tokenAttribute, ("Token", $"0x{prop.MetadataToken:X8}")); - - // Add custom attribute attributes - foreach (var ca in prop.CustomAttributes) - AddCustomAttribute(module, mProp, ca); - - // Add property to type - mType.Properties.Add(mProp); - return mProp; - } - - // Add an event to a type - private EventDef AddEvent(ModuleDef module, TypeDef mType, EventInfo evt) { - var mEvent = new EventDefUser(evt.Name, GetTypeRef(module, evt.EventHandlerType), (EventAttributes) evt.Attributes); - - mEvent.AddMethod = AddMethod(module, mType, evt.AddMethod); - mEvent.RemoveMethod = AddMethod(module, mType, evt.RemoveMethod); - mEvent.InvokeMethod = AddMethod(module, mType, evt.RaiseMethod); - - // Add token attribute - mEvent.AddAttribute(module, tokenAttribute, ("Token", $"0x{evt.MetadataToken:X8}")); - - // Add custom attribute attributes - foreach (var ca in evt.CustomAttributes) - AddCustomAttribute(module, mEvent, ca); - - // Add property to type - mType.Events.Add(mEvent); - return mEvent; - } - - // Add a method to a type - private MethodDef AddMethod(ModuleDef module, TypeDef mType, MethodBase method) { - // Undefined method - if (method == null) - return null; - - // Return type and parameter signature - MethodSig s; - - // Static or instance - if (method.IsStatic) - s = MethodSig.CreateStatic( - method is MethodInfo mi ? GetTypeSig(module, mi.ReturnType) : module.CorLibTypes.Void, - method.DeclaredParameters.Select(p => GetTypeSig(module, p.ParameterType)) - .ToArray()); - else - s = MethodSig.CreateInstance( - method is MethodInfo mi? GetTypeSig(module, mi.ReturnType) : module.CorLibTypes.Void, - method.DeclaredParameters.Select(p => GetTypeSig(module, p.ParameterType)) - .ToArray()); - - // Definition - var mMethod = new MethodDefUser(method.Name, s, (MethodImplAttributes) method.MethodImplementationFlags, (MethodAttributes) method.Attributes); - - // Generic type parameters - foreach (var gp in method.GetGenericArguments()) { - var p = new GenericParamUser((ushort) gp.GenericParameterPosition, (GenericParamAttributes) gp.GenericParameterAttributes, gp.Name); - - // Generic constraints (types and interfaces) - foreach (var c in gp.GetGenericParameterConstraints()) - p.GenericParamConstraints.Add(new GenericParamConstraintUser(GetTypeRef(module, c))); - - mMethod.GenericParameters.Add(p); - } - - // Parameter names and default values - foreach (var param in method.DeclaredParameters) { - var p = new ParamDefUser(param.Name, (ushort) (param.Position + 1), (ParamAttributes) param.Attributes); - - if (param.HasDefaultValue) - p.Constant = new ConstantUser(param.DefaultValue); - - // Add offset attribute if metadata present - if (param.DefaultValueMetadataAddress != 0) - p.AddAttribute(module, metadataOffsetAttribute, ("Offset", $"0x{param.DefaultValueMetadataAddress:X8}")); - - // Add custom attribute attributes - foreach (var ca in param.CustomAttributes) - AddCustomAttribute(module, p, ca); - - mMethod.ParamDefs.Add(p); - } - - // Everything that's not extern, abstract or a delegate type should have a method body - if ((method.Attributes & System.Reflection.MethodAttributes.PinvokeImpl) == 0 - && method.DeclaringType.BaseType?.FullName != "System.MulticastDelegate" - && !method.IsAbstract) { - mMethod.Body = new CilBody(); - var inst = mMethod.Body.Instructions; - - // Return nothing if return type is void - if (mMethod.ReturnType.FullName == "System.Void") - inst.Add(OpCodes.Ret.ToInstruction()); - - // Return default for value type or enum - else if (mMethod.ReturnType.IsValueType || ((MethodInfo) method).ReturnType.IsEnum) { - var result = new Local(mMethod.ReturnType); - mMethod.Body.Variables.Add(result); - - inst.Add(OpCodes.Ldloca_S.ToInstruction(result)); - // NOTE: This line creates a reference to an external mscorlib.dll, which we'd prefer to avoid - inst.Add(OpCodes.Initobj.ToInstruction(mMethod.ReturnType.ToTypeDefOrRef())); - inst.Add(OpCodes.Ldloc_0.ToInstruction()); - inst.Add(OpCodes.Ret.ToInstruction()); - } - - // Return null for reference types - else { - inst.Add(OpCodes.Ldnull.ToInstruction()); - inst.Add(OpCodes.Ret.ToInstruction()); - } - } - - // Add token attribute - mMethod.AddAttribute(module, tokenAttribute, ("Token", $"0x{method.MetadataToken:X8}")); - - // Add method pointer attribute - if (method.VirtualAddress.HasValue) { - var args = new List<(string,object)> { - ("RVA", (method.VirtualAddress.Value.Start - model.Package.BinaryImage.ImageBase).ToAddressString()), - ("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start))), - ("VA", method.VirtualAddress.Value.Start.ToAddressString()) - }; - if (method.Definition.slot != ushort.MaxValue) - args.Add(("Slot", method.Definition.slot.ToString())); - - mMethod.AddAttribute(module, addressAttribute, args.ToArray()); - } - - // Add custom attribute attributes - foreach (var ca in method.CustomAttributes) - AddCustomAttribute(module, mMethod, ca); - - // Add method to type - mType.Methods.Add(mMethod); - return mMethod; - } - - // Add a custom attributes attribute to an item, or the attribute itself if it is in our direct apply list - private CustomAttribute AddCustomAttribute(ModuleDef module, IHasCustomAttribute def, CustomAttributeData ca) { - if (directApplyAttributes.TryGetValue(ca.AttributeType, out var attrDef) && attrDef != null) - return def.AddAttribute(module, attrDef); - - return def.AddAttribute(module, attributeAttribute, - ("Name", ca.AttributeType.Name), - ("RVA", (ca.VirtualAddress.Start - model.Package.BinaryImage.ImageBase).ToAddressString()), - ("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start))) - ); - } - - // Generate type recursively with all nested types and add to module - private TypeDefUser AddType(ModuleDef module, TypeInfo type) { - var mType = CreateType(module, type); - - // Add to attribute apply list if we're looking for it - if (directApplyAttributes.ContainsKey(type)) - directApplyAttributes[type] = mType; - - // Add type to module - module.Types.Add(mType); - return mType; - } - - // Convert Il2CppInspector TypeInfo into type reference and import to specified module - private ITypeDefOrRef GetTypeRef(ModuleDef module, TypeInfo type) - => GetTypeSig(module, type).ToTypeDefOrRef(); - - // Convert Il2CppInspector TypeInfo into type signature and import to specified module - private TypeSig GetTypeSig(ModuleDef module, TypeInfo type) - => module.Import(GetTypeSigImpl(module, type)); - - // Convert Il2CppInspector TypeInfo into type signature - private TypeSig GetTypeSigImpl(ModuleDef module, TypeInfo type) { - if (type == null) - return null; - - // Generic type parameter (VAR) - if (type.IsGenericTypeParameter) - return new GenericVar(type.GenericParameterPosition); - - // Generic method parameter (MVAR) - if (type.IsGenericMethodParameter) - return new GenericMVar(type.GenericParameterPosition); - - // Array and single-dimension zero-indexed array (ARRAY / SZARRAY) - if (type.IsArray) - if (type.GetArrayRank() == 1) - return new SZArraySig(GetTypeSig(module, type.ElementType)); - else - return new ArraySig(GetTypeSig(module, type.ElementType), type.GetArrayRank()); - - // Pointer (PTR) - if (type.IsPointer) - return new PtrSig(GetTypeSig(module, type.ElementType)); - - // Reference (BYREF) - if (type.IsByRef) - return new ByRefSig(GetTypeSig(module, type.ElementType)); - - // Get module that owns the type - var typeOwnerModule = modules[type.Assembly]; - var typeOwnerModuleRef = new ModuleRefUser(typeOwnerModule); - - // Get reference to type; use nested type as resolution scope if applicable - var typeSig = new TypeRefUser(typeOwnerModule, type.Namespace, type.BaseName, - type.DeclaringType != null? (IResolutionScope) GetTypeRef(module, type.DeclaringType).ScopeType : typeOwnerModuleRef) - .ToTypeSig(); - - // Non-generic type (CLASS / VALUETYPE) - if (!type.GetGenericArguments().Any()) - return typeSig; - - // Generic type requires generic arguments (GENERICINST) - var genericInstSig = new GenericInstSig(typeSig.ToClassOrValueTypeSig(), type.GenericTypeArguments.Length); - - foreach (var gp in type.GetGenericArguments()) - genericInstSig.GenericArguments.Add(GetTypeSig(module, gp)); - - return genericInstSig; - } - - // Generate and save all DLLs - public void Write(string outputPath, EventHandler statusCallback = null) { - - // Create folder for DLLs - Directory.CreateDirectory(outputPath); - - // Get all custom attributes with no parameters - // We'll add these directly to objects instead of the attribute generator function pointer - directApplyAttributes = model.TypesByDefinitionIndex - .Where(t => t.BaseType?.FullName == "System.Attribute" - && !t.DeclaredFields.Any() && !t.DeclaredProperties.Any()) - .ToDictionary(t => t, t => (TypeDef) null); - - // Generate blank assemblies - // We have to do this before adding anything else so we can reference every module - modules.Clear(); - - foreach (var asm in model.Assemblies) { - // Create assembly and add primary module to list - var module = CreateAssembly(asm.ShortName); - modules.Add(asm, module); - } - - // Generate our custom types assembly (relies on mscorlib.dll being added above) - if (!SuppressMetadata) { - var baseDll = CreateBaseAssembly(); - - // Write base assembly to disk - baseDll.Write(Path.Combine(outputPath, baseDll.Name)); - } - - // Add assembly custom attribute attributes (must do this after all assemblies are created due to type referencing) - foreach (var asm in model.Assemblies) { - var module = modules[asm]; - - foreach (var ca in asm.CustomAttributes) - AddCustomAttribute(module, module.Assembly, ca); - - // Add token attributes - module.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.ImageDefinition.token:X8}")); - module.Assembly.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.MetadataToken:X8}")); - } - - // Add all types - foreach (var asm in model.Assemblies) { - statusCallback?.Invoke(this, "Preparing " + asm.ShortName); - foreach (var type in asm.DefinedTypes.Where(t => !t.IsNested)) - AddType(modules[asm], type); - } - - // Write all assemblies to disk - foreach (var asm in modules.Values) { - statusCallback?.Invoke(this, "Generating " + asm.Name); - asm.Write(Path.Combine(outputPath, asm.Name)); - } - } - } -} +/* + Copyright 2017-2020 Perfare - https://github.com/Perfare/Il2CppDumper + Copyright 2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + + All rights reserved. +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using dnlib.DotNet; +using dnlib.DotNet.Emit; +using Il2CppInspector.Reflection; + +namespace Il2CppInspector.Outputs +{ + public static class dnlibExtensions + { + // Add a default parameterless constructor that calls a specified base constructor + public static MethodDef AddDefaultConstructor(this TypeDef type, IMethod @base) { + var ctor = new MethodDefUser(".ctor", MethodSig.CreateInstance(type.Module.CorLibTypes.Void), + MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName); + + var ctorBody = new CilBody(); + ctorBody.Instructions.Add(OpCodes.Ldarg_0.ToInstruction()); + ctorBody.Instructions.Add(OpCodes.Call.ToInstruction(@base)); + ctorBody.Instructions.Add(OpCodes.Ret.ToInstruction()); + ctor.Body = ctorBody; + + type.Methods.Add(ctor); + return ctor; + } + + // Add custom attribute to item with named property arguments + // 'module' is the module that owns 'type'; type.Module may still be null when this is called + public static CustomAttribute AddAttribute(this IHasCustomAttribute def, ModuleDef module, TypeDef attrTypeDef, params (string prop, object value)[] args) { + + // If SuppressMetadata is set, our own attributes will never be generated so attrTypeDef will be null + if (attrTypeDef == null) + return null; + + var attRef = module.Import(attrTypeDef); + var attCtorRef = new MemberRefUser(attrTypeDef.Module, ".ctor", MethodSig.CreateInstance(module.CorLibTypes.Void), attRef); + + // Attribute arguments + var attrArgs = args.Select(a => + new CANamedArgument(true, module.CorLibTypes.String, a.prop, new CAArgument(module.CorLibTypes.String, a.value))); + + var attr = new CustomAttribute(attCtorRef, null, attrArgs); + + def.CustomAttributes.Add(attr); + return attr; + } + } + + // Output module to create .NET DLLs containing type definitions + public class AssemblyShims + { + // Suppress informational attributes + public bool SuppressMetadata { get; set; } + + // .NET type model + private readonly TypeModel model; + + // Our custom attributes + private TypeDef addressAttribute; + private TypeDef fieldOffsetAttribute; + private TypeDef staticFieldOffsetAttribute; + private TypeDef attributeAttribute; + private TypeDef metadataOffsetAttribute; + private TypeDef metadataPreviewAttribute; + private TypeDef tokenAttribute; + + // The namespace for our custom types + private const string rootNamespace = "Il2CppInspector.DLL"; + + // All modules (single-module assemblies) + private Dictionary modules = []; + private Dictionary> types = []; + + // Custom attributes we will apply directly instead of with a custom attribute function pointer + private Dictionary directApplyAttributes; + + public AssemblyShims(TypeModel model) => this.model = model; + + // Generate base DLL with our custom types + private ModuleDef CreateBaseAssembly() { + // Create DLL with our custom types + var module = CreateAssembly("Il2CppInspector.dll"); + + // Import our IL2CPP application's copy of System.Attribute + // to avoid introducing a dependency on System.Private.CoreLib (.NET Core) from Il2CppInspector itself + var attributeType = model.TypesByFullName["System.Attribute"]; + var attributeCtor = attributeType.DeclaredConstructors.First(c => !c.IsPublic && !c.IsStatic); + var attributeTypeRef = GetTypeRef(module, attributeType); + var attributeCtorRef = new MemberRefUser(attributeTypeRef.Module, ".ctor", MethodSig.CreateInstance(module.CorLibTypes.Void), attributeTypeRef); + + var stringField = new FieldSig(module.CorLibTypes.String); + + // Create a type deriving from System.Attribute and add it to the assembly + TypeDefUser createAttribute(string name) { + var attribute = new TypeDefUser(rootNamespace, name, attributeTypeRef); + attribute.Attributes = TypeAttributes.Public | TypeAttributes.BeforeFieldInit; + module.Types.Add(attribute); + return attribute; + } + + // Create our custom attributes for compatibility with Il2CppDumper + // TODO: New format with numeric values where applicable + addressAttribute = createAttribute("AddressAttribute"); + addressAttribute.Fields.Add(new FieldDefUser("RVA", stringField, FieldAttributes.Public)); + addressAttribute.Fields.Add(new FieldDefUser("Offset", stringField, FieldAttributes.Public)); + addressAttribute.Fields.Add(new FieldDefUser("VA", stringField, FieldAttributes.Public)); + addressAttribute.Fields.Add(new FieldDefUser("Slot", stringField, FieldAttributes.Public)); + addressAttribute.AddDefaultConstructor(attributeCtorRef); + + fieldOffsetAttribute = createAttribute("FieldOffsetAttribute"); + fieldOffsetAttribute.Fields.Add(new FieldDefUser("Offset", stringField, FieldAttributes.Public)); + fieldOffsetAttribute.AddDefaultConstructor(attributeCtorRef); + + staticFieldOffsetAttribute = createAttribute("StaticFieldOffsetAttribute"); + staticFieldOffsetAttribute.Fields.Add(new FieldDefUser("Offset", stringField, FieldAttributes.Public)); + staticFieldOffsetAttribute.AddDefaultConstructor(attributeCtorRef); + + attributeAttribute = createAttribute("AttributeAttribute"); + attributeAttribute.Fields.Add(new FieldDefUser("Name", stringField, FieldAttributes.Public)); + attributeAttribute.Fields.Add(new FieldDefUser("RVA", stringField, FieldAttributes.Public)); + attributeAttribute.Fields.Add(new FieldDefUser("Offset", stringField, FieldAttributes.Public)); + attributeAttribute.AddDefaultConstructor(attributeCtorRef); + + metadataOffsetAttribute = createAttribute("MetadataOffsetAttribute"); + metadataOffsetAttribute.Fields.Add(new FieldDefUser("Offset", stringField, FieldAttributes.Public)); + metadataOffsetAttribute.AddDefaultConstructor(attributeCtorRef); + + metadataPreviewAttribute = createAttribute("MetadataPreviewAttribute"); + metadataPreviewAttribute.Fields.Add(new FieldDefUser("Data", stringField, FieldAttributes.Public)); + metadataPreviewAttribute.AddDefaultConstructor(attributeCtorRef); + + tokenAttribute = createAttribute("TokenAttribute"); + tokenAttribute.Fields.Add(new FieldDefUser("Token", stringField, FieldAttributes.Public)); + tokenAttribute.AddDefaultConstructor(attributeCtorRef); + + return module; + } + + // Create a new DLL assembly definition + private ModuleDefUser CreateAssembly(string name) { + // Create module + var module = new ModuleDefUser(name) { Kind = ModuleKind.Dll }; + + // Create assembly + var ourVersion = System.Reflection.Assembly.GetAssembly(typeof(Il2CppInspector)).GetName().Version; + var asm = new AssemblyDefUser(name.Replace(".dll", ""), ourVersion); + + // Add module to assembly + asm.Modules.Add(module); + return module; + } + + // Create a shallow type definition that only populates the type itself and its nested types. + // Used for custom attributes. + private TypeDefUser CreateTypeShallow(ModuleDef module, TypeInfo type) + { + // Initialize with base class + var mType = new TypeDefUser(type.Namespace, type.BaseName, GetTypeRef(module, type.BaseType)) + { + Attributes = (TypeAttributes)type.Attributes + }; + + // Add nested types + foreach (var nestedType in type.DeclaredNestedTypes) + mType.NestedTypes.Add(CreateTypeShallow(module, nestedType)); + + if (!types.TryAdd(module, new Dictionary {[type] = mType})) + types[module][type] = mType; + + // Add to attribute apply list if we're looking for it + if (directApplyAttributes.ContainsKey(type)) + directApplyAttributes[type] = mType; + + return mType; + } + + // Populate shallow type definition with all members, events, etc. + // Type definition is done in a two-stage process so that attributes can reference the type beforehand + private TypeDefUser PopulateType(ModuleDef module, TypeDefUser mType, TypeInfo type) { + + // Generic parameters + foreach (var gp in type.GenericTypeParameters) { + var p = new GenericParamUser((ushort) gp.GenericParameterPosition, (GenericParamAttributes) gp.GenericParameterAttributes, gp.Name); + + // Generic constraints (types and interfaces) + foreach (var c in gp.GetGenericParameterConstraints()) + p.GenericParamConstraints.Add(new GenericParamConstraintUser(GetTypeRef(module, c))); + + mType.GenericParameters.Add(p); + } + + // Interfaces + foreach (var @interface in type.ImplementedInterfaces) + mType.Interfaces.Add(new InterfaceImplUser(GetTypeRef(module, @interface))); + + // Add fields + foreach (var field in type.DeclaredFields) + AddField(module, mType, field); + + // Add properties + foreach (var prop in type.DeclaredProperties) + AddProperty(module, mType, prop); + + // Add events + foreach (var evt in type.DeclaredEvents) + AddEvent(module, mType, evt); + + // Add methods that aren't properties or events + var props = type.DeclaredProperties.SelectMany(p => new[] { p.GetMethod, p.SetMethod }).Where(m => m != null); + var events = type.DeclaredEvents.SelectMany(p => new[] { p.AddMethod, p.RemoveMethod, p.RaiseMethod }).Where(m => m != null); + + foreach (var method in type.DeclaredConstructors.AsEnumerable().Concat(type.DeclaredMethods).Except(props).Except(events)) + AddMethod(module, mType, method); + + // Add token attribute + if (type.Definition != null) + mType.AddAttribute(module, tokenAttribute, ("Token", $"0x{type.MetadataToken:X8}")); + + // Add custom attribute attributes + foreach (var ca in type.CustomAttributes) + AddCustomAttribute(module, mType, ca); + + return mType; + } + + // Add a field to a type + private FieldDef AddField(ModuleDef module, TypeDef mType, FieldInfo field) { + var s = new FieldSig(GetTypeSig(module, field.FieldType)); + + var mField = new FieldDefUser(field.Name, s, (FieldAttributes) field.Attributes); + + // Default value + if (field.HasDefaultValue) + mField.Constant = new ConstantUser(field.DefaultValue); + + // Add offset attribute if no default value but metadata present + else if (field.HasFieldRVA || field.IsLiteral) + mField.AddAttribute(module, metadataOffsetAttribute, ("Offset", $"0x{field.DefaultValueMetadataAddress:X8}")); + + // Static array initializer preview + if (field.HasFieldRVA) { + var preview = model.Package.Metadata.ReadBytes((long) field.DefaultValueMetadataAddress, 8); + var previewText = string.Join(" ", preview.Select(b => $"{b:x2}")); + + mField.AddAttribute(module, metadataPreviewAttribute, ("Data", previewText)); + } + + // Field offset + if (!field.IsStatic) + mField.AddAttribute(module, fieldOffsetAttribute, ("Offset", $"0x{field.Offset:X2}")); + else if (!field.IsLiteral) + mField.AddAttribute(module, staticFieldOffsetAttribute, ("Offset", $"0x{field.Offset:X2}")); + + // Add token attribute + mField.AddAttribute(module, tokenAttribute, ("Token", $"0x{field.MetadataToken:X8}")); + + // Add custom attribute attributes + foreach (var ca in field.CustomAttributes) + AddCustomAttribute(module, mField, ca); + + mType.Fields.Add(mField); + return mField; + } + + // Add a property to a type + private PropertyDef AddProperty(ModuleDef module, TypeDef mType, PropertyInfo prop) { + PropertySig s; + + // Static or instance + if (prop.GetMethod?.IsStatic ?? prop.SetMethod.IsStatic) + s = PropertySig.CreateStatic(GetTypeSig(module, prop.PropertyType)); + else + s = PropertySig.CreateInstance(GetTypeSig(module, prop.PropertyType)); + + var mProp = new PropertyDefUser(prop.Name, s, (PropertyAttributes) prop.Attributes); + + mProp.GetMethod = AddMethod(module, mType, prop.GetMethod); + mProp.SetMethod = AddMethod(module, mType, prop.SetMethod); + + // Add token attribute + // Generic properties and constructed properties (from disperate get/set methods) have no definition + if (prop.Definition != null) + mProp.AddAttribute(module, tokenAttribute, ("Token", $"0x{prop.MetadataToken:X8}")); + + // Add custom attribute attributes + foreach (var ca in prop.CustomAttributes) + AddCustomAttribute(module, mProp, ca); + + // Add property to type + mType.Properties.Add(mProp); + return mProp; + } + + // Add an event to a type + private EventDef AddEvent(ModuleDef module, TypeDef mType, EventInfo evt) { + var mEvent = new EventDefUser(evt.Name, GetTypeRef(module, evt.EventHandlerType), (EventAttributes) evt.Attributes); + + mEvent.AddMethod = AddMethod(module, mType, evt.AddMethod); + mEvent.RemoveMethod = AddMethod(module, mType, evt.RemoveMethod); + mEvent.InvokeMethod = AddMethod(module, mType, evt.RaiseMethod); + + // Add token attribute + mEvent.AddAttribute(module, tokenAttribute, ("Token", $"0x{evt.MetadataToken:X8}")); + + // Add custom attribute attributes + foreach (var ca in evt.CustomAttributes) + AddCustomAttribute(module, mEvent, ca); + + // Add property to type + mType.Events.Add(mEvent); + return mEvent; + } + + // Add a method to a type + private MethodDef AddMethod(ModuleDef module, TypeDef mType, MethodBase method) { + // Undefined method + if (method == null) + return null; + + // Return type and parameter signature + var s = GetMethodSig(module, method); + + // Definition + var mMethod = new MethodDefUser(method.Name, s, (MethodImplAttributes) method.MethodImplementationFlags, (MethodAttributes) method.Attributes); + + // Generic type parameters + foreach (var gp in method.GetGenericArguments()) { + var p = new GenericParamUser((ushort) gp.GenericParameterPosition, (GenericParamAttributes) gp.GenericParameterAttributes, gp.Name); + + // Generic constraints (types and interfaces) + foreach (var c in gp.GetGenericParameterConstraints()) + p.GenericParamConstraints.Add(new GenericParamConstraintUser(GetTypeRef(module, c))); + + mMethod.GenericParameters.Add(p); + } + + // Parameter names and default values + foreach (var param in method.DeclaredParameters) { + var p = new ParamDefUser(param.Name, (ushort) (param.Position + 1), (ParamAttributes) param.Attributes); + + if (param.HasDefaultValue) + p.Constant = new ConstantUser(param.DefaultValue); + + // Add offset attribute if metadata present + if (param.DefaultValueMetadataAddress != 0) + p.AddAttribute(module, metadataOffsetAttribute, ("Offset", $"0x{param.DefaultValueMetadataAddress:X8}")); + + // Add custom attribute attributes + foreach (var ca in param.CustomAttributes) + AddCustomAttribute(module, p, ca); + + mMethod.ParamDefs.Add(p); + } + + // Everything that's not extern, abstract or a delegate type should have a method body + if ((method.Attributes & System.Reflection.MethodAttributes.PinvokeImpl) == 0 + && method.DeclaringType.BaseType?.FullName != "System.MulticastDelegate" + && !method.IsAbstract) { + mMethod.Body = new CilBody(); + var inst = mMethod.Body.Instructions; + + // Return nothing if return type is void + if (mMethod.ReturnType.FullName == "System.Void") + inst.Add(OpCodes.Ret.ToInstruction()); + + // Return default for value type or enum + else if (mMethod.ReturnType.IsValueType || ((MethodInfo) method).ReturnType.IsEnum) { + var result = new Local(mMethod.ReturnType); + mMethod.Body.Variables.Add(result); + + inst.Add(OpCodes.Ldloca_S.ToInstruction(result)); + // NOTE: This line creates a reference to an external mscorlib.dll, which we'd prefer to avoid + inst.Add(OpCodes.Initobj.ToInstruction(mMethod.ReturnType.ToTypeDefOrRef())); + inst.Add(OpCodes.Ldloc_0.ToInstruction()); + inst.Add(OpCodes.Ret.ToInstruction()); + } + + // Return null for reference types + else { + inst.Add(OpCodes.Ldnull.ToInstruction()); + inst.Add(OpCodes.Ret.ToInstruction()); + } + } + + // Add token attribute + mMethod.AddAttribute(module, tokenAttribute, ("Token", $"0x{method.MetadataToken:X8}")); + + // Add method pointer attribute + if (method.VirtualAddress.HasValue) { + var args = new List<(string,object)> { + ("RVA", (method.VirtualAddress.Value.Start - model.Package.BinaryImage.ImageBase).ToAddressString()), + ("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(method.VirtualAddress.Value.Start))), + ("VA", method.VirtualAddress.Value.Start.ToAddressString()) + }; + if (method.Definition.slot != ushort.MaxValue) + args.Add(("Slot", method.Definition.slot.ToString())); + + mMethod.AddAttribute(module, addressAttribute, args.ToArray()); + } + + // Add custom attribute attributes + foreach (var ca in method.CustomAttributes) + AddCustomAttribute(module, mMethod, ca); + + // Add method to type + mType.Methods.Add(mMethod); + return mMethod; + } + + private MethodSig GetMethodSig(ModuleDef module, MethodBase method) + { + if (method.IsStatic) + return MethodSig.CreateStatic( + method is MethodInfo mi ? GetTypeSig(module, mi.ReturnType) : module.CorLibTypes.Void, + method.DeclaredParameters.Select(p => GetTypeSig(module, p.ParameterType)) + .ToArray()); + else + return MethodSig.CreateInstance( + method is MethodInfo mi ? GetTypeSig(module, mi.ReturnType) : module.CorLibTypes.Void, + method.DeclaredParameters.Select(p => GetTypeSig(module, p.ParameterType)) + .ToArray()); + } + + // Add a custom attributes attribute to an item, or the attribute itself if it is in our direct apply list + private CustomAttribute AddCustomAttribute(ModuleDef module, IHasCustomAttribute def, CustomAttributeData ca) { + if (directApplyAttributes.TryGetValue(ca.AttributeType, out var attrDef) && attrDef != null) + return AddAttribute(def, module, attrDef, ca); + + return def.AddAttribute(module, attributeAttribute, + ("Name", ca.AttributeType.Name), + ("RVA", (ca.VirtualAddress.Start - model.Package.BinaryImage.ImageBase).ToAddressString()), + ("Offset", string.Format("0x{0:X}", model.Package.BinaryImage.MapVATR(ca.VirtualAddress.Start))) + ); + } + + private CustomAttribute AddAttribute(IHasCustomAttribute def, ModuleDef module, TypeDef attrTypeDef, CustomAttributeData cad) + { + if (cad.CtorInfo == null) + return def.AddAttribute(module, attrTypeDef); + + var ctorInfo = cad.CtorInfo; + + var attRef = module.Import(attrTypeDef); + var attCtor = GetMethodSig(module, ctorInfo.Ctor); + var attCtorRef = new MemberRefUser(attrTypeDef.Module, ".ctor", attCtor, attRef); + + var attr = new CustomAttribute(attCtorRef); + + foreach (var argument in ctorInfo.Arguments) + attr.ConstructorArguments.Add(GetArgument(argument)); + + foreach (var field in ctorInfo.Fields) + attr.NamedArguments.Add(new CANamedArgument(true, GetTypeSig(module, field.Field.FieldType), field.Field.CSharpName, GetArgument(field))); + + foreach (var property in ctorInfo.Properties) + attr.NamedArguments.Add(new CANamedArgument(false, GetTypeSig(module, property.Property.PropertyType), property.Property.CSharpName, GetArgument(property))); + + def.CustomAttributes.Add(attr); + + return attr; + + CAArgument GetArgument(CustomAttributeArgument argument) + { + var typeSig = GetTypeSig(module, argument.Type); + + switch (argument.Value) + { + case TypeInfo info: + var sig = GetTypeSig(module, info); + return new CAArgument(typeSig, sig); + case CustomAttributeArgument[] argumentArray: + return new CAArgument(new SZArraySig(typeSig), + argumentArray.Select(GetArgument).ToList()); + case object[] caArray: + return new CAArgument(new SZArraySig(typeSig), + caArray.Select(x => new CustomAttributeArgument + { + Type = argument.Type, + Value = x + }).Select(GetArgument).ToList()); + default: + return new CAArgument(typeSig, argument.Value); + } + } + } + + // Generate type recursively with all nested types and add to module + private TypeDefUser AddType(ModuleDef module, TypeInfo type) { + var mType = CreateTypeShallow(module, type); + + // Add type to module + module.Types.Add(mType); + return mType; + } + + // Convert Il2CppInspector TypeInfo into type reference and import to specified module + private ITypeDefOrRef GetTypeRef(ModuleDef module, TypeInfo type) + => GetTypeSig(module, type).ToTypeDefOrRef(); + + // Convert Il2CppInspector TypeInfo into type signature and import to specified module + private TypeSig GetTypeSig(ModuleDef module, TypeInfo type) + => module.Import(GetTypeSigImpl(module, type)); + + // Convert Il2CppInspector TypeInfo into type signature + private TypeSig GetTypeSigImpl(ModuleDef module, TypeInfo type) { + if (type == null) + return null; + + // Generic type parameter (VAR) + if (type.IsGenericTypeParameter) + return new GenericVar(type.GenericParameterPosition); + + // Generic method parameter (MVAR) + if (type.IsGenericMethodParameter) + return new GenericMVar(type.GenericParameterPosition); + + // Array and single-dimension zero-indexed array (ARRAY / SZARRAY) + if (type.IsArray) + if (type.GetArrayRank() == 1) + return new SZArraySig(GetTypeSig(module, type.ElementType)); + else + return new ArraySig(GetTypeSig(module, type.ElementType), type.GetArrayRank()); + + // Pointer (PTR) + if (type.IsPointer) + return new PtrSig(GetTypeSig(module, type.ElementType)); + + // Reference (BYREF) + if (type.IsByRef) + return new ByRefSig(GetTypeSig(module, type.ElementType)); + + // Get module that owns the type + var typeOwnerModule = modules[type.Assembly]; + var typeOwnerModuleRef = new ModuleRefUser(typeOwnerModule); + + // Get reference to type; use nested type as resolution scope if applicable + var typeSig = new TypeRefUser(typeOwnerModule, type.Namespace, type.BaseName, + type.DeclaringType != null? (IResolutionScope) GetTypeRef(module, type.DeclaringType).ScopeType : typeOwnerModuleRef) + .ToTypeSig(); + + // Non-generic type (CLASS / VALUETYPE) + if (!type.GetGenericArguments().Any()) + return typeSig; + + // Generic type requires generic arguments (GENERICINST) + var genericInstSig = new GenericInstSig(typeSig.ToClassOrValueTypeSig(), type.GenericTypeArguments.Length); + + foreach (var gp in type.GetGenericArguments()) + genericInstSig.GenericArguments.Add(GetTypeSig(module, gp)); + + return genericInstSig; + } + + // Generate and save all DLLs + public void Write(string outputPath, EventHandler statusCallback = null) + { + + // Create folder for DLLs + Directory.CreateDirectory(outputPath); + + if (model.Package.Version >= 29) + { + // We can now apply all attributes directly. + directApplyAttributes = model.TypesByDefinitionIndex + .Where(IsAttributeType) + .ToDictionary(x => x, _ => (TypeDef) null); + } + else + { + // Get all custom attributes with no parameters + // We'll add these directly to objects instead of the attribute generator function pointer + directApplyAttributes = model.TypesByDefinitionIndex + .Where(t => IsAttributeType(t) + && t.DeclaredFields.Count == 0 + && t.DeclaredProperties.Count == 0) + .ToDictionary(t => t, t => (TypeDef)null); + } + + // Generate blank assemblies + // We have to do this before adding anything else so we can reference every module + modules.Clear(); + + foreach (var asm in model.Assemblies) { + // Create assembly and add primary module to list + var module = CreateAssembly(asm.ShortName); + modules.Add(asm, module); + } + + // Generate our custom types assembly (relies on mscorlib.dll being added above) + if (!SuppressMetadata) { + var baseDll = CreateBaseAssembly(); + + // Write base assembly to disk + baseDll.Write(Path.Combine(outputPath, baseDll.Name)); + } + + // Add all types + foreach (var asm in model.Assemblies) { + statusCallback?.Invoke(this, "Preparing " + asm.ShortName); + foreach (var type in asm.DefinedTypes.Where(t => !t.IsNested)) + AddType(modules[asm], type); + } + + foreach (var asm in model.Assemblies) + { + statusCallback?.Invoke(this, "Populating " + asm.ShortName); + var module = modules[asm]; + + // Add assembly custom attribute attributes (must do this after all assemblies and types are created due to type referencing) + foreach (var ca in asm.CustomAttributes) + AddCustomAttribute(module, module.Assembly, ca); + + // Add token attributes + module.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.ImageDefinition.token:X8}")); + module.Assembly.AddAttribute(module, tokenAttribute, ("Token", $"0x{asm.MetadataToken:X8}")); + + if (types.TryGetValue(module, out var shallowTypes)) + foreach (var (typeInfo, typeDef) in shallowTypes) + PopulateType(module, typeDef, typeInfo); + } + + // Write all assemblies to disk + foreach (var asm in modules.Values) { + statusCallback?.Invoke(this, "Generating " + asm.Name); + asm.Write(Path.Combine(outputPath, asm.Name)); + } + + return; + + static bool IsAttributeType(TypeInfo type) => + type.FullName == "System.Attribute" || (type.BaseType != null && IsAttributeType(type.BaseType)); + } + } +} diff --git a/Il2CppInspector.Common/Outputs/CSharpCodeStubs.cs b/Il2CppInspector.Common/Outputs/CSharpCodeStubs.cs index 4f5bdd2..dd03198 100644 --- a/Il2CppInspector.Common/Outputs/CSharpCodeStubs.cs +++ b/Il2CppInspector.Common/Outputs/CSharpCodeStubs.cs @@ -1,725 +1,725 @@ -// Copyright (c) 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty -// All rights reserved - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Il2CppInspector.Properties; -using Il2CppInspector.Reflection; -using Assembly = Il2CppInspector.Reflection.Assembly; -using CustomAttributeData = Il2CppInspector.Reflection.CustomAttributeData; -using MethodInfo = Il2CppInspector.Reflection.MethodInfo; -using TypeInfo = Il2CppInspector.Reflection.TypeInfo; - -namespace Il2CppInspector.Outputs -{ - public class CSharpCodeStubs - { - private readonly TypeModel model; - private Exception lastException; - - // Namespace prefixes whose contents should be skipped - public List ExcludedNamespaces { get; set; } - - // Make adjustments to ensure that the generated code compiles - public bool MustCompile { get; set; } - - // Suppress binary metadata in code comments - public bool SuppressMetadata { get; set; } - - private const string CGAttribute = "System.Runtime.CompilerServices.CompilerGeneratedAttribute"; - private const string FBAttribute = "System.Runtime.CompilerServices.FixedBufferAttribute"; - private const string ExtAttribute = "System.Runtime.CompilerServices.ExtensionAttribute"; - private const string AsyncAttribute = "System.Runtime.CompilerServices.AsyncStateMachineAttribute"; - private const string DMAttribute = "System.Reflection.DefaultMemberAttribute"; - - // Assembly attributes we have already emitted - private HashSet usedAssemblyAttributes = new HashSet(); - private readonly object usedAssemblyAttributesLock = new object(); - - public CSharpCodeStubs(TypeModel model) => this.model = model; - - // Get the last error that occurred and clear the error state - public Exception GetAndClearLastException() { - var ex = lastException; - lastException = null; - return ex; - } - - public void WriteSingleFile(string outFile) => WriteSingleFile(outFile, t => t.Index); - - public void WriteSingleFile(string outFile, Func orderBy) { - usedAssemblyAttributes.Clear(); - writeFile(outFile, model.Assemblies.SelectMany(x => x.DefinedTypes).OrderBy(orderBy)); - } - - public void WriteFilesByNamespace(string outPath, Func orderBy, bool flattenHierarchy) { - usedAssemblyAttributes.Clear(); - Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes).GroupBy(t => t.Namespace), ns => { - var relPath = !string.IsNullOrEmpty(ns.Key) ? ns.Key : "global"; - writeFile(Path.Combine(outPath, (flattenHierarchy ? relPath : Path.Combine(relPath.Split('.'))) + ".cs"), - ns.OrderBy(orderBy)); - }); - } - - public void WriteFilesByAssembly(string outPath, Func orderBy, bool separateAttributes) { - usedAssemblyAttributes.Clear(); - Parallel.ForEach(model.Assemblies, asm => { - // Sort namespaces into alphabetical order, then sort types within the namespaces by the specified sort function - if (writeFile(Path.Combine(outPath, Path.GetFileNameWithoutExtension(asm.ShortName) + ".cs"), asm.DefinedTypes.OrderBy(t => t.Namespace).ThenBy(orderBy), outputAssemblyAttributes: !separateAttributes) - && separateAttributes) { - File.WriteAllText(Path.Combine(outPath, $"AssemblyInfo_{Path.GetFileNameWithoutExtension(asm.ShortName)}.cs"), generateAssemblyInfo(new [] {asm})); - } - }); - } - - public void WriteFilesByClass(string outPath, bool flattenHierarchy) { - usedAssemblyAttributes.Clear(); - Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes), type => { - string relPath = $"{type.Namespace}{(type.Namespace.Length > 0 ? "." : "")}{Regex.Replace(type.Name, "`[0-9]", "")}"; - writeFile(Path.Combine(outPath, flattenHierarchy ? relPath : Path.Combine(relPath.Split('.')) + ".cs"), new[] {type}); - }); - } - - public HashSet WriteFilesByClassTree(string outPath, bool separateAttributes) { - usedAssemblyAttributes.Clear(); - var usedAssemblies = new HashSet(); - - // Each thread tracks its own list of used assemblies and they are merged as each thread completes - Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes), - () => new HashSet(), - (type, _, used) => { - string relPath = Path.Combine($"{type.Namespace}{(type.Namespace.Length > 0 ? "." : "")}{Regex.Replace(type.Name, "`[0-9]", "")}".Split('.')); - if (writeFile(Path.Combine(outPath, Path.GetFileNameWithoutExtension(type.Assembly.ShortName), $"{relPath}.cs"), new[] {type}, outputAssemblyAttributes: !separateAttributes)) - used.Add(type.Assembly); - return used; - }, - usedPartition => { - lock (usedAssemblies) usedAssemblies.UnionWith(usedPartition); - } - ); - - if (separateAttributes && usedAssemblies.Any() && lastException == null) - foreach (var asm in usedAssemblies) - File.WriteAllText(Path.Combine(outPath, Path.GetFileNameWithoutExtension(asm.ShortName), "AssemblyInfo.cs"), generateAssemblyInfo(new [] {asm})); - - return usedAssemblies; - } - - // Create a Visual Studio solution - public void WriteSolution(string outPath, string unityPath, string unityAssembliesPath) { - // Required settings - MustCompile = true; - - // Output source files in tree format with separate assembly attributes - var assemblies = WriteFilesByClassTree(outPath, true); - - if (lastException != null) - return; - - // Per-project (per-assembly) solution definition and configuration - var slnProjectDefs = new StringBuilder(); - var slnProjectConfigs = new StringBuilder(); - - foreach (var asm in assemblies) { - var guid = Guid.NewGuid(); - var name = Path.GetFileNameWithoutExtension(asm.ShortName); - var csProjFile = Path.Combine(name, $"{name}.csproj"); - - var def = Resources.SlnProjectDefinition - .Replace("%PROJECTGUID%", guid.ToString()) - .Replace("%PROJECTNAME%", name) - .Replace("%CSPROJRELATIVEPATH%", csProjFile); - - slnProjectDefs.Append(def); - - var config = Resources.SlnProjectConfiguration - .Replace("%PROJECTGUID%", guid.ToString()); - - slnProjectConfigs.Append(config); - - // Determine all the assemblies on which this assembly depends - var dependencyTypes = asm.DefinedTypes.SelectMany(t => t.GetAllTypeReferences()) - .Union(asm.CustomAttributes.SelectMany(a => a.AttributeType.GetAllTypeReferences())) - .Distinct(); - var dependencyAssemblies = dependencyTypes.Select(t => t.Assembly).Distinct() - .Except(new[] {asm}); - - // Only create project references to those assemblies actually output in our solution - dependencyAssemblies = dependencyAssemblies.Intersect(assemblies); - - var referenceXml = string.Concat(dependencyAssemblies.Select( - a => $@" " + "\n" - )); - - // Create a .csproj file using the project Guid - var csProj = Resources.CsProjTemplate - .Replace("%PROJECTGUID%", guid.ToString()) - .Replace("%ASSEMBLYNAME%", name) - .Replace("%UNITYPATH%", unityPath) - .Replace("%SCRIPTASSEMBLIES%", unityAssembliesPath) - .Replace("%PROJECTREFERENCES%", referenceXml); - - File.WriteAllText(Path.Combine(outPath, csProjFile), csProj); - } - - // Merge everything into .sln file - var sln = Resources.CsSlnTemplate - .Replace("%PROJECTDEFINITIONS%", slnProjectDefs.ToString()) - .Replace("%PROJECTCONFIGURATIONS%", slnProjectConfigs.ToString()); - - var filename = Path.GetFileName(outPath); - if (filename == "") - filename = "Il2CppProject"; - File.WriteAllText(Path.Combine(outPath, $"{filename}.sln"), sln); - } - - private bool writeFile(string outFile, IEnumerable types, bool useNamespaceSyntax = true, bool outputAssemblyAttributes = true) { - - var nsRefs = new HashSet(); - var code = new StringBuilder(); - var nsContext = ""; - var usedTypes = new List(); - - // Determine all namespace references (note: this may include some that aren't actually used due to output suppression in generateType() - // We have to do this first so that we can generate properly scoped type references in the code - foreach (var type in types) { - var refs = type.GetAllTypeReferences(); - var ns = refs.Where(r => !string.IsNullOrEmpty(r.Namespace) && r.Namespace != type.Namespace).Select(r => r.Namespace); - nsRefs.UnionWith(ns); - } - - // Determine assemblies used in this file - var assemblies = types.Select(t => t.Assembly).Distinct(); - - // Add assembly attribute namespaces to reference list - if (outputAssemblyAttributes) - nsRefs.UnionWith(assemblies.SelectMany(a => a.CustomAttributes).Select(a => a.AttributeType.Namespace)); - - var results = new ConcurrentBag>(); - - // Generate each type - Parallel.ForEach(types, new ParallelOptions { MaxDegreeOfParallelism = Math.Max(Environment.ProcessorCount / 2, 1) }, - () => new Dictionary(), - (type, _, dict) => { - // Skip namespace and any children if requested - if (ExcludedNamespaces?.Any(x => x == type.Namespace || type.Namespace.StartsWith(x + ".")) ?? false) - return dict; - - // Don't output global::Locale if desired - if (MustCompile - && type.Name == "Locale" && type.Namespace == string.Empty - && type.BaseType.FullName == "System.Object" - && type.IsClass && type.IsSealed && type.IsNotPublic && !type.ContainsGenericParameters - && type.DeclaredMembers.Count == type.DeclaredMethods.Count - && type.GetMethods("GetText").Length == type.DeclaredMethods.Count) - return dict; - - // Assembly.DefinedTypes returns nested types in the assembly by design - ignore them - if (type.IsNested) - return dict; - - // Get code - var code = generateType(type, nsRefs); - if (code.Length > 0) - dict.Add(type, code); - return dict; - }, - dict => results.Add(dict)); - - // Flatten - var sortedResults = results.SelectMany(d => d).ToDictionary(i => i.Key, i => i.Value); - - // Process in order according to original sorted type list - foreach (var type in types) { - if (!sortedResults.TryGetValue(type, out var text)) - continue; - - // Determine if we need to change namespace (after we have established the code block is not empty) - if (useNamespaceSyntax) { - if (type.Namespace != nsContext) { - if (!string.IsNullOrEmpty(nsContext)) - code.Remove(code.Length - 1, 1).Append("}\n\n"); - - if (!string.IsNullOrEmpty(type.Namespace)) - code.Append("namespace " + type.Namespace + "\n{\n"); - - nsContext = type.Namespace; - } - - if (!string.IsNullOrEmpty(nsContext)) { - text.Insert(0, "\t"); - text.Replace("\n", "\n\t"); - text.Length--; - } - } - - // Append namespace - if (!useNamespaceSyntax) - code.Append($"// Namespace: {(!string.IsNullOrEmpty(type.Namespace) ? type.Namespace : "")}\n"); - - // Append type definition - code.Append(text); - code.Append("\n"); - - // Add to list of used types - usedTypes.Add(type); - } - - // Stop if nothing to output - if (!usedTypes.Any()) - return false; - - // Close namespace - if (useNamespaceSyntax && !string.IsNullOrEmpty(nsContext)) - code.Remove(code.Length - 1, 1).Append("}\n"); - - // Determine using directives (put System namespaces first) - nsRefs.Clear(); - foreach (var type in usedTypes) { - var refs = type.GetAllTypeReferences(); - var ns = refs.Where(r => !string.IsNullOrEmpty(r.Namespace) && r.Namespace != type.Namespace).Select(r => r.Namespace); - nsRefs.UnionWith(ns); - } - nsRefs.UnionWith(assemblies.SelectMany(a => a.CustomAttributes).Select(a => a.AttributeType.Namespace)); - - var usings = nsRefs.OrderBy(n => (n.StartsWith("System.") || n == "System") ? "0" + n : "1" + n); - - // Ensure output directory exists and is not a file - var dir = string.Join("_", Path.GetDirectoryName(outFile).Split(Path.GetInvalidPathChars())); - if (!string.IsNullOrEmpty(dir)) { - try { - Directory.CreateDirectory(dir); - } - catch (IOException ex) { - lastException = ex; - return false; - } - } - - // Sanitize leafname (might be class name with invalid characters) - var leafname = string.Join("_", Path.GetFileName(outFile).Split(Path.GetInvalidFileNameChars())); - - outFile = Path.Combine(dir, leafname); - - // Create output file - bool fileWritten = false; - do { - try { - using StreamWriter writer = new StreamWriter(new FileStream(outFile, FileMode.Create), Encoding.UTF8); - - // Write preamble - writer.Write(@"/* - * Generated code file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty - */ - -"); - - // Output using directives - writer.Write(string.Concat(usings.Select(n => $"using {n};\n"))); - if (nsRefs.Any()) - writer.Write("\n"); - - // Output assembly information and attributes - writer.Write(generateAssemblyInfo(assemblies, nsRefs, outputAssemblyAttributes) + "\n\n"); - - // Output type definitions - writer.Write(code); - - fileWritten = true; - } - catch (IOException ex) { - // If we get "file is in use by another process", we are probably writing a duplicate class in another thread - // Wait a bit and try again - if ((uint) ex.HResult != 0x80070020) - throw; - - System.Threading.Thread.Sleep(100); - } - } while (!fileWritten); - - return true; - } - - private string generateAssemblyInfo(IEnumerable assemblies, IEnumerable namespaces = null, bool outputAssemblyAttributes = true) { - var text = new StringBuilder(); - - foreach (var asm in assemblies) { - text.Append($"// Image {asm.Index}: {asm.ShortName} - Assembly: {asm.FullName}"); - if (!SuppressMetadata) - text.Append($" - Types {asm.ImageDefinition.typeStart}-{asm.ImageDefinition.typeStart + asm.ImageDefinition.typeCount - 1}"); - text.AppendLine(); - - // Assembly-level attributes - if (outputAssemblyAttributes) - lock (usedAssemblyAttributesLock) { - text.Append(asm.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute) - .Except(usedAssemblyAttributes ?? new HashSet()) - .OrderBy(a => a.AttributeType.Name) - .ToString(new Scope { Current = null, Namespaces = namespaces ?? new List() }, attributePrefix: "assembly: ", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); - if (asm.CustomAttributes.Any()) - text.Append("\n"); - - usedAssemblyAttributes.UnionWith(asm.CustomAttributes); - } - } - return text.ToString().TrimEnd(); - } - - private StringBuilder generateType(TypeInfo type, IEnumerable namespaces, string prefix = "") { - // Don't output compiler-generated types if desired - if (MustCompile && type.GetCustomAttributes(CGAttribute).Any()) - return new StringBuilder(); - - var codeBlocks = new Dictionary(); - var usedMethods = new List(); - StringBuilder sb; - - var scope = new Scope { - Current = type, - Namespaces = namespaces - }; - - // Fields - sb = new StringBuilder(); - if (!type.IsEnum) { - foreach (var field in type.DeclaredFields) { - if (MustCompile && field.GetCustomAttributes(CGAttribute).Any()) - continue; - - if (field.IsNotSerialized) - sb.Append(prefix + "\t[NonSerialized]\n"); - - // Attributes - sb.Append(field.CustomAttributes.Where(a => a.AttributeType.FullName != FBAttribute).OrderBy(a => a.AttributeType.Name) - .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); - sb.Append(prefix + "\t"); - sb.Append(field.GetModifierString()); - - // Fixed buffers - if (field.GetCustomAttributes(FBAttribute).Any()) { - if (!SuppressMetadata) - sb.Append($"/* {field.GetCustomAttributes(FBAttribute)[0].VirtualAddress.ToAddressString()} */ "); - sb.Append($"{field.FieldType.DeclaredFields[0].FieldType.GetScopedCSharpName(scope)} {field.CSharpName}[0]"); // FixedElementField - } - // Regular fields - else - sb.Append($"{field.FieldType.GetScopedCSharpName(scope)} {field.CSharpName}"); - if (field.HasDefaultValue) - sb.Append($" = {field.GetDefaultValueString(scope)}"); - sb.Append(";"); - // Don't output field indices for const fields (they don't have any storage) - // or open generic types (they aren't known until runtime) - if (!field.IsLiteral && !SuppressMetadata && !type.ContainsGenericParameters) - sb.Append($" // 0x{(uint) field.Offset:X2}"); - // Output metadata file offset for const fields - if (field.IsLiteral && !SuppressMetadata) - sb.Append($" // Metadata: {field.DefaultValueMetadataAddress.ToAddressString()}"); - // For static array initializers, output metadata address and preview - if (field.HasFieldRVA && !SuppressMetadata) { - var preview = model.Package.Metadata.ReadBytes((long) field.DefaultValueMetadataAddress, 8); - var previewText = string.Join(" ", preview.Select(b => $"{b:x2}")); - - sb.Append($" // Starts with: {previewText} - Metadata: {field.DefaultValueMetadataAddress.ToAddressString()}"); - } - sb.Append("\n"); - } - codeBlocks.Add("Fields", sb); - } - - // Properties - sb = new StringBuilder(); - var hasIndexer = false; - foreach (var prop in type.DeclaredProperties) { - // Attributes - sb.Append(prop.CustomAttributes.OrderBy(a => a.AttributeType.Name) - .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); - - // The access mask enum values go from 1 (private) to 6 (public) in order from most to least restrictive - var getAccess = (prop.GetMethod?.Attributes ?? 0) & MethodAttributes.MemberAccessMask; - var setAccess = (prop.SetMethod?.Attributes ?? 0) & MethodAttributes.MemberAccessMask; - - // In case the access level of both is the same and the selected method is null, pick the other one (rare edge case) - var primary = (getAccess >= setAccess ? prop.GetMethod : prop.SetMethod) ?? prop.GetMethod ?? prop.SetMethod; - sb.Append($"{prefix}\t{primary.GetModifierString()}{prop.PropertyType.GetScopedCSharpName(scope)} "); - - // Non-indexer; non-auto-properties should have a body - var needsBody = MustCompile && !type.IsInterface && !type.IsAbstract && !prop.IsAutoProperty; - - var getBody = needsBody? " => default;" : ";"; - var setBody = needsBody? " {}" : ";"; - if ((!prop.CanRead || !prop.GetMethod.DeclaredParameters.Any()) && (!prop.CanWrite || prop.SetMethod.DeclaredParameters.Count == 1)) - sb.Append($"{prop.CSharpName} {{ "); - - // Indexer - else { - // Replace indexer name (usually "Item" but not always) with "this" - preserves explicit interface implementations - if (prop.CSharpName.IndexOf('.') != -1) - sb.Append(prop.CSharpName.Substring(0, prop.CSharpName.LastIndexOf('.') + 1)); - sb.Append("this[" + string.Join(", ", primary.DeclaredParameters.SkipLast(getAccess >= setAccess ? 0 : 1) - .Select(p => p.GetParameterString(scope, !SuppressMetadata, MustCompile))) + "] { "); - getBody = " => default;"; - setBody = " {}"; - hasIndexer = true; - } - - sb.Append((prop.CanRead? prop.GetMethod.CustomAttributes.Where(a => !MustCompile || a.AttributeType.FullName != CGAttribute) - .ToString(scope, inline: true, emitPointer: !SuppressMetadata, mustCompile: MustCompile) - + (getAccess < setAccess? prop.GetMethod.GetAccessModifierString() : "") + $"get{getBody} " : "") - // Auto-properties must have get accessors (exclude indexers) - + (MustCompile && !prop.CanRead && setBody == ";"? "get; " : "") - + (prop.CanWrite? prop.SetMethod.CustomAttributes.Where(a => !MustCompile || a.AttributeType.FullName != CGAttribute) - .ToString(scope, inline: true, emitPointer: !SuppressMetadata, mustCompile: MustCompile) - + (setAccess < getAccess? prop.SetMethod.GetAccessModifierString() : "") + $"set{setBody} " : "") + "}"); - if (!SuppressMetadata) { - if ((prop.CanRead && prop.GetMethod.VirtualAddress != null) || (prop.CanWrite && prop.SetMethod.VirtualAddress != null)) - sb.Append(" // "); - sb.Append((prop.CanRead && prop.GetMethod.VirtualAddress != null ? prop.GetMethod.VirtualAddress.ToAddressString() + " " : "") - + (prop.CanWrite && prop.SetMethod.VirtualAddress != null ? prop.SetMethod.VirtualAddress.ToAddressString() : "")); - } - sb.Append("\n"); - - usedMethods.Add(prop.GetMethod); - usedMethods.Add(prop.SetMethod); - } - codeBlocks.Add("Properties", sb); - - // Events - sb = new StringBuilder(); - foreach (var evt in type.DeclaredEvents) { - // Attributes - sb.Append(evt.CustomAttributes.OrderBy(a => a.AttributeType.Name) - .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); - - string modifiers = evt.AddMethod?.GetModifierString(); - sb.Append($"{prefix}\t{modifiers}event {evt.EventHandlerType.GetScopedCSharpName(scope)} {evt.CSharpName}"); - - if (!MustCompile) { - sb.Append(" {\n"); - var m = new Dictionary(); - if (evt.AddMethod != null) m.Add("add", evt.AddMethod.VirtualAddress); - if (evt.RemoveMethod != null) m.Add("remove", evt.RemoveMethod.VirtualAddress); - if (evt.RaiseMethod != null) m.Add("raise", evt.RaiseMethod.VirtualAddress); - sb.Append(string.Join("\n", m.Select(x => $"{prefix}\t\t{x.Key};{(SuppressMetadata? "" : " // " + x.Value.ToAddressString())}")) + "\n" + prefix + "\t}\n"); - } else - sb.Append(";\n"); - - usedMethods.Add(evt.AddMethod); - usedMethods.Add(evt.RemoveMethod); - usedMethods.Add(evt.RaiseMethod); - } - codeBlocks.Add("Events", sb); - - // Nested types - codeBlocks.Add("Nested types", new StringBuilder().AppendJoin("\n", type.DeclaredNestedTypes - .Select(n => generateType(n, namespaces, prefix + "\t")).Where(c => c.Length > 0))); - - // Constructors - sb = new StringBuilder(); - var fields = type.DeclaredFields.Where(f => !f.GetCustomAttributes(CGAttribute).Any()); - - // Crete a parameterless constructor for every relevant type when making code that compiles to mitigate CS1729 and CS7036 - if (MustCompile && !type.IsInterface && !(type.IsAbstract && type.IsSealed) && !type.IsValueType - && type.DeclaredConstructors.All(c => c.IsStatic || c.DeclaredParameters.Any())) - sb.Append($"{prefix}\t{(type.IsAbstract? "protected" : "public")} {type.CSharpBaseName}() {{}} // Dummy constructor\n"); - - foreach (var method in type.DeclaredConstructors) { - // Attributes - sb.Append(method.CustomAttributes.OrderBy(a => a.AttributeType.Name) - .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); - - sb.Append($"{prefix}\t{method.GetModifierString()}{method.DeclaringType.CSharpBaseName}{method.GetTypeParametersString(scope)}"); - sb.Append($"({method.GetParametersString(scope, !SuppressMetadata)})"); - - if (MustCompile) { - // Class constructor - if (method.IsAbstract) - sb.Append(";"); - else if (!type.IsValueType) - sb.Append(" {}"); - - // Struct constructor - else { - // Parameterized struct constructors must call the parameterless constructor to create the object - // if the object has any auto-implemented properties - if (type.DeclaredProperties.Any() && method.DeclaredParameters.Any()) - sb.Append(" : this()"); - - // Struct construvctors must initialize all fields in the struct - if (fields.Any()) { - var paramNames = method.DeclaredParameters.Select(p => p.Name); - sb.Append(" {\n" + string.Join("\n", fields - .Where(f => !f.IsLiteral && f.IsStatic == method.IsStatic) - .Select(f => $"{prefix}\t\t{(paramNames.Contains(f.Name) ? "this." : "")}{f.Name} = default;")) - + $"\n{prefix}\t}}"); - } else - sb.Append(" {}"); - } - } else - sb.Append(";"); - - sb.Append((!SuppressMetadata && method.VirtualAddress != null ? $" // {method.VirtualAddress.ToAddressString()}" : "") + "\n"); - } - codeBlocks.Add("Constructors", sb); - - // Methods - // Don't re-output methods for constructors, properties, events etc. - var methods = type.DeclaredMethods.Except(usedMethods).Where(m => m.CustomAttributes.All(a => a.AttributeType.FullName != ExtAttribute)); - codeBlocks.Add("Methods", methods - .Select(m => generateMethod(m, scope, prefix)) - .Aggregate(new StringBuilder(), (r, i) => r.Append(i))); - - usedMethods.AddRange(methods); - - // Extension methods - codeBlocks.Add("Extension methods", type.DeclaredMethods.Except(usedMethods) - .Select(m => generateMethod(m, scope, prefix)) - .Aggregate(new StringBuilder(), (r, i) => r.Append(i))); - - // Type declaration - sb = new StringBuilder(); - - if (type.IsImport) - sb.Append(prefix + "[ComImport]\n"); - if (type.IsSerializable) - sb.Append(prefix + "[Serializable]\n"); - - // DefaultMemberAttribute should be output if it is present and the type does not have an indexer, otherwise suppressed - // See https://docs.microsoft.com/en-us/dotnet/api/system.reflection.defaultmemberattribute?view=netframework-4.8 - sb.Append(type.CustomAttributes.Where(a => (a.AttributeType.FullName != DMAttribute || !hasIndexer) && a.AttributeType.FullName != ExtAttribute) - .OrderBy(a => a.AttributeType.Name).ToString(scope, prefix, emitPointer: !SuppressMetadata, mustCompile: MustCompile)); - - // Roll-up multicast delegates to use the 'delegate' syntactic sugar - if (type.IsClass && type.IsSealed && type.BaseType?.FullName == "System.MulticastDelegate") { - sb.Append(prefix + type.GetAccessModifierString()); - - var del = type.GetMethod("Invoke"); - // IL2CPP doesn't seem to retain return type attributes - //sb.Append(del.ReturnType.CustomAttributes.ToString(prefix, "return: ", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); - if (del.RequiresUnsafeContext) - sb.Append("unsafe "); - sb.Append($"delegate {del.ReturnType.GetScopedCSharpName(scope)} {type.GetCSharpTypeDeclarationName()}("); - sb.Append(del.GetParametersString(scope, !SuppressMetadata) + ");"); - if (!SuppressMetadata) - sb.Append($" // TypeDefIndex: {type.Index}; {del.VirtualAddress.ToAddressString()}"); - sb.Append("\n"); - return sb; - } - - sb.Append(prefix + type.GetModifierString()); - - var @base = type.NonInheritedInterfaces.Select(x => x.GetScopedCSharpName(scope, isPartOfTypeDeclaration: true)).ToList(); - if (type.BaseType != null && type.BaseType.FullName != "System.Object" && type.BaseType.FullName != "System.ValueType" && !type.IsEnum) - @base.Insert(0, type.BaseType.GetScopedCSharpName(scope, isPartOfTypeDeclaration: true)); - if (type.IsEnum && type.GetEnumUnderlyingType().FullName != "System.Int32") // enums derive from int by default - @base.Insert(0, type.GetEnumUnderlyingType().GetScopedCSharpName(scope)); - var baseText = @base.Count > 0 ? " : " + string.Join(", ", @base) : string.Empty; - - sb.Append($"{type.GetCSharpTypeDeclarationName()}{baseText}"); - if (!SuppressMetadata) - sb.Append($" // TypeDefIndex: {type.Index}"); - sb.Append("\n"); - - foreach (var gp in type.GetGenericArguments()) { - var constraint = gp.GetTypeConstraintsString(scope); - if (constraint != string.Empty) - sb.Append($"{prefix}\t{constraint}\n"); - } - - sb.Append(prefix + "{\n"); - - // Enumeration - if (type.IsEnum) { - sb.AppendJoin(",\n", type.GetEnumNames().Zip(type.GetEnumValues().OfType(), - (k, v) => new { k, v }).OrderBy(x => x.v).Select(x => $"{prefix}\t{x.k} = {x.v}")); - sb.Append("\n"); - } - - // Type definition - else - sb.AppendJoin("\n", codeBlocks.Where(b => b.Value.Length > 0).Select(b => prefix + "\t// " + b.Key + "\n" + b.Value)); - - sb.Append(prefix + "}\n"); - return sb; - } - - private StringBuilder generateMethod(MethodInfo method, Scope scope, string prefix) { - var writer = new StringBuilder(); - - if (MustCompile && method.GetCustomAttributes(CGAttribute).Any()) - return writer; - - // Attributes - writer.Append(method.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute && a.AttributeType.FullName != AsyncAttribute) - .OrderBy(a => a.AttributeType.Name) - .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); - - // IL2CPP doesn't seem to retain return type attributes - //writer.Append(method.ReturnType.CustomAttributes.ToString(prefix + "\t", "return: ", emitPointer: !SuppressMetadata)); - writer.Append($"{prefix}\t{method.GetModifierString()}"); - - // Finalizers become destructors - if (method.Name == "Finalize" && method.IsVirtual && method.ReturnType.FullName == "System.Void" && method.IsFamily) - writer.Append("~" + method.DeclaringType.CSharpBaseName); - - // Regular method or operator overload - else if (method.Name != "op_Implicit" && method.Name != "op_Explicit") - writer.Append($"{method.ReturnParameter.GetReturnParameterString(scope)} {method.CSharpName}{method.GetTypeParametersString(scope)}"); - - // User-defined conversion operator - else - writer.Append($"{method.CSharpName}{method.ReturnType.GetScopedCSharpName(scope)}"); - - // Parameters - writer.Append("(" + method.GetParametersString(scope, !SuppressMetadata) + ")"); - - // Generic type constraints - foreach (var gp in method.GetGenericArguments()) { - var constraint = gp.GetTypeConstraintsString(scope); - if (constraint != string.Empty) - writer.Append($"\n{prefix}\t\t{constraint}"); - } - - // Body - var methodBody = MustCompile? method switch { - // Abstract method - { IsAbstract: true } => ";", - - // Extern method - { Attributes: var a } when (a & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl => ";", - - // Method with out parameters - { DeclaredParameters: var d } when d.Any(p => p.IsOut) => - " {\n" + string.Join("\n", d.Where(p => p.IsOut).Select(p => $"{prefix}\t\t{p.Name} = default;")) - + (method.ReturnType.FullName != "System.Void"? $"\n{prefix}\t\treturn default;" : "") - + $"\n{prefix}\t}}", - - // No return type - { ReturnType: var retType } when retType.FullName == "System.Void" => " {}", - - // Ref return type - { ReturnType: var retType } when retType.IsByRef => " => ref _refReturnTypeFor" + method.CSharpName + ";", - - // Regular return type - _ => " => default;" - } - - // Only make a method body if we are trying to compile the output - : ";"; - - writer.Append(methodBody + (!SuppressMetadata && method.VirtualAddress != null ? $" // {method.VirtualAddress.ToAddressString()}" : "") + "\n"); - - // Ref return type requires us to invent a field - if (MustCompile && method.ReturnType.IsByRef) - writer.Append($"{prefix}\tprivate {method.ReturnType.GetScopedCSharpName(scope)} _refReturnTypeFor{method.CSharpName}; // Dummy field\n"); - - return writer; - } - } -} +// Copyright (c) 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty +// All rights reserved + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Il2CppInspector.Properties; +using Il2CppInspector.Reflection; +using Assembly = Il2CppInspector.Reflection.Assembly; +using CustomAttributeData = Il2CppInspector.Reflection.CustomAttributeData; +using MethodInfo = Il2CppInspector.Reflection.MethodInfo; +using TypeInfo = Il2CppInspector.Reflection.TypeInfo; + +namespace Il2CppInspector.Outputs +{ + public class CSharpCodeStubs + { + private readonly TypeModel model; + private Exception lastException; + + // Namespace prefixes whose contents should be skipped + public List ExcludedNamespaces { get; set; } + + // Make adjustments to ensure that the generated code compiles + public bool MustCompile { get; set; } + + // Suppress binary metadata in code comments + public bool SuppressMetadata { get; set; } + + private const string CGAttribute = "System.Runtime.CompilerServices.CompilerGeneratedAttribute"; + private const string FBAttribute = "System.Runtime.CompilerServices.FixedBufferAttribute"; + private const string ExtAttribute = "System.Runtime.CompilerServices.ExtensionAttribute"; + private const string AsyncAttribute = "System.Runtime.CompilerServices.AsyncStateMachineAttribute"; + private const string DMAttribute = "System.Reflection.DefaultMemberAttribute"; + + // Assembly attributes we have already emitted + private HashSet usedAssemblyAttributes = new HashSet(); + private readonly object usedAssemblyAttributesLock = new object(); + + public CSharpCodeStubs(TypeModel model) => this.model = model; + + // Get the last error that occurred and clear the error state + public Exception GetAndClearLastException() { + var ex = lastException; + lastException = null; + return ex; + } + + public void WriteSingleFile(string outFile) => WriteSingleFile(outFile, t => t.Index); + + public void WriteSingleFile(string outFile, Func orderBy) { + usedAssemblyAttributes.Clear(); + writeFile(outFile, model.Assemblies.SelectMany(x => x.DefinedTypes).OrderBy(orderBy)); + } + + public void WriteFilesByNamespace(string outPath, Func orderBy, bool flattenHierarchy) { + usedAssemblyAttributes.Clear(); + Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes).GroupBy(t => t.Namespace), ns => { + var relPath = !string.IsNullOrEmpty(ns.Key) ? ns.Key : "global"; + writeFile(Path.Combine(outPath, (flattenHierarchy ? relPath : Path.Combine(relPath.Split('.'))) + ".cs"), + ns.OrderBy(orderBy)); + }); + } + + public void WriteFilesByAssembly(string outPath, Func orderBy, bool separateAttributes) { + usedAssemblyAttributes.Clear(); + Parallel.ForEach(model.Assemblies, asm => { + // Sort namespaces into alphabetical order, then sort types within the namespaces by the specified sort function + if (writeFile(Path.Combine(outPath, Path.GetFileNameWithoutExtension(asm.ShortName) + ".cs"), asm.DefinedTypes.OrderBy(t => t.Namespace).ThenBy(orderBy), outputAssemblyAttributes: !separateAttributes) + && separateAttributes) { + File.WriteAllText(Path.Combine(outPath, $"AssemblyInfo_{Path.GetFileNameWithoutExtension(asm.ShortName)}.cs"), generateAssemblyInfo(new [] {asm})); + } + }); + } + + public void WriteFilesByClass(string outPath, bool flattenHierarchy) { + usedAssemblyAttributes.Clear(); + Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes), type => { + string relPath = $"{type.Namespace}{(type.Namespace.Length > 0 ? "." : "")}{Regex.Replace(type.Name, "`[0-9]", "")}"; + writeFile(Path.Combine(outPath, flattenHierarchy ? relPath : Path.Combine(relPath.Split('.')) + ".cs"), new[] {type}); + }); + } + + public HashSet WriteFilesByClassTree(string outPath, bool separateAttributes) { + usedAssemblyAttributes.Clear(); + var usedAssemblies = new HashSet(); + + // Each thread tracks its own list of used assemblies and they are merged as each thread completes + Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes), + () => new HashSet(), + (type, _, used) => { + string relPath = Path.Combine($"{type.Namespace}{(type.Namespace.Length > 0 ? "." : "")}{Regex.Replace(type.Name, "`[0-9]", "")}".Split('.')); + if (writeFile(Path.Combine(outPath, Path.GetFileNameWithoutExtension(type.Assembly.ShortName), $"{relPath}.cs"), new[] {type}, outputAssemblyAttributes: !separateAttributes)) + used.Add(type.Assembly); + return used; + }, + usedPartition => { + lock (usedAssemblies) usedAssemblies.UnionWith(usedPartition); + } + ); + + if (separateAttributes && usedAssemblies.Any() && lastException == null) + foreach (var asm in usedAssemblies) + File.WriteAllText(Path.Combine(outPath, Path.GetFileNameWithoutExtension(asm.ShortName), "AssemblyInfo.cs"), generateAssemblyInfo(new [] {asm})); + + return usedAssemblies; + } + + // Create a Visual Studio solution + public void WriteSolution(string outPath, string unityPath, string unityAssembliesPath) { + // Required settings + MustCompile = true; + + // Output source files in tree format with separate assembly attributes + var assemblies = WriteFilesByClassTree(outPath, true); + + if (lastException != null) + return; + + // Per-project (per-assembly) solution definition and configuration + var slnProjectDefs = new StringBuilder(); + var slnProjectConfigs = new StringBuilder(); + + foreach (var asm in assemblies) { + var guid = Guid.NewGuid(); + var name = Path.GetFileNameWithoutExtension(asm.ShortName); + var csProjFile = Path.Combine(name, $"{name}.csproj"); + + var def = Resources.SlnProjectDefinition + .Replace("%PROJECTGUID%", guid.ToString()) + .Replace("%PROJECTNAME%", name) + .Replace("%CSPROJRELATIVEPATH%", csProjFile); + + slnProjectDefs.Append(def); + + var config = Resources.SlnProjectConfiguration + .Replace("%PROJECTGUID%", guid.ToString()); + + slnProjectConfigs.Append(config); + + // Determine all the assemblies on which this assembly depends + var dependencyTypes = asm.DefinedTypes.SelectMany(t => t.GetAllTypeReferences()) + .Union(asm.CustomAttributes.SelectMany(a => a.AttributeType.GetAllTypeReferences())) + .Distinct(); + var dependencyAssemblies = dependencyTypes.Select(t => t.Assembly).Distinct() + .Except(new[] {asm}); + + // Only create project references to those assemblies actually output in our solution + dependencyAssemblies = dependencyAssemblies.Intersect(assemblies); + + var referenceXml = string.Concat(dependencyAssemblies.Select( + a => $@" " + "\n" + )); + + // Create a .csproj file using the project Guid + var csProj = Resources.CsProjTemplate + .Replace("%PROJECTGUID%", guid.ToString()) + .Replace("%ASSEMBLYNAME%", name) + .Replace("%UNITYPATH%", unityPath) + .Replace("%SCRIPTASSEMBLIES%", unityAssembliesPath) + .Replace("%PROJECTREFERENCES%", referenceXml); + + File.WriteAllText(Path.Combine(outPath, csProjFile), csProj); + } + + // Merge everything into .sln file + var sln = Resources.CsSlnTemplate + .Replace("%PROJECTDEFINITIONS%", slnProjectDefs.ToString()) + .Replace("%PROJECTCONFIGURATIONS%", slnProjectConfigs.ToString()); + + var filename = Path.GetFileName(outPath); + if (filename == "") + filename = "Il2CppProject"; + File.WriteAllText(Path.Combine(outPath, $"{filename}.sln"), sln); + } + + private bool writeFile(string outFile, IEnumerable types, bool useNamespaceSyntax = true, bool outputAssemblyAttributes = true) { + + var nsRefs = new HashSet(); + var code = new StringBuilder(); + var nsContext = ""; + var usedTypes = new List(); + + // Determine all namespace references (note: this may include some that aren't actually used due to output suppression in generateType() + // We have to do this first so that we can generate properly scoped type references in the code + foreach (var type in types) { + var refs = type.GetAllTypeReferences(); + var ns = refs.Where(r => !string.IsNullOrEmpty(r.Namespace) && r.Namespace != type.Namespace).Select(r => r.Namespace); + nsRefs.UnionWith(ns); + } + + // Determine assemblies used in this file + var assemblies = types.Select(t => t.Assembly).Distinct(); + + // Add assembly attribute namespaces to reference list + if (outputAssemblyAttributes) + nsRefs.UnionWith(assemblies.SelectMany(a => a.CustomAttributes).SelectMany(a => a.GetAllTypeReferences()).Select(x => x.Namespace)); + + var results = new ConcurrentBag>(); + + // Generate each type + Parallel.ForEach(types, new ParallelOptions { MaxDegreeOfParallelism = Math.Max(Environment.ProcessorCount / 2, 1) }, + () => new Dictionary(), + (type, _, dict) => { + // Skip namespace and any children if requested + if (ExcludedNamespaces?.Any(x => x == type.Namespace || type.Namespace.StartsWith(x + ".")) ?? false) + return dict; + + // Don't output global::Locale if desired + if (MustCompile + && type.Name == "Locale" && type.Namespace == string.Empty + && type.BaseType.FullName == "System.Object" + && type.IsClass && type.IsSealed && type.IsNotPublic && !type.ContainsGenericParameters + && type.DeclaredMembers.Count == type.DeclaredMethods.Count + && type.GetMethods("GetText").Length == type.DeclaredMethods.Count) + return dict; + + // Assembly.DefinedTypes returns nested types in the assembly by design - ignore them + if (type.IsNested) + return dict; + + // Get code + var code = generateType(type, nsRefs); + if (code.Length > 0) + dict.Add(type, code); + return dict; + }, + dict => results.Add(dict)); + + // Flatten + var sortedResults = results.SelectMany(d => d).ToDictionary(i => i.Key, i => i.Value); + + // Process in order according to original sorted type list + foreach (var type in types) { + if (!sortedResults.TryGetValue(type, out var text)) + continue; + + // Determine if we need to change namespace (after we have established the code block is not empty) + if (useNamespaceSyntax) { + if (type.Namespace != nsContext) { + if (!string.IsNullOrEmpty(nsContext)) + code.Remove(code.Length - 1, 1).Append("}\n\n"); + + if (!string.IsNullOrEmpty(type.Namespace)) + code.Append("namespace " + type.Namespace + "\n{\n"); + + nsContext = type.Namespace; + } + + if (!string.IsNullOrEmpty(nsContext)) { + text.Insert(0, "\t"); + text.Replace("\n", "\n\t"); + text.Length--; + } + } + + // Append namespace + if (!useNamespaceSyntax) + code.Append($"// Namespace: {(!string.IsNullOrEmpty(type.Namespace) ? type.Namespace : "")}\n"); + + // Append type definition + code.Append(text); + code.Append("\n"); + + // Add to list of used types + usedTypes.Add(type); + } + + // Stop if nothing to output + if (!usedTypes.Any()) + return false; + + // Close namespace + if (useNamespaceSyntax && !string.IsNullOrEmpty(nsContext)) + code.Remove(code.Length - 1, 1).Append("}\n"); + + // Determine using directives (put System namespaces first) + nsRefs.Clear(); + foreach (var type in usedTypes) { + var refs = type.GetAllTypeReferences(); + var ns = refs.Where(r => !string.IsNullOrEmpty(r.Namespace) && r.Namespace != type.Namespace).Select(r => r.Namespace); + nsRefs.UnionWith(ns); + } + nsRefs.UnionWith(assemblies.SelectMany(a => a.CustomAttributes).SelectMany(a => a.GetAllTypeReferences()).Select(x => x.Namespace)); + + var usings = nsRefs.OrderBy(n => (n.StartsWith("System.") || n == "System") ? "0" + n : "1" + n); + + // Ensure output directory exists and is not a file + var dir = string.Join("_", Path.GetDirectoryName(outFile).Split(Path.GetInvalidPathChars())); + if (!string.IsNullOrEmpty(dir)) { + try { + Directory.CreateDirectory(dir); + } + catch (IOException ex) { + lastException = ex; + return false; + } + } + + // Sanitize leafname (might be class name with invalid characters) + var leafname = string.Join("_", Path.GetFileName(outFile).Split(Path.GetInvalidFileNameChars())); + + outFile = Path.Combine(dir, leafname); + + // Create output file + bool fileWritten = false; + do { + try { + using StreamWriter writer = new StreamWriter(new FileStream(outFile, FileMode.Create), Encoding.UTF8); + + // Write preamble + writer.Write(@"/* + * Generated code file by Il2CppInspector - http://www.djkaty.com - https://github.com/djkaty + */ + +"); + + // Output using directives + writer.Write(string.Concat(usings.Select(n => $"using {n};\n"))); + if (nsRefs.Any()) + writer.Write("\n"); + + // Output assembly information and attributes + writer.Write(generateAssemblyInfo(assemblies, nsRefs, outputAssemblyAttributes) + "\n\n"); + + // Output type definitions + writer.Write(code); + + fileWritten = true; + } + catch (IOException ex) { + // If we get "file is in use by another process", we are probably writing a duplicate class in another thread + // Wait a bit and try again + if ((uint) ex.HResult != 0x80070020) + throw; + + System.Threading.Thread.Sleep(100); + } + } while (!fileWritten); + + return true; + } + + private string generateAssemblyInfo(IEnumerable assemblies, IEnumerable namespaces = null, bool outputAssemblyAttributes = true) { + var text = new StringBuilder(); + + foreach (var asm in assemblies) { + text.Append($"// Image {asm.Index}: {asm.ShortName} - Assembly: {asm.FullName}"); + if (!SuppressMetadata) + text.Append($" - Types {asm.ImageDefinition.typeStart}-{asm.ImageDefinition.typeStart + asm.ImageDefinition.typeCount - 1}"); + text.AppendLine(); + + // Assembly-level attributes + if (outputAssemblyAttributes) + lock (usedAssemblyAttributesLock) { + text.Append(asm.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute) + .Except(usedAssemblyAttributes ?? new HashSet()) + .OrderBy(a => a.AttributeType.Name) + .ToString(new Scope { Current = null, Namespaces = namespaces ?? new List() }, attributePrefix: "assembly: ", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); + if (asm.CustomAttributes.Any()) + text.Append("\n"); + + usedAssemblyAttributes.UnionWith(asm.CustomAttributes); + } + } + return text.ToString().TrimEnd(); + } + + private StringBuilder generateType(TypeInfo type, IEnumerable namespaces, string prefix = "") { + // Don't output compiler-generated types if desired + if (MustCompile && type.GetCustomAttributes(CGAttribute).Any()) + return new StringBuilder(); + + var codeBlocks = new Dictionary(); + var usedMethods = new List(); + StringBuilder sb; + + var scope = new Scope { + Current = type, + Namespaces = namespaces + }; + + // Fields + sb = new StringBuilder(); + if (!type.IsEnum) { + foreach (var field in type.DeclaredFields) { + if (MustCompile && field.GetCustomAttributes(CGAttribute).Any()) + continue; + + if (field.IsNotSerialized) + sb.Append(prefix + "\t[NonSerialized]\n"); + + // Attributes + sb.Append(field.CustomAttributes.Where(a => a.AttributeType.FullName != FBAttribute).OrderBy(a => a.AttributeType.Name) + .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); + sb.Append(prefix + "\t"); + sb.Append(field.GetModifierString()); + + // Fixed buffers + if (field.GetCustomAttributes(FBAttribute).Any()) { + if (!SuppressMetadata) + sb.Append($"/* {field.GetCustomAttributes(FBAttribute)[0].VirtualAddress.ToAddressString()} */ "); + sb.Append($"{field.FieldType.DeclaredFields[0].FieldType.GetScopedCSharpName(scope)} {field.CSharpName}[0]"); // FixedElementField + } + // Regular fields + else + sb.Append($"{field.FieldType.GetScopedCSharpName(scope)} {field.CSharpName}"); + if (field.HasDefaultValue) + sb.Append($" = {field.GetDefaultValueString(scope)}"); + sb.Append(";"); + // Don't output field indices for const fields (they don't have any storage) + // or open generic types (they aren't known until runtime) + if (!field.IsLiteral && !SuppressMetadata && !type.ContainsGenericParameters) + sb.Append($" // 0x{(uint) field.Offset:X2}"); + // Output metadata file offset for const fields + if (field.IsLiteral && !SuppressMetadata) + sb.Append($" // Metadata: {field.DefaultValueMetadataAddress.ToAddressString()}"); + // For static array initializers, output metadata address and preview + if (field.HasFieldRVA && !SuppressMetadata) { + var preview = model.Package.Metadata.ReadBytes((long) field.DefaultValueMetadataAddress, 8); + var previewText = string.Join(" ", preview.Select(b => $"{b:x2}")); + + sb.Append($" // Starts with: {previewText} - Metadata: {field.DefaultValueMetadataAddress.ToAddressString()}"); + } + sb.Append("\n"); + } + codeBlocks.Add("Fields", sb); + } + + // Properties + sb = new StringBuilder(); + var hasIndexer = false; + foreach (var prop in type.DeclaredProperties) { + // Attributes + sb.Append(prop.CustomAttributes.OrderBy(a => a.AttributeType.Name) + .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); + + // The access mask enum values go from 1 (private) to 6 (public) in order from most to least restrictive + var getAccess = (prop.GetMethod?.Attributes ?? 0) & MethodAttributes.MemberAccessMask; + var setAccess = (prop.SetMethod?.Attributes ?? 0) & MethodAttributes.MemberAccessMask; + + // In case the access level of both is the same and the selected method is null, pick the other one (rare edge case) + var primary = (getAccess >= setAccess ? prop.GetMethod : prop.SetMethod) ?? prop.GetMethod ?? prop.SetMethod; + sb.Append($"{prefix}\t{primary.GetModifierString()}{prop.PropertyType.GetScopedCSharpName(scope)} "); + + // Non-indexer; non-auto-properties should have a body + var needsBody = MustCompile && !type.IsInterface && !type.IsAbstract && !prop.IsAutoProperty; + + var getBody = needsBody? " => default;" : ";"; + var setBody = needsBody? " {}" : ";"; + if ((!prop.CanRead || !prop.GetMethod.DeclaredParameters.Any()) && (!prop.CanWrite || prop.SetMethod.DeclaredParameters.Count == 1)) + sb.Append($"{prop.CSharpName} {{ "); + + // Indexer + else { + // Replace indexer name (usually "Item" but not always) with "this" - preserves explicit interface implementations + if (prop.CSharpName.IndexOf('.') != -1) + sb.Append(prop.CSharpName.Substring(0, prop.CSharpName.LastIndexOf('.') + 1)); + sb.Append("this[" + string.Join(", ", primary.DeclaredParameters.SkipLast(getAccess >= setAccess ? 0 : 1) + .Select(p => p.GetParameterString(scope, !SuppressMetadata, MustCompile))) + "] { "); + getBody = " => default;"; + setBody = " {}"; + hasIndexer = true; + } + + sb.Append((prop.CanRead? prop.GetMethod.CustomAttributes.Where(a => !MustCompile || a.AttributeType.FullName != CGAttribute) + .ToString(scope, inline: true, emitPointer: !SuppressMetadata, mustCompile: MustCompile) + + (getAccess < setAccess? prop.GetMethod.GetAccessModifierString() : "") + $"get{getBody} " : "") + // Auto-properties must have get accessors (exclude indexers) + + (MustCompile && !prop.CanRead && setBody == ";"? "get; " : "") + + (prop.CanWrite? prop.SetMethod.CustomAttributes.Where(a => !MustCompile || a.AttributeType.FullName != CGAttribute) + .ToString(scope, inline: true, emitPointer: !SuppressMetadata, mustCompile: MustCompile) + + (setAccess < getAccess? prop.SetMethod.GetAccessModifierString() : "") + $"set{setBody} " : "") + "}"); + if (!SuppressMetadata) { + if ((prop.CanRead && prop.GetMethod.VirtualAddress != null) || (prop.CanWrite && prop.SetMethod.VirtualAddress != null)) + sb.Append(" // "); + sb.Append((prop.CanRead && prop.GetMethod.VirtualAddress != null ? prop.GetMethod.VirtualAddress.ToAddressString() + " " : "") + + (prop.CanWrite && prop.SetMethod.VirtualAddress != null ? prop.SetMethod.VirtualAddress.ToAddressString() : "")); + } + sb.Append("\n"); + + usedMethods.Add(prop.GetMethod); + usedMethods.Add(prop.SetMethod); + } + codeBlocks.Add("Properties", sb); + + // Events + sb = new StringBuilder(); + foreach (var evt in type.DeclaredEvents) { + // Attributes + sb.Append(evt.CustomAttributes.OrderBy(a => a.AttributeType.Name) + .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); + + string modifiers = evt.AddMethod?.GetModifierString(); + sb.Append($"{prefix}\t{modifiers}event {evt.EventHandlerType.GetScopedCSharpName(scope)} {evt.CSharpName}"); + + if (!MustCompile) { + sb.Append(" {\n"); + var m = new Dictionary(); + if (evt.AddMethod != null) m.Add("add", evt.AddMethod.VirtualAddress); + if (evt.RemoveMethod != null) m.Add("remove", evt.RemoveMethod.VirtualAddress); + if (evt.RaiseMethod != null) m.Add("raise", evt.RaiseMethod.VirtualAddress); + sb.Append(string.Join("\n", m.Select(x => $"{prefix}\t\t{x.Key};{(SuppressMetadata? "" : " // " + x.Value.ToAddressString())}")) + "\n" + prefix + "\t}\n"); + } else + sb.Append(";\n"); + + usedMethods.Add(evt.AddMethod); + usedMethods.Add(evt.RemoveMethod); + usedMethods.Add(evt.RaiseMethod); + } + codeBlocks.Add("Events", sb); + + // Nested types + codeBlocks.Add("Nested types", new StringBuilder().AppendJoin("\n", type.DeclaredNestedTypes + .Select(n => generateType(n, namespaces, prefix + "\t")).Where(c => c.Length > 0))); + + // Constructors + sb = new StringBuilder(); + var fields = type.DeclaredFields.Where(f => !f.GetCustomAttributes(CGAttribute).Any()); + + // Crete a parameterless constructor for every relevant type when making code that compiles to mitigate CS1729 and CS7036 + if (MustCompile && !type.IsInterface && !(type.IsAbstract && type.IsSealed) && !type.IsValueType + && type.DeclaredConstructors.All(c => c.IsStatic || c.DeclaredParameters.Any())) + sb.Append($"{prefix}\t{(type.IsAbstract? "protected" : "public")} {type.CSharpBaseName}() {{}} // Dummy constructor\n"); + + foreach (var method in type.DeclaredConstructors) { + // Attributes + sb.Append(method.CustomAttributes.OrderBy(a => a.AttributeType.Name) + .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); + + sb.Append($"{prefix}\t{method.GetModifierString()}{method.DeclaringType.CSharpBaseName}{method.GetTypeParametersString(scope)}"); + sb.Append($"({method.GetParametersString(scope, !SuppressMetadata)})"); + + if (MustCompile) { + // Class constructor + if (method.IsAbstract) + sb.Append(";"); + else if (!type.IsValueType) + sb.Append(" {}"); + + // Struct constructor + else { + // Parameterized struct constructors must call the parameterless constructor to create the object + // if the object has any auto-implemented properties + if (type.DeclaredProperties.Any() && method.DeclaredParameters.Any()) + sb.Append(" : this()"); + + // Struct construvctors must initialize all fields in the struct + if (fields.Any()) { + var paramNames = method.DeclaredParameters.Select(p => p.Name); + sb.Append(" {\n" + string.Join("\n", fields + .Where(f => !f.IsLiteral && f.IsStatic == method.IsStatic) + .Select(f => $"{prefix}\t\t{(paramNames.Contains(f.Name) ? "this." : "")}{f.Name} = default;")) + + $"\n{prefix}\t}}"); + } else + sb.Append(" {}"); + } + } else + sb.Append(";"); + + sb.Append((!SuppressMetadata && method.VirtualAddress != null ? $" // {method.VirtualAddress.ToAddressString()}" : "") + "\n"); + } + codeBlocks.Add("Constructors", sb); + + // Methods + // Don't re-output methods for constructors, properties, events etc. + var methods = type.DeclaredMethods.Except(usedMethods).Where(m => m.CustomAttributes.All(a => a.AttributeType.FullName != ExtAttribute)); + codeBlocks.Add("Methods", methods + .Select(m => generateMethod(m, scope, prefix)) + .Aggregate(new StringBuilder(), (r, i) => r.Append(i))); + + usedMethods.AddRange(methods); + + // Extension methods + codeBlocks.Add("Extension methods", type.DeclaredMethods.Except(usedMethods) + .Select(m => generateMethod(m, scope, prefix)) + .Aggregate(new StringBuilder(), (r, i) => r.Append(i))); + + // Type declaration + sb = new StringBuilder(); + + if (type.IsImport) + sb.Append(prefix + "[ComImport]\n"); + if (type.IsSerializable) + sb.Append(prefix + "[Serializable]\n"); + + // DefaultMemberAttribute should be output if it is present and the type does not have an indexer, otherwise suppressed + // See https://docs.microsoft.com/en-us/dotnet/api/system.reflection.defaultmemberattribute?view=netframework-4.8 + sb.Append(type.CustomAttributes.Where(a => (a.AttributeType.FullName != DMAttribute || !hasIndexer) && a.AttributeType.FullName != ExtAttribute) + .OrderBy(a => a.AttributeType.Name).ToString(scope, prefix, emitPointer: !SuppressMetadata, mustCompile: MustCompile)); + + // Roll-up multicast delegates to use the 'delegate' syntactic sugar + if (type.IsClass && type.IsSealed && type.BaseType?.FullName == "System.MulticastDelegate") { + sb.Append(prefix + type.GetAccessModifierString()); + + var del = type.GetMethod("Invoke"); + // IL2CPP doesn't seem to retain return type attributes + //sb.Append(del.ReturnType.CustomAttributes.ToString(prefix, "return: ", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); + if (del.RequiresUnsafeContext) + sb.Append("unsafe "); + sb.Append($"delegate {del.ReturnType.GetScopedCSharpName(scope)} {type.GetCSharpTypeDeclarationName()}("); + sb.Append(del.GetParametersString(scope, !SuppressMetadata) + ");"); + if (!SuppressMetadata) + sb.Append($" // TypeDefIndex: {type.Index}; {del.VirtualAddress.ToAddressString()}"); + sb.Append("\n"); + return sb; + } + + sb.Append(prefix + type.GetModifierString()); + + var @base = type.NonInheritedInterfaces.Select(x => x.GetScopedCSharpName(scope, isPartOfTypeDeclaration: true)).ToList(); + if (type.BaseType != null && type.BaseType.FullName != "System.Object" && type.BaseType.FullName != "System.ValueType" && !type.IsEnum) + @base.Insert(0, type.BaseType.GetScopedCSharpName(scope, isPartOfTypeDeclaration: true)); + if (type.IsEnum && type.GetEnumUnderlyingType().FullName != "System.Int32") // enums derive from int by default + @base.Insert(0, type.GetEnumUnderlyingType().GetScopedCSharpName(scope)); + var baseText = @base.Count > 0 ? " : " + string.Join(", ", @base) : string.Empty; + + sb.Append($"{type.GetCSharpTypeDeclarationName()}{baseText}"); + if (!SuppressMetadata) + sb.Append($" // TypeDefIndex: {type.Index}"); + sb.Append("\n"); + + foreach (var gp in type.GetGenericArguments()) { + var constraint = gp.GetTypeConstraintsString(scope); + if (constraint != string.Empty) + sb.Append($"{prefix}\t{constraint}\n"); + } + + sb.Append(prefix + "{\n"); + + // Enumeration + if (type.IsEnum) { + sb.AppendJoin(",\n", type.GetEnumNames().Zip(type.GetEnumValues().OfType(), + (k, v) => new { k, v }).OrderBy(x => x.v).Select(x => $"{prefix}\t{x.k} = {x.v}")); + sb.Append("\n"); + } + + // Type definition + else + sb.AppendJoin("\n", codeBlocks.Where(b => b.Value.Length > 0).Select(b => prefix + "\t// " + b.Key + "\n" + b.Value)); + + sb.Append(prefix + "}\n"); + return sb; + } + + private StringBuilder generateMethod(MethodInfo method, Scope scope, string prefix) { + var writer = new StringBuilder(); + + if (MustCompile && method.GetCustomAttributes(CGAttribute).Any()) + return writer; + + // Attributes + writer.Append(method.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute && a.AttributeType.FullName != AsyncAttribute) + .OrderBy(a => a.AttributeType.Name) + .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); + + // IL2CPP doesn't seem to retain return type attributes + //writer.Append(method.ReturnType.CustomAttributes.ToString(prefix + "\t", "return: ", emitPointer: !SuppressMetadata)); + writer.Append($"{prefix}\t{method.GetModifierString()}"); + + // Finalizers become destructors + if (method.Name == "Finalize" && method.IsVirtual && method.ReturnType.FullName == "System.Void" && method.IsFamily) + writer.Append("~" + method.DeclaringType.CSharpBaseName); + + // Regular method or operator overload + else if (method.Name != "op_Implicit" && method.Name != "op_Explicit") + writer.Append($"{method.ReturnParameter.GetReturnParameterString(scope)} {method.CSharpName}{method.GetTypeParametersString(scope)}"); + + // User-defined conversion operator + else + writer.Append($"{method.CSharpName}{method.ReturnType.GetScopedCSharpName(scope)}"); + + // Parameters + writer.Append("(" + method.GetParametersString(scope, !SuppressMetadata) + ")"); + + // Generic type constraints + foreach (var gp in method.GetGenericArguments()) { + var constraint = gp.GetTypeConstraintsString(scope); + if (constraint != string.Empty) + writer.Append($"\n{prefix}\t\t{constraint}"); + } + + // Body + var methodBody = MustCompile? method switch { + // Abstract method + { IsAbstract: true } => ";", + + // Extern method + { Attributes: var a } when (a & MethodAttributes.PinvokeImpl) == MethodAttributes.PinvokeImpl => ";", + + // Method with out parameters + { DeclaredParameters: var d } when d.Any(p => p.IsOut) => + " {\n" + string.Join("\n", d.Where(p => p.IsOut).Select(p => $"{prefix}\t\t{p.Name} = default;")) + + (method.ReturnType.FullName != "System.Void"? $"\n{prefix}\t\treturn default;" : "") + + $"\n{prefix}\t}}", + + // No return type + { ReturnType: var retType } when retType.FullName == "System.Void" => " {}", + + // Ref return type + { ReturnType: var retType } when retType.IsByRef => " => ref _refReturnTypeFor" + method.CSharpName + ";", + + // Regular return type + _ => " => default;" + } + + // Only make a method body if we are trying to compile the output + : ";"; + + writer.Append(methodBody + (!SuppressMetadata && method.VirtualAddress != null ? $" // {method.VirtualAddress.ToAddressString()}" : "") + "\n"); + + // Ref return type requires us to invent a field + if (MustCompile && method.ReturnType.IsByRef) + writer.Append($"{prefix}\tprivate {method.ReturnType.GetScopedCSharpName(scope)} _refReturnTypeFor{method.CSharpName}; // Dummy field\n"); + + return writer; + } + } +} diff --git a/Il2CppInspector.Common/Reflection/CustomAttributeData.cs b/Il2CppInspector.Common/Reflection/CustomAttributeData.cs index 135ef31..42f0d49 100644 --- a/Il2CppInspector.Common/Reflection/CustomAttributeData.cs +++ b/Il2CppInspector.Common/Reflection/CustomAttributeData.cs @@ -1,89 +1,158 @@ -/* - Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty - - All rights reserved. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Il2CppInspector.Reflection -{ - // See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.customattributedata?view=netframework-4.8 - public class CustomAttributeData - { - // IL2CPP-specific data - public TypeModel Model => AttributeType.Assembly.Model; - public int Index { get; set; } - - // The type of the attribute - public TypeInfo AttributeType { get; set; } - - public (ulong Start, ulong End) VirtualAddress => - // The last one will be wrong but there is no way to calculate it - (Model.Package.CustomAttributeGenerators[Index], Model.Package.FunctionAddresses[Model.Package.CustomAttributeGenerators[Index]]); - - // C++ method names - // TODO: Known issue here where we should be using CppDeclarationGenerator.TypeNamer to ensure uniqueness - public string Name => $"{AttributeType.Name.ToCIdentifier()}_CustomAttributesCacheGenerator"; - - // C++ method signature - public string Signature => $"void {Name}(CustomAttributesCache *)"; - - public override string ToString() => "[" + AttributeType.FullName + "]"; - - // Get the machine code of the C++ function - public byte[] GetMethodBody() => Model.Package.BinaryImage.ReadMappedBytes(VirtualAddress.Start, (int) (VirtualAddress.End - VirtualAddress.Start)); - - // Get all the custom attributes for a given assembly, type, member or parameter - private static IEnumerable getCustomAttributes(Assembly asm, int customAttributeIndex) { - if (customAttributeIndex < 0) - yield break; - - var pkg = asm.Model.Package; - - // Attribute type ranges weren't included before v21 (customASttributeGenerators was though) - if (pkg.Version < 21) - yield break; - - if (pkg.Version < 29) - { - var range = pkg.AttributeTypeRanges[customAttributeIndex]; - for (var i = range.start; i < range.start + range.count; i++) - { - var typeIndex = pkg.AttributeTypeIndices[i]; - - if (asm.Model.AttributesByIndices.TryGetValue(i, out var attribute)) - { - yield return attribute; - continue; - } - - attribute = new CustomAttributeData { Index = customAttributeIndex, AttributeType = asm.Model.TypesByReferenceIndex[typeIndex] }; - - asm.Model.AttributesByIndices.TryAdd(i, attribute); - yield return attribute; - } - } - else - { - Console.WriteLine("Skipping custom attributes for 29+"); - yield break; - } - } - - private static IList getCustomAttributes(Assembly asm, int token, int customAttributeIndex) => - getCustomAttributes(asm, asm.Model.GetCustomAttributeIndex(asm, token, customAttributeIndex)).ToList(); - - public static IList GetCustomAttributes(Assembly asm) => getCustomAttributes(asm, asm.MetadataToken, asm.AssemblyDefinition.customAttributeIndex); - public static IList GetCustomAttributes(EventInfo evt) => getCustomAttributes(evt.Assembly, evt.MetadataToken, evt.Definition.customAttributeIndex); - public static IList GetCustomAttributes(FieldInfo field) => getCustomAttributes(field.Assembly, field.MetadataToken, field.Definition.customAttributeIndex); - public static IList GetCustomAttributes(MethodBase method) => getCustomAttributes(method.Assembly, method.MetadataToken, method.Definition.customAttributeIndex); - public static IList GetCustomAttributes(ParameterInfo param) => getCustomAttributes(param.DeclaringMethod.Assembly, param.MetadataToken, param.Definition.customAttributeIndex); - public static IList GetCustomAttributes(PropertyInfo prop) - => prop.Definition != null ? getCustomAttributes(prop.Assembly, prop.MetadataToken, prop.Definition.customAttributeIndex) : new List(); - public static IList GetCustomAttributes(TypeInfo type) => type.Definition != null? getCustomAttributes(type.Assembly, type.MetadataToken, type.Definition.customAttributeIndex) : new List(); - } -} +/* + Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + + All rights reserved. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Il2CppInspector.Reflection +{ + // See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.customattributedata?view=netframework-4.8 + public class CustomAttributeData + { + // IL2CPP-specific data + public TypeModel Model => AttributeType.Assembly.Model; + public int Index { get; set; } + + // The type of the attribute + public TypeInfo AttributeType { get; set; } + + // v29 custom attribute info + public CustomAttributeCtor CtorInfo { get; set; } + + // Pre-v29 Properties used for stub Attributes + + public (ulong Start, ulong End) VirtualAddress => CtorInfo != null ? (0, 0) : + // The last one will be wrong but there is no way to calculate it + (Model.Package.CustomAttributeGenerators[Index], Model.Package.FunctionAddresses[Model.Package.CustomAttributeGenerators[Index]]); + + // C++ method names + // TODO: Known issue here where we should be using CppDeclarationGenerator.TypeNamer to ensure uniqueness + public string Name => $"{AttributeType.Name.ToCIdentifier()}_CustomAttributesCacheGenerator"; + + // C++ method signature + public string Signature => $"void {Name}(CustomAttributesCache *)"; + + public override string ToString() => "[" + AttributeType.FullName + "]"; + + // Get the machine code of the C++ function + public byte[] GetMethodBody() => Model.Package.BinaryImage.ReadMappedBytes(VirtualAddress.Start, (int) (VirtualAddress.End - VirtualAddress.Start)); + + public IEnumerable GetAllTypeReferences() + { + yield return AttributeType; + + if (CtorInfo != null) + { + foreach (var typeRef in GetTypeReferences(CtorInfo.Arguments)) + yield return typeRef; + + foreach (var typeRef in GetTypeReferences(CtorInfo.Fields)) + yield return typeRef; + + foreach (var typeRef in GetTypeReferences(CtorInfo.Properties)) + yield return typeRef; + } + + yield break; + + static IEnumerable GetTypeReferences(IEnumerable arguments) + { + foreach (var arg in arguments) + { + yield return arg.Type; + + switch (arg.Value) + { + case TypeInfo info: + yield return info; + break; + case CustomAttributeArgument[] array: + foreach (var info in GetTypeReferences(array)) + yield return info; + break; + case TypeInfo[] infos: + foreach (var info in infos) + yield return info; + break; + } + } + } + } + + // Get all the custom attributes for a given assembly, type, member or parameter + private static IEnumerable getCustomAttributes(Assembly asm, int customAttributeIndex) { + if (customAttributeIndex < 0) + yield break; + + var pkg = asm.Model.Package; + + // Attribute type ranges weren't included before v21 (customASttributeGenerators was though) + if (pkg.Version < 21) + yield break; + + if (pkg.Version < 29) + { + var range = pkg.AttributeTypeRanges[customAttributeIndex]; + for (var i = range.start; i < range.start + range.count; i++) + { + var typeIndex = pkg.AttributeTypeIndices[i]; + + if (asm.Model.AttributesByIndices.TryGetValue(i, out var attribute)) + { + yield return attribute; + continue; + } + + attribute = new CustomAttributeData { Index = customAttributeIndex, AttributeType = asm.Model.TypesByReferenceIndex[typeIndex] }; + + asm.Model.AttributesByIndices.TryAdd(i, attribute); + yield return attribute; + } + } + else + { + if (!asm.Model.AttributesByDataIndices.TryGetValue(customAttributeIndex, out var attributes)) + { + var range = pkg.Metadata.AttributeDataRanges[customAttributeIndex]; + var next = pkg.Metadata.AttributeDataRanges[customAttributeIndex + 1]; + + var startOffset = pkg.Metadata.Header.attributeDataOffset + range.startOffset; + var endOffset = pkg.Metadata.Header.attributeDataOffset + next.startOffset; + + var reader = new CustomAttributeDataReader(pkg, asm, pkg.Metadata, startOffset, endOffset); + if (reader.Count == 0) + yield break; + + attributes = reader.Read().Select((x, i) => new CustomAttributeData + { + AttributeType = x.Ctor.DeclaringType, + CtorInfo = x, + Index = i, + }).ToList(); + + asm.Model.AttributesByDataIndices[customAttributeIndex] = attributes; + } + + foreach (var attribute in attributes) + yield return attribute; + } + } + + private static IList getCustomAttributes(Assembly asm, int token, int customAttributeIndex) => + getCustomAttributes(asm, asm.Model.GetCustomAttributeIndex(asm, token, customAttributeIndex)).ToList(); + + public static IList GetCustomAttributes(Assembly asm) => getCustomAttributes(asm, asm.MetadataToken, asm.AssemblyDefinition.customAttributeIndex); + public static IList GetCustomAttributes(EventInfo evt) => getCustomAttributes(evt.Assembly, evt.MetadataToken, evt.Definition.customAttributeIndex); + public static IList GetCustomAttributes(FieldInfo field) => getCustomAttributes(field.Assembly, field.MetadataToken, field.Definition.customAttributeIndex); + public static IList GetCustomAttributes(MethodBase method) => getCustomAttributes(method.Assembly, method.MetadataToken, method.Definition.customAttributeIndex); + public static IList GetCustomAttributes(ParameterInfo param) => getCustomAttributes(param.DeclaringMethod.Assembly, param.MetadataToken, param.Definition.customAttributeIndex); + public static IList GetCustomAttributes(PropertyInfo prop) + => prop.Definition != null ? getCustomAttributes(prop.Assembly, prop.MetadataToken, prop.Definition.customAttributeIndex) : new List(); + public static IList GetCustomAttributes(TypeInfo type) => type.Definition != null? getCustomAttributes(type.Assembly, type.MetadataToken, type.Definition.customAttributeIndex) : new List(); + } +} diff --git a/Il2CppInspector.Common/Reflection/Extensions.cs b/Il2CppInspector.Common/Reflection/Extensions.cs index 5eefedd..0590eb6 100644 --- a/Il2CppInspector.Common/Reflection/Extensions.cs +++ b/Il2CppInspector.Common/Reflection/Extensions.cs @@ -6,7 +6,6 @@ using System; using System.Collections.Generic; -using System.Data; using System.Globalization; using System.Linq; using System.Text; @@ -22,29 +21,90 @@ namespace Il2CppInspector.Reflection var sb = new StringBuilder(); foreach (var cad in attributes) { - // Find a constructor that either has no parameters, or all optional parameters - var parameterlessConstructor = cad.AttributeType.DeclaredConstructors.Any(c => !c.IsStatic && c.IsPublic && c.DeclaredParameters.All(p => p.IsOptional)); + if (cad.CtorInfo != null) + { + // v29+ attribute handling + // We now have much more information, so we can reconstruct the actual attribute + var ctor = cad.CtorInfo; - // IL2CPP doesn't retain attribute arguments so we have to comment out those with non-optional arguments if we want the output to compile - var commentStart = mustCompile && !parameterlessConstructor? inline? "/* " : "// " : ""; - var commentEnd = commentStart.Length > 0 && inline? " */" : ""; - var arguments = ""; + var name = ctor.Ctor.DeclaringType.GetScopedCSharpName(scope); + var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal); + if (suffix != -1) + name = name[..suffix]; - // Set AttributeUsage(AttributeTargets.All) if making output that compiles to mitigate CS0592 - if (mustCompile && cad.AttributeType.FullName == "System.AttributeUsageAttribute") { - commentStart = ""; - commentEnd = ""; - arguments = "(AttributeTargets.All)"; + sb.Append(linePrefix); + sb.Append('['); + sb.Append(attributePrefix); + sb.Append(name); + + var totalCount = ctor.Arguments.Length + ctor.Fields.Length + ctor.Properties.Length; + + if (totalCount > 0) + { + // We have parameters, need to use brackets + sb.Append('('); + + var totalIndex = 0; + foreach (var argument in ctor.Arguments) + { + sb.Append(argument.Value.ToCSharpValue(argument.Type, scope)); + if (++totalIndex != totalCount) + sb.Append(", "); + } + + foreach (var field in ctor.Fields) + { + sb.Append(field.Field.CSharpName); + sb.Append(" = "); + sb.Append(field.Value.ToCSharpValue(field.Type, scope)); + if (++totalIndex != totalCount) + sb.Append(", "); + } + + foreach (var property in ctor.Properties) + { + sb.Append(property.Property.CSharpName); + sb.Append(" = "); + sb.Append(property.Value.ToCSharpValue(property.Type, scope)); + if (++totalIndex != totalCount) + sb.Append(", "); + } + + sb.Append(')'); + } + + sb.Append(']'); + sb.Append(inline ? " " : "\n"); } + else + { + // Pre-v29 attribute handling - var name = cad.AttributeType.GetScopedCSharpName(scope); - var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal); - if (suffix != -1) - name = name[..suffix]; - sb.Append($"{linePrefix}{commentStart}[{attributePrefix}{name}{arguments}]{commentEnd}"); - if (emitPointer) - sb.Append($" {(inline? "/*" : "//")} {cad.VirtualAddress.ToAddressString()}{(inline? " */" : "")}"); - sb.Append(inline? " ":"\n"); + // Find a constructor that either has no parameters, or all optional parameters + var parameterlessConstructor = cad.AttributeType.DeclaredConstructors.Any(c => !c.IsStatic && c.IsPublic && c.DeclaredParameters.All(p => p.IsOptional)); + + // IL2CPP doesn't retain attribute arguments so we have to comment out those with non-optional arguments if we want the output to compile + var commentStart = mustCompile && !parameterlessConstructor ? inline ? "/* " : "// " : ""; + var commentEnd = commentStart.Length > 0 && inline ? " */" : ""; + var arguments = ""; + + // Set AttributeUsage(AttributeTargets.All) if making output that compiles to mitigate CS0592 + if (mustCompile && cad.AttributeType.FullName == "System.AttributeUsageAttribute") + { + commentStart = ""; + commentEnd = ""; + arguments = "(AttributeTargets.All)"; + } + + var name = cad.AttributeType.GetScopedCSharpName(scope); + var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal); + if (suffix != -1) + name = name[..suffix]; + sb.Append($"{linePrefix}{commentStart}[{attributePrefix}{name}{arguments}]{commentEnd}"); + if (emitPointer) + sb.Append($" {(inline ? "/*" : "//")} {cad.VirtualAddress.ToAddressString()}{(inline ? " */" : "")}"); + sb.Append(inline ? " " : "\n"); + } } return sb.ToString(); @@ -120,31 +180,60 @@ namespace Il2CppInspector.Reflection // Output a value in C#-friendly syntax public static string ToCSharpValue(this object value, TypeInfo type, Scope usingScope = null) { - if (value is bool) - return (bool) value ? "true" : "false"; - if (value is float f) - return value switch { - float.PositiveInfinity => "1F / 0F", - float.NegativeInfinity => "-1F / 0F", - float.NaN => "0F / 0F", - _ => f.ToString(CultureInfo.InvariantCulture) + "f" - }; - if (value is double d) - return value switch { - double.PositiveInfinity => "1D / 0D", - double.NegativeInfinity => "-1D / 0D", - double.NaN => "0D / 0D", - _ => d.ToString(CultureInfo.InvariantCulture) - }; - if (value is string str) { - return $"\"{str.ToEscapedString()}\""; - } - if (value is char) { - var cValue = (int) (char) value; - if (cValue < 32 || cValue > 126) - return $"'\\x{cValue:x4}'"; - return $"'{value}'"; + switch (value) + { + case bool b: + return b ? "true" : "false"; + case float f: + return value switch { + float.PositiveInfinity => "1F / 0F", + float.NegativeInfinity => "-1F / 0F", + float.NaN => "0F / 0F", + _ => f.ToString(CultureInfo.InvariantCulture) + "f" + }; + case double d: + return value switch { + double.PositiveInfinity => "1D / 0D", + double.NegativeInfinity => "-1D / 0D", + double.NaN => "0D / 0D", + _ => d.ToString(CultureInfo.InvariantCulture) + }; + case string str: + return $"\"{str.ToEscapedString()}\""; + case char c: + { + var cValue = (int) c; + if (cValue < 32 || cValue > 126) + return $"'\\x{cValue:x4}'"; + return $"'{value}'"; + } + case TypeInfo typeInfo: + return $"typeof({typeInfo.GetScopedCSharpName(usingScope)})"; + case object[] array: + var arraySb = new StringBuilder(); + arraySb.Append("new "); + arraySb.Append(type.GetScopedCSharpName(usingScope)); + arraySb.Append('['); + arraySb.Append(array.Length); + arraySb.Append(']'); + + if (array.Length > 0) + { + arraySb.Append(" {"); + for (int i = 0; i < array.Length; i++) + { + if (array[i] is CustomAttributeArgument arrayArgument) // Used for array with different entries, see BlobReader for more info + arraySb.Append(arrayArgument.Value.ToCSharpValue(arrayArgument.Type, usingScope)); + + if (i + 1 != array.Length) + arraySb.Append(", "); + } + arraySb.Append(" }"); + } + + return arraySb.ToString(); } + if (type.IsEnum) { var flags = type.GetCustomAttributes("System.FlagsAttribute").Any(); var values = type.GetEnumNames().Zip(type.GetEnumValues().OfType(), (k, v) => new {k, v}).ToDictionary(x => x.k, x => x.v); diff --git a/Il2CppInspector.Common/Reflection/FieldInfo.cs b/Il2CppInspector.Common/Reflection/FieldInfo.cs index 3a37411..d4cc2d2 100644 --- a/Il2CppInspector.Common/Reflection/FieldInfo.cs +++ b/Il2CppInspector.Common/Reflection/FieldInfo.cs @@ -1,158 +1,158 @@ -/* - Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty - - All rights reserved. -*/ - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; - -namespace Il2CppInspector.Reflection { - public class FieldInfo : MemberInfo - { - // IL2CPP-specific data - public Il2CppFieldDefinition Definition { get; } - public int Index { get; } - // Root definition: the field with Definition != null - protected readonly FieldInfo rootDefinition; - - // Offsets for reference types start at 0x8 or 0x10 due to Il2CppObject "header" containing 2 pointers - // Value types don't have this header but the offsets are still stored as starting at 0x8 or 0x10, so we have to subtract this - // Open generic types have offsets that aren't known until runtime - private readonly long rawOffset; - public long Offset => DeclaringType.ContainsGenericParameters? 0 : - rawOffset - (DeclaringType.IsValueType && !IsStatic? (Assembly.Model.Package.BinaryImage.Bits / 8) * 2 : 0); - - public bool HasFieldRVA => (Attributes & FieldAttributes.HasFieldRVA) != 0; - public ulong DefaultValueMetadataAddress { get; } - - // Custom attributes for this member - public override IEnumerable CustomAttributes => CustomAttributeData.GetCustomAttributes(rootDefinition); - - public bool HasDefaultValue => (Attributes & FieldAttributes.HasDefault) != 0; - public object DefaultValue { get; } - - public string GetDefaultValueString(Scope usingScope = null) => HasDefaultValue ? DefaultValue.ToCSharpValue(FieldType, usingScope) : ""; - - // Information/flags about the field - public FieldAttributes Attributes { get; } - - // Type of field - private readonly TypeRef fieldTypeReference; - public TypeInfo FieldType => fieldTypeReference.Value; - - // For the Is* definitions below, see: - // https://docs.microsoft.com/en-us/dotnet/api/system.reflection.fieldinfo.isfamilyandassembly?view=netframework-4.7.1#System_Reflection_FieldInfo_IsFamilyAndAssembly - - // True if the field is declared as internal - public bool IsAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Assembly; - - // True if the field is declared as protected - public bool IsFamily => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Family; - - // True if the field is declared as 'protected private' (always false) - public bool IsFamilyAndAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamANDAssem; - - // True if the field is declared as protected public - public bool IsFamilyOrAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamORAssem; - - // True if the field is declared as readonly - public bool IsInitOnly => (Attributes & FieldAttributes.InitOnly) == FieldAttributes.InitOnly; - - // True if the field is const - public bool IsLiteral => (Attributes & FieldAttributes.Literal) == FieldAttributes.Literal; - - // True if the field has the NonSerialized attribute - public bool IsNotSerialized => (Attributes & FieldAttributes.NotSerialized) == FieldAttributes.NotSerialized; - - // True if the field is extern - public bool IsPinvokeImpl => (Attributes & FieldAttributes.PinvokeImpl) == FieldAttributes.PinvokeImpl; - - // True if the field is declared a private - public bool IsPrivate => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Private; - - // True if the field is declared as public - public bool IsPublic => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Public; - - // True if the field has a special name - public bool IsSpecialName => (Attributes & FieldAttributes.SpecialName) == FieldAttributes.SpecialName; - - // True if the field is declared as static - public bool IsStatic => (Attributes & FieldAttributes.Static) == FieldAttributes.Static; - - // Returns true if using this field requires that the using method is declared as unsafe - public bool RequiresUnsafeContext => FieldType.RequiresUnsafeContext || GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any(); - - public override MemberTypes MemberType => MemberTypes.Field; - - public FieldInfo(Il2CppInspector pkg, int fieldIndex, TypeInfo declaringType) : - base(declaringType) { - Definition = pkg.Fields[fieldIndex]; - MetadataToken = (int) Definition.token; - Index = fieldIndex; - Name = pkg.Strings[Definition.nameIndex]; - - rawOffset = pkg.FieldOffsets[fieldIndex]; - - rootDefinition = this; - - fieldTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, Definition.typeIndex); - var fieldType = pkg.TypeReferences[Definition.typeIndex]; - - // Copy attributes - Attributes = (FieldAttributes) fieldType.attrs; - - // Default initialization value if present - if (pkg.FieldDefaultValue.TryGetValue(fieldIndex, out (ulong address, object variant) value)) { - DefaultValue = value.variant; - DefaultValueMetadataAddress = value.address; - } - } - - public FieldInfo(FieldInfo fieldDef, TypeInfo declaringType) : base(declaringType) { - if (fieldDef.Definition == null) - throw new ArgumentException("Argument must be a bare field definition"); - - rootDefinition = fieldDef; - - Name = fieldDef.Name; - Attributes = fieldDef.Attributes; - fieldTypeReference = TypeRef.FromTypeInfo(fieldDef.FieldType.SubstituteGenericArguments(declaringType.GetGenericArguments())); - - DefaultValue = fieldDef.DefaultValue; - DefaultValueMetadataAddress = fieldDef.DefaultValueMetadataAddress; - } - - public string GetAccessModifierString() => this switch { - { IsPrivate: true } => "private ", - { IsPublic: true } => "public ", - { IsFamily: true } => "protected ", - { IsAssembly: true } => "internal ", - { IsFamilyOrAssembly: true } => "protected internal ", - { IsFamilyAndAssembly: true } => "private protected ", - _ => "" - }; - - public string GetModifierString() { - var modifiers = new StringBuilder(GetAccessModifierString()); - - if (IsLiteral) - modifiers.Append("const "); - // All const fields are also static by implication - else if (IsStatic) - modifiers.Append("static "); - if (IsInitOnly) - modifiers.Append("readonly "); - if (RequiresUnsafeContext) - modifiers.Append("unsafe "); - if (IsPinvokeImpl) - modifiers.Append("extern "); - if (GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any()) - modifiers.Append("fixed "); - return modifiers.ToString(); - } - } +/* + Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + + All rights reserved. +*/ + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Il2CppInspector.Reflection { + public class FieldInfo : MemberInfo // L-TODO: Add support for [ThreadLocal] fields + { + // IL2CPP-specific data + public Il2CppFieldDefinition Definition { get; } + public int Index { get; } + // Root definition: the field with Definition != null + protected readonly FieldInfo rootDefinition; + + // Offsets for reference types start at 0x8 or 0x10 due to Il2CppObject "header" containing 2 pointers + // Value types don't have this header but the offsets are still stored as starting at 0x8 or 0x10, so we have to subtract this + // Open generic types have offsets that aren't known until runtime + private readonly long rawOffset; + public long Offset => DeclaringType.ContainsGenericParameters? 0 : + rawOffset - (DeclaringType.IsValueType && !IsStatic? (Assembly.Model.Package.BinaryImage.Bits / 8) * 2 : 0); + + public bool HasFieldRVA => (Attributes & FieldAttributes.HasFieldRVA) != 0; + public ulong DefaultValueMetadataAddress { get; } + + // Custom attributes for this member + public override IEnumerable CustomAttributes => CustomAttributeData.GetCustomAttributes(rootDefinition); + + public bool HasDefaultValue => (Attributes & FieldAttributes.HasDefault) != 0; + public object DefaultValue { get; } + + public string GetDefaultValueString(Scope usingScope = null) => HasDefaultValue ? DefaultValue.ToCSharpValue(FieldType, usingScope) : ""; + + // Information/flags about the field + public FieldAttributes Attributes { get; } + + // Type of field + private readonly TypeRef fieldTypeReference; + public TypeInfo FieldType => fieldTypeReference.Value; + + // For the Is* definitions below, see: + // https://docs.microsoft.com/en-us/dotnet/api/system.reflection.fieldinfo.isfamilyandassembly?view=netframework-4.7.1#System_Reflection_FieldInfo_IsFamilyAndAssembly + + // True if the field is declared as internal + public bool IsAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Assembly; + + // True if the field is declared as protected + public bool IsFamily => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Family; + + // True if the field is declared as 'protected private' (always false) + public bool IsFamilyAndAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamANDAssem; + + // True if the field is declared as protected public + public bool IsFamilyOrAssembly => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.FamORAssem; + + // True if the field is declared as readonly + public bool IsInitOnly => (Attributes & FieldAttributes.InitOnly) == FieldAttributes.InitOnly; + + // True if the field is const + public bool IsLiteral => (Attributes & FieldAttributes.Literal) == FieldAttributes.Literal; + + // True if the field has the NonSerialized attribute + public bool IsNotSerialized => (Attributes & FieldAttributes.NotSerialized) == FieldAttributes.NotSerialized; + + // True if the field is extern + public bool IsPinvokeImpl => (Attributes & FieldAttributes.PinvokeImpl) == FieldAttributes.PinvokeImpl; + + // True if the field is declared a private + public bool IsPrivate => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Private; + + // True if the field is declared as public + public bool IsPublic => (Attributes & FieldAttributes.FieldAccessMask) == FieldAttributes.Public; + + // True if the field has a special name + public bool IsSpecialName => (Attributes & FieldAttributes.SpecialName) == FieldAttributes.SpecialName; + + // True if the field is declared as static + public bool IsStatic => (Attributes & FieldAttributes.Static) == FieldAttributes.Static; + + // Returns true if using this field requires that the using method is declared as unsafe + public bool RequiresUnsafeContext => FieldType.RequiresUnsafeContext || GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any(); + + public override MemberTypes MemberType => MemberTypes.Field; + + public FieldInfo(Il2CppInspector pkg, int fieldIndex, TypeInfo declaringType) : + base(declaringType) { + Definition = pkg.Fields[fieldIndex]; + MetadataToken = (int) Definition.token; + Index = fieldIndex; + Name = pkg.Strings[Definition.nameIndex]; + + rawOffset = pkg.FieldOffsets[fieldIndex]; + + rootDefinition = this; + + fieldTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, Definition.typeIndex); + var fieldType = pkg.TypeReferences[Definition.typeIndex]; + + // Copy attributes + Attributes = (FieldAttributes) fieldType.attrs; + + // Default initialization value if present + if (pkg.FieldDefaultValue.TryGetValue(fieldIndex, out (ulong address, object variant) value)) { + DefaultValue = value.variant; + DefaultValueMetadataAddress = value.address; + } + } + + public FieldInfo(FieldInfo fieldDef, TypeInfo declaringType) : base(declaringType) { + if (fieldDef.Definition == null) + throw new ArgumentException("Argument must be a bare field definition"); + + rootDefinition = fieldDef; + + Name = fieldDef.Name; + Attributes = fieldDef.Attributes; + fieldTypeReference = TypeRef.FromTypeInfo(fieldDef.FieldType.SubstituteGenericArguments(declaringType.GetGenericArguments())); + + DefaultValue = fieldDef.DefaultValue; + DefaultValueMetadataAddress = fieldDef.DefaultValueMetadataAddress; + } + + public string GetAccessModifierString() => this switch { + { IsPrivate: true } => "private ", + { IsPublic: true } => "public ", + { IsFamily: true } => "protected ", + { IsAssembly: true } => "internal ", + { IsFamilyOrAssembly: true } => "protected internal ", + { IsFamilyAndAssembly: true } => "private protected ", + _ => "" + }; + + public string GetModifierString() { + var modifiers = new StringBuilder(GetAccessModifierString()); + + if (IsLiteral) + modifiers.Append("const "); + // All const fields are also static by implication + else if (IsStatic) + modifiers.Append("static "); + if (IsInitOnly) + modifiers.Append("readonly "); + if (RequiresUnsafeContext) + modifiers.Append("unsafe "); + if (IsPinvokeImpl) + modifiers.Append("extern "); + if (GetCustomAttributes("System.Runtime.CompilerServices.FixedBufferAttribute").Any()) + modifiers.Append("fixed "); + return modifiers.ToString(); + } + } } \ No newline at end of file diff --git a/Il2CppInspector.Common/Reflection/TypeInfo.cs b/Il2CppInspector.Common/Reflection/TypeInfo.cs index 16e578b..cfc54eb 100644 --- a/Il2CppInspector.Common/Reflection/TypeInfo.cs +++ b/Il2CppInspector.Common/Reflection/TypeInfo.cs @@ -1,1184 +1,1184 @@ -/* - Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty - Copyright 2020 Robert Xiao - https://robertxiao.ca - - All rights reserved. -*/ - -using System; -using System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; - -namespace Il2CppInspector.Reflection -{ - public class TypeInfo : MemberInfo - { - // IL2CPP-specific data - public Il2CppTypeDefinition Definition { get; } - public int Index { get; } = -1; - - // This dictionary will cache all instantiated generic types out of this definition. - // Only valid for GenericTypeDefinition - not valid on instantiated types! - private Dictionary genericTypeInstances; - public class TypeArgumentsComparer : EqualityComparer - { - public override bool Equals(TypeInfo[] x, TypeInfo[] y) { - return ((IStructuralEquatable)x).Equals(y, StructuralComparisons.StructuralEqualityComparer); - } - - public override int GetHashCode(TypeInfo[] obj) { - return ((IStructuralEquatable)obj).GetHashCode(StructuralComparisons.StructuralEqualityComparer); - } - } - - - // Cached derived types - private Dictionary generatedArrayTypes = new Dictionary(); - private TypeInfo generatedByRefType; - private TypeInfo generatedPointerType; - - // Information/flags about the type - // Undefined if the Type represents a generic type parameter - public TypeAttributes Attributes { get; } - - public TypeInfo BaseType { - get { - if (IsPointer || IsByRef) - return null; - if (IsArray) - return Assembly.Model.TypesByFullName["System.Array"]; - if (Definition != null) { - if (Definition.parentIndex >= 0) - return Assembly.Model.TypesByReferenceIndex[Definition.parentIndex]; - } - if (genericTypeDefinition != null) { - return genericTypeDefinition.BaseType.SubstituteGenericArguments(genericArguments); - } - if (IsGenericParameter) { - var res = GetGenericParameterConstraints().Where(t => !t.IsInterface).FirstOrDefault(); - if (res != null) - return res; - } - if (Namespace != "System" || BaseName != "Object") - return Assembly.Model.TypesByFullName["System.Object"]; - return null; - } - } - - public override TypeInfo DeclaringType { - get { - if (Definition != null) { - /* Type definition */ - if (Definition.declaringTypeIndex == -1) - return null; - var type = Assembly.Model.TypesByReferenceIndex[Definition.declaringTypeIndex]; - if (type == null) { - /* This might happen while initially setting up the types */ - var typeRef = Assembly.Model.Package.TypeReferences[Definition.declaringTypeIndex]; - type = Assembly.Model.TypesByDefinitionIndex[(int)typeRef.datapoint]; - } - return type; - } - if (genericTypeDefinition != null) { - // Generic parameters are *not* substituted in the DeclaringType - return genericTypeDefinition.DeclaringType; - } - return base.DeclaringType; - } - } - - // True if the type contains unresolved generic type parameters - public bool ContainsGenericParameters => (HasElementType && ElementType.ContainsGenericParameters) || IsGenericParameter || genericArguments.Any(ga => ga.ContainsGenericParameters); - - // Custom attributes for this member - public override IEnumerable CustomAttributes => CustomAttributeData.GetCustomAttributes(genericTypeDefinition ?? this); - - private List declaredConstructors; - public ReadOnlyCollection DeclaredConstructors { - get { - if (declaredConstructors != null) - return declaredConstructors.AsReadOnly(); - if (genericTypeDefinition != null) { - var result = genericTypeDefinition.DeclaredConstructors.Select(c => new ConstructorInfo(c, this)).ToList(); - declaredConstructors = result; - return result.AsReadOnly(); - } - return new List().AsReadOnly(); - } - } - - private List declaredEvents; - public ReadOnlyCollection DeclaredEvents { - get { - if (declaredEvents != null) - return declaredEvents.AsReadOnly(); - if (genericTypeDefinition != null) { - var result = genericTypeDefinition.DeclaredEvents.Select(c => new EventInfo(c, this)).ToList(); - declaredEvents = result; - return result.AsReadOnly(); - } - return new List().AsReadOnly(); - } - } - - private List declaredFields; - public ReadOnlyCollection DeclaredFields { - get { - if (declaredFields != null) - return declaredFields.AsReadOnly(); - if (genericTypeDefinition != null) { - var result = genericTypeDefinition.DeclaredFields.Select(c => new FieldInfo(c, this)).ToList(); - declaredFields = result; - return result.AsReadOnly(); - } - return new List().AsReadOnly(); - } - } - - public List DeclaredMembers => new IEnumerable[] { - DeclaredConstructors, DeclaredEvents, DeclaredFields, DeclaredMethods, - DeclaredNestedTypes, DeclaredProperties - }.SelectMany(m => m).ToList(); - - private List declaredMethods; - public ReadOnlyCollection DeclaredMethods { - get { - if (declaredMethods != null) - return declaredMethods.AsReadOnly(); - if (genericTypeDefinition != null) { - var result = genericTypeDefinition.DeclaredMethods.Select(c => new MethodInfo(c, this)).ToList(); - declaredMethods = result; - return result.AsReadOnly(); - } - return new List().AsReadOnly(); - } - } - - private readonly TypeRef[] declaredNestedTypes; - public IEnumerable DeclaredNestedTypes { - get { - if (declaredNestedTypes != null) - return declaredNestedTypes.Select(x => x.Value); - /* Type parameters are not substituted into nested classes, - * as nested classes aren't required to use the parameters - * from the containing class. - * This also matches the behaviour of the C# reflection API. - */ - if (genericTypeDefinition != null) - return genericTypeDefinition.DeclaredNestedTypes; - return Enumerable.Empty(); - } - } - - private List declaredProperties; - public ReadOnlyCollection DeclaredProperties { - get { - if (declaredProperties != null) - return declaredProperties.AsReadOnly(); - if (genericTypeDefinition != null) { - var result = genericTypeDefinition.DeclaredProperties.Select(c => new PropertyInfo(c, this)).ToList(); - declaredProperties = result; - return result.AsReadOnly(); - } - return new List().AsReadOnly(); - } - } - - // Get a field by its name - public FieldInfo GetField(string name) => DeclaredFields.FirstOrDefault(f => f.Name == name); - - private TypeRef[] genericParameterConstraints; - - // Get type constraints on a generic parameter - public TypeInfo[] GetGenericParameterConstraints() => genericParameterConstraints?.Select(t => t.Value)?.ToArray() ?? Array.Empty(); - - private readonly TypeInfo genericTypeDefinition; - - /* https://docs.microsoft.com/en-us/dotnet/api/system.type.getgenerictypedefinition?view=netframework-4.8 */ - public TypeInfo GetGenericTypeDefinition() { - if (genericTypeDefinition != null) - return genericTypeDefinition; - if (IsGenericTypeDefinition) - return this; - throw new InvalidOperationException("This method can only be called on generic types"); - } - - public ConstructorInfo GetConstructorByDefinition(ConstructorInfo definition) { - if (genericTypeDefinition != null) { - var collection = genericTypeDefinition.DeclaredConstructors; - for (int i = 0; i < collection.Count; i++) { - if (collection[i].RootDefinition == definition.RootDefinition) - return DeclaredConstructors[i]; - } - } - return definition; - } - - // Get a method or constructor by the base type definition of that method - public MethodInfo GetMethodByDefinition(MethodInfo definition) { - if (genericTypeDefinition != null) { - var collection = genericTypeDefinition.DeclaredMethods; - for (int i = 0; i < collection.Count; i++) { - if (collection[i].RootDefinition == definition.RootDefinition) - return DeclaredMethods[i]; - } - } - return definition; - } - - // Get a method by its name - public MethodInfo GetMethod(string name) => DeclaredMethods.FirstOrDefault(m => m.Name == name); - - // Get all methods with same name (overloads) - public MethodInfo[] GetMethods(string name) => DeclaredMethods.Where(m => m.Name == name).ToArray(); - - // Get methods including inherited methods - public MethodInfo[] GetAllMethods() { - var methods = new List>(); - - // Specifically return a list in order of most derived to least derived - for (var type = this; type != null; type = type.BaseType) - methods.Add(type.DeclaredMethods); - - return methods.SelectMany(m => m).ToArray(); - } - - // Get a property by its name - public PropertyInfo GetProperty(string name) => DeclaredProperties.FirstOrDefault(p => p.Name == name); - - public MethodBase[] GetVTable() { - if (Definition != null) { - MetadataUsage[] vt = Assembly.Model.Package.GetVTable(Definition); - MethodBase[] res = new MethodBase[vt.Length]; - for (int i = 0; i < vt.Length; i++) { - if (vt[i] != null) - res[i] = Assembly.Model.GetMetadataUsageMethod(vt[i]); - } - return res; - } else if (genericTypeDefinition != null) { - MethodBase[] baseVt = genericTypeDefinition.GetVTable(); - MethodBase[] res = new MethodBase[baseVt.Length]; - for (int i = 0; i < baseVt.Length; i++) { - if (baseVt[i] == null) - continue; - var declaringType = baseVt[i].DeclaringType.SubstituteGenericArguments(genericArguments); - if (baseVt[i] is ConstructorInfo ci) - res[i] = declaringType.GetConstructorByDefinition(ci); - else - res[i] = declaringType.GetMethodByDefinition((MethodInfo)baseVt[i]); - } - return res; - } - return Array.Empty(); - } - - // Method that the type is declared in if this is a type parameter of a generic method - // TODO: Make a unit test from this: https://docs.microsoft.com/en-us/dotnet/api/system.type.declaringmethod?view=netframework-4.8 - public MethodBase DeclaringMethod { get; } - - // IsGenericTypeParameter and IsGenericMethodParameter from https://github.com/dotnet/corefx/issues/23883 - public bool IsGenericTypeParameter => IsGenericParameter && DeclaringMethod == null; - public bool IsGenericMethodParameter => IsGenericParameter && DeclaringMethod != null; - - // Gets the type of the object encompassed or referred to by the current array, pointer or reference type - public TypeInfo ElementType { get; } - - #region Names - public string BaseName => base.Name; - - private static string unmangleName(string name) { - var index = name.IndexOf("`", StringComparison.Ordinal); - if (index != -1) - name = name.Remove(index); - return name; - } - - // Get rid of generic backticks and invalid characters - public string CSharpBaseName => unmangleName(base.Name).ToCIdentifier(); - - // C# colloquial name of the type (if available) - public override string CSharpName { - get { - if (HasElementType) { - var n = ElementType.CSharpName; - if (IsByRef) - n = "ref " + n; - if (IsArray) - n += "[" + new string(',', GetArrayRank() - 1) + "]"; - if (IsPointer) - n += "*"; - return n; - } else { - var s = Namespace + "." + base.Name; - var i = Il2CppConstants.FullNameTypeString.IndexOf(s); - var n = (i != -1 ? Il2CppConstants.CSharpTypeString[i] : CSharpBaseName); - var ga = GetGenericArguments(); - if (ga.Any()) - n += "<" + string.Join(", ", ga.Select(x => x.CSharpName)) + ">"; - if (s == "System.Nullable`1" && ga.Any()) - n = ga[0].CSharpName + "?"; - return n; - } - } - } - - // C# name as it would be written in a type declaration - public string GetCSharpTypeDeclarationName(bool includeVariance = false) { - if (HasElementType) { - var n = ElementType.GetCSharpTypeDeclarationName(); - if (IsByRef) - n = "ref " + n; - if (IsArray) - n += "[" + new string(',', GetArrayRank() - 1) + "]"; - if (IsPointer) - n += "*"; - return n; - } else { - var n = CSharpBaseName; - var ga = IsNested ? GetGenericArguments().Where(p => DeclaringType.GetGenericArguments().All(dp => dp.Name != p.Name)) : GetGenericArguments(); - if (ga.Any()) - n += "<" + string.Join(", ", ga.Select(x => (!x.IsGenericTypeParameter ? x.Namespace + "." : "") + x.GetCSharpTypeDeclarationName(includeVariance: true))) + ">"; - if (includeVariance) { - if ((GenericParameterAttributes & GenericParameterAttributes.Covariant) == GenericParameterAttributes.Covariant) - n = "out " + n; - if ((GenericParameterAttributes & GenericParameterAttributes.Contravariant) == GenericParameterAttributes.Contravariant) - n = "in " + n; - } - return n; - } - } - - // Display name of object - public override string Name { - get { - if (IsGenericParameter) - return base.Name; - if (HasElementType) { - var n = ElementType.Name; - if (IsArray) - n += "[" + new string(',', GetArrayRank() - 1) + "]"; - if (IsByRef) - n += "&"; - if (IsPointer) - n += "*"; - return n; - } else { - /* XXX This is not exactly accurate to C# Type.Name: - * Type.Name should be the bare name (with & * [] suffixes) - * but without nested types or generic arguments */ - var n = base.Name; - if (DeclaringType != null) - n = DeclaringType.Name + "+" + n; - var ga = GetGenericArguments(); - if (ga.Any()) - n += "[" + string.Join(",", ga.Select(x => x.Namespace != Namespace ? x.FullName ?? x.Name : x.Name)) + "]"; - return n; - } - } - } - - // Type name including namespace - // Fully qualified generic type names from the C# compiler use backtick and arity rather than a list of generic arguments - public string FullName { - get { - if (IsGenericParameter) - return null; - if (HasElementType) { - var n = ElementType.FullName; - if (n == null) - return null; - if (IsArray) - n += "[" + new string(',', GetArrayRank() - 1) + "]"; - if (IsByRef) - n += "&"; - if (IsPointer) - n += "*"; - return n; - } else { - var n = base.Name; - if (DeclaringType != null) - n = DeclaringType.FullName + "+" + n; - else if (Namespace.Length > 0) - n = Namespace + "." + n; - return n; - } - } - } - - // Returns the minimally qualified type name required to refer to this type within the specified 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; - - // This is the scope in which this type is currently being used - // If Scope.Current is null, our scope is at the assembly level - var usingScope = scope.Current?.FullName.Replace('+', '.') ?? ""; - - // This is the scope in which this type's definition is located - var declaringScope = DeclaringType?.FullName.Replace('+', '.') ?? Namespace; - - // Are we in the same scope as the scope the type is defined in? Save ourselves a bunch of work if so - if (usingScope == declaringScope) - return base.Name; - - // We're also in the same scope the type is defined in if we're looking for a nested type - // that is declared in a type we derive from - for (var b = scope.Current?.BaseType; b != null; b = b.BaseType) - if (b.FullName.Replace('+', '.') == declaringScope) - return base.Name; - - // Find first difference in the declaring scope from the using scope, moving one namespace/type name at a time - var diff = 1; - usingScope += "."; - declaringScope += "."; - while (usingScope.IndexOf('.', diff) == declaringScope.IndexOf('.', diff) - && usingScope.IndexOf('.', diff) != -1 - && usingScope.Substring(0, usingScope.IndexOf('.', diff)) - == declaringScope.Substring(0, declaringScope.IndexOf('.', diff))) - diff = usingScope.IndexOf('.', diff) + 1; - usingScope = usingScope.Remove(usingScope.Length - 1); - declaringScope = declaringScope.Remove(declaringScope.Length - 1); - - // This is the mutual root namespace and optionally nested types that the two scopes share - var mutualRootScope = usingScope.Substring(0, diff - 1); - - // Determine if the using scope is a child of the declaring scope (always a child if declaring scope is empty) - var usingScopeIsChildOfDeclaringScope = string.IsNullOrEmpty(declaringScope) || (usingScope + ".").StartsWith(declaringScope + "."); - - // Determine using directive to use - var usingDirective = - - // If the scope of usage is inside the scope in which the type is declared, no additional scope is needed - // but we still need to check for ancestor conflicts below - usingScopeIsChildOfDeclaringScope ? declaringScope - - // Check to see if there is a namespace in our using directives which brings this type into scope - // Sort by descending order of length to search the deepest namespaces first - : scope.Namespaces?.OrderByDescending(n => n.Length).FirstOrDefault(n => declaringScope == n || declaringScope.StartsWith(n + ".")); - - // minimallyScopedName will eventually contain the least qualified name needed to access the type - // Initially we set it as follows: - // - The non-mutual part of the declaring scope if there is a mutual root scope - // - The fully-qualified type name if there is no mutual root scope - // - The leaf name if the declaring scope and mutual root scope are the same - // The first two must be checked in this order to avoid a . at the start - // when the mutual root scope and declaring scope are both empty - var minimallyScopedName = - declaringScope == mutualRootScope ? base.Name : - string.IsNullOrEmpty(mutualRootScope) ? declaringScope + '.' + base.Name : - declaringScope.Substring(mutualRootScope.Length + 1) + '.' + base.Name; - - // Find the outermost type name if the wanted type is a nested type (if we need it below) - string outerTypeName = ""; - if (!usingScopeIsChildOfDeclaringScope) - for (var d = this; d != null; d = d.DeclaringType) - outerTypeName = d.BaseName; - - // Are there any ancestor nested types or namespaces in the using scope with the same name as the wanted type's unqualified name? - // If so, the ancestor name will hide the type we are trying to reference, so we need to provide a higher-level scope - - // If the using scope is a child of the declaring scope, we can try every parent scope until we find one that doesn't hide the type - // Otherwise, we just try the unqualified outer (least nested) type name to make sure it's accessible - // and revert to the fully qualified name if it's hidden - var nsAndTypeHierarchy = usingScopeIsChildOfDeclaringScope ? - usingDirective.Split('.').Append(minimallyScopedName).ToArray() - : new[] { outerTypeName }; - - var hidden = true; - var foundTypeInAncestorScope = false; - string testTypeName = ""; - - for (var depth = nsAndTypeHierarchy.Length - 1; depth >= 0 && hidden; depth--) { - testTypeName = nsAndTypeHierarchy[depth] + (testTypeName.Length > 0 ? "." : "") + testTypeName; - - hidden = false; - for (var d = scope.Current; d != null && !hidden && !foundTypeInAncestorScope; d = d.DeclaringType) { - // If neither condition is true, the wanted type is not hidden by the type we are testing - foundTypeInAncestorScope = d.FullName == FullName; - hidden = !foundTypeInAncestorScope && d.BaseName == testTypeName; - } - - // We found the shortest non-hidden scope we can use - // For a child scope, use the shortest found scope - // Otherwise, we've confirmed the outer nested type name is not hidden so go ahead and use the nested type name without a namespace - if (!hidden) - minimallyScopedName = usingScopeIsChildOfDeclaringScope ? testTypeName : Name.Replace('+', '.'); - - // If the wanted type is an unhidden ancestor, we don't need any additional scope at all - if (foundTypeInAncestorScope) - minimallyScopedName = base.Name; - } - - // If there are multiple using directives that would allow the same minimally scoped name to be used, - // then the minimally scoped name is ambiguous and we can't use it - // Note that if the wanted type is an unhidden outer class relative to the using scope, this takes precedence and there can be no ambiguity - if (!foundTypeInAncestorScope) { - // Only test the outermost type name - outerTypeName = minimallyScopedName.Split('.')[0]; - - // Take matching type names from all namespaces in scope - var matchingNamespaces = scope.Namespaces? - .Where(n => Assembly.Model.TypesByFullName.ContainsKey(n + "." + outerTypeName)).ToList() ?? new List(); - - // The global namespace is in scope so take every matching type from that too - if (Assembly.Model.TypesByFullName.ContainsKey(outerTypeName)) - matchingNamespaces.Add(""); - - // More than one possible matching namespace? If so, the type reference is ambiguous - if (matchingNamespaces.Count > 1) { - // TODO: This can be improved to cut off a new mutual root that doesn't cause ambiguity - minimallyScopedName = usedType; - } - - // No matching namespaces, not hidden, no mutual root scope in the file and no using directive? - // If so, the type's namespace is completely out of scope so use the fully-qualified type name - if (matchingNamespaces.Count == 0 && !hidden && string.IsNullOrEmpty(mutualRootScope) && usingDirective == null) - minimallyScopedName = usedType; - } - - // Finally, check if the selected name has ambiguity with any available namespaces in the current scope - // If so, use the full name with the mutual root scope cut off from the start - var checkNamespaces = scope.Namespaces? - .Select(ns => (!string.IsNullOrEmpty(ns)? ns + "." : "") + minimallyScopedName).ToList() ?? new List(); - - if (Assembly.Model.Namespaces.Intersect(checkNamespaces).Any()) - minimallyScopedName = mutualRootScope.Length > 0 ? usedType.Substring(mutualRootScope.Length + 1) : usedType; - - // Check current namespace and all ancestors too - else { - checkNamespaces.Clear(); - var ancestorUsingScope = "." + usingScope; - while (ancestorUsingScope.IndexOf(".", StringComparison.Ordinal) != -1) { - ancestorUsingScope = ancestorUsingScope.Substring(0, ancestorUsingScope.LastIndexOf(".", StringComparison.Ordinal)); - checkNamespaces.Add((ancestorUsingScope.Length > 0 ? ancestorUsingScope.Substring(1) + "." : "") + minimallyScopedName); - } - - if (Assembly.Model.Namespaces.Intersect(checkNamespaces).Any()) - minimallyScopedName = mutualRootScope.Length > 0 ? usedType.Substring(mutualRootScope.Length + 1) : "global::" + usedType; - } - - // If the final name starts with ".", it's a reference to the global namespace (ie. unnamed root namespace) - if (minimallyScopedName.StartsWith(".")) - minimallyScopedName = "global::" + minimallyScopedName.Substring(1); - - return minimallyScopedName; - } - - // C#-friendly type name as it should be used in the scope of a given type - public string GetScopedCSharpName(Scope usingScope = null, bool omitRef = false, bool isPartOfTypeDeclaration = false) { - // Unscoped name if no using scope specified - if (usingScope == null) - return CSharpName; - - // Generic parameters don't have a scope - if (IsGenericParameter) - return CSharpName; - - var s = Namespace + "." + base.Name; - - // 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); - n = unmangleName(n).ToCIdentifier(allowScopeQualifiers: true); - - // Generic type parameters and type arguments - // Inheriting from a base class or implementing an interface use the type's declaring scope, not the type's scope itself - // for generic type parameters - var outerScope = usingScope; - if (isPartOfTypeDeclaration) - outerScope = new Scope { Current = usingScope.Current?.DeclaringType, Namespaces = usingScope.Namespaces }; - - var g = string.Join(", ", getGenericTypeParameters(usingScope).Select(x => x.GetScopedCSharpName(outerScope))); - if (!string.IsNullOrEmpty(g)) - n += "<" + g + ">"; - - // Nullable types - if (s == "System.Nullable`1" && GetGenericArguments().Any()) - n = GetGenericArguments()[0].GetScopedCSharpName(usingScope) + "?"; - - // Arrays, pointers, references - if (HasElementType) - n = ElementType.GetScopedCSharpName(usingScope); - - return (IsByRef && !omitRef ? "ref " : "") + n + (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + (IsPointer ? "*" : ""); - } - #endregion - - // Get the generic type parameters for a specific usage of this type based on its scope, - // or all generic type parameters if no scope specified - private IEnumerable getGenericTypeParameters(Scope scope = null) { - var ga = GetGenericArguments(); - - // If no scope or empty scope specified, or no type parameters, stop here - if (scope?.Current == null || !ga.Any()) - return ga; - - // In order to elide generic type parameters, the using scope must be a parent of the declaring scope - // Determine if the using scope is a parent of the declaring scope (always a child if using scope is empty) - var usingScopeIsParent = false; - for (var s = DeclaringType; s != null && !usingScopeIsParent; s = s.DeclaringType) - if (s == scope.Current) - usingScopeIsParent = true; - - if (!usingScopeIsParent) - return ga; - - // Get the generic type parameters available in the using scope - // (no need to recurse because every nested type inherits all of the generic type parameters of all of its ancestors) - var gasInScope = scope.Current.GetGenericArguments(); - - // Return all of the generic type parameters this type uses minus those already in scope - return ga.Where(p => gasInScope.All(pp => pp.Name != p.Name)); - } - - public GenericParameterAttributes GenericParameterAttributes { get; } - - // Generic parameter position in list of non-concrete type parameters - // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.genericparameterposition?view=netframework-4.8 - private int genericParameterPosition; - public int GenericParameterPosition { - get => IsGenericParameter ? genericParameterPosition : throw new InvalidOperationException("The current type does not represent a type parameter"); - private set => genericParameterPosition = value; - } - - // For a generic type definition: the list of generic type parameters - // For an open generic type: a mix of generic type parameters and generic type arguments - // For a closed generic type: the list of generic type arguments - private readonly TypeInfo[] genericArguments = Array.Empty(); - - public TypeInfo[] GenericTypeParameters => IsGenericTypeDefinition ? genericArguments : Array.Empty(); - - public TypeInfo[] GenericTypeArguments => !IsGenericTypeDefinition ? genericArguments : Array.Empty(); - - // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.getgenericarguments?view=netframework-4.8 - public TypeInfo[] GetGenericArguments() => genericArguments; - - // True if an array, pointer or reference, otherwise false - // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.haselementtype?view=netframework-4.8 - public bool HasElementType => ElementType != null; - - private readonly TypeRef[] implementedInterfaceReferences; - public IEnumerable ImplementedInterfaces { - get { - if (Definition != null) - return implementedInterfaceReferences.Select(x => x.Value); - if (genericTypeDefinition != null) - return genericTypeDefinition.ImplementedInterfaces.Select(t => t.SubstituteGenericArguments(genericArguments)); - if (IsGenericParameter) - return GetGenericParameterConstraints().Where(t => t.IsInterface); - return Enumerable.Empty(); - } - } - - // Get only interfaces not inherited from base interfaces - public IEnumerable NonInheritedInterfaces => ImplementedInterfaces.Except(ImplementedInterfaces.SelectMany(t => t.ImplementedInterfaces)); - - public bool IsAbstract => (Attributes & TypeAttributes.Abstract) == TypeAttributes.Abstract; - public bool IsArray { get; } - public bool IsByRef { get; } - public bool IsClass => (Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Class; - public bool IsEnum { get; } - public bool IsGenericParameter { get; } - public bool IsGenericType { get; } - public bool IsGenericTypeDefinition => (Definition != null) && genericArguments.Any(); - public bool IsImport => (Attributes & TypeAttributes.Import) == TypeAttributes.Import; - public bool IsInterface => (Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Interface; - public bool IsNested => (MemberType & MemberTypes.NestedType) == MemberTypes.NestedType; - public bool IsNestedAssembly => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedAssembly; - public bool IsNestedFamANDAssem => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamANDAssem; - public bool IsNestedFamily => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamily; - public bool IsNestedFamORAssem => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamORAssem; - public bool IsNestedPrivate => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPrivate; - public bool IsNestedPublic => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPublic; - public bool IsNotPublic => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NotPublic; - public bool IsPointer { get; } - // Primitive types table: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/built-in-types-table (we exclude Object and String) - 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; - public bool IsSerializable => (Attributes & TypeAttributes.Serializable) == TypeAttributes.Serializable; - public bool IsSpecialName => (Attributes & TypeAttributes.SpecialName) == TypeAttributes.SpecialName; - public bool IsValueType => BaseType?.FullName == "System.ValueType"; - - // Helper function for determining if using this type as a field, parameter etc. requires that field or method to be declared as unsafe - public bool RequiresUnsafeContext => IsPointer || (HasElementType && ElementType.RequiresUnsafeContext); - - // May get overridden by Il2CppType-based constructor below - public override MemberTypes MemberType { get; } = MemberTypes.TypeInfo; - - private string @namespace; - public string Namespace { - get => !string.IsNullOrEmpty(@namespace) ? @namespace : DeclaringType?.Namespace ?? ""; - set => @namespace = value; - } - - // Number of dimensions of an array - private readonly int arrayRank; - public int GetArrayRank() => arrayRank; - - public string[] GetEnumNames() => IsEnum ? DeclaredFields.Where(x => x.Name != "value__").Select(x => x.Name).ToArray() : throw new InvalidOperationException("Type is not an enumeration"); - - // The underlying type of an enumeration (int by default) - private readonly TypeRef enumUnderlyingTypeReference = null; - - public TypeInfo GetEnumUnderlyingType() { - if (!IsEnum) - return null; - return enumUnderlyingTypeReference.Value; - } - - public Array GetEnumValues() => IsEnum ? DeclaredFields.Where(x => x.Name != "value__").Select(x => x.DefaultValue).ToArray() : throw new InvalidOperationException("Type is not an enumeration"); - - // Initialize type from TypeDef using specified index in metadata - public TypeInfo(int typeIndex, Assembly owner) : base(owner) { - var pkg = Assembly.Model.Package; - - Definition = pkg.TypeDefinitions[typeIndex]; - MetadataToken = (int) Definition.token; - Index = typeIndex; - Namespace = Regex.Replace(pkg.Strings[Definition.namespaceIndex], @"[^A-Za-z0-9_\-\.<>{}]", ""); - Name = pkg.Strings[Definition.nameIndex]; - - // Nested type? - if (Definition.declaringTypeIndex >= 0) { - MemberType |= MemberTypes.NestedType; - } - - // Add to global type definition list - Assembly.Model.TypesByDefinitionIndex[Index] = this; - - // Generic type definition? - if (Definition.genericContainerIndex >= 0) { - IsGenericType = true; - IsGenericParameter = false; - - // Store the generic type parameters for later instantiation - var container = pkg.GenericContainers[Definition.genericContainerIndex]; - - genericArguments = Enumerable.Range((int)container.genericParameterStart, container.type_argc) - .Select(index => Assembly.Model.GetGenericParameterType(index)).ToArray(); - genericTypeInstances = new Dictionary(new TypeArgumentsComparer()); - genericTypeInstances[genericArguments] = this; - } - - // TODO: Move this to after we've populated TypesByReferenceIndex, since FullName might touch that - Assembly.Model.TypesByFullName[FullName] = this; - - // Copy attributes - Attributes = (TypeAttributes) Definition.flags; - - // Enumerations - bit 1 of bitfield indicates this (also the baseTypeReference will be System.Enum) - if (((Definition.bitfield >> 1) & 1) == 1) { - IsEnum = true; - enumUnderlyingTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, Definition.elementTypeIndex); - } - - // Pass-by-reference type - // NOTE: This should actually always evaluate to false in the current implementation - // This field is no longer present in metadata v27 - // IsByRef = Index == Definition.byrefTypeIndex; - IsByRef = false; - - // Add all implemented interfaces - implementedInterfaceReferences = new TypeRef[Definition.interfaces_count]; - for (var i = 0; i < Definition.interfaces_count; i++) - implementedInterfaceReferences[i] = TypeRef.FromReferenceIndex(Assembly.Model, pkg.InterfaceUsageIndices[Definition.interfacesStart + i]); - - // Add all nested types - declaredNestedTypes = new TypeRef[Definition.nested_type_count]; - for (var n = 0; n < Definition.nested_type_count; n++) - declaredNestedTypes[n] = TypeRef.FromDefinitionIndex(Assembly.Model, pkg.NestedTypeIndices[Definition.nestedTypesStart + n]); - - // Add all fields - declaredFields = new List(); - for (var f = Definition.fieldStart; f < Definition.fieldStart + Definition.field_count; f++) - declaredFields.Add(new FieldInfo(pkg, f, this)); - - // Add all methods - declaredConstructors = new List(); - declaredMethods = new List(); - for (var m = Definition.methodStart; m < Definition.methodStart + Definition.method_count; m++) { - var method = new MethodInfo(pkg, m, this); - if (method.Name == ConstructorInfo.ConstructorName || method.Name == ConstructorInfo.TypeConstructorName) - declaredConstructors.Add(new ConstructorInfo(pkg, m, this)); - else - declaredMethods.Add(method); - } - - // Add all properties - declaredProperties = new List(); - for (var p = Definition.propertyStart; p < Definition.propertyStart + Definition.property_count; p++) - declaredProperties.Add(new PropertyInfo(pkg, p, this)); - - // There are rare cases when explicitly implemented interface properties - // are only given as methods in the metadata. Find these and add them as properties - var eip = DeclaredMethods.Where(m => m.Name.Contains(".get_") || m.Name.Contains(".set_")) - .Except(DeclaredProperties.Select(p => p.GetMethod)) - .Except(DeclaredProperties.Select(p => p.SetMethod)); - - // Build a paired list of getters and setters - var pairedEip = new List<(MethodInfo get, MethodInfo set)>(); - foreach (var p in eip) { - // Discern property name - var n = p.Name.Replace(".get_", ".").Replace(".set_", "."); - - // Find setter with no matching getter - if (p.Name.Contains(".get_")) - if (pairedEip.FirstOrDefault(pe => pe.get == null && pe.set.Name == p.Name.Replace(".get_", ".set_")) is (MethodInfo get, MethodInfo set) method) { - pairedEip.Remove(method); - pairedEip.Add((p, method.set)); - } else - pairedEip.Add((p, null)); - - // Find getter with no matching setter - if (p.Name.Contains(".set_")) - if (pairedEip.FirstOrDefault(pe => pe.set == null && pe.get.Name == p.Name.Replace(".set_", ".get_")) is (MethodInfo get, MethodInfo set) method) { - pairedEip.Remove(method); - pairedEip.Add((method.get, p)); - } else - pairedEip.Add((null, p)); - } - - foreach (var prop in pairedEip) - declaredProperties.Add(new PropertyInfo(prop.get, prop.set, this)); - - // Add all events - declaredEvents = new List(); - for (var e = Definition.eventStart; e < Definition.eventStart + Definition.event_count; e++) - declaredEvents.Add(new EventInfo(pkg, e, this)); - - // TODO: Events have the same edge case issue as properties above, eg. PoGo 0.35.0 - } - - // Initialize a type from a concrete generic instance - private TypeInfo(TypeInfo genericTypeDef, TypeInfo[] genericArgs) : base(genericTypeDef.Assembly) { - if (!genericTypeDef.IsGenericTypeDefinition) - throw new InvalidOperationException(genericTypeDef.Name + " is not a generic type definition."); - - genericTypeDefinition = genericTypeDef; - - // Same visibility attributes as generic type definition - Attributes = genericTypeDefinition.Attributes; - - // Even though this isn't a TypeDef, we have to set this so that DeclaringType works in later references - Index = genericTypeDefinition.Index; - - // Same name as generic type definition - Namespace = genericTypeDefinition.Namespace; - Name = genericTypeDefinition.BaseName; // use BaseName to exclude the type parameters so we can supply our own - MemberType = genericTypeDefinition.MemberType; - - IsGenericParameter = false; - IsGenericType = true; - - genericArguments = genericArgs; - } - - // Substitutes the elements of an array of types for the type parameters of the current generic type definition - // and returns a TypeInfo object representing the resulting constructed type. - // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.makegenerictype?view=netframework-4.8 - public TypeInfo MakeGenericType(params TypeInfo[] typeArguments) { - if (typeArguments.Length != genericArguments.Length) { - throw new ArgumentException("The number of generic arguments provided does not match the generic type definition."); - } - - TypeInfo result; - if (genericTypeInstances.TryGetValue(typeArguments, out result)) - return result; - result = new TypeInfo(this, typeArguments); - genericTypeInstances[typeArguments] = result; - return result; - } - - public TypeInfo SubstituteGenericArguments(TypeInfo[] typeArguments, TypeInfo[] methodArguments = null) { - if (!ContainsGenericParameters) - return this; - - if (IsGenericTypeParameter) - return typeArguments[GenericParameterPosition]; - else if (IsGenericMethodParameter) - return methodArguments[GenericParameterPosition]; - else if (IsGenericTypeDefinition) - return MakeGenericType(typeArguments); - else if (HasElementType) { - var elementType = ElementType.SubstituteGenericArguments(typeArguments, methodArguments); - if (IsArray) - return elementType.MakeArrayType(GetArrayRank()); - else if (IsByRef) - return elementType.MakeByRefType(); - else if (IsPointer) - return elementType.MakePointerType(); - throw new InvalidOperationException("TypeInfo element type state is invalid!"); - } else { - var newGenericArguments = genericArguments.Select(x => x.SubstituteGenericArguments(typeArguments, methodArguments)); - return genericTypeDefinition.MakeGenericType(newGenericArguments.ToArray()); - } - } - - // Initialize a type that is a generic parameter of a generic type - // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.isgenerictype?view=netframework-4.8 - public TypeInfo(TypeInfo declaringType, Il2CppGenericParameter param) : base(declaringType) { - // Same visibility attributes as declaring type - Attributes = declaringType.Attributes; - - // Same namespace as declaring type - Namespace = declaringType.Namespace; - - // Special constraints - GenericParameterAttributes = (GenericParameterAttributes)param.flags; - - // Type constraints - genericParameterConstraints = new TypeRef[param.constraintsCount]; - for (int c = 0; c < param.constraintsCount; c++) - genericParameterConstraints[c] = TypeRef.FromReferenceIndex(Assembly.Model, Assembly.Model.Package.GenericConstraintIndices[param.constraintsStart + c]); - - // Base type of object (set by default) - // TODO: ImplementedInterfaces should be set to interface types constraints - - // Name of parameter - Name = Assembly.Model.Package.Strings[param.nameIndex]; - - // Position - GenericParameterPosition = param.num; - - IsGenericParameter = true; - IsGenericType = false; - } - - // Initialize a type that is a generic parameter of a generic method - public TypeInfo(MethodBase declaringMethod, Il2CppGenericParameter param) : this(declaringMethod.DeclaringType, param) - => DeclaringMethod = declaringMethod; - - // Initialize a type that is an array of the specified type - private TypeInfo(TypeInfo elementType, int rank) : base(elementType.Assembly) { - ElementType = elementType; - IsArray = true; - - Namespace = ElementType.Namespace; - Name = ElementType.Name; - arrayRank = rank; - } - - // Initialize a type that is a reference or pointer to the specified type - private TypeInfo(TypeInfo underlyingType, bool isPointer) : base(underlyingType.Assembly) { - ElementType = underlyingType; - if (isPointer) { - IsPointer = true; - } else { - IsByRef = true; - } - - Namespace = ElementType.Namespace; - Name = ElementType.Name; - } - - public TypeInfo MakeArrayType(int rank = 1) { - TypeInfo type; - if (generatedArrayTypes.TryGetValue(rank, out type)) - return type; - type = new TypeInfo(this, rank); - generatedArrayTypes[rank] = type; - return type; - } - - public TypeInfo MakeByRefType() { - if (generatedByRefType == null) - generatedByRefType = new TypeInfo(this, isPointer: false); - return generatedByRefType; - } - - public TypeInfo MakePointerType() { - if (generatedPointerType == null) - generatedPointerType = new TypeInfo(this, isPointer: true); - return generatedPointerType; - } - - // Get all the other types directly referenced by this type (single level depth; no recursion) - public List GetAllTypeReferences() { - var refs = new HashSet(); - - // Fixed attributes - if (IsImport) - refs.Add(Assembly.Model.TypesByFullName["System.Runtime.InteropServices.ComVisibleAttribute"]); - if (IsSerializable) - refs.Add(Assembly.Model.TypesByFullName["System.SerializableAttribute"]); - - // Constructor, event, field, method, nested type, property attributes - var attrs = DeclaredMembers.SelectMany(m => m.CustomAttributes); - refs.UnionWith(attrs.Select(a => a.AttributeType)); - - // Events - refs.UnionWith(DeclaredEvents.Select(e => e.EventHandlerType)); - - // Fields - refs.UnionWith(DeclaredFields.Select(f => f.FieldType)); - - // Properties (return type of getters or argument type of setters) - refs.UnionWith(DeclaredProperties.Select(p => p.PropertyType)); - - // Nested types - refs.UnionWith(DeclaredNestedTypes); - refs.UnionWith(DeclaredNestedTypes.SelectMany(n => n.GetAllTypeReferences())); - - // Constructors - refs.UnionWith(DeclaredConstructors.SelectMany(m => m.DeclaredParameters).Select(p => p.ParameterType)); - - // Methods (includes event add/remove/raise, property get/set methods and extension methods) - refs.UnionWith(DeclaredMethods.Select(m => m.ReturnParameter.ParameterType)); - refs.UnionWith(DeclaredMethods.SelectMany(m => m.DeclaredParameters).Select(p => p.ParameterType)); - - // Method generic type parameters and constraints - refs.UnionWith(DeclaredMethods.SelectMany(m => m.GetGenericArguments())); - refs.UnionWith(DeclaredMethods.SelectMany(m => m.GetGenericArguments()) - .SelectMany(p => p.GetGenericParameterConstraints())); - - // Type declaration attributes - refs.UnionWith(CustomAttributes.Select(a => a.AttributeType)); - - // Parent type - if (BaseType != null) - refs.Add(BaseType); - - // Declaring type - if (DeclaringType != null) - refs.Add(DeclaringType); - - // Element type - if (HasElementType) - refs.Add(ElementType); - - // Enum type - if (IsEnum) - refs.Add(GetEnumUnderlyingType()); - - // Generic type definition - if (genericTypeDefinition != null) - refs.Add(genericTypeDefinition); - - // Generic type parameters and constraints - refs.UnionWith(GetGenericArguments()); - refs.UnionWith(GetGenericParameterConstraints()); - - // Generic type constraints of type parameters in generic type definition - refs.UnionWith(GenericTypeParameters.SelectMany(p => p.GetGenericParameterConstraints())); - - // Implemented interfaces - refs.UnionWith(ImplementedInterfaces); - - // Repeatedly replace arrays, pointers and references with their element types - while (refs.Any(r => r.HasElementType)) - refs = refs.Select(r => r.HasElementType ? r.ElementType : r).ToHashSet(); - - // Type arguments in generic types that may have been a field, method parameter etc. - IEnumerable genericArguments = refs.ToList(); - do { - genericArguments = genericArguments.SelectMany(r => r.GetGenericArguments()); - refs.UnionWith(genericArguments); - } while (genericArguments.Any()); - - // Remove anonymous types - refs.RemoveWhere(r => string.IsNullOrEmpty(r.FullName)); - - IEnumerable refList = refs; - - // Eliminated named duplicates (the HashSet removes instance duplicates) - refList = refList.GroupBy(r => r.FullName).Select(p => p.First()); - - // Remove System.Object - refList = refList.Where(r => r.FullName != "System.Object"); - - return refList.ToList(); - } - - public string GetAccessModifierString() => this switch - { - { IsPublic: true } => "public ", - { IsNotPublic: true } => "internal ", - - { IsNestedPublic: true } => "public ", - { IsNestedPrivate: true } => "private ", - { IsNestedFamily: true } => "protected ", - { IsNestedAssembly: true } => "internal ", - { IsNestedFamORAssem: true } => "protected internal ", - { IsNestedFamANDAssem: true } => "private protected ", - _ => throw new InvalidOperationException("Unknown type access modifier") - }; - - public string GetModifierString() { - var modifiers = new StringBuilder(GetAccessModifierString()); - - // An abstract sealed class is a static class - if (IsAbstract && IsSealed) - modifiers.Append("static "); - else { - if (IsAbstract && !IsInterface) - modifiers.Append("abstract "); - if (IsSealed && !IsValueType && !IsEnum) - modifiers.Append("sealed "); - } - if (IsInterface) - modifiers.Append("interface "); - else if (IsValueType) - modifiers.Append("struct "); - else if (IsEnum) - modifiers.Append("enum "); - else - modifiers.Append("class "); - - return modifiers.ToString(); - } - - public string GetTypeConstraintsString(Scope scope) { - if (!IsGenericParameter) - return string.Empty; - - var typeConstraints = GetGenericParameterConstraints(); - if (GenericParameterAttributes == GenericParameterAttributes.None && typeConstraints.Length == 0) - return string.Empty; - - // Check if we are in a nested type, and if so, exclude ourselves if we are a generic type parameter from the outer type - // All constraints are inherited automatically by all nested types so we only have to look at the immediate outer type - if (DeclaringMethod == null && DeclaringType.IsNested && DeclaringType.DeclaringType.GetGenericArguments().Any(p => p.Name == Name)) - return string.Empty; - - // Check if we are in an overriding method, and if so, exclude ourselves if we are a generic type parameter from the base method - // All constraints are inherited automatically by all overriding methods so we only have to look at the immediate base method - if (DeclaringMethod != null && DeclaringMethod.IsVirtual && !DeclaringMethod.IsAbstract && !DeclaringMethod.IsFinal - && (DeclaringMethod.Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.ReuseSlot) { - // Find nearest ancestor base method which has us as a generic type parameter - var method = DeclaringMethod.DeclaringType.BaseType.GetAllMethods() - .FirstOrDefault(m => m.IsHideBySig && m.IsVirtual && DeclaringMethod.SignatureEquals(m) && m.GetGenericArguments().Any(p => p.Name == Name)); - - // Stop if we are inherited from a base method - if (method != null) - return string.Empty; - } - - var constraintList = typeConstraints.Where(c => c.FullName != "System.ValueType").Select(c => c.GetScopedCSharpName(scope)).ToList(); - - // struct or class must be the first constraint specified - if ((GenericParameterAttributes & GenericParameterAttributes.NotNullableValueTypeConstraint) == GenericParameterAttributes.NotNullableValueTypeConstraint) - constraintList.Insert(0, "struct"); - if ((GenericParameterAttributes & GenericParameterAttributes.ReferenceTypeConstraint) == GenericParameterAttributes.ReferenceTypeConstraint) - constraintList.Insert(0, "class"); - - if ((GenericParameterAttributes & GenericParameterAttributes.DefaultConstructorConstraint) == GenericParameterAttributes.DefaultConstructorConstraint - && !constraintList.Contains("struct")) - // new() must be the last constraint specified - constraintList.Add("new()"); - - // Covariance/contravariance constraints can lead to an empty constraint list - if (!constraintList.Any()) - return string.Empty; - - return "where " + Name + " : " + string.Join(", ", constraintList); - } - - public override string ToString() => Name; - } +/* + Copyright 2017-2021 Katy Coe - http://www.djkaty.com - https://github.com/djkaty + Copyright 2020 Robert Xiao - https://robertxiao.ca + + All rights reserved. +*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; + +namespace Il2CppInspector.Reflection +{ + public class TypeInfo : MemberInfo + { + // IL2CPP-specific data + public Il2CppTypeDefinition Definition { get; } + public int Index { get; } = -1; + + // This dictionary will cache all instantiated generic types out of this definition. + // Only valid for GenericTypeDefinition - not valid on instantiated types! + private Dictionary genericTypeInstances; + public class TypeArgumentsComparer : EqualityComparer + { + public override bool Equals(TypeInfo[] x, TypeInfo[] y) { + return ((IStructuralEquatable)x).Equals(y, StructuralComparisons.StructuralEqualityComparer); + } + + public override int GetHashCode(TypeInfo[] obj) { + return ((IStructuralEquatable)obj).GetHashCode(StructuralComparisons.StructuralEqualityComparer); + } + } + + + // Cached derived types + private Dictionary generatedArrayTypes = new Dictionary(); + private TypeInfo generatedByRefType; + private TypeInfo generatedPointerType; + + // Information/flags about the type + // Undefined if the Type represents a generic type parameter + public TypeAttributes Attributes { get; } + + public TypeInfo BaseType { + get { + if (IsPointer || IsByRef) + return null; + if (IsArray) + return Assembly.Model.TypesByFullName["System.Array"]; + if (Definition != null) { + if (Definition.parentIndex >= 0) + return Assembly.Model.TypesByReferenceIndex[Definition.parentIndex]; + } + if (genericTypeDefinition != null) { + return genericTypeDefinition.BaseType.SubstituteGenericArguments(genericArguments); + } + if (IsGenericParameter) { + var res = GetGenericParameterConstraints().Where(t => !t.IsInterface).FirstOrDefault(); + if (res != null) + return res; + } + if (Namespace != "System" || BaseName != "Object") + return Assembly.Model.TypesByFullName["System.Object"]; + return null; + } + } + + public override TypeInfo DeclaringType { + get { + if (Definition != null) { + /* Type definition */ + if (Definition.declaringTypeIndex == -1) + return null; + var type = Assembly.Model.TypesByReferenceIndex[Definition.declaringTypeIndex]; + if (type == null) { + /* This might happen while initially setting up the types */ + var typeRef = Assembly.Model.Package.TypeReferences[Definition.declaringTypeIndex]; + type = Assembly.Model.TypesByDefinitionIndex[(int)typeRef.datapoint]; + } + return type; + } + if (genericTypeDefinition != null) { + // Generic parameters are *not* substituted in the DeclaringType + return genericTypeDefinition.DeclaringType; + } + return base.DeclaringType; + } + } + + // True if the type contains unresolved generic type parameters + public bool ContainsGenericParameters => (HasElementType && ElementType.ContainsGenericParameters) || IsGenericParameter || genericArguments.Any(ga => ga.ContainsGenericParameters); + + // Custom attributes for this member + public override IEnumerable CustomAttributes => CustomAttributeData.GetCustomAttributes(genericTypeDefinition ?? this); + + private List declaredConstructors; + public ReadOnlyCollection DeclaredConstructors { + get { + if (declaredConstructors != null) + return declaredConstructors.AsReadOnly(); + if (genericTypeDefinition != null) { + var result = genericTypeDefinition.DeclaredConstructors.Select(c => new ConstructorInfo(c, this)).ToList(); + declaredConstructors = result; + return result.AsReadOnly(); + } + return new List().AsReadOnly(); + } + } + + private List declaredEvents; + public ReadOnlyCollection DeclaredEvents { + get { + if (declaredEvents != null) + return declaredEvents.AsReadOnly(); + if (genericTypeDefinition != null) { + var result = genericTypeDefinition.DeclaredEvents.Select(c => new EventInfo(c, this)).ToList(); + declaredEvents = result; + return result.AsReadOnly(); + } + return new List().AsReadOnly(); + } + } + + private List declaredFields; + public ReadOnlyCollection DeclaredFields { + get { + if (declaredFields != null) + return declaredFields.AsReadOnly(); + if (genericTypeDefinition != null) { + var result = genericTypeDefinition.DeclaredFields.Select(c => new FieldInfo(c, this)).ToList(); + declaredFields = result; + return result.AsReadOnly(); + } + return new List().AsReadOnly(); + } + } + + public List DeclaredMembers => new IEnumerable[] { + DeclaredConstructors, DeclaredEvents, DeclaredFields, DeclaredMethods, + DeclaredNestedTypes, DeclaredProperties + }.SelectMany(m => m).ToList(); + + private List declaredMethods; + public ReadOnlyCollection DeclaredMethods { + get { + if (declaredMethods != null) + return declaredMethods.AsReadOnly(); + if (genericTypeDefinition != null) { + var result = genericTypeDefinition.DeclaredMethods.Select(c => new MethodInfo(c, this)).ToList(); + declaredMethods = result; + return result.AsReadOnly(); + } + return new List().AsReadOnly(); + } + } + + private readonly TypeRef[] declaredNestedTypes; + public IEnumerable DeclaredNestedTypes { + get { + if (declaredNestedTypes != null) + return declaredNestedTypes.Select(x => x.Value); + /* Type parameters are not substituted into nested classes, + * as nested classes aren't required to use the parameters + * from the containing class. + * This also matches the behaviour of the C# reflection API. + */ + if (genericTypeDefinition != null) + return genericTypeDefinition.DeclaredNestedTypes; + return Enumerable.Empty(); + } + } + + private List declaredProperties; + public ReadOnlyCollection DeclaredProperties { + get { + if (declaredProperties != null) + return declaredProperties.AsReadOnly(); + if (genericTypeDefinition != null) { + var result = genericTypeDefinition.DeclaredProperties.Select(c => new PropertyInfo(c, this)).ToList(); + declaredProperties = result; + return result.AsReadOnly(); + } + return new List().AsReadOnly(); + } + } + + // Get a field by its name + public FieldInfo GetField(string name) => DeclaredFields.FirstOrDefault(f => f.Name == name); + + private TypeRef[] genericParameterConstraints; + + // Get type constraints on a generic parameter + public TypeInfo[] GetGenericParameterConstraints() => genericParameterConstraints?.Select(t => t.Value)?.ToArray() ?? Array.Empty(); + + private readonly TypeInfo genericTypeDefinition; + + /* https://docs.microsoft.com/en-us/dotnet/api/system.type.getgenerictypedefinition?view=netframework-4.8 */ + public TypeInfo GetGenericTypeDefinition() { + if (genericTypeDefinition != null) + return genericTypeDefinition; + if (IsGenericTypeDefinition) + return this; + throw new InvalidOperationException("This method can only be called on generic types"); + } + + public ConstructorInfo GetConstructorByDefinition(ConstructorInfo definition) { + if (genericTypeDefinition != null) { + var collection = genericTypeDefinition.DeclaredConstructors; + for (int i = 0; i < collection.Count; i++) { + if (collection[i].RootDefinition == definition.RootDefinition) + return DeclaredConstructors[i]; + } + } + return definition; + } + + // Get a method or constructor by the base type definition of that method + public MethodInfo GetMethodByDefinition(MethodInfo definition) { + if (genericTypeDefinition != null) { + var collection = genericTypeDefinition.DeclaredMethods; + for (int i = 0; i < collection.Count; i++) { + if (collection[i].RootDefinition == definition.RootDefinition) + return DeclaredMethods[i]; + } + } + return definition; + } + + // Get a method by its name + public MethodInfo GetMethod(string name) => DeclaredMethods.FirstOrDefault(m => m.Name == name); + + // Get all methods with same name (overloads) + public MethodInfo[] GetMethods(string name) => DeclaredMethods.Where(m => m.Name == name).ToArray(); + + // Get methods including inherited methods + public MethodInfo[] GetAllMethods() { + var methods = new List>(); + + // Specifically return a list in order of most derived to least derived + for (var type = this; type != null; type = type.BaseType) + methods.Add(type.DeclaredMethods); + + return methods.SelectMany(m => m).ToArray(); + } + + // Get a property by its name + public PropertyInfo GetProperty(string name) => DeclaredProperties.FirstOrDefault(p => p.Name == name); + + public MethodBase[] GetVTable() { + if (Definition != null) { + MetadataUsage[] vt = Assembly.Model.Package.GetVTable(Definition); + MethodBase[] res = new MethodBase[vt.Length]; + for (int i = 0; i < vt.Length; i++) { + if (vt[i] != null) + res[i] = Assembly.Model.GetMetadataUsageMethod(vt[i]); + } + return res; + } else if (genericTypeDefinition != null) { + MethodBase[] baseVt = genericTypeDefinition.GetVTable(); + MethodBase[] res = new MethodBase[baseVt.Length]; + for (int i = 0; i < baseVt.Length; i++) { + if (baseVt[i] == null) + continue; + var declaringType = baseVt[i].DeclaringType.SubstituteGenericArguments(genericArguments); + if (baseVt[i] is ConstructorInfo ci) + res[i] = declaringType.GetConstructorByDefinition(ci); + else + res[i] = declaringType.GetMethodByDefinition((MethodInfo)baseVt[i]); + } + return res; + } + return Array.Empty(); + } + + // Method that the type is declared in if this is a type parameter of a generic method + // TODO: Make a unit test from this: https://docs.microsoft.com/en-us/dotnet/api/system.type.declaringmethod?view=netframework-4.8 + public MethodBase DeclaringMethod { get; } + + // IsGenericTypeParameter and IsGenericMethodParameter from https://github.com/dotnet/corefx/issues/23883 + public bool IsGenericTypeParameter => IsGenericParameter && DeclaringMethod == null; + public bool IsGenericMethodParameter => IsGenericParameter && DeclaringMethod != null; + + // Gets the type of the object encompassed or referred to by the current array, pointer or reference type + public TypeInfo ElementType { get; } + + #region Names + public string BaseName => base.Name; + + private static string unmangleName(string name) { + var index = name.IndexOf("`", StringComparison.Ordinal); + if (index != -1) + name = name.Remove(index); + return name; + } + + // Get rid of generic backticks and invalid characters + public string CSharpBaseName => unmangleName(base.Name).ToCIdentifier(); + + // C# colloquial name of the type (if available) + public override string CSharpName { + get { + if (HasElementType) { + var n = ElementType.CSharpName; + if (IsByRef) + n = "ref " + n; + if (IsArray) + n += "[" + new string(',', GetArrayRank() - 1) + "]"; + if (IsPointer) + n += "*"; + return n; + } else { + var s = Namespace + "." + base.Name; + var i = Il2CppConstants.FullNameTypeString.IndexOf(s); + var n = (i != -1 ? Il2CppConstants.CSharpTypeString[i] : CSharpBaseName); + var ga = GetGenericArguments(); + if (ga.Any()) + n += "<" + string.Join(", ", ga.Select(x => x.CSharpName)) + ">"; + if (s == "System.Nullable`1" && ga.Any()) + n = ga[0].CSharpName + "?"; + return n; + } + } + } + + // C# name as it would be written in a type declaration + public string GetCSharpTypeDeclarationName(bool includeVariance = false) { + if (HasElementType) { + var n = ElementType.GetCSharpTypeDeclarationName(); + if (IsByRef) + n = "ref " + n; + if (IsArray) + n += "[" + new string(',', GetArrayRank() - 1) + "]"; + if (IsPointer) + n += "*"; + return n; + } else { + var n = CSharpBaseName; + var ga = IsNested ? GetGenericArguments().Where(p => DeclaringType.GetGenericArguments().All(dp => dp.Name != p.Name)) : GetGenericArguments(); + if (ga.Any()) + n += "<" + string.Join(", ", ga.Select(x => (!x.IsGenericTypeParameter ? x.Namespace + "." : "") + x.GetCSharpTypeDeclarationName(includeVariance: true))) + ">"; + if (includeVariance) { + if ((GenericParameterAttributes & GenericParameterAttributes.Covariant) == GenericParameterAttributes.Covariant) + n = "out " + n; + if ((GenericParameterAttributes & GenericParameterAttributes.Contravariant) == GenericParameterAttributes.Contravariant) + n = "in " + n; + } + return n; + } + } + + // Display name of object + public override string Name { + get { + if (IsGenericParameter) + return base.Name; + if (HasElementType) { + var n = ElementType.Name; + if (IsArray) + n += "[" + new string(',', GetArrayRank() - 1) + "]"; + if (IsByRef) + n += "&"; + if (IsPointer) + n += "*"; + return n; + } else { + /* XXX This is not exactly accurate to C# Type.Name: + * Type.Name should be the bare name (with & * [] suffixes) + * but without nested types or generic arguments */ + var n = base.Name; + if (DeclaringType != null) + n = DeclaringType.Name + "+" + n; + var ga = GetGenericArguments(); + if (ga.Any()) + n += "[" + string.Join(",", ga.Select(x => x.Namespace != Namespace ? x.FullName ?? x.Name : x.Name)) + "]"; + return n; + } + } + } + + // Type name including namespace + // Fully qualified generic type names from the C# compiler use backtick and arity rather than a list of generic arguments + public string FullName { + get { + if (IsGenericParameter) + return null; + if (HasElementType) { + var n = ElementType.FullName; + if (n == null) + return null; + if (IsArray) + n += "[" + new string(',', GetArrayRank() - 1) + "]"; + if (IsByRef) + n += "&"; + if (IsPointer) + n += "*"; + return n; + } else { + var n = base.Name; + if (DeclaringType != null) + n = DeclaringType.FullName + "+" + n; + else if (Namespace.Length > 0) + n = Namespace + "." + n; + return n; + } + } + } + + // Returns the minimally qualified type name required to refer to this type within the specified 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; + + // This is the scope in which this type is currently being used + // If Scope.Current is null, our scope is at the assembly level + var usingScope = scope.Current?.FullName.Replace('+', '.') ?? ""; + + // This is the scope in which this type's definition is located + var declaringScope = DeclaringType?.FullName.Replace('+', '.') ?? Namespace; + + // Are we in the same scope as the scope the type is defined in? Save ourselves a bunch of work if so + if (usingScope == declaringScope) + return base.Name; + + // We're also in the same scope the type is defined in if we're looking for a nested type + // that is declared in a type we derive from + for (var b = scope.Current?.BaseType; b != null; b = b.BaseType) + if (b.FullName.Replace('+', '.') == declaringScope) + return base.Name; + + // Find first difference in the declaring scope from the using scope, moving one namespace/type name at a time + var diff = 1; + usingScope += "."; + declaringScope += "."; + while (usingScope.IndexOf('.', diff) == declaringScope.IndexOf('.', diff) + && usingScope.IndexOf('.', diff) != -1 + && usingScope.Substring(0, usingScope.IndexOf('.', diff)) + == declaringScope.Substring(0, declaringScope.IndexOf('.', diff))) + diff = usingScope.IndexOf('.', diff) + 1; + usingScope = usingScope.Remove(usingScope.Length - 1); + declaringScope = declaringScope.Remove(declaringScope.Length - 1); + + // This is the mutual root namespace and optionally nested types that the two scopes share + var mutualRootScope = usingScope.Substring(0, diff - 1); + + // Determine if the using scope is a child of the declaring scope (always a child if declaring scope is empty) + var usingScopeIsChildOfDeclaringScope = string.IsNullOrEmpty(declaringScope) || (usingScope + ".").StartsWith(declaringScope + "."); + + // Determine using directive to use + var usingDirective = + + // If the scope of usage is inside the scope in which the type is declared, no additional scope is needed + // but we still need to check for ancestor conflicts below + usingScopeIsChildOfDeclaringScope ? declaringScope + + // Check to see if there is a namespace in our using directives which brings this type into scope + // Sort by descending order of length to search the deepest namespaces first + : scope.Namespaces?.OrderByDescending(n => n.Length).FirstOrDefault(n => declaringScope == n || declaringScope.StartsWith(n + ".")); + + // minimallyScopedName will eventually contain the least qualified name needed to access the type + // Initially we set it as follows: + // - The non-mutual part of the declaring scope if there is a mutual root scope + // - The fully-qualified type name if there is no mutual root scope + // - The leaf name if the declaring scope and mutual root scope are the same + // The first two must be checked in this order to avoid a . at the start + // when the mutual root scope and declaring scope are both empty + var minimallyScopedName = + declaringScope == mutualRootScope ? base.Name : + string.IsNullOrEmpty(mutualRootScope) ? declaringScope + '.' + base.Name : + declaringScope.Substring(mutualRootScope.Length + 1) + '.' + base.Name; + + // Find the outermost type name if the wanted type is a nested type (if we need it below) + string outerTypeName = ""; + if (!usingScopeIsChildOfDeclaringScope) + for (var d = this; d != null; d = d.DeclaringType) + outerTypeName = d.BaseName; + + // Are there any ancestor nested types or namespaces in the using scope with the same name as the wanted type's unqualified name? + // If so, the ancestor name will hide the type we are trying to reference, so we need to provide a higher-level scope + + // If the using scope is a child of the declaring scope, we can try every parent scope until we find one that doesn't hide the type + // Otherwise, we just try the unqualified outer (least nested) type name to make sure it's accessible + // and revert to the fully qualified name if it's hidden + var nsAndTypeHierarchy = usingScopeIsChildOfDeclaringScope ? + usingDirective.Split('.').Append(minimallyScopedName).ToArray() + : new[] { outerTypeName }; + + var hidden = true; + var foundTypeInAncestorScope = false; + string testTypeName = ""; + + for (var depth = nsAndTypeHierarchy.Length - 1; depth >= 0 && hidden; depth--) { + testTypeName = nsAndTypeHierarchy[depth] + (testTypeName.Length > 0 ? "." : "") + testTypeName; + + hidden = false; + for (var d = scope.Current; d != null && !hidden && !foundTypeInAncestorScope; d = d.DeclaringType) { + // If neither condition is true, the wanted type is not hidden by the type we are testing + foundTypeInAncestorScope = d.FullName == FullName; + hidden = !foundTypeInAncestorScope && d.BaseName == testTypeName; + } + + // We found the shortest non-hidden scope we can use + // For a child scope, use the shortest found scope + // Otherwise, we've confirmed the outer nested type name is not hidden so go ahead and use the nested type name without a namespace + if (!hidden) + minimallyScopedName = usingScopeIsChildOfDeclaringScope ? testTypeName : Name.Replace('+', '.'); + + // If the wanted type is an unhidden ancestor, we don't need any additional scope at all + if (foundTypeInAncestorScope) + minimallyScopedName = base.Name; + } + + // If there are multiple using directives that would allow the same minimally scoped name to be used, + // then the minimally scoped name is ambiguous and we can't use it + // Note that if the wanted type is an unhidden outer class relative to the using scope, this takes precedence and there can be no ambiguity + if (!foundTypeInAncestorScope) { + // Only test the outermost type name + outerTypeName = minimallyScopedName.Split('.')[0]; + + // Take matching type names from all namespaces in scope + var matchingNamespaces = scope.Namespaces? + .Where(n => Assembly.Model.TypesByFullName.ContainsKey(n + "." + outerTypeName)).ToList() ?? new List(); + + // The global namespace is in scope so take every matching type from that too + if (Assembly.Model.TypesByFullName.ContainsKey(outerTypeName)) + matchingNamespaces.Add(""); + + // More than one possible matching namespace? If so, the type reference is ambiguous + if (matchingNamespaces.Count > 1) { + // TODO: This can be improved to cut off a new mutual root that doesn't cause ambiguity + minimallyScopedName = usedType; + } + + // No matching namespaces, not hidden, no mutual root scope in the file and no using directive? + // If so, the type's namespace is completely out of scope so use the fully-qualified type name + if (matchingNamespaces.Count == 0 && !hidden && string.IsNullOrEmpty(mutualRootScope) && usingDirective == null) + minimallyScopedName = usedType; + } + + // Finally, check if the selected name has ambiguity with any available namespaces in the current scope + // If so, use the full name with the mutual root scope cut off from the start + var checkNamespaces = scope.Namespaces? + .Select(ns => (!string.IsNullOrEmpty(ns)? ns + "." : "") + minimallyScopedName).ToList() ?? new List(); + + if (Assembly.Model.Namespaces.Intersect(checkNamespaces).Any()) + minimallyScopedName = mutualRootScope.Length > 0 ? usedType.Substring(mutualRootScope.Length + 1) : usedType; + + // Check current namespace and all ancestors too + else { + checkNamespaces.Clear(); + var ancestorUsingScope = "." + usingScope; + while (ancestorUsingScope.IndexOf(".", StringComparison.Ordinal) != -1) { + ancestorUsingScope = ancestorUsingScope.Substring(0, ancestorUsingScope.LastIndexOf(".", StringComparison.Ordinal)); + checkNamespaces.Add((ancestorUsingScope.Length > 0 ? ancestorUsingScope.Substring(1) + "." : "") + minimallyScopedName); + } + + if (Assembly.Model.Namespaces.Intersect(checkNamespaces).Any()) + minimallyScopedName = mutualRootScope.Length > 0 ? usedType.Substring(mutualRootScope.Length + 1) : "global::" + usedType; + } + + // If the final name starts with ".", it's a reference to the global namespace (ie. unnamed root namespace) + if (minimallyScopedName.StartsWith(".")) + minimallyScopedName = "global::" + minimallyScopedName.Substring(1); + + return minimallyScopedName; + } + + // C#-friendly type name as it should be used in the scope of a given type + public string GetScopedCSharpName(Scope usingScope = null, bool omitRef = false, bool isPartOfTypeDeclaration = false) { + // Unscoped name if no using scope specified + if (usingScope == null) + return CSharpName; + + // Generic parameters don't have a scope + if (IsGenericParameter) + return CSharpName; + + var s = Namespace + "." + base.Name; + + // 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); + n = unmangleName(n).ToCIdentifier(allowScopeQualifiers: true); + + // Generic type parameters and type arguments + // Inheriting from a base class or implementing an interface use the type's declaring scope, not the type's scope itself + // for generic type parameters + var outerScope = usingScope; + if (isPartOfTypeDeclaration) + outerScope = new Scope { Current = usingScope.Current?.DeclaringType, Namespaces = usingScope.Namespaces }; + + var g = string.Join(", ", getGenericTypeParameters(usingScope).Select(x => x.GetScopedCSharpName(outerScope))); + if (!string.IsNullOrEmpty(g)) + n += "<" + g + ">"; + + // Nullable types + if (s == "System.Nullable`1" && GetGenericArguments().Any()) + n = GetGenericArguments()[0].GetScopedCSharpName(usingScope) + "?"; + + // Arrays, pointers, references + if (HasElementType) + n = ElementType.GetScopedCSharpName(usingScope); + + return (IsByRef && !omitRef ? "ref " : "") + n + (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + (IsPointer ? "*" : ""); + } + #endregion + + // Get the generic type parameters for a specific usage of this type based on its scope, + // or all generic type parameters if no scope specified + private IEnumerable getGenericTypeParameters(Scope scope = null) { + var ga = GetGenericArguments(); + + // If no scope or empty scope specified, or no type parameters, stop here + if (scope?.Current == null || !ga.Any()) + return ga; + + // In order to elide generic type parameters, the using scope must be a parent of the declaring scope + // Determine if the using scope is a parent of the declaring scope (always a child if using scope is empty) + var usingScopeIsParent = false; + for (var s = DeclaringType; s != null && !usingScopeIsParent; s = s.DeclaringType) + if (s == scope.Current) + usingScopeIsParent = true; + + if (!usingScopeIsParent) + return ga; + + // Get the generic type parameters available in the using scope + // (no need to recurse because every nested type inherits all of the generic type parameters of all of its ancestors) + var gasInScope = scope.Current.GetGenericArguments(); + + // Return all of the generic type parameters this type uses minus those already in scope + return ga.Where(p => gasInScope.All(pp => pp.Name != p.Name)); + } + + public GenericParameterAttributes GenericParameterAttributes { get; } + + // Generic parameter position in list of non-concrete type parameters + // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.genericparameterposition?view=netframework-4.8 + private int genericParameterPosition; + public int GenericParameterPosition { + get => IsGenericParameter ? genericParameterPosition : throw new InvalidOperationException("The current type does not represent a type parameter"); + private set => genericParameterPosition = value; + } + + // For a generic type definition: the list of generic type parameters + // For an open generic type: a mix of generic type parameters and generic type arguments + // For a closed generic type: the list of generic type arguments + private readonly TypeInfo[] genericArguments = Array.Empty(); + + public TypeInfo[] GenericTypeParameters => IsGenericTypeDefinition ? genericArguments : Array.Empty(); + + public TypeInfo[] GenericTypeArguments => !IsGenericTypeDefinition ? genericArguments : Array.Empty(); + + // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.getgenericarguments?view=netframework-4.8 + public TypeInfo[] GetGenericArguments() => genericArguments; + + // True if an array, pointer or reference, otherwise false + // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.haselementtype?view=netframework-4.8 + public bool HasElementType => ElementType != null; + + private readonly TypeRef[] implementedInterfaceReferences; + public IEnumerable ImplementedInterfaces { + get { + if (Definition != null) + return implementedInterfaceReferences.Select(x => x.Value); + if (genericTypeDefinition != null) + return genericTypeDefinition.ImplementedInterfaces.Select(t => t.SubstituteGenericArguments(genericArguments)); + if (IsGenericParameter) + return GetGenericParameterConstraints().Where(t => t.IsInterface); + return Enumerable.Empty(); + } + } + + // Get only interfaces not inherited from base interfaces + public IEnumerable NonInheritedInterfaces => ImplementedInterfaces.Except(ImplementedInterfaces.SelectMany(t => t.ImplementedInterfaces)); + + public bool IsAbstract => (Attributes & TypeAttributes.Abstract) == TypeAttributes.Abstract; + public bool IsArray { get; } + public bool IsByRef { get; } + public bool IsClass => (Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Class; + public bool IsEnum { get; } + public bool IsGenericParameter { get; } + public bool IsGenericType { get; } + public bool IsGenericTypeDefinition => (Definition != null) && genericArguments.Any(); + public bool IsImport => (Attributes & TypeAttributes.Import) == TypeAttributes.Import; + public bool IsInterface => (Attributes & TypeAttributes.ClassSemanticsMask) == TypeAttributes.Interface; + public bool IsNested => (MemberType & MemberTypes.NestedType) == MemberTypes.NestedType; + public bool IsNestedAssembly => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedAssembly; + public bool IsNestedFamANDAssem => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamANDAssem; + public bool IsNestedFamily => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamily; + public bool IsNestedFamORAssem => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedFamORAssem; + public bool IsNestedPrivate => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPrivate; + public bool IsNestedPublic => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NestedPublic; + public bool IsNotPublic => (Attributes & TypeAttributes.VisibilityMask) == TypeAttributes.NotPublic; + public bool IsPointer { get; } + // Primitive types table: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/built-in-types-table (we exclude Object and String) + 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; + public bool IsSerializable => (Attributes & TypeAttributes.Serializable) == TypeAttributes.Serializable; + public bool IsSpecialName => (Attributes & TypeAttributes.SpecialName) == TypeAttributes.SpecialName; + public bool IsValueType => BaseType?.FullName == "System.ValueType"; + + // Helper function for determining if using this type as a field, parameter etc. requires that field or method to be declared as unsafe + public bool RequiresUnsafeContext => IsPointer || (HasElementType && ElementType.RequiresUnsafeContext); + + // May get overridden by Il2CppType-based constructor below + public override MemberTypes MemberType { get; } = MemberTypes.TypeInfo; + + private string @namespace; + public string Namespace { + get => !string.IsNullOrEmpty(@namespace) ? @namespace : DeclaringType?.Namespace ?? ""; + set => @namespace = value; + } + + // Number of dimensions of an array + private readonly int arrayRank; + public int GetArrayRank() => arrayRank; + + public string[] GetEnumNames() => IsEnum ? DeclaredFields.Where(x => x.Name != "value__").Select(x => x.Name).ToArray() : throw new InvalidOperationException("Type is not an enumeration"); + + // The underlying type of an enumeration (int by default) + private readonly TypeRef enumUnderlyingTypeReference = null; + + public TypeInfo GetEnumUnderlyingType() { + if (!IsEnum) + return null; + return enumUnderlyingTypeReference.Value; + } + + public Array GetEnumValues() => IsEnum ? DeclaredFields.Where(x => x.Name != "value__").Select(x => x.DefaultValue).ToArray() : throw new InvalidOperationException("Type is not an enumeration"); + + // Initialize type from TypeDef using specified index in metadata + public TypeInfo(int typeIndex, Assembly owner) : base(owner) { + var pkg = Assembly.Model.Package; + + Definition = pkg.TypeDefinitions[typeIndex]; + MetadataToken = (int) Definition.token; + Index = typeIndex; + Namespace = Regex.Replace(pkg.Strings[Definition.namespaceIndex], @"[^A-Za-z0-9_\-\.<>{}]", ""); + Name = pkg.Strings[Definition.nameIndex]; + + // Nested type? + if (Definition.declaringTypeIndex >= 0) { + MemberType |= MemberTypes.NestedType; + } + + // Add to global type definition list + Assembly.Model.TypesByDefinitionIndex[Index] = this; + + // Generic type definition? + if (Definition.genericContainerIndex >= 0) { + IsGenericType = true; + IsGenericParameter = false; + + // Store the generic type parameters for later instantiation + var container = pkg.GenericContainers[Definition.genericContainerIndex]; + + genericArguments = Enumerable.Range((int)container.genericParameterStart, container.type_argc) + .Select(index => Assembly.Model.GetGenericParameterType(index)).ToArray(); + genericTypeInstances = new Dictionary(new TypeArgumentsComparer()); + genericTypeInstances[genericArguments] = this; + } + + // TODO: Move this to after we've populated TypesByReferenceIndex, since FullName might touch that + Assembly.Model.TypesByFullName[FullName] = this; + + // Copy attributes + Attributes = (TypeAttributes) Definition.flags; + + // Enumerations - bit 1 of bitfield indicates this (also the baseTypeReference will be System.Enum) + if (((Definition.bitfield >> 1) & 1) == 1) { + IsEnum = true; + enumUnderlyingTypeReference = TypeRef.FromReferenceIndex(Assembly.Model, Definition.elementTypeIndex); + } + + // Pass-by-reference type + // NOTE: This should actually always evaluate to false in the current implementation + // This field is no longer present in metadata v27 + // IsByRef = Index == Definition.byrefTypeIndex; + IsByRef = false; + + // Add all implemented interfaces + implementedInterfaceReferences = new TypeRef[Definition.interfaces_count]; + for (var i = 0; i < Definition.interfaces_count; i++) + implementedInterfaceReferences[i] = TypeRef.FromReferenceIndex(Assembly.Model, pkg.InterfaceUsageIndices[Definition.interfacesStart + i]); + + // Add all nested types + declaredNestedTypes = new TypeRef[Definition.nested_type_count]; + for (var n = 0; n < Definition.nested_type_count; n++) + declaredNestedTypes[n] = TypeRef.FromDefinitionIndex(Assembly.Model, pkg.NestedTypeIndices[Definition.nestedTypesStart + n]); + + // Add all fields + declaredFields = new List(); + for (var f = Definition.fieldStart; f < Definition.fieldStart + Definition.field_count; f++) + declaredFields.Add(new FieldInfo(pkg, f, this)); + + // Add all methods + declaredConstructors = new List(); + declaredMethods = new List(); + for (var m = Definition.methodStart; m < Definition.methodStart + Definition.method_count; m++) { + var method = new MethodInfo(pkg, m, this); + if (method.Name == ConstructorInfo.ConstructorName || method.Name == ConstructorInfo.TypeConstructorName) + declaredConstructors.Add(new ConstructorInfo(pkg, m, this)); + else + declaredMethods.Add(method); + } + + // Add all properties + declaredProperties = new List(); + for (var p = Definition.propertyStart; p < Definition.propertyStart + Definition.property_count; p++) + declaredProperties.Add(new PropertyInfo(pkg, p, this)); + + // There are rare cases when explicitly implemented interface properties + // are only given as methods in the metadata. Find these and add them as properties + var eip = DeclaredMethods.Where(m => m.Name.Contains(".get_") || m.Name.Contains(".set_")) + .Except(DeclaredProperties.Select(p => p.GetMethod)) + .Except(DeclaredProperties.Select(p => p.SetMethod)); + + // Build a paired list of getters and setters + var pairedEip = new List<(MethodInfo get, MethodInfo set)>(); + foreach (var p in eip) { + // Discern property name + var n = p.Name.Replace(".get_", ".").Replace(".set_", "."); + + // Find setter with no matching getter + if (p.Name.Contains(".get_")) + if (pairedEip.FirstOrDefault(pe => pe.get == null && pe.set.Name == p.Name.Replace(".get_", ".set_")) is (MethodInfo get, MethodInfo set) method) { + pairedEip.Remove(method); + pairedEip.Add((p, method.set)); + } else + pairedEip.Add((p, null)); + + // Find getter with no matching setter + if (p.Name.Contains(".set_")) + if (pairedEip.FirstOrDefault(pe => pe.set == null && pe.get.Name == p.Name.Replace(".set_", ".get_")) is (MethodInfo get, MethodInfo set) method) { + pairedEip.Remove(method); + pairedEip.Add((method.get, p)); + } else + pairedEip.Add((null, p)); + } + + foreach (var prop in pairedEip) + declaredProperties.Add(new PropertyInfo(prop.get, prop.set, this)); + + // Add all events + declaredEvents = new List(); + for (var e = Definition.eventStart; e < Definition.eventStart + Definition.event_count; e++) + declaredEvents.Add(new EventInfo(pkg, e, this)); + + // TODO: Events have the same edge case issue as properties above, eg. PoGo 0.35.0 + } + + // Initialize a type from a concrete generic instance + private TypeInfo(TypeInfo genericTypeDef, TypeInfo[] genericArgs) : base(genericTypeDef.Assembly) { + if (!genericTypeDef.IsGenericTypeDefinition) + throw new InvalidOperationException(genericTypeDef.Name + " is not a generic type definition."); + + genericTypeDefinition = genericTypeDef; + + // Same visibility attributes as generic type definition + Attributes = genericTypeDefinition.Attributes; + + // Even though this isn't a TypeDef, we have to set this so that DeclaringType works in later references + Index = genericTypeDefinition.Index; + + // Same name as generic type definition + Namespace = genericTypeDefinition.Namespace; + Name = genericTypeDefinition.BaseName; // use BaseName to exclude the type parameters so we can supply our own + MemberType = genericTypeDefinition.MemberType; + + IsGenericParameter = false; + IsGenericType = true; + + genericArguments = genericArgs; + } + + // Substitutes the elements of an array of types for the type parameters of the current generic type definition + // and returns a TypeInfo object representing the resulting constructed type. + // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.makegenerictype?view=netframework-4.8 + public TypeInfo MakeGenericType(params TypeInfo[] typeArguments) { + if (typeArguments.Length != genericArguments.Length) { + throw new ArgumentException("The number of generic arguments provided does not match the generic type definition."); + } + + TypeInfo result; + if (genericTypeInstances.TryGetValue(typeArguments, out result)) + return result; + result = new TypeInfo(this, typeArguments); + genericTypeInstances[typeArguments] = result; + return result; + } + + public TypeInfo SubstituteGenericArguments(TypeInfo[] typeArguments, TypeInfo[] methodArguments = null) { + if (!ContainsGenericParameters) + return this; + + if (IsGenericTypeParameter) + return typeArguments[GenericParameterPosition]; + else if (IsGenericMethodParameter) + return methodArguments[GenericParameterPosition]; + else if (IsGenericTypeDefinition) + return MakeGenericType(typeArguments); + else if (HasElementType) { + var elementType = ElementType.SubstituteGenericArguments(typeArguments, methodArguments); + if (IsArray) + return elementType.MakeArrayType(GetArrayRank()); + else if (IsByRef) + return elementType.MakeByRefType(); + else if (IsPointer) + return elementType.MakePointerType(); + throw new InvalidOperationException("TypeInfo element type state is invalid!"); + } else { + var newGenericArguments = genericArguments.Select(x => x.SubstituteGenericArguments(typeArguments, methodArguments)); + return genericTypeDefinition.MakeGenericType(newGenericArguments.ToArray()); + } + } + + // Initialize a type that is a generic parameter of a generic type + // See: https://docs.microsoft.com/en-us/dotnet/api/system.type.isgenerictype?view=netframework-4.8 + public TypeInfo(TypeInfo declaringType, Il2CppGenericParameter param) : base(declaringType) { + // Same visibility attributes as declaring type + Attributes = declaringType.Attributes; + + // Same namespace as declaring type + Namespace = declaringType.Namespace; + + // Special constraints + GenericParameterAttributes = (GenericParameterAttributes)param.flags; + + // Type constraints + genericParameterConstraints = new TypeRef[param.constraintsCount]; + for (int c = 0; c < param.constraintsCount; c++) + genericParameterConstraints[c] = TypeRef.FromReferenceIndex(Assembly.Model, Assembly.Model.Package.GenericConstraintIndices[param.constraintsStart + c]); + + // Base type of object (set by default) + // TODO: ImplementedInterfaces should be set to interface types constraints + + // Name of parameter + Name = Assembly.Model.Package.Strings[param.nameIndex]; + + // Position + GenericParameterPosition = param.num; + + IsGenericParameter = true; + IsGenericType = false; + } + + // Initialize a type that is a generic parameter of a generic method + public TypeInfo(MethodBase declaringMethod, Il2CppGenericParameter param) : this(declaringMethod.DeclaringType, param) + => DeclaringMethod = declaringMethod; + + // Initialize a type that is an array of the specified type + private TypeInfo(TypeInfo elementType, int rank) : base(elementType.Assembly) { + ElementType = elementType; + IsArray = true; + + Namespace = ElementType.Namespace; + Name = ElementType.Name; + arrayRank = rank; + } + + // Initialize a type that is a reference or pointer to the specified type + private TypeInfo(TypeInfo underlyingType, bool isPointer) : base(underlyingType.Assembly) { + ElementType = underlyingType; + if (isPointer) { + IsPointer = true; + } else { + IsByRef = true; + } + + Namespace = ElementType.Namespace; + Name = ElementType.Name; + } + + public TypeInfo MakeArrayType(int rank = 1) { + TypeInfo type; + if (generatedArrayTypes.TryGetValue(rank, out type)) + return type; + type = new TypeInfo(this, rank); + generatedArrayTypes[rank] = type; + return type; + } + + public TypeInfo MakeByRefType() { + if (generatedByRefType == null) + generatedByRefType = new TypeInfo(this, isPointer: false); + return generatedByRefType; + } + + public TypeInfo MakePointerType() { + if (generatedPointerType == null) + generatedPointerType = new TypeInfo(this, isPointer: true); + return generatedPointerType; + } + + // Get all the other types directly referenced by this type (single level depth; no recursion) + public List GetAllTypeReferences() { + var refs = new HashSet(); + + // Fixed attributes + if (IsImport) + refs.Add(Assembly.Model.TypesByFullName["System.Runtime.InteropServices.ComVisibleAttribute"]); + if (IsSerializable) + refs.Add(Assembly.Model.TypesByFullName["System.SerializableAttribute"]); + + // Constructor, event, field, method, nested type, property attributes + var attrs = DeclaredMembers.SelectMany(m => m.CustomAttributes); + refs.UnionWith(attrs.SelectMany(a => a.GetAllTypeReferences())); + + // Events + refs.UnionWith(DeclaredEvents.Select(e => e.EventHandlerType)); + + // Fields + refs.UnionWith(DeclaredFields.Select(f => f.FieldType)); + + // Properties (return type of getters or argument type of setters) + refs.UnionWith(DeclaredProperties.Select(p => p.PropertyType)); + + // Nested types + refs.UnionWith(DeclaredNestedTypes); + refs.UnionWith(DeclaredNestedTypes.SelectMany(n => n.GetAllTypeReferences())); + + // Constructors + refs.UnionWith(DeclaredConstructors.SelectMany(m => m.DeclaredParameters).Select(p => p.ParameterType)); + + // Methods (includes event add/remove/raise, property get/set methods and extension methods) + refs.UnionWith(DeclaredMethods.Select(m => m.ReturnParameter.ParameterType)); + refs.UnionWith(DeclaredMethods.SelectMany(m => m.DeclaredParameters).Select(p => p.ParameterType)); + + // Method generic type parameters and constraints + refs.UnionWith(DeclaredMethods.SelectMany(m => m.GetGenericArguments())); + refs.UnionWith(DeclaredMethods.SelectMany(m => m.GetGenericArguments()) + .SelectMany(p => p.GetGenericParameterConstraints())); + + // Type declaration attributes + refs.UnionWith(CustomAttributes.SelectMany(a => a.GetAllTypeReferences())); + + // Parent type + if (BaseType != null) + refs.Add(BaseType); + + // Declaring type + if (DeclaringType != null) + refs.Add(DeclaringType); + + // Element type + if (HasElementType) + refs.Add(ElementType); + + // Enum type + if (IsEnum) + refs.Add(GetEnumUnderlyingType()); + + // Generic type definition + if (genericTypeDefinition != null) + refs.Add(genericTypeDefinition); + + // Generic type parameters and constraints + refs.UnionWith(GetGenericArguments()); + refs.UnionWith(GetGenericParameterConstraints()); + + // Generic type constraints of type parameters in generic type definition + refs.UnionWith(GenericTypeParameters.SelectMany(p => p.GetGenericParameterConstraints())); + + // Implemented interfaces + refs.UnionWith(ImplementedInterfaces); + + // Repeatedly replace arrays, pointers and references with their element types + while (refs.Any(r => r.HasElementType)) + refs = refs.Select(r => r.HasElementType ? r.ElementType : r).ToHashSet(); + + // Type arguments in generic types that may have been a field, method parameter etc. + IEnumerable genericArguments = refs.ToList(); + do { + genericArguments = genericArguments.SelectMany(r => r.GetGenericArguments()); + refs.UnionWith(genericArguments); + } while (genericArguments.Any()); + + // Remove anonymous types + refs.RemoveWhere(r => string.IsNullOrEmpty(r.FullName)); + + IEnumerable refList = refs; + + // Eliminated named duplicates (the HashSet removes instance duplicates) + refList = refList.GroupBy(r => r.FullName).Select(p => p.First()); + + // Remove System.Object + refList = refList.Where(r => r.FullName != "System.Object"); + + return refList.ToList(); + } + + public string GetAccessModifierString() => this switch + { + { IsPublic: true } => "public ", + { IsNotPublic: true } => "internal ", + + { IsNestedPublic: true } => "public ", + { IsNestedPrivate: true } => "private ", + { IsNestedFamily: true } => "protected ", + { IsNestedAssembly: true } => "internal ", + { IsNestedFamORAssem: true } => "protected internal ", + { IsNestedFamANDAssem: true } => "private protected ", + _ => throw new InvalidOperationException("Unknown type access modifier") + }; + + public string GetModifierString() { + var modifiers = new StringBuilder(GetAccessModifierString()); + + // An abstract sealed class is a static class + if (IsAbstract && IsSealed) + modifiers.Append("static "); + else { + if (IsAbstract && !IsInterface) + modifiers.Append("abstract "); + if (IsSealed && !IsValueType && !IsEnum) + modifiers.Append("sealed "); + } + if (IsInterface) + modifiers.Append("interface "); + else if (IsValueType) + modifiers.Append("struct "); + else if (IsEnum) + modifiers.Append("enum "); + else + modifiers.Append("class "); + + return modifiers.ToString(); + } + + public string GetTypeConstraintsString(Scope scope) { + if (!IsGenericParameter) + return string.Empty; + + var typeConstraints = GetGenericParameterConstraints(); + if (GenericParameterAttributes == GenericParameterAttributes.None && typeConstraints.Length == 0) + return string.Empty; + + // Check if we are in a nested type, and if so, exclude ourselves if we are a generic type parameter from the outer type + // All constraints are inherited automatically by all nested types so we only have to look at the immediate outer type + if (DeclaringMethod == null && DeclaringType.IsNested && DeclaringType.DeclaringType.GetGenericArguments().Any(p => p.Name == Name)) + return string.Empty; + + // Check if we are in an overriding method, and if so, exclude ourselves if we are a generic type parameter from the base method + // All constraints are inherited automatically by all overriding methods so we only have to look at the immediate base method + if (DeclaringMethod != null && DeclaringMethod.IsVirtual && !DeclaringMethod.IsAbstract && !DeclaringMethod.IsFinal + && (DeclaringMethod.Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.ReuseSlot) { + // Find nearest ancestor base method which has us as a generic type parameter + var method = DeclaringMethod.DeclaringType.BaseType.GetAllMethods() + .FirstOrDefault(m => m.IsHideBySig && m.IsVirtual && DeclaringMethod.SignatureEquals(m) && m.GetGenericArguments().Any(p => p.Name == Name)); + + // Stop if we are inherited from a base method + if (method != null) + return string.Empty; + } + + var constraintList = typeConstraints.Where(c => c.FullName != "System.ValueType").Select(c => c.GetScopedCSharpName(scope)).ToList(); + + // struct or class must be the first constraint specified + if ((GenericParameterAttributes & GenericParameterAttributes.NotNullableValueTypeConstraint) == GenericParameterAttributes.NotNullableValueTypeConstraint) + constraintList.Insert(0, "struct"); + if ((GenericParameterAttributes & GenericParameterAttributes.ReferenceTypeConstraint) == GenericParameterAttributes.ReferenceTypeConstraint) + constraintList.Insert(0, "class"); + + if ((GenericParameterAttributes & GenericParameterAttributes.DefaultConstructorConstraint) == GenericParameterAttributes.DefaultConstructorConstraint + && !constraintList.Contains("struct")) + // new() must be the last constraint specified + constraintList.Add("new()"); + + // Covariance/contravariance constraints can lead to an empty constraint list + if (!constraintList.Any()) + return string.Empty; + + return "where " + Name + " : " + string.Join(", ", constraintList); + } + + public override string ToString() => Name; + } } \ No newline at end of file diff --git a/Il2CppInspector.Common/Reflection/TypeModel.cs b/Il2CppInspector.Common/Reflection/TypeModel.cs index 40d7847..fe71a12 100644 --- a/Il2CppInspector.Common/Reflection/TypeModel.cs +++ b/Il2CppInspector.Common/Reflection/TypeModel.cs @@ -57,6 +57,7 @@ namespace Il2CppInspector.Reflection // List of all generated CustomAttributeData objects by their instanceIndex into AttributeTypeIndices public ConcurrentDictionary AttributesByIndices { get; } = new ConcurrentDictionary(); + public ConcurrentDictionary> AttributesByDataIndices { get; } = []; // List of unique custom attributes generators indexed by type (multiple indices above may refer to a single generator function) public Dictionary> CustomAttributeGenerators { get; } @@ -254,7 +255,7 @@ namespace Il2CppInspector.Reflection // Primitive types default: - underlyingType = getTypeDefinitionFromTypeEnum(typeRef.type); + underlyingType = GetTypeDefinitionFromTypeEnum(typeRef.type); break; } @@ -263,12 +264,20 @@ namespace Il2CppInspector.Reflection } // Basic primitive types are specified via a flag value - private TypeInfo getTypeDefinitionFromTypeEnum(Il2CppTypeEnum t) { - if ((int)t >= Il2CppConstants.FullNameTypeString.Count) - return null; + public TypeInfo GetTypeDefinitionFromTypeEnum(Il2CppTypeEnum t) + { + // IL2CPP_TYPE_IL2CPP_TYPE_INDEX is handled seperately because it has enum value 0xff + var fqn = t switch + { + Il2CppTypeEnum.IL2CPP_TYPE_IL2CPP_TYPE_INDEX => "System.Type", + _ => (int) t >= Il2CppConstants.FullNameTypeString.Count + ? null + : Il2CppConstants.FullNameTypeString[(int) t] + }; - var fqn = Il2CppConstants.FullNameTypeString[(int)t]; - return TypesByFullName[fqn]; + return fqn == null + ? null + : TypesByFullName[fqn]; } // Get a TypeRef by its virtual address @@ -311,12 +320,11 @@ namespace Il2CppInspector.Reflection if (Package.Version <= 24.0) return customAttributeIndex; - if (Package.Version >= 29) + // From v24.1 onwards, token was added to Il2CppCustomAttributeTypeRange and each Il2CppImageDefinition noted the CustomAttributeTypeRanges for the image + // v29 uses this same system but with CustomAttributeDataRanges instead + if (!Package.AttributeIndicesByToken[asm.ImageDefinition.customAttributeStart].TryGetValue((uint)token, out var index)) return -1; - // From v24.1 onwards, token was added to Il2CppCustomAttributeTypeRange and each Il2CppImageDefinition noted the CustomAttributeTypeRanges for the image - if (!Package.AttributeIndicesByToken[asm.ImageDefinition.customAttributeStart].TryGetValue((uint) token, out var index)) - return -1; return index; } diff --git a/Il2CppInspector.Common/Utils/BlobReader.cs b/Il2CppInspector.Common/Utils/BlobReader.cs index 2cbb5fa..4afd850 100644 --- a/Il2CppInspector.Common/Utils/BlobReader.cs +++ b/Il2CppInspector.Common/Utils/BlobReader.cs @@ -1,6 +1,7 @@ using NoisyCowStudios.Bin2Object; using System.Text; using System; +using System.Diagnostics; namespace Il2CppInspector.Utils; @@ -70,6 +71,10 @@ public static class BlobReader if (length == -1) break; + // This is only used in custom arguments. + // We actually want the reflection TypeInfo here, but as we do not have it yet + // we store everything in a custom array type to be changed out later in the TypeModel. + var arrayElementType = ReadEncodedTypeEnum(inspector, blob, out var arrayElementDef); var arrayElementsAreDifferent = blob.ReadByte(); @@ -79,10 +84,10 @@ public static class BlobReader for (int i = 0; i < length; i++) { var elementType = ReadEncodedTypeEnum(inspector, blob, out var elementTypeDef); - array[i] = new ConstantBlobArrayElement(elementTypeDef, GetConstantValueFromBlob(inspector, elementType, blob)); + array[i] = new ConstantBlobArrayElement(elementTypeDef, GetConstantValueFromBlob(inspector, elementType, blob), elementType); } - value = new ConstantBlobArray(arrayElementDef, array); + value = new ConstantBlobArray(arrayElementDef, array, true, arrayElementType); } else { @@ -92,7 +97,7 @@ public static class BlobReader array[i] = GetConstantValueFromBlob(inspector, arrayElementType, blob); } - value = new ConstantBlobArray(arrayElementDef, array); + value = new ConstantBlobArray(arrayElementDef, array, false, arrayElementType); } break; @@ -107,8 +112,11 @@ public static class BlobReader value = inspector.TypeReferences[index]; break; - - + case Il2CppTypeEnum.IL2CPP_TYPE_VALUETYPE: + break; + default: + Debugger.Break(); + break; } return value; @@ -123,15 +131,19 @@ public static class BlobReader if (typeEnum == Il2CppTypeEnum.IL2CPP_TYPE_ENUM) { var typeIndex = blob.ReadCompressedInt32(); - enumType = inspector.TypeDefinitions[typeIndex]; - typeEnum = inspector.TypeReferences[enumType.byvalTypeIndex].type; + var typeHandle = (uint)inspector.TypeReferences[typeIndex].datapoint; + enumType = inspector.TypeDefinitions[typeHandle]; + var elementTypeHandle = inspector.TypeReferences[enumType.elementTypeIndex].datapoint; + var elementType = inspector.TypeDefinitions[elementTypeHandle]; + + typeEnum = inspector.TypeReferences[elementType.byvalTypeIndex].type; } // This technically also handles SZARRAY (System.Array) and all others by just returning their system type return typeEnum; } - public record ConstantBlobArray(Il2CppTypeDefinition ArrayTypeDef, object[] Elements); + public record ConstantBlobArray(Il2CppTypeDefinition ArrayTypeDef, object[] Elements, bool DifferentElements, Il2CppTypeEnum ArrayTypeEnum); - public record ConstantBlobArrayElement(Il2CppTypeDefinition TypeDef, object value); + public record ConstantBlobArrayElement(Il2CppTypeDefinition TypeDef, object Value, Il2CppTypeEnum TypeEnum); } \ No newline at end of file