From 32608874cc0914e4fc510f0cb61002e6e7a24704 Mon Sep 17 00:00:00 2001 From: Katy Coe Date: Sun, 17 Nov 2019 22:27:44 +0100 Subject: [PATCH] Output: Handle scoped type name resolution conflicts (CS0104) --- Il2CppDumper/Il2CppCSharpDumper.cs | 114 +++++++++++------- Il2CppInspector/Reflection/ConstructorInfo.cs | 2 +- .../Reflection/CustomAttributeData.cs | 2 +- Il2CppInspector/Reflection/Extensions.cs | 14 ++- Il2CppInspector/Reflection/MethodBase.cs | 14 +-- Il2CppInspector/Reflection/MethodInfo.cs | 2 +- Il2CppInspector/Reflection/ParameterInfo.cs | 20 +-- Il2CppInspector/Reflection/Scope.cs | 20 +++ Il2CppInspector/Reflection/TypeInfo.cs | 101 +++++++++++++++- 9 files changed, 217 insertions(+), 72 deletions(-) create mode 100644 Il2CppInspector/Reflection/Scope.cs diff --git a/Il2CppDumper/Il2CppCSharpDumper.cs b/Il2CppDumper/Il2CppCSharpDumper.cs index 7c6a92b..5ec955d 100644 --- a/Il2CppDumper/Il2CppCSharpDumper.cs +++ b/Il2CppDumper/Il2CppCSharpDumper.cs @@ -74,6 +74,21 @@ namespace Il2CppInspector 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 + nsRefs.UnionWith(assemblies.SelectMany(a => a.CustomAttributes).Select(a => a.AttributeType.Namespace)); + + // Generate each type foreach (var type in types) { // Skip namespace and any children if requested @@ -85,7 +100,7 @@ namespace Il2CppInspector continue; // Get code - var text = generateType(type); + var text = generateType(type, nsRefs); if (string.IsNullOrEmpty(text)) continue; @@ -111,11 +126,6 @@ namespace Il2CppInspector if (!useNamespaceSyntax) code.Append($"// Namespace: {(!string.IsNullOrEmpty(type.Namespace) ? type.Namespace : "")}\n"); - // Determine namespace references (note: this may include some that aren't actually used due to output suppression in generateType() - var refs = type.GetAllTypeReferences(); - var ns = refs.Where(r => !string.IsNullOrEmpty(r.Namespace) && r.Namespace != type.Namespace).Select(r => r.Namespace); - nsRefs.UnionWith(ns); - // Append type definition code.Append(text + "\n"); @@ -131,13 +141,15 @@ namespace Il2CppInspector if (useNamespaceSyntax && !string.IsNullOrEmpty(nsContext)) code.Remove(code.Length - 1, 1).Append("}\n"); - // Determine assemblies used in this file - var assemblies = types.Select(t => t.Assembly).Distinct(); - - // Add assembly attribute namespaces to reference list + // 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)); - // Determine using directives (put System namespaces first) var usings = nsRefs.OrderBy(n => (n.StartsWith("System.") || n == "System") ? "0" + n : "1" + n); // Ensure output directory exists @@ -160,13 +172,13 @@ namespace Il2CppInspector writer.Write("\n"); // Output assembly information and attributes - writer.Write(generateAssemblyInfo(assemblies, excludedAssemblyAttributes) + "\n\n"); + writer.Write(generateAssemblyInfo(assemblies, nsRefs, excludedAssemblyAttributes) + "\n\n"); // Output type definitions writer.Write(code); } - private string generateAssemblyInfo(IEnumerable assemblies, IEnumerable excludedAttributes = null) { + private string generateAssemblyInfo(IEnumerable assemblies, IEnumerable namespaces, IEnumerable excludedAttributes = null) { var text = new StringBuilder(); foreach (var asm in assemblies) { @@ -176,14 +188,14 @@ namespace Il2CppInspector text.Append(asm.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute) .Except(excludedAttributes ?? new List()) .OrderBy(a => a.AttributeType.Name) - .ToString(attributePrefix: "assembly: ", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); + .ToString(new Scope { Current = null, Namespaces = namespaces }, attributePrefix: "assembly: ", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); if (asm.CustomAttributes.Any()) text.Append("\n"); } return text.ToString().TrimEnd(); } - private string generateType(TypeInfo type, string prefix = "") { + private string generateType(TypeInfo type, IEnumerable namespaces, string prefix = "") { // Don't output compiler-generated types if desired if (SuppressGenerated && type.GetCustomAttributes(CGAttribute).Any()) return string.Empty; @@ -192,6 +204,11 @@ namespace Il2CppInspector var usedMethods = new List(); var sb = new StringBuilder(); + var scope = new Scope { + Current = type, + Namespaces = namespaces + }; + // Fields sb.Clear(); if (!type.IsEnum) { @@ -203,7 +220,8 @@ namespace Il2CppInspector sb.Append(prefix + "\t[NonSerialized]\n"); // Attributes - sb.Append(field.CustomAttributes.Where(a => a.AttributeType.FullName != FBAttribute).OrderBy(a => a.AttributeType.Name).ToString(prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); + sb.Append(field.CustomAttributes.Where(a => a.AttributeType.FullName != FBAttribute).OrderBy(a => a.AttributeType.Name) + .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); sb.Append(prefix + "\t"); sb.Append(field.GetModifierString()); @@ -211,11 +229,11 @@ namespace Il2CppInspector if (field.GetCustomAttributes(FBAttribute).Any()) { if (!SuppressMetadata) sb.Append($"/* {field.GetCustomAttributes(FBAttribute)[0].VirtualAddress.ToAddressString()} */ "); - sb.Append($"{field.FieldType.DeclaredFields[0].FieldType.CSharpName} {field.Name}[0]"); // FixedElementField + sb.Append($"{field.FieldType.DeclaredFields[0].FieldType.GetScopedCSharpName(scope)} {field.Name}[0]"); // FixedElementField } // Regular fields else - sb.Append($"{field.FieldType.CSharpName} {field.Name}"); + sb.Append($"{field.FieldType.GetScopedCSharpName(scope)} {field.Name}"); if (field.HasDefaultValue) sb.Append($" = {field.DefaultValueString}"); sb.Append(";"); @@ -234,14 +252,15 @@ namespace Il2CppInspector sb.Clear(); foreach (var prop in type.DeclaredProperties) { // Attributes - sb.Append(prop.CustomAttributes.OrderBy(a => a.AttributeType.Name).ToString(prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); + sb.Append(prop.CustomAttributes.OrderBy(a => a.AttributeType.Name) + .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); // 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; var primary = getAccess >= setAccess ? prop.GetMethod : prop.SetMethod; - sb.Append($"{prefix}\t{primary.GetModifierString()}{prop.PropertyType.CSharpName} "); + sb.Append($"{prefix}\t{primary.GetModifierString(scope)}{prop.PropertyType.GetScopedCSharpName(scope)} "); // Non-indexer if ((!prop.CanRead || !prop.GetMethod.DeclaredParameters.Any()) && (!prop.CanWrite || prop.SetMethod.DeclaredParameters.Count == 1)) @@ -249,13 +268,13 @@ namespace Il2CppInspector // Indexer else sb.Append("this[" + string.Join(", ", primary.DeclaredParameters.SkipLast(getAccess >= setAccess? 0 : 1) - .Select(p => p.GetParameterString(!SuppressMetadata, CommentAttributes))) + "] { "); + .Select(p => p.GetParameterString(scope, !SuppressMetadata, CommentAttributes))) + "] { "); sb.Append((prop.CanRead? prop.GetMethod.CustomAttributes.Where(a => !SuppressGenerated || a.AttributeType.FullName != CGAttribute) - .ToString(inline: true, emitPointer: !SuppressMetadata, mustCompile: CommentAttributes) + .ToString(scope, inline: true, emitPointer: !SuppressMetadata, mustCompile: CommentAttributes) + (getAccess < setAccess? prop.GetMethod.GetAccessModifierString() : "") + "get; " : "") + (prop.CanWrite? prop.SetMethod.CustomAttributes.Where(a => !SuppressGenerated || a.AttributeType.FullName != CGAttribute) - .ToString(inline: true, emitPointer: !SuppressMetadata, mustCompile: CommentAttributes) + .ToString(scope, inline: true, emitPointer: !SuppressMetadata, mustCompile: CommentAttributes) + (setAccess < getAccess? prop.SetMethod.GetAccessModifierString() : "") + "set; " : "") + "}"); if (!SuppressMetadata) { if ((prop.CanRead && prop.GetMethod.VirtualAddress != null) || (prop.CanWrite && prop.SetMethod.VirtualAddress != null)) @@ -274,10 +293,11 @@ namespace Il2CppInspector sb.Clear(); foreach (var evt in type.DeclaredEvents) { // Attributes - sb.Append(evt.CustomAttributes.OrderBy(a => a.AttributeType.Name).ToString(prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); + sb.Append(evt.CustomAttributes.OrderBy(a => a.AttributeType.Name) + .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); - string modifiers = evt.AddMethod?.GetModifierString(); - sb.Append($"{prefix}\t{modifiers}event {evt.EventHandlerType.CSharpName} {evt.Name} {{\n"); + string modifiers = evt.AddMethod?.GetModifierString(scope); + sb.Append($"{prefix}\t{modifiers}event {evt.EventHandlerType.GetScopedCSharpName(scope)} {evt.Name} {{\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); @@ -290,16 +310,18 @@ namespace Il2CppInspector codeBlocks.Add("Events", sb.ToString()); // Nested types - codeBlocks.Add("Nested types", string.Join("\n", type.DeclaredNestedTypes.Select(n => generateType(n, prefix + "\t")).Where(c => !string.IsNullOrEmpty(c)))); + codeBlocks.Add("Nested types", string.Join("\n", type.DeclaredNestedTypes + .Select(n => generateType(n, namespaces, prefix + "\t")).Where(c => !string.IsNullOrEmpty(c)))); // Constructors sb.Clear(); foreach (var method in type.DeclaredConstructors) { // Attributes - sb.Append(method.CustomAttributes.OrderBy(a => a.AttributeType.Name).ToString(prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); + sb.Append(method.CustomAttributes.OrderBy(a => a.AttributeType.Name) + .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); - sb.Append($"{prefix}\t{method.GetModifierString()}{method.DeclaringType.UnmangledBaseName}{method.GetTypeParametersString()}("); - sb.Append(method.GetParametersString(!SuppressMetadata) + ")" + (method.IsAbstract? ";" : @" {}")); + sb.Append($"{prefix}\t{method.GetModifierString(scope)}{method.DeclaringType.UnmangledBaseName}{method.GetTypeParametersString(scope)}("); + sb.Append(method.GetParametersString(scope, !SuppressMetadata) + ")" + (method.IsAbstract? ";" : @" {}")); sb.Append((!SuppressMetadata && method.VirtualAddress != null ? $" // {method.VirtualAddress.ToAddressString()}" : "") + "\n"); } codeBlocks.Add("Constructors", sb.ToString()); @@ -307,11 +329,11 @@ namespace Il2CppInspector // 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", string.Concat(methods.Select(m => generateMethod(m, prefix)))); + codeBlocks.Add("Methods", string.Concat(methods.Select(m => generateMethod(m, scope, prefix)))); usedMethods.AddRange(methods); // Extension methods - codeBlocks.Add("Extension methods", string.Concat(type.DeclaredMethods.Except(usedMethods).Select(m => generateMethod(m, prefix)))); + codeBlocks.Add("Extension methods", string.Concat(type.DeclaredMethods.Except(usedMethods).Select(m => generateMethod(m, scope, prefix)))); // Type declaration sb.Clear(); @@ -324,7 +346,7 @@ namespace Il2CppInspector // TODO: 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 && a.AttributeType.FullName != ExtAttribute) - .OrderBy(a => a.AttributeType.Name).ToString(prefix, emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); + .OrderBy(a => a.AttributeType.Name).ToString(scope, prefix, emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); // Roll-up multicast delegates to use the 'delegate' syntactic sugar if (type.IsClass && type.IsSealed && type.BaseType?.FullName == "System.MulticastDelegate") { @@ -335,8 +357,8 @@ namespace Il2CppInspector //sb.Append(del.ReturnType.CustomAttributes.ToString(prefix, "return: ", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); if (del.RequiresUnsafeContext) sb.Append("unsafe "); - sb.Append($"delegate {del.ReturnType.CSharpName} {type.CSharpTypeDeclarationName}("); - sb.Append(del.GetParametersString(!SuppressMetadata) + ");"); + sb.Append($"delegate {del.ReturnType.GetScopedCSharpName(scope)} {type.CSharpTypeDeclarationName}("); + sb.Append(del.GetParametersString(scope, !SuppressMetadata) + ");"); if (!SuppressMetadata) sb.Append($" // TypeDefIndex: {type.Index}; {del.VirtualAddress.ToAddressString()}"); sb.Append("\n"); @@ -345,11 +367,11 @@ namespace Il2CppInspector sb.Append(prefix + type.GetModifierString()); - var @base = type.ImplementedInterfaces.Select(x => x.CSharpName).ToList(); + var @base = type.ImplementedInterfaces.Select(x => x.GetScopedCSharpName(scope)).ToList(); if (type.BaseType != null && type.BaseType.FullName != "System.Object" && type.BaseType.FullName != "System.ValueType" && !type.IsEnum) - @base.Insert(0, type.BaseType.CSharpName); + @base.Insert(0, type.BaseType.GetScopedCSharpName(scope)); if (type.IsEnum && type.GetEnumUnderlyingType().FullName != "System.Int32") // enums derive from int by default - @base.Insert(0, type.GetEnumUnderlyingType().CSharpName); + @base.Insert(0, type.GetEnumUnderlyingType().GetScopedCSharpName(scope)); var baseText = @base.Count > 0 ? " : " + string.Join(", ", @base) : string.Empty; sb.Append($"{type.CSharpTypeDeclarationName}{baseText}"); @@ -359,7 +381,7 @@ namespace Il2CppInspector if (type.GenericTypeParameters != null) foreach (var gp in type.GenericTypeParameters) { - var constraint = gp.GetTypeConstraintsString(); + var constraint = gp.GetTypeConstraintsString(scope); if (constraint != string.Empty) sb.Append($"{prefix}\t{constraint}\n"); } @@ -380,7 +402,7 @@ namespace Il2CppInspector return sb.ToString(); } - private string generateMethod(MethodInfo method, string prefix) { + private string generateMethod(MethodInfo method, Scope scope, string prefix) { if (SuppressGenerated && method.GetCustomAttributes(CGAttribute).Any()) return string.Empty; @@ -388,20 +410,20 @@ namespace Il2CppInspector // Attributes writer.Append(method.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute).OrderBy(a => a.AttributeType.Name) - .ToString(prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); + .ToString(scope, prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); // 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()}"); + writer.Append($"{prefix}\t{method.GetModifierString(scope)}"); if (method.Name != "op_Implicit" && method.Name != "op_Explicit") - writer.Append($"{method.ReturnParameter.GetReturnParameterString()} {method.CSharpName}{method.GetTypeParametersString()}"); + writer.Append($"{method.ReturnParameter.GetReturnParameterString(scope)} {method.CSharpName}{method.GetTypeParametersString(scope)}"); else - writer.Append($"{method.CSharpName}{method.ReturnType.CSharpName}"); - writer.Append("(" + method.GetParametersString(!SuppressMetadata) + ")"); + writer.Append($"{method.CSharpName}{method.ReturnType.GetScopedCSharpName(scope)}"); + writer.Append("(" + method.GetParametersString(scope, !SuppressMetadata) + ")"); if (method.GenericTypeParameters != null) foreach (var gp in method.GenericTypeParameters) { - var constraint = gp.GetTypeConstraintsString(); + var constraint = gp.GetTypeConstraintsString(scope); if (constraint != string.Empty) writer.Append($"\n{prefix}\t\t{constraint}"); } diff --git a/Il2CppInspector/Reflection/ConstructorInfo.cs b/Il2CppInspector/Reflection/ConstructorInfo.cs index 7d12308..d5d9d94 100644 --- a/Il2CppInspector/Reflection/ConstructorInfo.cs +++ b/Il2CppInspector/Reflection/ConstructorInfo.cs @@ -22,7 +22,7 @@ namespace Il2CppInspector.Reflection public override string ToString() => DeclaringType.Name + "(" + string.Join(", ", DeclaredParameters.Select(x => x.ParameterType.Name)) + ")"; - public override string GetSignatureString() => Name + GetTypeParametersString() + public override string GetSignatureString(Scope usingScope) => Name + GetTypeParametersString(usingScope) + "(" + string.Join(",", DeclaredParameters.Select(x => x.GetSignatureString())) + ")"; } } \ No newline at end of file diff --git a/Il2CppInspector/Reflection/CustomAttributeData.cs b/Il2CppInspector/Reflection/CustomAttributeData.cs index 3486e9f..95438a5 100644 --- a/Il2CppInspector/Reflection/CustomAttributeData.cs +++ b/Il2CppInspector/Reflection/CustomAttributeData.cs @@ -61,7 +61,7 @@ namespace Il2CppInspector.Reflection public static IList GetCustomAttributes(EventInfo evt) => getCustomAttributes(evt.Assembly, evt.Definition.token, evt.Definition.customAttributeIndex); public static IList GetCustomAttributes(FieldInfo field) => getCustomAttributes(field.Assembly, field.Definition.token, field.Definition.customAttributeIndex); public static IList GetCustomAttributes(MethodBase method) => getCustomAttributes(method.Assembly, method.Definition.token, method.Definition.customAttributeIndex); - public static IList GetCustomAttributes(ParameterInfo param) => getCustomAttributes(param.Member.Assembly, param.Definition.token, param.Definition.customAttributeIndex); + public static IList GetCustomAttributes(ParameterInfo param) => getCustomAttributes(param.DeclaringMethod.Assembly, param.Definition.token, param.Definition.customAttributeIndex); public static IList GetCustomAttributes(PropertyInfo prop) => getCustomAttributes(prop.Assembly, prop.Definition.token, prop.Definition.customAttributeIndex); public static IList GetCustomAttributes(TypeInfo type) => getCustomAttributes(type.Assembly, type.Definition.token, type.Definition.customAttributeIndex); } diff --git a/Il2CppInspector/Reflection/Extensions.cs b/Il2CppInspector/Reflection/Extensions.cs index ae42946..f6d8879 100644 --- a/Il2CppInspector/Reflection/Extensions.cs +++ b/Il2CppInspector/Reflection/Extensions.cs @@ -1,4 +1,10 @@ -using System; +/* + Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + + All rights reserved. +*/ + +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -8,8 +14,8 @@ namespace Il2CppInspector.Reflection public static class Extensions { // Convert a list of CustomAttributeData objects into C#-friendly attribute usages - public static string ToString(this IEnumerable attributes, string linePrefix = "", string attributePrefix = "", - bool inline = false, bool emitPointer = false, bool mustCompile = false) { + public static string ToString(this IEnumerable attributes, Scope scope = null, + string linePrefix = "", string attributePrefix = "", bool inline = false, bool emitPointer = false, bool mustCompile = false) { var sb = new StringBuilder(); foreach (var cad in attributes) { @@ -20,7 +26,7 @@ namespace Il2CppInspector.Reflection var commentStart = mustCompile && !parameterlessConstructor? inline? "/* " : "// " : ""; var commentEnd = commentStart.Length > 0 && inline? " */" : ""; - var name = cad.AttributeType.CSharpName; + var name = cad.AttributeType.GetScopedCSharpName(scope); var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal); if (suffix != -1) name = name[..suffix]; diff --git a/Il2CppInspector/Reflection/MethodBase.cs b/Il2CppInspector/Reflection/MethodBase.cs index af7bf74..ffccff1 100644 --- a/Il2CppInspector/Reflection/MethodBase.cs +++ b/Il2CppInspector/Reflection/MethodBase.cs @@ -128,7 +128,7 @@ namespace Il2CppInspector.Reflection _ => "" }; - public string GetModifierString() { + public string GetModifierString(Scope usingScope) { // Interface methods and properties have no visible modifiers (they are always declared 'public abstract') if (DeclaringType.IsInterface) return string.Empty; @@ -151,7 +151,7 @@ namespace Il2CppInspector.Reflection modifiers.Append("extern "); // Method hiding - if ((DeclaringType.BaseType?.GetAllMethods().Any(m => m.GetSignatureString() == GetSignatureString() && m.IsHideBySig) ?? false) + if ((DeclaringType.BaseType?.GetAllMethods().Any(m => m.GetSignatureString(usingScope) == GetSignatureString(usingScope) && m.IsHideBySig) ?? false) && (((Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.ReuseSlot && !IsVirtual) || (Attributes & MethodAttributes.VtableLayoutMask) == MethodAttributes.NewSlot)) modifiers.Append($"new "); @@ -166,13 +166,13 @@ namespace Il2CppInspector.Reflection } // Get C# syntax-friendly list of parameters - public string GetParametersString(bool emitPointer = false, bool commentAttributes = false) - => string.Join(", ", DeclaredParameters.Select(p => p.GetParameterString(emitPointer, commentAttributes))); + public string GetParametersString(Scope usingScope, bool emitPointer = false, bool commentAttributes = false) + => string.Join(", ", DeclaredParameters.Select(p => p.GetParameterString(usingScope, emitPointer, commentAttributes))); - public string GetTypeParametersString() => GenericTypeParameters == null? "" : - "<" + string.Join(", ", GenericTypeParameters.Select(p => p.CSharpName)) + ">"; + public string GetTypeParametersString(Scope usingScope) => GenericTypeParameters == null? "" : + "<" + string.Join(", ", GenericTypeParameters.Select(p => p.GetScopedCSharpName(usingScope))) + ">"; - public abstract string GetSignatureString(); + public abstract string GetSignatureString(Scope usingScope); // List of operator overload metadata names // https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/operator-overloads diff --git a/Il2CppInspector/Reflection/MethodInfo.cs b/Il2CppInspector/Reflection/MethodInfo.cs index f5e67e3..fada3ee 100644 --- a/Il2CppInspector/Reflection/MethodInfo.cs +++ b/Il2CppInspector/Reflection/MethodInfo.cs @@ -33,7 +33,7 @@ namespace Il2CppInspector.Reflection // TODO: Generic arguments (and on ConstructorInfo) public override string ToString() => ReturnType.Name + " " + Name + "(" + string.Join(", ", DeclaredParameters.Select(x => x.ParameterType.Name)) + ")"; - public override string GetSignatureString() => ReturnParameter.GetSignatureString() + " " + Name + GetTypeParametersString() + public override string GetSignatureString(Scope usingScope) => ReturnParameter.GetSignatureString() + " " + Name + GetTypeParametersString(usingScope) + "(" + string.Join(",", DeclaredParameters.Select(x => x.GetSignatureString())) + ")"; } } \ No newline at end of file diff --git a/Il2CppInspector/Reflection/ParameterInfo.cs b/Il2CppInspector/Reflection/ParameterInfo.cs index 15ce59e..173a21b 100644 --- a/Il2CppInspector/Reflection/ParameterInfo.cs +++ b/Il2CppInspector/Reflection/ParameterInfo.cs @@ -37,15 +37,15 @@ namespace Il2CppInspector.Reflection public bool IsOut => (Attributes & ParameterAttributes.Out) != 0; public bool IsRetval => (Attributes & ParameterAttributes.Retval) != 0; - // The member in which the parameter is defined - public MemberInfo Member { get; } + // The method in which the parameter is defined + public MethodBase DeclaringMethod { get; } // Name of parameter public string Name { get; } // Type of this parameter private readonly int paramTypeUsage; - public TypeInfo ParameterType => Member.Assembly.Model.GetTypeFromUsage(paramTypeUsage, MemberTypes.TypeInfo); + public TypeInfo ParameterType => DeclaringMethod.Assembly.Model.GetTypeFromUsage(paramTypeUsage, MemberTypes.TypeInfo); // Zero-indexed position of the parameter in parameter list public int Position { get; } @@ -53,7 +53,7 @@ namespace Il2CppInspector.Reflection // Create a parameter. Specify paramIndex == -1 for a return type parameter public ParameterInfo(Il2CppInspector pkg, int paramIndex, MethodBase declaringMethod) { Index = paramIndex; - Member = declaringMethod; + DeclaringMethod = declaringMethod; if (paramIndex == -1) { Position = -1; @@ -96,15 +96,15 @@ namespace Il2CppInspector.Reflection + (IsByRef? "ref " : "") + (IsOut? "out " : ""); - private string getCSharpSignatureString() => $"{GetModifierString()}{ParameterType.CSharpName}"; + private string getCSharpSignatureString(Scope scope) => $"{GetModifierString()}{ParameterType.GetScopedCSharpName(scope)}"; public string GetSignatureString() => $"{GetModifierString()}{ParameterType.FullName}"; - public string GetParameterString(bool emitPointer = false, bool compileAttributes = false) => IsRetval? null : - (Position == 0 && Member.GetCustomAttributes("System.Runtime.CompilerServices.ExtensionAttribute").Any()? "this ":"") - + $"{CustomAttributes.ToString(inline: true, emitPointer: emitPointer, mustCompile: compileAttributes).Replace("[ParamArray]", "params")}" - + $"{getCSharpSignatureString()} {Name}" + public string GetParameterString(Scope usingScope, bool emitPointer = false, bool compileAttributes = false) => IsRetval? null : + (Position == 0 && DeclaringMethod.GetCustomAttributes("System.Runtime.CompilerServices.ExtensionAttribute").Any()? "this ":"") + + $"{CustomAttributes.ToString(usingScope, inline: true, emitPointer: emitPointer, mustCompile: compileAttributes).Replace("[ParamArray]", "params")}" + + $"{getCSharpSignatureString(usingScope)} {Name}" + (HasDefaultValue ? " = " + DefaultValue.ToCSharpValue() + (emitPointer && !(DefaultValue is null)? $" /* Metadata: 0x{(uint) DefaultValueMetadataAddress:X8} */" : "") : ""); - public string GetReturnParameterString() => !IsRetval? null : getCSharpSignatureString(); + public string GetReturnParameterString(Scope scope) => !IsRetval? null : getCSharpSignatureString(scope); } } \ No newline at end of file diff --git a/Il2CppInspector/Reflection/Scope.cs b/Il2CppInspector/Reflection/Scope.cs new file mode 100644 index 0000000..ba657fe --- /dev/null +++ b/Il2CppInspector/Reflection/Scope.cs @@ -0,0 +1,20 @@ +/* + Copyright 2017-2019 Katy Coe - http://www.hearthcode.org - http://www.djkaty.com + + All rights reserved. +*/ + +using System.Collections.Generic; + +namespace Il2CppInspector.Reflection +{ + // A code scope with which to evaluate how to output type references + public class Scope + { + // The scope we are currently in + public TypeInfo Current; + + // The list of namespace using directives in the file + public IEnumerable Namespaces; + } +} diff --git a/Il2CppInspector/Reflection/TypeInfo.cs b/Il2CppInspector/Reflection/TypeInfo.cs index 014741b..334488c 100644 --- a/Il2CppInspector/Reflection/TypeInfo.cs +++ b/Il2CppInspector/Reflection/TypeInfo.cs @@ -143,6 +143,103 @@ namespace Il2CppInspector.Reflection { + (IsArray? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + (IsPointer? "*" : ""); + // Returns the minimally qualified type name required to refer to this type within the specified scope + public string GetScopedFullName(Scope scope) { + // 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 ?? ""; + + // This is the scope in which this type's definition is located + var declaringScope = DeclaringType?.FullName ?? Namespace; + + // If the scope of usage is inside the scope in which the type is declared, no additional scope is needed + if ((usingScope + ".").StartsWith(declaringScope + ".") || (usingScope + "+").StartsWith(declaringScope + "+")) + return base.Name; + + // Global (unnamed) namespace? + string scopedName; + if (string.IsNullOrEmpty(declaringScope)) + scopedName = base.Name; + + // Find first difference in the declaring scope from the using scope, moving one namespace/type name at a time + else { + var diff = 0; + + usingScope += "."; + 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.Substring(0, usingScope.Length -1) + "+"; + 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; + + scopedName = declaringScope.Substring(diff) + (DeclaringType != null? "+" : ".") + base.Name; + } + + // At this point, scopedName contains the minimum required scope, discounting any using directives + // or whether there are conflicts with any ancestor scope + + // Check to see if there is a namespace in our using directives which brings this type into scope + var usingRef = scope.Namespaces.OrderByDescending(n => n.Length).FirstOrDefault(n => scopedName.StartsWith(n + ".")); + var minimallyScopedName = usingRef == null ? scopedName : scopedName.Substring(usingRef.Length + 1); + + // minimallyScopedName now contains the minimum required scope, taking using directives into account + + // Are there any ancestors in the using scope with the same type name as the first part of the minimally scoped name? + // If so, the ancestor type name will hide the type we are trying to reference, + // so we need to provide the scope ignoring any using directives + var firstPart = minimallyScopedName.Split('.')[0].Split('+')[0]; + for (var d = scope.Current; d != null; d = d.DeclaringType) + if (d.BaseName == firstPart) + return scopedName.Replace('+', '.'); + + // 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: We should check all the parts, not just the first part, but this works in the vast majority of cases + if (scope.Namespaces.Count(n => Assembly.Model.TypesByFullName.ContainsKey(n + "." + firstPart)) > 1) + return scopedName.Replace('+', '.'); + + return minimallyScopedName.Replace('+', '.'); + } + + // C#-friendly type name as it should be used in the scope of a given type + public string GetScopedCSharpName(Scope usingScope = null) { + // Unscoped name if no using scope specified + if (usingScope == null) + 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); + + // Unmangle generic type names + if (n?.IndexOf("`", StringComparison.Ordinal) != -1) + n = n?.Remove(n.IndexOf("`", StringComparison.Ordinal)); + + // Generic type parameters and type arguments + var g = (GenericTypeParameters != null ? "<" + string.Join(", ", GenericTypeParameters.Select(x => x.GetScopedCSharpName(usingScope))) + ">" : ""); + g = (GenericTypeArguments != null ? "<" + string.Join(", ", GenericTypeArguments.Select(x => x.GetScopedCSharpName(usingScope))) + ">" : g); + n += g; + + // Nullable types + if (s == "System.Nullable`1" && GenericTypeArguments.Any()) + n = GenericTypeArguments[0].GetScopedCSharpName(usingScope) + "?"; + + // Arrays, pointers, references + if (HasElementType) + n = ElementType.GetScopedCSharpName(usingScope); + + return n + (IsArray ? "[" + new string(',', GetArrayRank() - 1) + "]" : "") + (IsPointer ? "*" : ""); + } + public GenericParameterAttributes GenericParameterAttributes { get; } public int GenericParameterPosition { get; } @@ -607,7 +704,7 @@ namespace Il2CppInspector.Reflection { return modifiers.ToString(); } - public string GetTypeConstraintsString() { + public string GetTypeConstraintsString(Scope scope) { if (!IsGenericParameter) return string.Empty; @@ -620,7 +717,7 @@ namespace Il2CppInspector.Reflection { if (DeclaringMethod == null && DeclaringType.IsNested && (DeclaringType.DeclaringType.GenericTypeParameters?.Any(p => p.Name == Name) ?? false)) return string.Empty; - var constraintList = typeConstraints.Where(c => c.FullName != "System.ValueType").Select(c => c.CSharpTypeDeclarationName).ToList(); + var constraintList = typeConstraints.Where(c => c.FullName != "System.ValueType").Select(c => c.GetScopedCSharpName(scope)).ToList(); if ((GenericParameterAttributes & GenericParameterAttributes.NotNullableValueTypeConstraint) == GenericParameterAttributes.NotNullableValueTypeConstraint) constraintList.Add("struct");