/*
Copyright (C) 2014-2019 de4dot@gmail.com
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see .
*/
// Simple hack to decompile code from the command line.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Security;
using System.Text;
using dnlib.DotNet;
using dnSpy.Console.Properties;
using dnSpy.Contracts.Decompiler;
using dnSpy.Contracts.Text;
using dnSpy.Contracts.Utilities;
using dnSpy.Decompiler.MSBuild;
namespace dnSpy_Console {
[Serializable]
sealed class ErrorException : Exception {
public ErrorException(string s)
: base(s) {
}
}
static class Program {
static int Main(string[] args) {
if (!dnlib.Settings.IsThreadSafe) {
Console.WriteLine("dnlib wasn't compiled with THREAD_SAFE defined");
return 1;
}
var oldEncoding = Console.OutputEncoding;
try {
// Make sure russian and chinese characters are shown correctly
Console.OutputEncoding = Encoding.UTF8;
return new DnSpyDecompiler().Run(args);
}
catch (Exception ex) {
Console.Error.WriteLine(ex.ToString());
return 1;
}
finally {
Console.OutputEncoding = oldEncoding;
}
}
}
readonly struct ConsoleColorPair {
public ConsoleColor? Foreground { get; }
public ConsoleColor? Background { get; }
public ConsoleColorPair(ConsoleColor? foreground, ConsoleColor? background) {
Foreground = foreground;
Background = background;
}
}
sealed class ColorProvider {
readonly Dictionary colors = new Dictionary();
public void Add(TextColor color, ConsoleColor? foreground, ConsoleColor? background = null) {
if (foreground is not null || background is not null)
colors[color] = new ConsoleColorPair(foreground, background);
}
public ConsoleColorPair? GetColor(TextColor? color) {
if (color is null)
return null;
return colors.TryGetValue(color.Value, out var ccPair) ? ccPair : (ConsoleColorPair?)null;
}
}
sealed class ConsoleColorizerOutput : IDecompilerOutput {
readonly ColorProvider colorProvider;
readonly TextWriter writer;
readonly Indenter indenter;
bool addIndent = true;
int position;
public int Length => position;
public int NextPosition => position + (addIndent ? indenter.String.Length : 0);
bool IDecompilerOutput.UsesCustomData => false;
public ConsoleColorizerOutput(TextWriter writer, ColorProvider colorProvider, Indenter indenter) {
this.writer = writer ?? throw new ArgumentNullException(nameof(writer));
this.colorProvider = colorProvider ?? throw new ArgumentNullException(nameof(colorProvider));
this.indenter = indenter ?? throw new ArgumentNullException(nameof(indenter));
}
void IDecompilerOutput.AddCustomData(string id, TData data) { }
public void IncreaseIndent() => indenter.IncreaseIndent();
public void DecreaseIndent() => indenter.DecreaseIndent();
public void WriteLine() {
var nlArray = newLineArray;
writer.Write(nlArray);
position += nlArray.Length;
addIndent = true;
}
static readonly char[] newLineArray = Environment.NewLine.ToCharArray();
void AddIndent() {
if (!addIndent)
return;
addIndent = false;
var s = indenter.String;
writer.Write(s);
position += s.Length;
}
void AddText(string text, object color) {
if (addIndent)
AddIndent();
var colorPair = colorProvider.GetColor(color as TextColor?);
if (colorPair is not null) {
if (colorPair.Value.Foreground is not null)
Console.ForegroundColor = colorPair.Value.Foreground.Value;
if (colorPair.Value.Background is not null)
Console.BackgroundColor = colorPair.Value.Background.Value;
writer.Write(text);
Console.ResetColor();
}
else
writer.Write(text);
position += text.Length;
}
void AddText(string text, int index, int length, object color) {
if (index == 0 && length == text.Length)
AddText(text, color);
else
AddText(text.Substring(index, length), color);
}
public void Write(string text, object color) => AddText(text, color);
public void Write(string text, int index, int length, object color) => AddText(text, index, length, color);
public void Write(string text, object? reference, DecompilerReferenceFlags flags, object color) => AddText(text, color);
public void Write(string text, int index, int length, object? reference, DecompilerReferenceFlags flags, object color) => AddText(text, index, length, color);
public override string ToString() => writer.ToString()!;
public void Dispose() => writer.Dispose();
}
sealed class DnSpyDecompiler : IMSBuildProjectWriterLogger {
bool isRecursive = false;
bool useGac = true;
bool addCorlibRef = true;
bool createSlnFile = true;
bool unpackResources = true;
bool createResX = true;
bool decompileBaml = true;
bool colorizeOutput;
Guid projectGuid = Guid.NewGuid();
int numThreads;
int mdToken;
int spaces;
string? typeName;
ProjectVersion projectVersion = ProjectVersion.VS2010;
string? outputDir;
string slnName = "solution.sln";
readonly List files;
readonly List asmPaths;
readonly List userGacPaths;
readonly List gacFiles;
string language = DecompilerConstants.LANGUAGE_CSHARP.ToString();
readonly DecompilationContext decompilationContext;
readonly ModuleContext moduleContext;
readonly AssemblyResolver assemblyResolver;
readonly IBamlDecompiler? bamlDecompiler;
readonly HashSet reservedOptions;
#if NET
readonly dnSpy.MainApp.DotNetAssemblyLoader dotNetAssemblyLoader = new dnSpy.MainApp.DotNetAssemblyLoader(System.Runtime.Loader.AssemblyLoadContext.Default);
#endif
static readonly char PATHS_SEP = Path.PathSeparator;
public DnSpyDecompiler() {
#if NET
// This assembly is always in the bin sub dir if one exists
dotNetAssemblyLoader.AddSearchPath(Path.GetDirectoryName(typeof(ILSpan).Assembly.Location)!);
#endif
files = new List();
asmPaths = new List();
userGacPaths = new List();
gacFiles = new List();
decompilationContext = new DecompilationContext();
moduleContext = ModuleDef.CreateModuleContext(); // Same as dnSpy.exe
assemblyResolver = (AssemblyResolver)moduleContext.AssemblyResolver;
assemblyResolver.EnableFrameworkRedirect = false; // Same as dnSpy.exe
assemblyResolver.FindExactMatch = true; // Same as dnSpy.exe
assemblyResolver.EnableTypeDefCache = true;
bamlDecompiler = TryLoadBamlDecompiler();
decompileBaml = bamlDecompiler is not null;
reservedOptions = GetReservedOptions();
colorizeOutput = !Console.IsOutputRedirected;
var langs = new List();
langs.AddRange(GetAllLanguages());
langs.Sort((a, b) => a.OrderUI.CompareTo(b.OrderUI));
allLanguages = langs.ToArray();
}
static IEnumerable GetAllLanguages() {
var asmNames = new string[] {
"dnSpy.Decompiler.ILSpy.Core",
};
foreach (var asmName in asmNames) {
foreach (var l in GetLanguagesInAssembly(asmName))
yield return l;
}
}
static IEnumerable GetLanguagesInAssembly(string asmName) {
var asm = TryLoad(asmName);
if (asm is not null) {
foreach (var type in asm.GetTypes()) {
if (!type.IsAbstract && !type.IsInterface && typeof(IDecompilerProvider).IsAssignableFrom(type)) {
var p = (IDecompilerProvider)Activator.CreateInstance(type)!;
foreach (var l in p.Create())
yield return l;
}
}
}
}
static IBamlDecompiler? TryLoadBamlDecompiler() => TryCreateType("dnSpy.BamlDecompiler.x", "dnSpy.BamlDecompiler.BamlDecompiler");
static Assembly? TryLoad(string asmName) {
try {
return Assembly.Load(asmName);
}
catch {
}
return null;
}
static T? TryCreateType(string asmName, string typeFullName) where T : class {
var asm = TryLoad(asmName);
var type = asm?.GetType(typeFullName);
return type is null ? default! : (T)Activator.CreateInstance(type)!;
}
public int Run(string[] args) {
try {
ParseCommandLine(args);
if (allLanguages.Length == 0)
throw new ErrorException(dnSpy_Console_Resources.NoLanguagesFound);
if (GetLanguageOrNull() is null)
throw new ErrorException(string.Format(dnSpy_Console_Resources.LanguageXDoesNotExist, language));
Decompile();
}
catch (ErrorException ex) {
PrintHelp();
Console.WriteLine();
Console.WriteLine(dnSpy_Console_Resources.Error1, ex.Message);
return 1;
}
catch (Exception ex) {
Dump(ex);
return 1;
}
return errors == 0 ? 0 : 1;
}
void PrintHelp() {
var progName = GetProgramBaseName();
Console.WriteLine(progName + " " + dnSpy_Console_Resources.UsageHeader, progName);
Console.WriteLine();
foreach (var info in usageInfos) {
var arg = info.Option;
if (info.OptionArgument is not null)
arg = arg + " " + info.OptionArgument;
Console.WriteLine(" {0,-12} {1}", arg, string.Format(info.Description, PATHS_SEP));
}
Console.WriteLine();
Console.WriteLine(dnSpy_Console_Resources.Languages);
foreach (var lang in AllLanguages)
Console.WriteLine(" {0} ({1})", lang.UniqueNameUI, lang.UniqueGuid.ToString("B"));
var langLists = GetLanguageOptions().Where(a => a[0].Settings.Options.Any()).ToArray();
if (langLists.Length > 0) {
Console.WriteLine();
Console.WriteLine(dnSpy_Console_Resources.LanguageOptions);
Console.WriteLine(dnSpy_Console_Resources.LanguageOptionsDesc);
foreach (var langList in langLists) {
Console.WriteLine();
foreach (var lang in langList)
Console.WriteLine(" {0} ({1})", lang.UniqueNameUI, lang.UniqueGuid.ToString("B"));
foreach (var opt in langList[0].Settings.Options)
Console.WriteLine(" {0}\t({1} = {2}) {3}", GetOptionName(opt), opt.Type.Name, opt.Value, opt.Description);
}
}
Console.WriteLine();
Console.WriteLine(dnSpy_Console_Resources.ExamplesHeader);
foreach (var info in helpInfos) {
Console.WriteLine(" " + progName + " " + info.CommandLine);
Console.WriteLine(" " + info.Description);
}
}
readonly struct UsageInfo {
public string Option { get; }
public string? OptionArgument { get; }
public string Description { get; }
public UsageInfo(string option, string? optionArgument, string description) {
Option = option;
OptionArgument = optionArgument;
Description = description;
}
}
static readonly UsageInfo[] usageInfos = new UsageInfo[] {
new UsageInfo("--asm-path", dnSpy_Console_Resources.CmdLinePath, dnSpy_Console_Resources.CmdLineDescription_AsmPath),
new UsageInfo("--user-gac", dnSpy_Console_Resources.CmdLinePath, dnSpy_Console_Resources.CmdLineDescription_UserGAC),
new UsageInfo("--no-gac", null, dnSpy_Console_Resources.CmdLineDescription_NoGAC),
new UsageInfo("--no-stdlib", null, dnSpy_Console_Resources.CmdLineDescription_NoStdLib),
new UsageInfo("--no-sln", null, dnSpy_Console_Resources.CmdLineDescription_NoSLN),
new UsageInfo("--sln-name", dnSpy_Console_Resources.CmdLineName, dnSpy_Console_Resources.CmdLineDescription_SlnName),
new UsageInfo("--threads", "N", dnSpy_Console_Resources.CmdLineDescription_NumberOfThreads),
new UsageInfo("--no-resources", null, dnSpy_Console_Resources.CmdLineDescription_NoResources),
new UsageInfo("--no-resx", null, dnSpy_Console_Resources.CmdLineDescription_NoResX),
new UsageInfo("--no-baml", null, dnSpy_Console_Resources.CmdLineDescription_NoBAML),
new UsageInfo("--no-color", null, dnSpy_Console_Resources.CmdLineDescription_NoColor),
new UsageInfo("--spaces", "N", dnSpy_Console_Resources.CmdLineDescription_Spaces),
new UsageInfo("--vs", "N", string.Format(dnSpy_Console_Resources.CmdLineDescription_VSVersion, 2017)),
new UsageInfo("--project-guid", "N", dnSpy_Console_Resources.CmdLineDescription_ProjectGUID),
new UsageInfo("-t", dnSpy_Console_Resources.CmdLineName, dnSpy_Console_Resources.CmdLineDescription_Type1),
new UsageInfo("--type", dnSpy_Console_Resources.CmdLineName, dnSpy_Console_Resources.CmdLineDescription_Type2),
new UsageInfo("--md", "N", dnSpy_Console_Resources.CmdLineDescription_MDToken),
new UsageInfo("--gac-file", dnSpy_Console_Resources.CmdLineAssembly, dnSpy_Console_Resources.CmdLineDescription_GACFile),
new UsageInfo("-r", null, dnSpy_Console_Resources.CmdLineDescription_RecursiveSearch),
new UsageInfo("-o", dnSpy_Console_Resources.CmdLineOutputDir, dnSpy_Console_Resources.CmdLineDescription_OutputDirectory),
new UsageInfo("-l", dnSpy_Console_Resources.CmdLineLanguage, dnSpy_Console_Resources.CmdLineDescription_Language),
};
readonly struct HelpInfo {
public string CommandLine { get; }
public string Description { get; }
public HelpInfo(string description, string commandLine) {
CommandLine = commandLine;
Description = description;
}
}
static readonly HelpInfo[] helpInfos = new HelpInfo[] {
new HelpInfo(dnSpy_Console_Resources.ExampleDescription1, @"-o C:\out\path C:\some\path"),
new HelpInfo(dnSpy_Console_Resources.ExampleDescription2, @"-o C:\out\path -r C:\some\path"),
new HelpInfo(dnSpy_Console_Resources.ExampleDescription3, @"-o C:\out\path C:\some\path\*.dll"),
new HelpInfo(dnSpy_Console_Resources.ExampleDescription4, @"--md 0x06000123 file.dll"),
new HelpInfo(dnSpy_Console_Resources.ExampleDescription5, @"-t system.int32 --gac-file ""mscorlib, Version=4.0.0.0"""),
};
string GetOptionName(IDecompilerOption opt, string? extraPrefix = null) {
var prefix = "--" + extraPrefix;
var o = prefix + FixInvalidSwitchChars((opt.Name is not null ? opt.Name : opt.Guid.ToString()));
if (reservedOptions.Contains(o))
o = prefix + FixInvalidSwitchChars(opt.Guid.ToString());
return o;
}
static string FixInvalidSwitchChars(string s) => s.Replace(' ', '-');
List> GetLanguageOptions() {
var list = new List>();
var dict = new Dictionary