From f71e06596838cb0a6c481c53bdd7a090e090f34b Mon Sep 17 00:00:00 2001 From: Katy Coe Date: Sun, 19 Jan 2020 02:59:59 +0100 Subject: [PATCH] CLI/Output: Add --separate-attributes option for assembly-level attributes --- Il2CppDumper/Il2CppCSharpDumper.cs | 63 ++++++++++++++++++++---------- Il2CppDumper/Program.cs | 9 +++-- README.md | 1 + 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/Il2CppDumper/Il2CppCSharpDumper.cs b/Il2CppDumper/Il2CppCSharpDumper.cs index 1f55114..006f6e4 100644 --- a/Il2CppDumper/Il2CppCSharpDumper.cs +++ b/Il2CppDumper/Il2CppCSharpDumper.cs @@ -10,6 +10,7 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using Il2CppInspector.Reflection; +using Assembly = Il2CppInspector.Reflection.Assembly; using CustomAttributeData = Il2CppInspector.Reflection.CustomAttributeData; using MethodInfo = Il2CppInspector.Reflection.MethodInfo; using TypeInfo = Il2CppInspector.Reflection.TypeInfo; @@ -55,11 +56,14 @@ namespace Il2CppInspector }); } - public void WriteFilesByAssembly(string outPath, Func 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 - writeFile($"{outPath}\\{asm.ShortName.Replace(".dll", "")}.cs", asm.DefinedTypes.OrderBy(t => t.Namespace).ThenBy(orderBy)); + if (writeFile($"{outPath}\\{asm.ShortName.Replace(".dll", "")}.cs", asm.DefinedTypes.OrderBy(t => t.Namespace).ThenBy(orderBy), outputAssemblyAttributes: !separateAttributes) + && separateAttributes) { + File.WriteAllText($"{outPath}\\AssemblyInfo_{asm.ShortName.Replace(".dll", "")}.cs", generateAssemblyInfo(new [] {asm})); + } }); } @@ -71,15 +75,28 @@ namespace Il2CppInspector }); } - public void WriteFilesByClassTree(string outPath) { + public void WriteFilesByClassTree(string outPath, bool separateAttributes) { usedAssemblyAttributes.Clear(); - Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes), type => { - writeFile($"{outPath}\\{type.Assembly.ShortName.Replace(".dll", "")}\\" + (type.Namespace + (type.Namespace.Length > 0 ? "." : "") + Regex.Replace(type.Name, "`[0-9]", "")) - .Replace('.', '\\') + ".cs", new[] {type}); - }); + 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) => { + if (writeFile($"{outPath}\\{type.Assembly.ShortName.Replace(".dll", "")}\\" + (type.Namespace + (type.Namespace.Length > 0 ? "." : "") + Regex.Replace(type.Name, "`[0-9]", "")) + .Replace('.', '\\') + ".cs", new[] {type}, outputAssemblyAttributes: !separateAttributes)) + used.Add(type.Assembly); + return used; + }, + usedPartition => usedAssemblies.UnionWith(usedPartition) + ); + + if (separateAttributes && usedAssemblies.Any()) + foreach (var asm in usedAssemblies) + File.WriteAllText($"{outPath}\\{asm.ShortName.Replace(".dll", "")}\\AssemblyInfo.cs", generateAssemblyInfo(new [] {asm})); } - private void writeFile(string outFile, IEnumerable types, bool useNamespaceSyntax = true) { + private bool writeFile(string outFile, IEnumerable types, bool useNamespaceSyntax = true, bool outputAssemblyAttributes = true) { var nsRefs = new HashSet(); var code = new StringBuilder(); @@ -98,7 +115,8 @@ namespace Il2CppInspector 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)); + if (outputAssemblyAttributes) + nsRefs.UnionWith(assemblies.SelectMany(a => a.CustomAttributes).Select(a => a.AttributeType.Namespace)); // Generate each type foreach (var type in types) { @@ -147,7 +165,7 @@ namespace Il2CppInspector // Stop if nothing to output if (!usedTypes.Any()) - return; + return false; // Close namespace if (useNamespaceSyntax && !string.IsNullOrEmpty(nsContext)) @@ -187,7 +205,7 @@ namespace Il2CppInspector writer.Write("\n"); // Output assembly information and attributes - writer.Write(generateAssemblyInfo(assemblies, nsRefs) + "\n\n"); + writer.Write(generateAssemblyInfo(assemblies, nsRefs, outputAssemblyAttributes) + "\n\n"); // Output type definitions writer.Write(code); @@ -203,25 +221,28 @@ namespace Il2CppInspector System.Threading.Thread.Sleep(100); } } while (!fileWritten); + + return true; } - private string generateAssemblyInfo(IEnumerable assemblies, IEnumerable namespaces) { + 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} - Types {asm.ImageDefinition.typeStart}-{asm.ImageDefinition.typeStart + asm.ImageDefinition.typeCount - 1}\n"); // Assembly-level attributes - 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 }, attributePrefix: "assembly: ", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); - if (asm.CustomAttributes.Any()) - text.Append("\n"); + 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); - } + usedAssemblyAttributes.UnionWith(asm.CustomAttributes); + } } return text.ToString().TrimEnd(); } diff --git a/Il2CppDumper/Program.cs b/Il2CppDumper/Program.cs index 52312e6..0f52620 100644 --- a/Il2CppDumper/Program.cs +++ b/Il2CppDumper/Program.cs @@ -53,6 +53,9 @@ namespace Il2CppInspector [Option('k', "must-compile", Required = false, HelpText = "Compilation tidying: try really hard to make code that compiles. Suppress generation of code for items with CompilerGenerated attribute. Comment out attributes without parameterless constructors or all-optional constructor arguments. Don't emit add/remove/raise on events. Specify AttributeTargets.All on classes with AttributeUsage attribute. Force auto-properties to have get accessors. Force regular properties to have bodies.")] public bool MustCompile { get; set; } + + [Option("separate-attributes", Required = false, HelpText = "Place assembly-level attributes in their own AssemblyInfo.cs files. Only used when layout is per-assembly or tree")] + public bool SeparateAssemblyAttributesFiles { get; set; } } // Adapted from: https://stackoverflow.com/questions/16376191/measuring-code-execution-time @@ -143,10 +146,10 @@ namespace Il2CppInspector break; case ("assembly", "index"): - writer.WriteFilesByAssembly(csOut, t => t.Index); + writer.WriteFilesByAssembly(csOut, t => t.Index, options.SeparateAssemblyAttributesFiles); break; case ("assembly", "name"): - writer.WriteFilesByAssembly(csOut, t => t.Name); + writer.WriteFilesByAssembly(csOut, t => t.Name, options.SeparateAssemblyAttributesFiles); break; case ("class", _): @@ -154,7 +157,7 @@ namespace Il2CppInspector break; case ("tree", _): - writer.WriteFilesByClassTree(csOut); + writer.WriteFilesByClassTree(csOut, options.SeparateAssemblyAttributesFiles); break; } diff --git a/README.md b/README.md index 7b3924d..dfe35a9 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,7 @@ File format and architecture are automatically detected. -f, --flatten Flatten the namespace hierarchy into a single folder rather than using per-namespace subfolders. Only used when layout is per-namespace or per-class. Ignored for tree layout -n, --suppress-metadata 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 -k, --must-compile Compilation tidying: try really hard to make code that compiles. Suppress generation of code for items with CompilerGenerated attribute. Comment out attributes without parameterless constructors or all-optional constructor arguments. Don't emit add/remove/raise on events. Specify AttributeTargets.All on classes with AttributeUsage attribute. Force auto-properties to have get accessors. Force regular properties to have bodies. + --separate-attributes Place assembly-level attributes in their own AssemblyInfo.cs files. Only used when layout is per-assembly or tree ``` Defaults if not specified: