Browse Source

Merge pull request #1423 from icsharpcode/refactor-ilspycmd

Switch ilspycmd to attribute usage (from builder)
pull/1440/head
Christoph Wille 7 years ago committed by GitHub
parent
commit
61f4180cc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj
  2. 186
      ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs
  3. 21
      ICSharpCode.Decompiler.Console/LICENSE
  4. 189
      ICSharpCode.Decompiler.Console/Program.cs
  5. 13
      ICSharpCode.Decompiler.Console/ProgramExitCodes.cs
  6. 3
      ICSharpCode.Decompiler.Console/Publish.cmd
  7. 33
      ICSharpCode.Decompiler.Console/README.md
  8. BIN
      ICSharpCode.Decompiler.Console/Running.gif
  9. 1
      ICSharpCode.Decompiler.Console/TypesParser.cs
  10. 19
      ICSharpCode.Decompiler.Console/ValidationAttributes.cs

2
ICSharpCode.Decompiler.Console/ICSharpCode.Decompiler.Console.csproj

@ -26,7 +26,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.2.5" /> <PackageReference Include="McMaster.Extensions.CommandLineUtils" Version="2.3.2" />
<PackageReference Include="ICSharpCode.Decompiler" Version="4.0.0.4521" /> <PackageReference Include="ICSharpCode.Decompiler" Version="4.0.0.4521" />
<PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" /> <PackageReference Include="System.IO.FileSystem.Primitives" Version="4.3.0" />

186
ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs

@ -0,0 +1,186 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Linq;
using McMaster.Extensions.CommandLineUtils;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Disassembler;
using System.Threading;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using ICSharpCode.Decompiler.DebugInfo;
// ReSharper disable All
namespace ICSharpCode.Decompiler.Console
{
[Command(Name = "ilspycmd", Description = "dotnet tool for decompiling .NET assemblies and generating portable PDBs",
ExtendedHelpText = @"
Remarks:
-o is valid with every option and required when using -p.
")]
[HelpOption("-h|--help")]
[ProjectOptionRequiresOutputDirectoryValidationAttribute]
class ILSpyCmdProgram
{
public static int Main(string[] args) => CommandLineApplication.Execute<ILSpyCmdProgram>(args);
[FileExists]
[Required]
[Argument(0, "Assembly file name", "The assembly that is being decompiled. This argument is mandatory.")]
public string InputAssemblyName { get; }
[DirectoryExists]
[Option("-o|--outputdir <directory>", "The output directory, if omitted decompiler output is written to standard out.", CommandOptionType.SingleValue)]
public string OutputDirectory { get; }
[Option("-p|--project", "Decompile assembly as compilable project. This requires the output directory option.", CommandOptionType.NoValue)]
public bool CreateCompilableProjectFlag { get; }
[Option("-t|--type <type-name>", "The fully qualified name of the type to decompile.", CommandOptionType.SingleValue)]
public string TypeName { get; }
[Option("-il|--ilcode", "Show IL code.", CommandOptionType.NoValue)]
public bool ShowILCodeFlag { get; }
[Option("-d|--debuginfo", "Generate PDB.", CommandOptionType.NoValue)]
public bool CreteDebugInfoFlag { get; }
[Option("-l|--list <entity-type(s)>", "Lists all entities of the specified type(s). Valid types: c(lass), i(interface), s(truct), d(elegate), e(num)", CommandOptionType.MultipleValue)]
public string[] EntityTypes { get; } = new string[0];
[Option("-v|--version", "Show version of ICSharpCode.Decompiler used.", CommandOptionType.NoValue)]
public bool ShowVersion { get; }
private int OnExecute(CommandLineApplication app)
{
TextWriter output = System.Console.Out;
bool outputDirectorySpecified = !String.IsNullOrEmpty(OutputDirectory);
try {
if (CreateCompilableProjectFlag) {
DecompileAsProject(InputAssemblyName, OutputDirectory);
} else if (EntityTypes.Any()) {
var values = EntityTypes.SelectMany(v => v.Split(',', ';')).ToArray();
HashSet<TypeKind> kinds = TypesParser.ParseSelection(values);
if (outputDirectorySpecified) {
string outputName = Path.GetFileNameWithoutExtension(InputAssemblyName);
output = File.CreateText(Path.Combine(OutputDirectory, outputName) + ".list.txt");
}
ListContent(InputAssemblyName, output, kinds);
} else if (ShowILCodeFlag) {
if (outputDirectorySpecified) {
string outputName = Path.GetFileNameWithoutExtension(InputAssemblyName);
output = File.CreateText(Path.Combine(OutputDirectory, outputName) + ".il");
}
ShowIL(InputAssemblyName, output);
} else if (CreteDebugInfoFlag) {
string pdbFileName = null;
if (outputDirectorySpecified) {
string outputName = Path.GetFileNameWithoutExtension(InputAssemblyName);
pdbFileName = Path.Combine(OutputDirectory, outputName) + ".pdb";
} else {
pdbFileName = Path.ChangeExtension(InputAssemblyName, ".pdb");
}
return GeneratePdbForAssembly(InputAssemblyName, pdbFileName, app);
} else if (ShowVersion) {
string vInfo = "ilspycmd: " + typeof(ILSpyCmdProgram).Assembly.GetName().Version.ToString() +
Environment.NewLine
+ "ICSharpCode.Decompiler: " +
typeof(FullTypeName).Assembly.GetName().Version.ToString();
output.WriteLine(vInfo);
} else {
if (outputDirectorySpecified) {
string outputName = Path.GetFileNameWithoutExtension(InputAssemblyName);
output = File.CreateText(Path.Combine(OutputDirectory,
(String.IsNullOrEmpty(TypeName) ? outputName : TypeName) + ".decompiled.cs"));
}
Decompile(InputAssemblyName, output, TypeName);
}
} catch (Exception ex) {
app.Error.WriteLine(ex.ToString());
return ProgramExitCodes.EX_SOFTWARE;
} finally {
output.Close();
}
return 0;
}
static CSharpDecompiler GetDecompiler(string assemblyFileName)
{
return new CSharpDecompiler(assemblyFileName, new DecompilerSettings() { ThrowOnAssemblyResolveErrors = false });
}
static void ListContent(string assemblyFileName, TextWriter output, ISet<TypeKind> kinds)
{
CSharpDecompiler decompiler = GetDecompiler(assemblyFileName);
foreach (var type in decompiler.TypeSystem.MainModule.TypeDefinitions) {
if (!kinds.Contains(type.Kind))
continue;
output.WriteLine($"{type.Kind} {type.FullName}");
}
}
static void ShowIL(string assemblyFileName, TextWriter output)
{
CSharpDecompiler decompiler = GetDecompiler(assemblyFileName);
ITextOutput textOutput = new PlainTextOutput();
ReflectionDisassembler disassembler = new ReflectionDisassembler(textOutput, CancellationToken.None);
disassembler.DisassembleNamespace(decompiler.TypeSystem.MainModule.RootNamespace.Name,
decompiler.TypeSystem.MainModule.PEFile,
decompiler.TypeSystem.MainModule.TypeDefinitions.Select(x => (TypeDefinitionHandle)x.MetadataToken));
output.WriteLine($"// IL code: {decompiler.TypeSystem.MainModule.AssemblyName}");
output.WriteLine(textOutput.ToString());
}
static void DecompileAsProject(string assemblyFileName, string outputDirectory)
{
WholeProjectDecompiler decompiler = new WholeProjectDecompiler();
var module = new PEFile(assemblyFileName);
decompiler.AssemblyResolver = new UniversalAssemblyResolver(assemblyFileName, false, module.Reader.DetectTargetFrameworkId());
decompiler.DecompileProject(module, outputDirectory);
}
static void Decompile(string assemblyFileName, TextWriter output, string typeName = null)
{
CSharpDecompiler decompiler = GetDecompiler(assemblyFileName);
if (typeName == null) {
output.Write(decompiler.DecompileWholeModuleAsString());
} else {
var name = new FullTypeName(typeName);
output.Write(decompiler.DecompileTypeAsString(name));
}
}
static int GeneratePdbForAssembly(string assemblyFileName, string pdbFileName, CommandLineApplication app)
{
var module = new PEFile(assemblyFileName,
new FileStream(assemblyFileName, FileMode.Open, FileAccess.Read),
PEStreamOptions.PrefetchEntireImage,
metadataOptions: MetadataReaderOptions.None);
if (!PortablePdbWriter.HasCodeViewDebugDirectoryEntry(module)) {
app.Error.WriteLine($"Cannot create PDB file for {assemblyFileName}, because it does not contain a PE Debug Directory Entry of type 'CodeView'.");
return ProgramExitCodes.EX_DATAERR;
}
using (FileStream stream = new FileStream(pdbFileName, FileMode.OpenOrCreate, FileAccess.Write)) {
var decompiler = GetDecompiler(assemblyFileName);
PortablePdbWriter.WritePdb(module, decompiler, new DecompilerSettings() { ThrowOnAssemblyResolveErrors = false }, stream);
}
return 0;
}
}
}

21
ICSharpCode.Decompiler.Console/LICENSE

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2017 Christoph Wille
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

189
ICSharpCode.Decompiler.Console/Program.cs

@ -1,189 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using McMaster.Extensions.CommandLineUtils;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Disassembler;
using System.Threading;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using ICSharpCode.Decompiler.DebugInfo;
// ReSharper disable InconsistentNaming
namespace ICSharpCode.Decompiler.Console
{
class Program
{
// https://www.freebsd.org/cgi/man.cgi?query=sysexits&apropos=0&sektion=0&manpath=FreeBSD+4.3-RELEASE&format=html
private const int EX_USAGE = 64;
private const int EX_DATAERR = 65;
private const int EX_NOINPUT = 66;
private const int EX_SOFTWARE = 70;
static int Main(string[] args)
{
// https://github.com/natemcmaster/CommandLineUtils/
// Older cmd line clients (for options reference): https://github.com/aerror2/ILSpy-For-MacOSX and https://github.com/andreif/ILSpyMono
var app = new CommandLineApplication();
app.LongVersionGetter = () => "ilspycmd " + typeof(FullTypeName).Assembly.GetName().Version.ToString();
app.HelpOption("-h|--help");
var inputAssemblyFileName = app.Argument("Assembly filename name", "The assembly that is being decompiled. This argument is mandatory.");
var projectOption = app.Option("-p|--project", "Decompile assembly as compilable project. This requires the output directory option.", CommandOptionType.NoValue);
var outputOption = app.Option("-o|--outputdir <directory>", "The output directory, if omitted decompiler output is written to standard out.", CommandOptionType.SingleValue);
var typeOption = app.Option("-t|--type <type-name>", "The fully qualified name of the type to decompile.", CommandOptionType.SingleValue);
var listOption = app.Option("-l|--list <entity-type(s)>", "Lists all entities of the specified type(s). Valid types: c(lass), i(interface), s(truct), d(elegate), e(num)", CommandOptionType.MultipleValue);
var ilViewerOption = app.Option("-il|--ilcode", "Show IL code.", CommandOptionType.NoValue);
var pdbGeneration = app.Option("-d|--debuginfo", "Generate PDB.", CommandOptionType.NoValue);
app.ExtendedHelpText = Environment.NewLine + "-o is valid with every option and required when using -p.";
app.ThrowOnUnexpectedArgument = false; // Ignore invalid arguments / options
app.OnExecute(() => {
// HACK : the CommandLineUtils package does not allow us to specify an argument as mandatory.
// Therefore we're implementing it as simple as possible.
if (inputAssemblyFileName.Value == null) {
app.ShowVersion();
app.ShowHint();
return -1;
}
if (!File.Exists(inputAssemblyFileName.Value)) {
app.Error.WriteLine($"ERROR: Input file not found.");
return EX_NOINPUT;
}
if (!outputOption.HasValue() && projectOption.HasValue()) {
app.Error.WriteLine($"ERROR: Output directory not speciified.");
return EX_USAGE;
}
if (outputOption.HasValue() && !Directory.Exists(outputOption.Value())) {
app.Error.WriteLine($"ERROR: Output directory '{outputOption.Value()}' does not exist.");
return EX_NOINPUT;
}
TextWriter output = System.Console.Out;
try {
if (projectOption.HasValue()) {
DecompileAsProject(inputAssemblyFileName.Value, outputOption.Value());
} else if (listOption.HasValue()) {
var values = listOption.Values.SelectMany(v => v.Split(',', ';')).ToArray();
HashSet<TypeKind> kinds = TypesParser.ParseSelection(values);
if (outputOption.HasValue()) {
string directory = outputOption.Value();
string outputName = Path.GetFileNameWithoutExtension(inputAssemblyFileName.Value);
output = File.CreateText(Path.Combine(directory, outputName) + ".list.txt");
}
ListContent(inputAssemblyFileName.Value, output, kinds);
} else if (ilViewerOption.HasValue()) {
if (outputOption.HasValue()) {
string directory = outputOption.Value();
string outputName = Path.GetFileNameWithoutExtension(inputAssemblyFileName.Value);
output = File.CreateText(Path.Combine(directory, outputName) + ".il");
}
ShowIL(inputAssemblyFileName.Value, output);
} else if (pdbGeneration.HasValue()) {
string pdbFileName = null;
if (outputOption.HasValue()) {
string directory = outputOption.Value();
string outputName = Path.GetFileNameWithoutExtension(inputAssemblyFileName.Value);
pdbFileName = Path.Combine(directory, outputName) + ".pdb";
} else {
pdbFileName = Path.ChangeExtension(inputAssemblyFileName.Value, ".pdb");
}
return GeneratePdbForAssembly(inputAssemblyFileName.Value, pdbFileName, app);
} else {
if (outputOption.HasValue()) {
string directory = outputOption.Value();
string outputName = Path.GetFileNameWithoutExtension(inputAssemblyFileName.Value);
output = File.CreateText(Path.Combine(directory, (typeOption.Value() ?? outputName) + ".decompiled.cs"));
}
Decompile(inputAssemblyFileName.Value, output, typeOption.Value());
}
} finally {
output.Close();
}
// do not use Console here!
return 0;
});
return app.Execute(args);
}
static CSharpDecompiler GetDecompiler(string assemblyFileName)
{
return new CSharpDecompiler(assemblyFileName, new DecompilerSettings() { ThrowOnAssemblyResolveErrors = false });
}
static void ListContent(string assemblyFileName, TextWriter output, ISet<TypeKind> kinds)
{
CSharpDecompiler decompiler = GetDecompiler(assemblyFileName);
foreach (var type in decompiler.TypeSystem.MainModule.TypeDefinitions) {
if (!kinds.Contains(type.Kind))
continue;
output.WriteLine($"{type.Kind} {type.FullName}");
}
}
static void ShowIL(string assemblyFileName, TextWriter output)
{
CSharpDecompiler decompiler = GetDecompiler(assemblyFileName);
ITextOutput textOutput = new PlainTextOutput();
ReflectionDisassembler disassembler = new ReflectionDisassembler(textOutput, CancellationToken.None);
disassembler.DisassembleNamespace(decompiler.TypeSystem.MainModule.RootNamespace.Name,
decompiler.TypeSystem.MainModule.PEFile,
decompiler.TypeSystem.MainModule.TypeDefinitions.Select(x => (TypeDefinitionHandle)x.MetadataToken));
output.WriteLine($"// IL code: {decompiler.TypeSystem.MainModule.AssemblyName}");
output.WriteLine(textOutput.ToString());
}
static void DecompileAsProject(string assemblyFileName, string outputDirectory)
{
WholeProjectDecompiler decompiler = new WholeProjectDecompiler();
var module = new PEFile(assemblyFileName);
decompiler.AssemblyResolver = new UniversalAssemblyResolver(assemblyFileName, false, module.Reader.DetectTargetFrameworkId());
decompiler.DecompileProject(module, outputDirectory);
}
static void Decompile(string assemblyFileName, TextWriter output, string typeName = null)
{
CSharpDecompiler decompiler = GetDecompiler(assemblyFileName);
if (typeName == null) {
output.Write(decompiler.DecompileWholeModuleAsString());
} else {
var name = new FullTypeName(typeName);
output.Write(decompiler.DecompileTypeAsString(name));
}
}
static int GeneratePdbForAssembly(string assemblyFileName, string pdbFileName, CommandLineApplication app)
{
var module = new PEFile(assemblyFileName,
new FileStream(assemblyFileName, FileMode.Open, FileAccess.Read),
PEStreamOptions.PrefetchEntireImage,
metadataOptions: MetadataReaderOptions.None);
if (!PortablePdbWriter.HasCodeViewDebugDirectoryEntry(module)) {
app.Error.WriteLine($"Cannot create PDB file for {assemblyFileName}, because it does not contain a PE Debug Directory Entry of type 'CodeView'.");
return EX_DATAERR;
}
using (FileStream stream = new FileStream(pdbFileName, FileMode.OpenOrCreate, FileAccess.Write)) {
try {
var decompiler = GetDecompiler(assemblyFileName);
PortablePdbWriter.WritePdb(module, decompiler, new DecompilerSettings() { ThrowOnAssemblyResolveErrors = false }, stream);
} catch (Exception ex) {
app.Error.WriteLine($"Cannot create PDB file for {assemblyFileName}");
app.Error.WriteLine(ex.ToString());
return EX_SOFTWARE;
}
}
return 0;
}
}
}

13
ICSharpCode.Decompiler.Console/ProgramExitCodes.cs

@ -0,0 +1,13 @@
// ReSharper disable InconsistentNaming
namespace ICSharpCode.Decompiler.Console
{
public class ProgramExitCodes
{
// https://www.freebsd.org/cgi/man.cgi?query=sysexits
public const int EX_USAGE = 64;
public const int EX_DATAERR = 65;
public const int EX_NOINPUT = 66;
public const int EX_SOFTWARE = 70;
}
}

3
ICSharpCode.Decompiler.Console/Publish.cmd

@ -1,3 +0,0 @@
dotnet publish -c release -r win7-x64
dotnet publish -c release -r osx-x64
dotnet publish -c release -r linux-x64

33
ICSharpCode.Decompiler.Console/README.md

@ -9,38 +9,23 @@ dotnet tool install ilspycmd -g
``` ```
ilspycmd -h ilspycmd -h
Usage: [arguments] [options] dotnet tool for decompiling .NET assemblies and generating portable PDBs
Usage: ilspycmd [arguments] [options]
Arguments: Arguments:
Assembly filename name The assembly that is being decompiled. This argument is mandatory. Assembly file name The assembly that is being decompiled. This argument is mandatory.
Options: Options:
-h|--help Show help information -h|--help Show help information
-p|--project Decompile assembly as compilable project. This requires the output directory option.
-o|--outputdir <directory> The output directory, if omitted decompiler output is written to standard out. -o|--outputdir <directory> The output directory, if omitted decompiler output is written to standard out.
-p|--project Decompile assembly as compilable project. This requires the output directory option.
-t|--type <type-name> The fully qualified name of the type to decompile. -t|--type <type-name> The fully qualified name of the type to decompile.
-l|--list <entity-type(s)> Lists all entities of the specified type(s). Valid types: c(lass), i(interface), s(truct),
d(elegate), e(num)
-il|--ilcode Show IL code. -il|--ilcode Show IL code.
-d|--debuginfo Generate PDB.
-l|--list <entity-type(s)> Lists all entities of the specified type(s). Valid types: c(lass), i(interface), s(truct), d(elegate), e(num)
-v|--version Show version of ICSharpCode.Decompiler used.
Remarks:
-o is valid with every option and required when using -p. -o is valid with every option and required when using -p.
``` ```
![dotnet-build-dance](Running.gif)
## X-Plat Notes
[SCD Article](https://www.hanselman.com/blog/SelfcontainedNETCoreApplications.aspx)
[RuntimeIdentifiers csproj documentation](https://docs.microsoft.com/en-us/dotnet/core/tools/csproj#additions)
[RID catalog](https://docs.microsoft.com/en-us/dotnet/core/rid-catalog)
```
dotnet build -r win10-x64
dotnet build -r osx-x64
dotnet publish -c release -r win10-x64
dotnet publish -c release -r osx-x64
PS \ilspy-console-netcoreapp\src\ilspycmd\bin\Release\netcoreapp2.0\win10-x64\publish> ./ilspycmd.exe ilspycmd.dll
```

BIN
ICSharpCode.Decompiler.Console/Running.gif

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

1
ICSharpCode.Decompiler.Console/TypesParser.cs

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.TypeSystem;
namespace ICSharpCode.Decompiler.Console namespace ICSharpCode.Decompiler.Console

19
ICSharpCode.Decompiler.Console/ValidationAttributes.cs

@ -0,0 +1,19 @@
using System;
using System.ComponentModel.DataAnnotations;
namespace ICSharpCode.Decompiler.Console
{
[AttributeUsage(AttributeTargets.Class)]
public class ProjectOptionRequiresOutputDirectoryValidationAttribute : ValidationAttribute
{
protected override ValidationResult IsValid(object value, ValidationContext context)
{
if (value is ILSpyCmdProgram obj) {
if (obj.CreateCompilableProjectFlag && String.IsNullOrEmpty(obj.OutputDirectory)) {
return new ValidationResult("--project cannot be used unless --outputdir is also specified");
}
}
return ValidationResult.Success;
}
}
}
Loading…
Cancel
Save