CLI/Output: Add --separate-attributes option for assembly-level attributes

This commit is contained in:
Katy Coe
2020-01-19 02:59:59 +01:00
parent 7704c9f3d9
commit f71e065968
3 changed files with 49 additions and 24 deletions

View File

@@ -10,6 +10,7 @@ using System.Text;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Threading.Tasks; using System.Threading.Tasks;
using Il2CppInspector.Reflection; using Il2CppInspector.Reflection;
using Assembly = Il2CppInspector.Reflection.Assembly;
using CustomAttributeData = Il2CppInspector.Reflection.CustomAttributeData; using CustomAttributeData = Il2CppInspector.Reflection.CustomAttributeData;
using MethodInfo = Il2CppInspector.Reflection.MethodInfo; using MethodInfo = Il2CppInspector.Reflection.MethodInfo;
using TypeInfo = Il2CppInspector.Reflection.TypeInfo; using TypeInfo = Il2CppInspector.Reflection.TypeInfo;
@@ -55,11 +56,14 @@ namespace Il2CppInspector
}); });
} }
public void WriteFilesByAssembly<TKey>(string outPath, Func<TypeInfo, TKey> orderBy) { public void WriteFilesByAssembly<TKey>(string outPath, Func<TypeInfo, TKey> orderBy, bool separateAttributes) {
usedAssemblyAttributes.Clear(); usedAssemblyAttributes.Clear();
Parallel.ForEach(model.Assemblies, asm => { Parallel.ForEach(model.Assemblies, asm => {
// Sort namespaces into alphabetical order, then sort types within the namespaces by the specified sort function // 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(); usedAssemblyAttributes.Clear();
Parallel.ForEach(model.Assemblies.SelectMany(x => x.DefinedTypes), type => { var usedAssemblies = new HashSet<Assembly>();
writeFile($"{outPath}\\{type.Assembly.ShortName.Replace(".dll", "")}\\" + (type.Namespace + (type.Namespace.Length > 0 ? "." : "") + Regex.Replace(type.Name, "`[0-9]", ""))
.Replace('.', '\\') + ".cs", new[] {type}); // 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<Assembly>(),
(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<TypeInfo> types, bool useNamespaceSyntax = true) { private bool writeFile(string outFile, IEnumerable<TypeInfo> types, bool useNamespaceSyntax = true, bool outputAssemblyAttributes = true) {
var nsRefs = new HashSet<string>(); var nsRefs = new HashSet<string>();
var code = new StringBuilder(); var code = new StringBuilder();
@@ -98,7 +115,8 @@ namespace Il2CppInspector
var assemblies = types.Select(t => t.Assembly).Distinct(); var assemblies = types.Select(t => t.Assembly).Distinct();
// Add assembly attribute namespaces to reference list // 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 // Generate each type
foreach (var type in types) { foreach (var type in types) {
@@ -147,7 +165,7 @@ namespace Il2CppInspector
// Stop if nothing to output // Stop if nothing to output
if (!usedTypes.Any()) if (!usedTypes.Any())
return; return false;
// Close namespace // Close namespace
if (useNamespaceSyntax && !string.IsNullOrEmpty(nsContext)) if (useNamespaceSyntax && !string.IsNullOrEmpty(nsContext))
@@ -187,7 +205,7 @@ namespace Il2CppInspector
writer.Write("\n"); writer.Write("\n");
// Output assembly information and attributes // Output assembly information and attributes
writer.Write(generateAssemblyInfo(assemblies, nsRefs) + "\n\n"); writer.Write(generateAssemblyInfo(assemblies, nsRefs, outputAssemblyAttributes) + "\n\n");
// Output type definitions // Output type definitions
writer.Write(code); writer.Write(code);
@@ -203,25 +221,28 @@ namespace Il2CppInspector
System.Threading.Thread.Sleep(100); System.Threading.Thread.Sleep(100);
} }
} while (!fileWritten); } while (!fileWritten);
return true;
} }
private string generateAssemblyInfo(IEnumerable<Reflection.Assembly> assemblies, IEnumerable<string> namespaces) { private string generateAssemblyInfo(IEnumerable<Reflection.Assembly> assemblies, IEnumerable<string> namespaces = null, bool outputAssemblyAttributes = true) {
var text = new StringBuilder(); var text = new StringBuilder();
foreach (var asm in assemblies) { 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"); 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 // Assembly-level attributes
lock (usedAssemblyAttributesLock) { if (outputAssemblyAttributes)
text.Append(asm.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute) lock (usedAssemblyAttributesLock) {
.Except(usedAssemblyAttributes ?? new HashSet<CustomAttributeData>()) text.Append(asm.CustomAttributes.Where(a => a.AttributeType.FullName != ExtAttribute)
.OrderBy(a => a.AttributeType.Name) .Except(usedAssemblyAttributes ?? new HashSet<CustomAttributeData>())
.ToString(new Scope { Current = null, Namespaces = namespaces }, attributePrefix: "assembly: ", emitPointer: !SuppressMetadata, mustCompile: MustCompile)); .OrderBy(a => a.AttributeType.Name)
if (asm.CustomAttributes.Any()) .ToString(new Scope { Current = null, Namespaces = namespaces ?? new List<string>() }, attributePrefix: "assembly: ", emitPointer: !SuppressMetadata, mustCompile: MustCompile));
text.Append("\n"); if (asm.CustomAttributes.Any())
text.Append("\n");
usedAssemblyAttributes.UnionWith(asm.CustomAttributes); usedAssemblyAttributes.UnionWith(asm.CustomAttributes);
} }
} }
return text.ToString().TrimEnd(); return text.ToString().TrimEnd();
} }

View File

@@ -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.")] [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; } 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 // Adapted from: https://stackoverflow.com/questions/16376191/measuring-code-execution-time
@@ -143,10 +146,10 @@ namespace Il2CppInspector
break; break;
case ("assembly", "index"): case ("assembly", "index"):
writer.WriteFilesByAssembly(csOut, t => t.Index); writer.WriteFilesByAssembly(csOut, t => t.Index, options.SeparateAssemblyAttributesFiles);
break; break;
case ("assembly", "name"): case ("assembly", "name"):
writer.WriteFilesByAssembly(csOut, t => t.Name); writer.WriteFilesByAssembly(csOut, t => t.Name, options.SeparateAssemblyAttributesFiles);
break; break;
case ("class", _): case ("class", _):
@@ -154,7 +157,7 @@ namespace Il2CppInspector
break; break;
case ("tree", _): case ("tree", _):
writer.WriteFilesByClassTree(csOut); writer.WriteFilesByClassTree(csOut, options.SeparateAssemblyAttributesFiles);
break; break;
} }

View File

@@ -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 -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 -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. -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: Defaults if not specified: