From b862fd99de870f0cecaad36ad444602453640e90 Mon Sep 17 00:00:00 2001 From: Katy Coe Date: Sat, 16 Nov 2019 21:33:49 +0100 Subject: [PATCH] Output: Implement --comment-attributes option --- Il2CppDumper/Il2CppCSharpDumper.cs | 29 +++++++++++++-------- Il2CppDumper/Program.cs | 12 ++++++--- Il2CppInspector/Reflection/Extensions.cs | 13 +++++++-- Il2CppInspector/Reflection/MethodBase.cs | 3 ++- Il2CppInspector/Reflection/ParameterInfo.cs | 4 +-- 5 files changed, 41 insertions(+), 20 deletions(-) diff --git a/Il2CppDumper/Il2CppCSharpDumper.cs b/Il2CppDumper/Il2CppCSharpDumper.cs index 0e7908e..4e4647b 100644 --- a/Il2CppDumper/Il2CppCSharpDumper.cs +++ b/Il2CppDumper/Il2CppCSharpDumper.cs @@ -26,6 +26,9 @@ namespace Il2CppInspector // Suppress binary metadata in code comments public bool SuppressMetadata { get; set; } + // Comment out custom attributes with non-optional constructor arguments + public bool CommentAttributes { 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"; @@ -161,7 +164,7 @@ namespace Il2CppInspector text.Append($"// Image {asm.Index}: {asm.FullName} - {asm.ImageDefinition.typeStart}-{asm.ImageDefinition.typeStart + asm.ImageDefinition.typeCount - 1}\n"); // Assembly-level attributes - text.Append(asm.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute).OrderBy(a => a.AttributeType.Name).ToString(attributePrefix: "assembly: ", emitPointer: !SuppressMetadata)); + text.Append(asm.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute).OrderBy(a => a.AttributeType.Name).ToString(attributePrefix: "assembly: ", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); if (asm.CustomAttributes.Any()) text.Append("\n"); } @@ -188,7 +191,7 @@ 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)); + sb.Append(field.CustomAttributes.Where(a => a.AttributeType.FullName != FBAttribute).OrderBy(a => a.AttributeType.Name).ToString(prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); sb.Append(prefix + "\t"); sb.Append(field.GetModifierString()); @@ -219,7 +222,7 @@ 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)); + sb.Append(prop.CustomAttributes.OrderBy(a => a.AttributeType.Name).ToString(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; @@ -233,11 +236,14 @@ namespace Il2CppInspector sb.Append($"{prop.Name} {{ "); // Indexer else - sb.Append("this[" + string.Join(", ", primary.DeclaredParameters.SkipLast(getAccess >= setAccess? 0 : 1).Select(p => p.GetParameterString(!SuppressMetadata))) + "] { "); + sb.Append("this[" + string.Join(", ", primary.DeclaredParameters.SkipLast(getAccess >= setAccess? 0 : 1) + .Select(p => p.GetParameterString(!SuppressMetadata, CommentAttributes))) + "] { "); - sb.Append((prop.CanRead? prop.GetMethod.CustomAttributes.Where(a => !SuppressGenerated || a.AttributeType.FullName != CGAttribute).ToString(inline: true, emitPointer: !SuppressMetadata) + sb.Append((prop.CanRead? prop.GetMethod.CustomAttributes.Where(a => !SuppressGenerated || a.AttributeType.FullName != CGAttribute) + .ToString(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) + + (prop.CanWrite? prop.SetMethod.CustomAttributes.Where(a => !SuppressGenerated || a.AttributeType.FullName != CGAttribute) + .ToString(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)) @@ -256,7 +262,7 @@ 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)); + sb.Append(evt.CustomAttributes.OrderBy(a => a.AttributeType.Name).ToString(prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); string modifiers = evt.AddMethod?.GetModifierString(); sb.Append($"{prefix}\t{modifiers}event {evt.EventHandlerType.CSharpName} {evt.Name} {{\n"); @@ -278,7 +284,7 @@ namespace Il2CppInspector sb.Clear(); foreach (var method in type.DeclaredConstructors) { // Attributes - sb.Append(method.CustomAttributes.OrderBy(a => a.AttributeType.Name).ToString(prefix + "\t", emitPointer: !SuppressMetadata)); + sb.Append(method.CustomAttributes.OrderBy(a => a.AttributeType.Name).ToString(prefix + "\t", emitPointer: !SuppressMetadata, mustCompile: CommentAttributes)); sb.Append($"{prefix}\t{method.GetModifierString()}{method.DeclaringType.UnmangledBaseName}{method.GetTypeParametersString()}("); sb.Append(method.GetParametersString(!SuppressMetadata) + ")" + (method.IsAbstract? ";" : @" {}")); @@ -306,7 +312,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)); + .OrderBy(a => a.AttributeType.Name).ToString(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") { @@ -314,7 +320,7 @@ namespace Il2CppInspector var del = type.GetMethod("Invoke"); // IL2CPP doesn't seem to retain return type attributes - //sb.Append(del.ReturnType.CustomAttributes.ToString(prefix, "return: ", emitPointer: !SuppressMetadata)); + //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}("); @@ -369,7 +375,8 @@ namespace Il2CppInspector var writer = new StringBuilder(); // Attributes - writer.Append(method.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute).OrderBy(a => a.AttributeType.Name).ToString(prefix + "\t", emitPointer: !SuppressMetadata)); + writer.Append(method.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute).OrderBy(a => a.AttributeType.Name) + .ToString(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)); diff --git a/Il2CppDumper/Program.cs b/Il2CppDumper/Program.cs index 3d87594..4b82100 100644 --- a/Il2CppDumper/Program.cs +++ b/Il2CppDumper/Program.cs @@ -46,11 +46,14 @@ namespace Il2CppInspector [Option('f', "flatten", Required = false, HelpText = "Flatten the namespace hierarchy into a single folder rather than using per-namespace subfolders. Only used when layout is per-namespace or per-class", Default = false)] public bool FlattenHierarchy { get; set; } - [Option('g', "no-suppress-cg", Required = false, HelpText = "Don't suppress generation of C# code for items with CompilerGenerated attribute", Default = false)] + [Option('n', "suppress-metadata", Required = false, HelpText = "Diff tidying: suppress method pointers, field offsets and type indices from C# output. Useful for comparing two versions of a binary for changes with a diff tool", Default = false)] + public bool SuppressMetadata { get; set; } + + [Option('g', "no-suppress-cg", Required = false, HelpText = "Compilation tidying: don't 'suppress generation of C# code for items with CompilerGenerated attribute", Default = false)] public bool DontSuppressCompilerGenerated { get; set; } - [Option('n', "suppress-metadata", Required = false, HelpText = "Suppress method pointers, field offsets and type indices from C# output. Useful for comparing two versions of a binary for changes with a diff tool", Default = false)] - public bool SuppressMetadata { get; set; } + [Option('a', "comment-attributes", Required = false, HelpText = "Compilation tidying: comment out attributes without parameterless constructors or all-optional constructor arguments")] + public bool CommentParameterizedAttributeConstructors { get; set; } } public static int Main(string[] args) => @@ -88,7 +91,8 @@ namespace Il2CppInspector var writer = new Il2CppCSharpDumper(model) { ExcludedNamespaces = options.ExcludedNamespaces.ToList(), SuppressGenerated = !options.DontSuppressCompilerGenerated, - SuppressMetadata = options.SuppressMetadata + SuppressMetadata = options.SuppressMetadata, + CommentAttributes = options.CommentParameterizedAttributeConstructors }; var imageSuffix = i++ > 0 ? "-" + (i - 1) : ""; diff --git a/Il2CppInspector/Reflection/Extensions.cs b/Il2CppInspector/Reflection/Extensions.cs index 514a601..ae42946 100644 --- a/Il2CppInspector/Reflection/Extensions.cs +++ b/Il2CppInspector/Reflection/Extensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; namespace Il2CppInspector.Reflection @@ -7,15 +8,23 @@ 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) { + public static string ToString(this IEnumerable attributes, string linePrefix = "", string attributePrefix = "", + bool inline = false, bool emitPointer = false, bool mustCompile = false) { 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.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 name = cad.AttributeType.CSharpName; var suffix = name.LastIndexOf("Attribute", StringComparison.Ordinal); if (suffix != -1) name = name[..suffix]; - sb.Append($"{linePrefix}[{attributePrefix}{name}]"); + sb.Append($"{linePrefix}{commentStart}[{attributePrefix}{name}]{commentEnd}"); if (emitPointer) sb.Append($" {(inline? "/*" : "//")} {cad.VirtualAddress.ToAddressString()}{(inline? " */" : "")}"); sb.Append(inline? " ":"\n"); diff --git a/Il2CppInspector/Reflection/MethodBase.cs b/Il2CppInspector/Reflection/MethodBase.cs index 21e7a3a..af7bf74 100644 --- a/Il2CppInspector/Reflection/MethodBase.cs +++ b/Il2CppInspector/Reflection/MethodBase.cs @@ -166,7 +166,8 @@ namespace Il2CppInspector.Reflection } // Get C# syntax-friendly list of parameters - public string GetParametersString(bool emitPointer = false) => string.Join(", ", DeclaredParameters.Select(p => p.GetParameterString(emitPointer))); + public string GetParametersString(bool emitPointer = false, bool commentAttributes = false) + => string.Join(", ", DeclaredParameters.Select(p => p.GetParameterString(emitPointer, commentAttributes))); public string GetTypeParametersString() => GenericTypeParameters == null? "" : "<" + string.Join(", ", GenericTypeParameters.Select(p => p.CSharpName)) + ">"; diff --git a/Il2CppInspector/Reflection/ParameterInfo.cs b/Il2CppInspector/Reflection/ParameterInfo.cs index 1cda427..15ce59e 100644 --- a/Il2CppInspector/Reflection/ParameterInfo.cs +++ b/Il2CppInspector/Reflection/ParameterInfo.cs @@ -99,9 +99,9 @@ namespace Il2CppInspector.Reflection private string getCSharpSignatureString() => $"{GetModifierString()}{ParameterType.CSharpName}"; public string GetSignatureString() => $"{GetModifierString()}{ParameterType.FullName}"; - public string GetParameterString(bool emitPointer = false) => IsRetval? null : + 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).Replace("[ParamArray]", "params")}" + + $"{CustomAttributes.ToString(inline: true, emitPointer: emitPointer, mustCompile: compileAttributes).Replace("[ParamArray]", "params")}" + $"{getCSharpSignatureString()} {Name}" + (HasDefaultValue ? " = " + DefaultValue.ToCSharpValue() + (emitPointer && !(DefaultValue is null)? $" /* Metadata: 0x{(uint) DefaultValueMetadataAddress:X8} */" : "") : "");