mirror of https://github.com/icsharpcode/ILSpy.git
6 changed files with 220 additions and 191 deletions
@ -0,0 +1,187 @@
@@ -0,0 +1,187 @@
|
||||
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)] |
||||
[AllowedValues("c", "i", "s", "d", "e", "class", "interface", "struct", "delegate", "enum", IgnoreCase = true)] |
||||
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; |
||||
} |
||||
} |
||||
} |
@ -1,189 +0,0 @@
@@ -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; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,13 @@
@@ -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; |
||||
} |
||||
} |
@ -0,0 +1,19 @@
@@ -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…
Reference in new issue