// Copyright (c) 2020 Siegfried Pammer // // 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. using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection.PortableExecutable; using System.Xml; using ICSharpCode.Decompiler.Metadata; namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler { /// /// A implementation that creates the projects in the SDK style format. /// sealed class ProjectFileWriterSdkStyle : IProjectFileWriter { const string AspNetCorePrefix = "Microsoft.AspNetCore"; const string PresentationFrameworkName = "PresentationFramework"; const string WindowsFormsName = "System.Windows.Forms"; const string TrueString = "True"; const string FalseString = "False"; const string AnyCpuString = "AnyCPU"; static readonly HashSet ImplicitReferences = new HashSet { "mscorlib", "netstandard", "PresentationFramework", "System", "System.Diagnostics.Debug", "System.Diagnostics.Tools", "System.Drawing", "System.Runtime", "System.Runtime.Extensions", "System.Windows.Forms", "System.Xaml", }; enum ProjectType { Default, WinForms, Wpf, Web } /// /// Creates a new instance of the class. /// /// A new instance of the class. public static IProjectFileWriter Create() => new ProjectFileWriterSdkStyle(); /// public void Write( TextWriter target, IProjectInfoProvider project, IEnumerable<(string itemType, string fileName)> files, PEFile module) { using (XmlTextWriter xmlWriter = new XmlTextWriter(target)) { xmlWriter.Formatting = Formatting.Indented; Write(xmlWriter, project, module); } } static void Write(XmlTextWriter xml, IProjectInfoProvider project, PEFile module) { xml.WriteStartElement("Project"); var projectType = GetProjectType(module); xml.WriteAttributeString("Sdk", GetSdkString(projectType)); PlaceIntoTag("PropertyGroup", xml, () => WriteAssemblyInfo(xml, module, projectType)); PlaceIntoTag("PropertyGroup", xml, () => WriteProjectInfo(xml, project)); PlaceIntoTag("ItemGroup", xml, () => WriteReferences(xml, module, project)); xml.WriteEndElement(); } static void PlaceIntoTag(string tagName, XmlTextWriter xml, Action content) { xml.WriteStartElement(tagName); try { content(); } finally { xml.WriteEndElement(); } } static void WriteAssemblyInfo(XmlTextWriter xml, PEFile module, ProjectType projectType) { xml.WriteElementString("AssemblyName", module.Name); // Since we create AssemblyInfo.cs manually, we need to disable the auto-generation xml.WriteElementString("GenerateAssemblyInfo", FalseString); // 'Library' is default, so only need to specify output type for executables if (!module.Reader.PEHeaders.IsDll) { WriteOutputType(xml, module.Reader.PEHeaders.PEHeader.Subsystem); } WriteDesktopExtensions(xml, projectType); string platformName = TargetServices.GetPlatformName(module); var targetFramework = TargetServices.DetectTargetFramework(module); if (targetFramework.Moniker == null) { throw new NotSupportedException($"Cannot decompile this assembly to a SDK style project. Use default project format instead."); } xml.WriteElementString("TargetFramework", targetFramework.Moniker); // 'AnyCPU' is default, so only need to specify platform if it differs if (platformName != AnyCpuString) { xml.WriteElementString("PlatformTarget", platformName); } if (platformName == AnyCpuString && (module.Reader.PEHeaders.CorHeader.Flags & CorFlags.Prefers32Bit) != 0) { xml.WriteElementString("Prefer32Bit", TrueString); } } static void WriteOutputType(XmlTextWriter xml, Subsystem moduleSubsystem) { switch (moduleSubsystem) { case Subsystem.WindowsGui: xml.WriteElementString("OutputType", "WinExe"); break; case Subsystem.WindowsCui: xml.WriteElementString("OutputType", "Exe"); break; } } static void WriteDesktopExtensions(XmlTextWriter xml, ProjectType projectType) { if (projectType == ProjectType.Wpf) { xml.WriteElementString("UseWPF", TrueString); } else if (projectType == ProjectType.WinForms) { xml.WriteElementString("UseWindowsForms", TrueString); } } static void WriteProjectInfo(XmlTextWriter xml, IProjectInfoProvider project) { xml.WriteElementString("LangVersion", project.LanguageVersion.ToString().Replace("CSharp", "").Replace('_', '.')); xml.WriteElementString("AllowUnsafeBlocks", TrueString); if (project.StrongNameKeyFile != null) { xml.WriteElementString("SignAssembly", TrueString); xml.WriteElementString("AssemblyOriginatorKeyFile", Path.GetFileName(project.StrongNameKeyFile)); } } static void WriteReferences(XmlTextWriter xml, PEFile module, IProjectInfoProvider project) { foreach (var reference in module.AssemblyReferences.Where(r => !ImplicitReferences.Contains(r.Name))) { xml.WriteStartElement("Reference"); xml.WriteAttributeString("Include", reference.Name); var asembly = project.AssemblyResolver.Resolve(reference); if (asembly != null) { xml.WriteElementString("HintPath", asembly.FileName); } xml.WriteEndElement(); } } static string GetSdkString(ProjectType projectType) { switch (projectType) { case ProjectType.WinForms: case ProjectType.Wpf: return "Microsoft.NET.Sdk.WindowsDesktop"; case ProjectType.Web: return "Microsoft.NET.Sdk.Web"; default: return "Microsoft.NET.Sdk"; } } static ProjectType GetProjectType(PEFile module) { foreach (var referenceName in module.AssemblyReferences.Select(r => r.Name)) { if (referenceName.StartsWith(AspNetCorePrefix, StringComparison.Ordinal)) { return ProjectType.Web; } if (referenceName == PresentationFrameworkName) { return ProjectType.Wpf; } if (referenceName == WindowsFormsName) { return ProjectType.WinForms; } } return ProjectType.Default; } } }