diff --git a/Il2CppInspector.Common/Reflection/ConstructorInfo.cs b/Il2CppInspector.Common/Reflection/ConstructorInfo.cs index da2a760..ad3843e 100644 --- a/Il2CppInspector.Common/Reflection/ConstructorInfo.cs +++ b/Il2CppInspector.Common/Reflection/ConstructorInfo.cs @@ -20,7 +20,11 @@ namespace Il2CppInspector.Reflection public ConstructorInfo(Il2CppInspector pkg, int methodIndex, TypeInfo declaringType) : base(pkg, methodIndex, declaringType) { } - public ConstructorInfo(Il2CppModel model, Il2CppMethodSpec spec, TypeInfo declaringType) : base(model, spec, declaringType) { } + public ConstructorInfo(ConstructorInfo methodDef, TypeInfo declaringType) : base(methodDef, declaringType) { } + + private ConstructorInfo(ConstructorInfo methodDef, TypeInfo[] typeArguments) : base(methodDef, typeArguments) { } + + protected override MethodBase MakeGenericMethodImpl(TypeInfo[] typeArguments) => new ConstructorInfo(this, typeArguments); public override string ToString() => DeclaringType.Name + GetFullTypeParametersString() + "(" + string.Join(", ", DeclaredParameters.Select(x => x.ParameterType.Name)) + ")"; diff --git a/Il2CppInspector.Common/Reflection/Il2CppModel.cs b/Il2CppInspector.Common/Reflection/Il2CppModel.cs index 8fbc79c..57ed022 100644 --- a/Il2CppInspector.Common/Reflection/Il2CppModel.cs +++ b/Il2CppInspector.Common/Reflection/Il2CppModel.cs @@ -98,11 +98,19 @@ namespace Il2CppInspector.Reflection declaringType = declaringType.MakeGenericType(genericArguments); } - // Concrete instance of a generic method - if (methodDefinition is MethodInfo) - GenericMethods[spec] = new MethodInfo(this, spec, declaringType); + MethodBase method; + if (methodDefinition is ConstructorInfo) + method = declaringType.GetConstructorByDefinition((ConstructorInfo)methodDefinition); else - GenericMethods[spec] = new ConstructorInfo(this, spec, declaringType); + method = declaringType.GetMethodByDefinition((MethodInfo)methodDefinition); + + if (spec.methodIndexIndex != -1) { + var genericInstance = Package.GenericInstances[spec.methodIndexIndex]; + var genericArguments = ResolveGenericArguments(genericInstance); + method = method.MakeGenericMethod(genericArguments); + } + method.VirtualAddress = Package.GetGenericMethodPointer(spec); + GenericMethods[spec] = method; } // Find all custom attribute generators (populate AttributesByIndices) (use ToList() to force evaluation) diff --git a/Il2CppInspector.Common/Reflection/MethodBase.cs b/Il2CppInspector.Common/Reflection/MethodBase.cs index a7e4c4b..0d0fd8c 100644 --- a/Il2CppInspector.Common/Reflection/MethodBase.cs +++ b/Il2CppInspector.Common/Reflection/MethodBase.cs @@ -17,7 +17,13 @@ namespace Il2CppInspector.Reflection // IL2CPP-specific data public Il2CppMethodDefinition Definition { get; } public int Index { get; } - public (ulong Start, ulong End)? VirtualAddress { get; } + public (ulong Start, ulong End)? VirtualAddress { get; set; } + // This dictionary will cache all instantiated generic methods. + // Only valid for GenericMethodDefinition - not valid on instantiated types! + private Dictionary genericMethodInstances; + + // Root method definition: the method with Definition != null + protected readonly MethodBase rootDefinition; // Method.Invoke implementation public MethodInvoker Invoker { get; set; } @@ -26,7 +32,7 @@ namespace Il2CppInspector.Reflection public MethodAttributes Attributes { get; protected set; } // Custom attributes for this member - public override IEnumerable CustomAttributes => CustomAttributeData.GetCustomAttributes(this); + public override IEnumerable CustomAttributes => CustomAttributeData.GetCustomAttributes(rootDefinition); public List DeclaredParameters { get; } = new List(); @@ -46,7 +52,7 @@ namespace Il2CppInspector.Reflection public virtual bool RequiresUnsafeContext => DeclaredParameters.Any(p => p.ParameterType.RequiresUnsafeContext); - // True if the method contains unresolved generic type parameters, or if it is a non-generic method in an open ganeric type + // True if the method contains unresolved generic type parameters, or if it is a non-generic method in an open generic type // See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.containsgenericparameters?view=netframework-4.8 public bool ContainsGenericParameters => DeclaringType.ContainsGenericParameters || genericArguments.Any(ga => ga.ContainsGenericParameters); @@ -61,9 +67,19 @@ namespace Il2CppInspector.Reflection // This was added in .NET Core 2.1 and isn't properly documented yet public bool IsConstructedGenericMethod => IsGenericMethod && !IsGenericMethodDefinition; + // Generic method definition: either a method with Definition != null, or an open method of a generic type + private readonly MethodBase genericMethodDefinition; + public MethodBase GetGenericMethodDefinition() { + if (genericMethodDefinition != null) + return genericMethodDefinition; + if (genericArguments.Any()) + return this; + throw new InvalidOperationException("This method can only be called on generic methods"); + } + // See: https://docs.microsoft.com/en-us/dotnet/api/system.reflection.methodbase.isgenericmethod?view=netframework-4.8 public bool IsGenericMethod { get; } - public bool IsGenericMethodDefinition => (Definition != null) && genericArguments.Any(); + public bool IsGenericMethodDefinition => (genericMethodDefinition == null) && genericArguments.Any(); // TODO: GetMethodBody() @@ -98,6 +114,8 @@ namespace Il2CppInspector.Reflection // Add to global method definition list Assembly.Model.MethodsByDefinitionIndex[Index] = this; + rootDefinition = this; + // Generic method definition? if (Definition.genericContainerIndex >= 0) { IsGenericMethod = true; @@ -106,6 +124,8 @@ namespace Il2CppInspector.Reflection var container = pkg.GenericContainers[Definition.genericContainerIndex]; genericArguments = Enumerable.Range((int)container.genericParameterStart, container.type_argc) .Select(index => Assembly.Model.GetGenericParameterType(index)).ToArray(); + genericMethodInstances = new Dictionary(new TypeInfo.TypeArgumentsComparer()); + genericMethodInstances[genericArguments] = this; } // Set method attributes @@ -145,30 +165,64 @@ namespace Il2CppInspector.Reflection DeclaredParameters.Add(new ParameterInfo(pkg, p, this)); } - // Initialize a method from a concrete generic method (MethodSpec) - protected MethodBase(Il2CppModel model, Il2CppMethodSpec spec, TypeInfo declaringType) : base(declaringType) { - var methodDef = model.MethodsByDefinitionIndex[spec.methodDefinitionIndex]; + protected MethodBase(MethodBase methodDef, TypeInfo declaringType) : base(declaringType) { + if (methodDef.Definition == null) + throw new ArgumentException("Argument must be a bare method definition"); + rootDefinition = methodDef; Name = methodDef.Name; Attributes = methodDef.Attributes; + VirtualAddress = methodDef.VirtualAddress; - if (spec.methodIndexIndex >= 0) { - IsGenericMethod = true; - genericArguments = model.ResolveGenericArguments(model.Package.GenericInstances[spec.methodIndexIndex]); - } else { - IsGenericMethod = methodDef.IsGenericMethod; - genericArguments = methodDef.GetGenericArguments(); - } + IsGenericMethod = methodDef.IsGenericMethod; + genericArguments = methodDef.GetGenericArguments(); var genericTypeArguments = declaringType.GetGenericArguments(); - // Substitute matching generic type parameters with concrete type arguments - DeclaredParameters = methodDef.DeclaredParameters - .Select(p => p.SubstituteGenericArguments(genericTypeArguments, genericArguments)) - .ToList(); + genericMethodInstances = new Dictionary(new TypeInfo.TypeArgumentsComparer()); + genericMethodInstances[genericArguments] = this; - VirtualAddress = model.Package.GetGenericMethodPointer(spec); + DeclaredParameters = rootDefinition.DeclaredParameters + .Select(p => p.SubstituteGenericArguments(this, genericTypeArguments, genericArguments)) + .ToList(); } + protected MethodBase(MethodBase methodDef, TypeInfo[] typeArguments) : base(methodDef.DeclaringType) { + if (!methodDef.IsGenericMethodDefinition) + throw new InvalidOperationException(methodDef.Name + " is not a generic method definition."); + + rootDefinition = methodDef.rootDefinition; + genericMethodDefinition = methodDef; + Name = methodDef.Name; + Attributes = methodDef.Attributes; + VirtualAddress = methodDef.VirtualAddress; + + IsGenericMethod = true; + genericArguments = typeArguments; + var genericTypeArguments = DeclaringType.GetGenericArguments(); + + DeclaredParameters = rootDefinition.DeclaredParameters + .Select(p => p.SubstituteGenericArguments(this, genericTypeArguments, genericArguments)) + .ToList(); + } + + // Strictly speaking, this should live in MethodInfo; constructors cannot have generic arguments. + // However, Il2Cpp unifies Constructor and Method to a much greater extent, so that's why this is + // here instead. + public MethodBase MakeGenericMethod(params TypeInfo[] typeArguments) { + if (typeArguments.Length != genericArguments.Length) { + throw new ArgumentException("The number of generic arguments provided does not match the generic type definition."); + } + + MethodBase result; + if (genericMethodInstances.TryGetValue(typeArguments, out result)) + return result; + result = MakeGenericMethodImpl(typeArguments); + genericMethodInstances[typeArguments] = result; + return result; + } + + protected abstract MethodBase MakeGenericMethodImpl(TypeInfo[] typeArguments); + public string GetAccessModifierString() => this switch { // Static constructors can not have an access level modifier { IsConstructor: true, IsStatic: true } => "", diff --git a/Il2CppInspector.Common/Reflection/MethodInfo.cs b/Il2CppInspector.Common/Reflection/MethodInfo.cs index a2a1750..ff87108 100644 --- a/Il2CppInspector.Common/Reflection/MethodInfo.cs +++ b/Il2CppInspector.Common/Reflection/MethodInfo.cs @@ -28,11 +28,18 @@ namespace Il2CppInspector.Reflection ReturnParameter = new ParameterInfo(pkg, -1, this); } - public MethodInfo(Il2CppModel model, Il2CppMethodSpec spec, TypeInfo declaringType) : base(model, spec, declaringType) { - var methodDef = model.MethodsByDefinitionIndex[spec.methodDefinitionIndex]; - ReturnParameter = ((MethodInfo)methodDef).ReturnParameter.SubstituteGenericArguments(declaringType.GetGenericArguments(), GetGenericArguments()); + public MethodInfo(MethodInfo methodDef, TypeInfo declaringType) : base(methodDef, declaringType) { + ReturnParameter = ((MethodInfo)rootDefinition).ReturnParameter + .SubstituteGenericArguments(this, DeclaringType.GetGenericArguments(), GetGenericArguments()); } + private MethodInfo(MethodInfo methodDef, TypeInfo[] typeArguments) : base(methodDef, typeArguments) { + ReturnParameter = ((MethodInfo)rootDefinition).ReturnParameter + .SubstituteGenericArguments(this, DeclaringType.GetGenericArguments(), GetGenericArguments()); + } + + protected override MethodBase MakeGenericMethodImpl(TypeInfo[] typeArguments) => new MethodInfo(this, typeArguments); + public override string ToString() => ReturnType.Name + " " + Name + GetFullTypeParametersString() + "(" + string.Join(", ", DeclaredParameters.Select(x => x.ParameterType.IsByRef? x.ParameterType.Name.TrimEnd('&') + " ByRef" : x.ParameterType.Name)) + ")"; diff --git a/Il2CppInspector.Common/Reflection/ParameterInfo.cs b/Il2CppInspector.Common/Reflection/ParameterInfo.cs index c4099ed..500bac6 100644 --- a/Il2CppInspector.Common/Reflection/ParameterInfo.cs +++ b/Il2CppInspector.Common/Reflection/ParameterInfo.cs @@ -18,11 +18,14 @@ namespace Il2CppInspector.Reflection public int Index { get; } public ulong DefaultValueMetadataAddress { get; } + // Root definition: the parameter with Definition != null + private readonly ParameterInfo rootDefinition; + // Information/flags about the parameter public ParameterAttributes Attributes { get; } // Custom attributes for this parameter - public IEnumerable CustomAttributes => CustomAttributeData.GetCustomAttributes(this); + public IEnumerable CustomAttributes => CustomAttributeData.GetCustomAttributes(rootDefinition); // True if the parameter has a default value public bool HasDefaultValue => (Attributes & ParameterAttributes.HasDefault) != 0; @@ -63,6 +66,7 @@ namespace Il2CppInspector.Reflection Definition = pkg.Params[Index]; Name = pkg.Strings[Definition.nameIndex]; + rootDefinition = this; // Handle unnamed/obfuscated parameter names if (string.IsNullOrEmpty(Name)) @@ -97,9 +101,10 @@ namespace Il2CppInspector.Reflection } // Create a concrete type parameter from a generic type parameter - public ParameterInfo(ParameterInfo generic, TypeInfo concrete) { + private ParameterInfo(ParameterInfo generic, MethodBase declaringMethod, TypeInfo concrete) { + rootDefinition = generic.rootDefinition; - DeclaringMethod = generic.DeclaringMethod; + DeclaringMethod = declaringMethod; Name = generic.Name; Position = generic.Position; Attributes = generic.Attributes; @@ -110,11 +115,11 @@ namespace Il2CppInspector.Reflection DefaultValueMetadataAddress = generic.DefaultValueMetadataAddress; } - public ParameterInfo SubstituteGenericArguments(TypeInfo[] typeArguments, TypeInfo[] methodArguments = null) { + public ParameterInfo SubstituteGenericArguments(MethodBase declaringMethod, TypeInfo[] typeArguments, TypeInfo[] methodArguments = null) { TypeInfo t = ParameterType.SubstituteGenericArguments(typeArguments, methodArguments); if (t == ParameterType) return this; - return new ParameterInfo(this, t); + return new ParameterInfo(this, declaringMethod, t); } // ref will be handled as part of the type name diff --git a/Il2CppInspector.Common/Reflection/TypeInfo.cs b/Il2CppInspector.Common/Reflection/TypeInfo.cs index 2bd49c7..fe4d2e3 100644 --- a/Il2CppInspector.Common/Reflection/TypeInfo.cs +++ b/Il2CppInspector.Common/Reflection/TypeInfo.cs @@ -7,6 +7,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Reflection; using System.Text; @@ -21,7 +22,7 @@ namespace Il2CppInspector.Reflection { // This dictionary will cache all instantiated generic types out of this definition. // Only valid for GenericTypeDefinition - not valid on instantiated types! private Dictionary genericTypeInstances; - private class TypeArgumentsComparer : EqualityComparer + public class TypeArgumentsComparer : EqualityComparer { public override bool Equals(TypeInfo[] x, TypeInfo[] y) { return ((IStructuralEquatable)x).Equals(y, StructuralComparisons.StructuralEqualityComparer); @@ -94,7 +95,20 @@ namespace Il2CppInspector.Reflection { // Custom attributes for this member public override IEnumerable CustomAttributes => CustomAttributeData.GetCustomAttributes(genericTypeDefinition ?? this); - public List DeclaredConstructors { get; } = new List(); + 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(); + } + } + public List DeclaredEvents { get; } = new List(); public List DeclaredFields { get; } = new List(); @@ -103,7 +117,19 @@ namespace Il2CppInspector.Reflection { DeclaredNestedTypes, DeclaredProperties }.SelectMany(m => m).ToList(); - public List DeclaredMethods { get; } = new List(); + 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 { @@ -142,6 +168,29 @@ namespace Il2CppInspector.Reflection { 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] == definition) + 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] == definition) + return DeclaredMethods[i]; + } + } + return definition; + } + // Get a method by its name public MethodInfo GetMethod(string name) => DeclaredMethods.FirstOrDefault(m => m.Name == name); @@ -692,12 +741,14 @@ namespace Il2CppInspector.Reflection { 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)); + declaredConstructors.Add(new ConstructorInfo(pkg, m, this)); else - DeclaredMethods.Add(method); + declaredMethods.Add(method); } // Add all properties @@ -776,7 +827,7 @@ namespace Il2CppInspector.Reflection { // 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) { + if (typeArguments.Length != genericArguments.Length) { throw new ArgumentException("The number of generic arguments provided does not match the generic type definition."); }