/* 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>(); foreach (var lang in AllLanguages) { if (!dict.TryGetValue(lang.Settings, out var opts)) { dict.Add(lang.Settings, opts = new List()); list.Add(opts); } opts.Add(lang); } return list; } void Dump(Exception? ex) { while (ex is not null) { Console.WriteLine(dnSpy_Console_Resources.Error1, ex.GetType()); Console.WriteLine(" {0}", ex.Message); Console.WriteLine(" {0}", ex.StackTrace); ex = ex.InnerException; } } string GetProgramBaseName() => GetBaseName(Environment.GetCommandLineArgs()[0]); string GetBaseName(string name) { int index = name.LastIndexOf(Path.DirectorySeparatorChar); if (index < 0) return name; return name.Substring(index + 1); } const string BOOLEAN_NO_PREFIX = "no-"; const string BOOLEAN_DONT_PREFIX = "dont-"; HashSet GetReservedOptions() { var hash = new HashSet(StringComparer.Ordinal); foreach (var a in ourOptions) { hash.Add("--" + a); hash.Add("--" + BOOLEAN_NO_PREFIX + a); hash.Add("--" + BOOLEAN_DONT_PREFIX + a); } return hash; } static readonly string[] ourOptions = new string[] { // Don't include 'no-' and 'dont-' "recursive", "output-dir", "lang", "asm-path", "user-gac", "gac", "stdlib", "sln", "sln-name", "threads", "vs", "resources", "resx", "baml", "color", "spaces", "type", "md", "gac-file", "project-guid", }; void ParseCommandLine(string[] args) { if (args.Length == 0) throw new ErrorException(dnSpy_Console_Resources.MissingOptions); bool canParseCommands = true; IDecompiler? lang = null; Dictionary setOptionValue)>? langDict = null; for (int i = 0; i < args.Length; i++) { if (lang is null) { lang = GetLanguage(); langDict = CreateDecompilerOptionsDictionary(lang); } var arg = args[i]; var next = i + 1 < args.Length ? args[i + 1] : null; if (arg.Length == 0) continue; // ********************************************************************** // If you add more '--' options here, also update 'string[] ourOptions' // ********************************************************************** if (canParseCommands && arg[0] == '-') { string? error; switch (arg) { case "--": canParseCommands = false; break; case "-r": case "--recursive": isRecursive = true; break; case "-o": case "--output-dir": if (next is null) throw new ErrorException(dnSpy_Console_Resources.MissingOutputDir); outputDir = Path.GetFullPath(next); i++; break; case "-l": case "--lang": if (next is null) throw new ErrorException(dnSpy_Console_Resources.MissingLanguageName); language = next; i++; if (GetLanguageOrNull() is null) throw new ErrorException(string.Format(dnSpy_Console_Resources.LanguageDoesNotExist, language)); lang = null; langDict = null; break; case "--asm-path": if (next is null) throw new ErrorException(dnSpy_Console_Resources.MissingAsmSearchPath); asmPaths.AddRange(next.Split(new char[] { PATHS_SEP }, StringSplitOptions.RemoveEmptyEntries)); i++; break; case "--user-gac": if (next is null) throw new ErrorException(dnSpy_Console_Resources.MissingUserGacPath); userGacPaths.AddRange(next.Split(new char[] { PATHS_SEP }, StringSplitOptions.RemoveEmptyEntries)); i++; break; case "--no-gac": useGac = false; break; case "--no-stdlib": addCorlibRef = false; break; case "--no-sln": createSlnFile = false; break; case "--sln-name": if (next is null) throw new ErrorException(dnSpy_Console_Resources.MissingSolutionName); slnName = next; i++; if (Path.IsPathRooted(slnName)) throw new ErrorException(string.Format(dnSpy_Console_Resources.InvalidSolutionName, slnName)); break; case "--threads": if (next is null) throw new ErrorException(dnSpy_Console_Resources.MissingNumberOfThreads); i++; numThreads = SimpleTypeConverter.ParseInt32(next, int.MinValue, int.MaxValue, out error); if (!string2.IsNullOrEmpty(error)) throw new ErrorException(error); break; case "--vs": if (next is null) throw new ErrorException(dnSpy_Console_Resources.MissingVSVersion); i++; int vsVer; vsVer = SimpleTypeConverter.ParseInt32(next, int.MinValue, int.MaxValue, out error); if (!string2.IsNullOrEmpty(error)) throw new ErrorException(error); switch (vsVer) { case 2005: projectVersion = ProjectVersion.VS2005; break; case 2008: projectVersion = ProjectVersion.VS2008; break; case 2010: projectVersion = ProjectVersion.VS2010; break; case 2012: projectVersion = ProjectVersion.VS2012; break; case 2013: projectVersion = ProjectVersion.VS2013; break; case 2015: projectVersion = ProjectVersion.VS2015; break; case 2017: projectVersion = ProjectVersion.VS2017; break; case 2019: projectVersion = ProjectVersion.VS2019; break; default: throw new ErrorException(string.Format(dnSpy_Console_Resources.InvalidVSVersion, vsVer)); } break; case "--no-resources": unpackResources = false; break; case "--no-resx": createResX = false; break; case "--no-baml": decompileBaml = false; break; case "--no-color": colorizeOutput = false; break; case "--spaces": if (next is null) throw new ErrorException(dnSpy_Console_Resources.MissingArgument); const int MIN_SPACES = 0, MAX_SPACES = 100; if (!int.TryParse(next, out spaces) || spaces < MIN_SPACES || spaces > MAX_SPACES) throw new ErrorException(string.Format(dnSpy_Console_Resources.InvalidSpacesArgument, MIN_SPACES, MAX_SPACES)); i++; break; case "-t": case "--type": if (next is null) throw new ErrorException(dnSpy_Console_Resources.MissingTypeName); i++; typeName = next; break; case "--md": if (next is null) throw new ErrorException(dnSpy_Console_Resources.MissingMDToken); i++; mdToken = SimpleTypeConverter.ParseInt32(next, int.MinValue, int.MaxValue, out error); if (!string2.IsNullOrEmpty(error)) throw new ErrorException(error); break; case "--gac-file": if (next is null) throw new ErrorException(dnSpy_Console_Resources.MissingGacFile); i++; gacFiles.Add(next); break; case "--project-guid": if (next is null || !Guid.TryParse(next, out projectGuid)) throw new ErrorException(dnSpy_Console_Resources.InvalidGuid); i++; break; default: (IDecompilerOption option, Action setOptionValue) tuple; Debug2.Assert(langDict is not null); if (langDict.TryGetValue(arg, out tuple)) { bool hasArg = tuple.option.Type != typeof(bool); if (hasArg && next is null) throw new ErrorException(dnSpy_Console_Resources.MissingOptionArgument); if (hasArg) i++; tuple.setOptionValue(next ?? string.Empty); break; } throw new ErrorException(string.Format(dnSpy_Console_Resources.InvalidOption, arg)); } } else files.Add(arg); } } static int ParseInt32(string s) { var v = SimpleTypeConverter.ParseInt32(s, int.MinValue, int.MaxValue, out var error); if (!string2.IsNullOrEmpty(error)) throw new ErrorException(error); return v; } static string ParseString(string s) => s; Dictionary setOptionValue)> CreateDecompilerOptionsDictionary(IDecompiler decompiler) { var dict = new Dictionary)>(); if (decompiler is null) return dict; foreach (var tmp in decompiler.Settings.Options) { var opt = tmp; if (opt.Type == typeof(bool)) { dict[GetOptionName(opt)] = (opt, new Action(a => opt.Value = true)); dict[GetOptionName(opt, BOOLEAN_NO_PREFIX)] = (opt, new Action(a => opt.Value = false)); dict[GetOptionName(opt, BOOLEAN_DONT_PREFIX)] = (opt, new Action(a => opt.Value = false)); } else if (opt.Type == typeof(int)) dict[GetOptionName(opt)] = (opt, new Action(a => opt.Value = ParseInt32(a))); else if (opt.Type == typeof(string)) dict[GetOptionName(opt)] = (opt, new Action(a => opt.Value = ParseString(a))); else Debug.Fail($"Unsupported type: {opt.Type}"); } return dict; } void AddSearchPath(string dir) { if (Directory.Exists(dir) && !addedPaths.Contains(dir)) { addedPaths.Add(dir); assemblyResolver.PreSearchPaths.Add(dir); } } readonly HashSet addedPaths = new HashSet(StringComparer.OrdinalIgnoreCase); void Decompile() { foreach (var dir in asmPaths) AddSearchPath(dir); foreach (var dir in userGacPaths) AddSearchPath(dir); assemblyResolver.UseGAC = useGac; var files = new List(GetDotNetFiles()); string guidStr = projectGuid.ToString(); int guidNum = int.Parse(guidStr.Substring(36 - 8, 8), NumberStyles.HexNumber); string guidFormat = guidStr.Substring(0, 36 - 8) + "{0:X8}"; foreach (var file in files.OrderBy(a => a.Module.Location, StringComparer.InvariantCultureIgnoreCase)) file.ProjectGuid = new Guid(string.Format(guidFormat, guidNum++)); if (mdToken != 0 || typeName is not null) { if (files.Count == 0) throw new ErrorException(dnSpy_Console_Resources.MissingDotNetFilename); if (files.Count != 1) throw new ErrorException(dnSpy_Console_Resources.OnlyOneFileCanBeDecompiled); IMemberDef? member; if (typeName is not null) member = FindType(files[0].Module, typeName); else member = files[0].Module.ResolveToken(mdToken) as IMemberDef; if (member is null) { if (typeName is not null) throw new ErrorException(string.Format(dnSpy_Console_Resources.CouldNotFindTypeX, typeName)); throw new ErrorException(dnSpy_Console_Resources.InvalidToken); } var writer = Console.Out; IDecompilerOutput output; if (colorizeOutput) output = new ConsoleColorizerOutput(writer, CreateColorProvider(), GetIndenter()); else output = new TextWriterDecompilerOutput(writer, GetIndenter()); var lang = GetLanguage(); if (member is MethodDef) lang.Decompile((MethodDef)member, output, decompilationContext); else if (member is FieldDef) lang.Decompile((FieldDef)member, output, decompilationContext); else if (member is PropertyDef) lang.Decompile((PropertyDef)member, output, decompilationContext); else if (member is EventDef) lang.Decompile((EventDef)member, output, decompilationContext); else if (member is TypeDef) lang.Decompile((TypeDef)member, output, decompilationContext); else throw new ErrorException(dnSpy_Console_Resources.InvalidMemberToDecompile); } else { if (string2.IsNullOrEmpty(outputDir)) throw new ErrorException(dnSpy_Console_Resources.MissingOutputDir); if (GetLanguage().ProjectFileExtension is null) throw new ErrorException(string.Format(dnSpy_Console_Resources.LanguageXDoesNotSupportProjects, GetLanguage().UniqueNameUI)); decompilationContext.AsyncMethodBodyDecompilation = false; var options = new ProjectCreatorOptions(outputDir, decompilationContext.CancellationToken); options.Logger = this; options.ProjectVersion = projectVersion; options.NumberOfThreads = numThreads; options.ProjectModules.AddRange(files); options.UserGACPaths.AddRange(userGacPaths); options.CreateDecompilerOutput = textWriter => new TextWriterDecompilerOutput(textWriter, GetIndenter()); if (createSlnFile && !string.IsNullOrEmpty(slnName)) options.SolutionFilename = slnName; var creator = new MSBuildProjectCreator(options); creator.Create(); } } Indenter GetIndenter() { if (spaces <= 0) return new Indenter(4, 4, true); return new Indenter(spaces, spaces, false); } static TypeDef? FindType(ModuleDef module, string name) => FindTypeFullName(module, name, StringComparer.Ordinal) ?? FindTypeFullName(module, name, StringComparer.OrdinalIgnoreCase) ?? FindTypeName(module, name, StringComparer.Ordinal) ?? FindTypeName(module, name, StringComparer.OrdinalIgnoreCase); static TypeDef? FindTypeFullName(ModuleDef module, string name, StringComparer comparer) { var sb = new StringBuilder(); return module.GetTypes().FirstOrDefault(a => { sb.Clear(); string s1, s2; if (comparer.Equals(s1 = FullNameFactory.FullName(a, false, null, sb), name)) return true; sb.Clear(); if (comparer.Equals(s2 = FullNameFactory.FullName(a, true, null, sb), name)) return true; sb.Clear(); if (comparer.Equals(CleanTypeName(s1), name)) return true; sb.Clear(); return comparer.Equals(CleanTypeName(s2), name); }); } static TypeDef? FindTypeName(ModuleDef module, string name, StringComparer comparer) { var sb = new StringBuilder(); return module.GetTypes().FirstOrDefault(a => { sb.Clear(); string s1, s2; if (comparer.Equals(s1 = FullNameFactory.Name(a, false, sb), name)) return true; sb.Clear(); if (comparer.Equals(s2 = FullNameFactory.Name(a, true, sb), name)) return true; sb.Clear(); if (comparer.Equals(CleanTypeName(s1), name)) return true; sb.Clear(); return comparer.Equals(CleanTypeName(s2), name); }); } static string CleanTypeName(string s) { int i = s.LastIndexOf('`'); if (i < 0) return s; return s.Substring(0, i); } IEnumerable GetDotNetFiles() { foreach (var file in files) { if (File.Exists(file)) { var info = OpenNetFile(file); if (info is null) throw new Exception(string.Format(dnSpy_Console_Resources.NotDotNetFile, file)); yield return info; } else if (Directory.Exists(file)) { foreach (var info in DumpDir(file, null)) yield return info; } else { var path = Path.GetDirectoryName(file); var name = Path.GetFileName(file); if (Directory.Exists(path)) { Debug2.Assert(path is not null); foreach (var info in DumpDir(path, name)) yield return info; } else throw new ErrorException(string.Format(dnSpy_Console_Resources.FileOrDirDoesNotExist, file)); } } // Don't use exact matching here so the user can tell us to load eg. "mscorlib, Version=4.0.0.0" which // is easier to type than the full assembly name var oldFindExactMatch = assemblyResolver.FindExactMatch; assemblyResolver.FindExactMatch = false; foreach (var asmName in gacFiles) { var asm = assemblyResolver.Resolve(new AssemblyNameInfo(asmName), null); if (asm is null) throw new ErrorException(string.Format(dnSpy_Console_Resources.CouldNotResolveGacFileX, asmName)); yield return CreateProjectModuleOptions(asm.ManifestModule); } assemblyResolver.FindExactMatch = oldFindExactMatch; } IEnumerable DumpDir(string path, string? pattern) { pattern ??= "*"; Stack stack = new Stack(); stack.Push(path); while (stack.Count > 0) { path = stack.Pop(); foreach (var info in DumpDir2(path, pattern)) yield return info; if (isRecursive) { foreach (var di in GetDirs(path)) stack.Push(di.FullName); } } } IEnumerable GetDirs(string path) { IEnumerable? fsysIter = null; try { fsysIter = new DirectoryInfo(path).EnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly); } catch (IOException) { } catch (UnauthorizedAccessException) { } catch (SecurityException) { } if (fsysIter is null) yield break; foreach (var info in fsysIter) { if ((info.Attributes & System.IO.FileAttributes.Directory) == 0) continue; DirectoryInfo? di = null; try { di = new DirectoryInfo(info.FullName); } catch (IOException) { } catch (UnauthorizedAccessException) { } catch (SecurityException) { } if (di is not null) yield return di; } } IEnumerable DumpDir2(string path, string pattern) { pattern ??= "*"; foreach (var fi in GetFiles(path, pattern)) { var info = OpenNetFile(fi.FullName); if (info is not null) yield return info; } } IEnumerable GetFiles(string path, string pattern) { IEnumerable? fsysIter = null; try { fsysIter = new DirectoryInfo(path).EnumerateFileSystemInfos(pattern, SearchOption.TopDirectoryOnly); } catch (IOException) { } catch (UnauthorizedAccessException) { } catch (SecurityException) { } if (fsysIter is null) yield break; foreach (var info in fsysIter) { if ((info.Attributes & System.IO.FileAttributes.Directory) != 0) continue; FileInfo? fi = null; try { fi = new FileInfo(info.FullName); } catch (IOException) { } catch (UnauthorizedAccessException) { } catch (SecurityException) { } if (fi is not null) yield return fi; } } ProjectModuleOptions? OpenNetFile(string file) { try { file = Path.GetFullPath(file); if (!File.Exists(file)) return null; return CreateProjectModuleOptions(ModuleDefMD.Load(file, moduleContext)); } catch { } return null; } ProjectModuleOptions CreateProjectModuleOptions(ModuleDef mod) { mod.EnableTypeDefFindCache = true; ((AssemblyResolver)moduleContext.AssemblyResolver).AddToCache(mod); AddSearchPath(Path.GetDirectoryName(mod.Location)!); var proj = new ProjectModuleOptions(mod, GetLanguage(), decompilationContext); proj.DontReferenceStdLib = !addCorlibRef; proj.UnpackResources = unpackResources; proj.CreateResX = createResX; proj.DecompileXaml = decompileBaml && bamlDecompiler is not null; var o = BamlDecompilerOptions.Create(GetLanguage()); var outputOptions = new XamlOutputOptions { IndentChars = "\t", NewLineChars = Environment.NewLine, NewLineOnAttributes = true, }; if (bamlDecompiler is not null) proj.DecompileBaml = (a, b, c, d) => bamlDecompiler.Decompile(a, b, c, o, d, outputOptions); return proj; } IDecompiler GetLanguage() => GetLanguageOrNull() ?? throw new InvalidOperationException(); IDecompiler? GetLanguageOrNull() { bool hasGuid = Guid.TryParse(language, out var guid); return AllLanguages.FirstOrDefault(a => { if (StringComparer.OrdinalIgnoreCase.Equals(language, a.UniqueNameUI)) return true; if (hasGuid && (guid.Equals(a.UniqueGuid) || guid.Equals(a.GenericGuid))) return true; return false; }); } IDecompiler[] AllLanguages => allLanguages; readonly IDecompiler[] allLanguages; public void Error(string message) { errors++; Console.Error.WriteLine(string.Format(dnSpy_Console_Resources.Error1, message)); } int errors; ColorProvider CreateColorProvider() { var provider = new ColorProvider(); provider.Add(TextColor.Operator, null, null); provider.Add(TextColor.Punctuation, null, null); provider.Add(TextColor.Number, null, null); provider.Add(TextColor.Comment, ConsoleColor.Green, null); provider.Add(TextColor.Keyword, ConsoleColor.Cyan, null); provider.Add(TextColor.String, ConsoleColor.DarkYellow, null); provider.Add(TextColor.VerbatimString, ConsoleColor.DarkYellow, null); provider.Add(TextColor.Char, ConsoleColor.DarkYellow, null); provider.Add(TextColor.Namespace, ConsoleColor.Yellow, null); provider.Add(TextColor.Type, ConsoleColor.Magenta, null); provider.Add(TextColor.SealedType, ConsoleColor.Magenta, null); provider.Add(TextColor.StaticType, ConsoleColor.Magenta, null); provider.Add(TextColor.Delegate, ConsoleColor.Magenta, null); provider.Add(TextColor.Enum, ConsoleColor.Magenta, null); provider.Add(TextColor.Interface, ConsoleColor.Magenta, null); provider.Add(TextColor.ValueType, ConsoleColor.Green, null); provider.Add(TextColor.Module, ConsoleColor.DarkMagenta, null); provider.Add(TextColor.TypeGenericParameter, ConsoleColor.Magenta, null); provider.Add(TextColor.MethodGenericParameter, ConsoleColor.Magenta, null); provider.Add(TextColor.InstanceMethod, ConsoleColor.DarkYellow, null); provider.Add(TextColor.StaticMethod, ConsoleColor.DarkYellow, null); provider.Add(TextColor.ExtensionMethod, ConsoleColor.DarkYellow, null); provider.Add(TextColor.InstanceField, ConsoleColor.Magenta, null); provider.Add(TextColor.EnumField, ConsoleColor.Magenta, null); provider.Add(TextColor.LiteralField, ConsoleColor.Magenta, null); provider.Add(TextColor.StaticField, ConsoleColor.Magenta, null); provider.Add(TextColor.InstanceEvent, ConsoleColor.Magenta, null); provider.Add(TextColor.StaticEvent, ConsoleColor.Magenta, null); provider.Add(TextColor.InstanceProperty, ConsoleColor.Magenta, null); provider.Add(TextColor.StaticProperty, ConsoleColor.Magenta, null); provider.Add(TextColor.Local, ConsoleColor.White, null); provider.Add(TextColor.Parameter, ConsoleColor.White, null); provider.Add(TextColor.PreprocessorKeyword, ConsoleColor.Blue, null); provider.Add(TextColor.PreprocessorText, null, null); provider.Add(TextColor.Label, ConsoleColor.DarkRed, null); provider.Add(TextColor.OpCode, ConsoleColor.Cyan, null); provider.Add(TextColor.ILDirective, ConsoleColor.Cyan, null); provider.Add(TextColor.ILModule, ConsoleColor.DarkMagenta, null); provider.Add(TextColor.ExcludedCode, null, null); provider.Add(TextColor.XmlDocCommentAttributeName, ConsoleColor.DarkGreen, null); provider.Add(TextColor.XmlDocCommentAttributeQuotes, ConsoleColor.DarkGreen, null); provider.Add(TextColor.XmlDocCommentAttributeValue, ConsoleColor.DarkGreen, null); provider.Add(TextColor.XmlDocCommentCDataSection, ConsoleColor.DarkGreen, null); provider.Add(TextColor.XmlDocCommentComment, ConsoleColor.DarkGreen, null); provider.Add(TextColor.XmlDocCommentDelimiter, ConsoleColor.DarkGreen, null); provider.Add(TextColor.XmlDocCommentEntityReference, ConsoleColor.DarkGreen, null); provider.Add(TextColor.XmlDocCommentName, ConsoleColor.DarkGreen, null); provider.Add(TextColor.XmlDocCommentProcessingInstruction, ConsoleColor.DarkGreen, null); provider.Add(TextColor.XmlDocCommentText, ConsoleColor.DarkGreen, null); provider.Add(TextColor.Error, ConsoleColor.Red, null); return provider; } } }