diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj index 4aaec9f79..f14b1077e 100644 --- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj +++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj @@ -88,6 +88,7 @@ + diff --git a/ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs b/ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs new file mode 100644 index 000000000..9a3b8b124 --- /dev/null +++ b/ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs @@ -0,0 +1,92 @@ +// Copyright (c) 2020 Daniel Grunwald +// +// 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 ICSharpCode.Decompiler.CSharp.ProjectDecompiler; +using NUnit.Framework; + +namespace ICSharpCode.Decompiler.Tests +{ + [TestFixture] + public sealed class TargetFrameworkTests + { + [TestCase(-1)] + [TestCase(0)] + [TestCase(1)] + [TestCase(99)] + [TestCase(int.MinValue)] + public void VerifyThrowsForInvalidVersion(int invalidVersion) + { + // Arrange - nothing + + // Act + void CreateInstance() => new TargetFramework(identifier: null, invalidVersion, profile: null); + + // Assert + Assert.Throws(CreateInstance); + } + + [TestCase(100, "v1.0")] + [TestCase(102, "v1.0.2")] + [TestCase(130, "v1.3")] + [TestCase(145, "v1.4.5")] + [TestCase(1670, "v16.7")] + [TestCase(1800, "v18.0")] + public void VerifyVersion(int version, string expectedVersion) + { + // Arrange - nothing + + // Act + var targetFramework = new TargetFramework(identifier: null, version, profile: null); + + // Assert + Assert.AreEqual(version, targetFramework.VersionNumber); + Assert.AreEqual(expectedVersion, targetFramework.VersionString); + } + + [Test] + public void VerifyPortableLibrary() + { + // Arrange + const string identifier = ".NETPortable"; + + // Act + var targetFramework = new TargetFramework(identifier, 100, profile: null); + + // Assert + Assert.IsTrue(targetFramework.IsPortableClassLibrary); + Assert.AreEqual(identifier, targetFramework.Identifier); + } + + [Test] + [Pairwise] + public void VerifyIdentifierAndProfile( + [Values(null, "", ".NETFramework")] string identifier, + [Values(null, "", ".Client")] string profile) + { + // Arrange - nothing + + // Act + var targetFramework = new TargetFramework(identifier, 100, profile); + + // Assert + Assert.AreEqual(identifier, targetFramework.Identifier); + Assert.AreEqual(profile, targetFramework.Profile); + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs new file mode 100644 index 000000000..9dbef8595 --- /dev/null +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs @@ -0,0 +1,41 @@ +// 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.Collections.Generic; +using System.IO; +using ICSharpCode.Decompiler.Metadata; + +namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler +{ + /// + /// An interface for a service that creates and writes a project file structure + /// for a specific module being decompiled. + /// + interface IProjectFileWriter + { + /// + /// Writes the content of a new project file for the specified being decompiled. + /// + /// The target to write to. + /// The information about the project being created. + /// A collection of source files to be included into the project, each item is a pair + /// of the project entry type and the file path. + /// The module being decompiled. + void Write(TextWriter target, IProjectInfoProvider project, IEnumerable<(string itemType, string fileName)> files, PEFile module); + } +} diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs new file mode 100644 index 000000000..6eb47a102 --- /dev/null +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs @@ -0,0 +1,49 @@ +// Copyright (c) 2020 Daniel Grunwald +// +// 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 ICSharpCode.Decompiler.Metadata; + +namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler +{ + /// + /// An interface that provides common information for a project being decompiled to. + /// + interface IProjectInfoProvider + { + /// + /// Gets the assembly resolver active for the project. + /// + IAssemblyResolver AssemblyResolver { get; } + + /// + /// Gets the C# language version of the project. + /// + LanguageVersion LanguageVersion { get; } + + /// + /// Gets the unique ID of the project. + /// + Guid ProjectGuid { get; } + + /// + /// Gets the name of the key file being used for strong name signing. Can be null if no file is available. + /// + string StrongNameKeyFile { get; } + } +} \ No newline at end of file diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs new file mode 100644 index 000000000..6f21f0567 --- /dev/null +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs @@ -0,0 +1,175 @@ +// 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; +using ICSharpCode.Decompiler.Solution; + +namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler +{ + /// + /// A implementation that creates the projects in the default format. + /// + sealed class ProjectFileWriterDefault : IProjectFileWriter + { + /// + /// Creates a new instance of the class. + /// + /// A new instance of the class. + public static IProjectFileWriter Create() => new ProjectFileWriterDefault(); + + /// + public void Write( + TextWriter target, + IProjectInfoProvider project, + IEnumerable<(string itemType, string fileName)> files, + PEFile module) + { + const string ns = "http://schemas.microsoft.com/developer/msbuild/2003"; + string platformName = TargetServices.GetPlatformName(module); + var targetFramework = TargetServices.DetectTargetFramework(module); + + List typeGuids = new List(); + if (targetFramework.IsPortableClassLibrary) + typeGuids.Add(ProjectTypeGuids.PortableLibrary); + typeGuids.Add(ProjectTypeGuids.CSharpWindows); + + using (XmlTextWriter w = new XmlTextWriter(target)) { + w.Formatting = Formatting.Indented; + w.WriteStartDocument(); + w.WriteStartElement("Project", ns); + w.WriteAttributeString("ToolsVersion", "4.0"); + w.WriteAttributeString("DefaultTargets", "Build"); + + w.WriteStartElement("PropertyGroup"); + w.WriteElementString("ProjectGuid", project.ProjectGuid.ToString("B").ToUpperInvariant()); + w.WriteElementString("ProjectTypeGuids", string.Join(";", typeGuids.Select(g => g.ToString("B").ToUpperInvariant()))); + + w.WriteStartElement("Configuration"); + w.WriteAttributeString("Condition", " '$(Configuration)' == '' "); + w.WriteValue("Debug"); + w.WriteEndElement(); // + + w.WriteStartElement("Platform"); + w.WriteAttributeString("Condition", " '$(Platform)' == '' "); + w.WriteValue(platformName); + w.WriteEndElement(); // + + if (module.Reader.PEHeaders.IsDll) { + w.WriteElementString("OutputType", "Library"); + } else { + switch (module.Reader.PEHeaders.PEHeader.Subsystem) { + case Subsystem.WindowsGui: + w.WriteElementString("OutputType", "WinExe"); + break; + case Subsystem.WindowsCui: + w.WriteElementString("OutputType", "Exe"); + break; + default: + w.WriteElementString("OutputType", "Library"); + break; + } + } + + w.WriteElementString("LangVersion", project.LanguageVersion.ToString().Replace("CSharp", "").Replace('_', '.')); + + w.WriteElementString("AssemblyName", module.Name); + if (targetFramework.Identifier != null) + w.WriteElementString("TargetFrameworkIdentifier", targetFramework.Identifier); + if (targetFramework.VersionString != null) + w.WriteElementString("TargetFrameworkVersion", targetFramework.VersionString); + if (targetFramework.Profile != null) + w.WriteElementString("TargetFrameworkProfile", targetFramework.Profile); + w.WriteElementString("WarningLevel", "4"); + w.WriteElementString("AllowUnsafeBlocks", "True"); + + if (project.StrongNameKeyFile != null) { + w.WriteElementString("SignAssembly", "True"); + w.WriteElementString("AssemblyOriginatorKeyFile", Path.GetFileName(project.StrongNameKeyFile)); + } + + w.WriteEndElement(); // + + w.WriteStartElement("PropertyGroup"); // platform-specific + w.WriteAttributeString("Condition", " '$(Platform)' == '" + platformName + "' "); + w.WriteElementString("PlatformTarget", platformName); + if (targetFramework.VersionNumber > 400 && platformName == "AnyCPU" && (module.Reader.PEHeaders.CorHeader.Flags & CorFlags.Prefers32Bit) == 0) { + w.WriteElementString("Prefer32Bit", "false"); + } + w.WriteEndElement(); // (platform-specific) + + w.WriteStartElement("PropertyGroup"); // Debug + w.WriteAttributeString("Condition", " '$(Configuration)' == 'Debug' "); + w.WriteElementString("OutputPath", "bin\\Debug\\"); + w.WriteElementString("DebugSymbols", "true"); + w.WriteElementString("DebugType", "full"); + w.WriteElementString("Optimize", "false"); + w.WriteEndElement(); // (Debug) + + w.WriteStartElement("PropertyGroup"); // Release + w.WriteAttributeString("Condition", " '$(Configuration)' == 'Release' "); + w.WriteElementString("OutputPath", "bin\\Release\\"); + w.WriteElementString("DebugSymbols", "true"); + w.WriteElementString("DebugType", "pdbonly"); + w.WriteElementString("Optimize", "true"); + w.WriteEndElement(); // (Release) + + + w.WriteStartElement("ItemGroup"); // References + foreach (var r in module.AssemblyReferences) { + if (r.Name != "mscorlib") { + w.WriteStartElement("Reference"); + w.WriteAttributeString("Include", r.Name); + var asm = project.AssemblyResolver.Resolve(r); + if (asm != null && !project.AssemblyResolver.IsGacAssembly(r)) { + w.WriteElementString("HintPath", asm.FileName); + } + w.WriteEndElement(); + } + } + w.WriteEndElement(); // (References) + + foreach (IGrouping gr in from f in files group f.fileName by f.itemType into g orderby g.Key select g) { + w.WriteStartElement("ItemGroup"); + foreach (string file in gr.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)) { + w.WriteStartElement(gr.Key); + w.WriteAttributeString("Include", file); + w.WriteEndElement(); + } + w.WriteEndElement(); + } + if (targetFramework.IsPortableClassLibrary) { + w.WriteStartElement("Import"); + w.WriteAttributeString("Project", "$(MSBuildExtensionsPath32)\\Microsoft\\Portable\\$(TargetFrameworkVersion)\\Microsoft.Portable.CSharp.targets"); + w.WriteEndElement(); + } else { + w.WriteStartElement("Import"); + w.WriteAttributeString("Project", "$(MSBuildToolsPath)\\Microsoft.CSharp.targets"); + w.WriteEndElement(); + } + + w.WriteEndDocument(); + } + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetFramework.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetFramework.cs new file mode 100644 index 000000000..43e8642ee --- /dev/null +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetFramework.cs @@ -0,0 +1,94 @@ +// 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.Text; + +namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler +{ + /// + /// A class describing the target framework of a module. + /// + sealed class TargetFramework + { + const string DotNetPortableIdentifier = ".NETPortable"; + + /// + /// Initializes a new instance of the class. + /// + /// The framework identifier string. Can be null. + /// The framework version string. Must be greater than 100 (where 100 corresponds to v1.0). + /// The framework profile. Can be null. + public TargetFramework(string identifier, int version, string profile) + { + if (version < 100) { + throw new ArgumentException("The version number must be greater than or equal to 100", nameof(version)); + } + + Identifier = identifier; + VersionNumber = version; + VersionString = "v" + GetVersionString(version); + Profile = profile; + IsPortableClassLibrary = identifier == DotNetPortableIdentifier; + } + + /// + /// Gets the target framework identifier. Can be null if not defined. + /// + public string Identifier { get; } + + /// + /// Gets the target framework version, e.g. "v4.5". + /// + public string VersionString { get; } + + /// + /// Gets the target framework version as integer (multiplied by 100), e.g. 450. + /// + public int VersionNumber { get; } + + /// + /// Gets the target framework profile. Can be null if not set or not available. + /// + public string Profile { get; } + + /// + /// Gets a value indicating whether the target is a portable class library (PCL). + /// + public bool IsPortableClassLibrary { get; } + + static string GetVersionString(int version) + { + int major = version / 100; + int minor = version % 100 / 10; + int patch = version % 10; + + var versionBuilder = new StringBuilder(8); + versionBuilder.Append(major); + versionBuilder.Append('.'); + versionBuilder.Append(minor); + + if (patch != 0) { + versionBuilder.Append('.'); + versionBuilder.Append(patch); + } + + return versionBuilder.ToString(); + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetServices.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetServices.cs new file mode 100644 index 000000000..94d1dc296 --- /dev/null +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetServices.cs @@ -0,0 +1,129 @@ +// 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.Linq; +using System.Reflection.PortableExecutable; +using ICSharpCode.Decompiler.Metadata; + +namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler +{ + /// + /// Helper services for determining the target framework and platform of a module. + /// + static class TargetServices + { + const string VersionToken = "Version="; + const string ProfileToken = "Profile="; + + /// + /// Gets the for the specified . + /// + /// The module to get the target framework description for. Cannot be null. + /// A new instance of the class that describes the specified . + /// + public static TargetFramework DetectTargetFramework(PEFile module) + { + if (module is null) { + throw new ArgumentNullException(nameof(module)); + } + + int versionNumber; + switch (module.GetRuntime()) { + case TargetRuntime.Net_1_0: + versionNumber = 100; + break; + + case TargetRuntime.Net_1_1: + versionNumber = 110; + break; + + case TargetRuntime.Net_2_0: + versionNumber = 200; + // TODO: Detect when .NET 3.0/3.5 is required + break; + + default: + versionNumber = 400; + break; + } + + string targetFrameworkIdentifier = null; + string targetFrameworkProfile = null; + + string targetFramework = module.DetectTargetFrameworkId(); + if (!string.IsNullOrEmpty(targetFramework)) { + string[] frameworkParts = targetFramework.Split(','); + targetFrameworkIdentifier = frameworkParts.FirstOrDefault(a => !a.StartsWith(VersionToken, StringComparison.OrdinalIgnoreCase) && !a.StartsWith(ProfileToken, StringComparison.OrdinalIgnoreCase)); + string frameworkVersion = frameworkParts.FirstOrDefault(a => a.StartsWith(VersionToken, StringComparison.OrdinalIgnoreCase)); + + if (frameworkVersion != null) { + versionNumber = int.Parse(frameworkVersion.Substring(VersionToken.Length + 1).Replace(".", "")); + if (versionNumber < 100) versionNumber *= 10; + } + + string frameworkProfile = frameworkParts.FirstOrDefault(a => a.StartsWith(ProfileToken, StringComparison.OrdinalIgnoreCase)); + if (frameworkProfile != null) + targetFrameworkProfile = frameworkProfile.Substring(ProfileToken.Length); + } + + return new TargetFramework(targetFrameworkIdentifier, versionNumber, targetFrameworkProfile); + } + + /// + /// Gets the string representation (name) of the target platform of the specified . + /// + /// The module to get the target framework description for. Cannot be null. + /// The platform name, e.g. "AnyCPU" or "x86". + public static string GetPlatformName(PEFile module) + { + if (module is null) { + throw new ArgumentNullException(nameof(module)); + } + + var headers = module.Reader.PEHeaders; + var architecture = headers.CoffHeader.Machine; + var characteristics = headers.CoffHeader.Characteristics; + var corflags = headers.CorHeader.Flags; + + switch (architecture) { + case Machine.I386: + if ((corflags & CorFlags.Prefers32Bit) != 0) + return "AnyCPU"; + + if ((corflags & CorFlags.Requires32Bit) != 0) + return "x86"; + + // According to ECMA-335, II.25.3.3.1 CorFlags.Requires32Bit and Characteristics.Bit32Machine must be in sync + // for assemblies containing managed code. However, this is not true for C++/CLI assemblies. + if ((corflags & CorFlags.ILOnly) == 0 && (characteristics & Characteristics.Bit32Machine) != 0) + return "x86"; + return "AnyCPU"; + + case Machine.Amd64: + return "x64"; + + case Machine.IA64: + return "Itanium"; + + default: + return architecture.ToString(); + } + } + } +} diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index 5e024b790..cc6b4503e 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -21,7 +21,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using System.Xml; using ICSharpCode.Decompiler.CSharp.OutputVisitor; using ICSharpCode.Decompiler.CSharp.Syntax; using ICSharpCode.Decompiler.CSharp.Transforms; @@ -29,9 +28,7 @@ using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; using System.Threading; using System.Text; -using System.Reflection.PortableExecutable; using System.Reflection.Metadata; -using static ICSharpCode.Decompiler.Metadata.DotNetCorePathFinderExtensions; using static ICSharpCode.Decompiler.Metadata.MetadataExtensions; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Solution; @@ -42,7 +39,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler /// /// Decompiles an assembly into a visual studio project file. /// - public class WholeProjectDecompiler + public class WholeProjectDecompiler : IProjectInfoProvider { #region Settings /// @@ -106,6 +103,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler ProjectGuid = projectGuid; AssemblyResolver = assemblyResolver ?? throw new ArgumentNullException(nameof(assemblyResolver)); DebugInfoProvider = debugInfoProvider; + projectWriter = ProjectFileWriterDefault.Create(); } // per-run members @@ -120,6 +118,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler /// protected string targetDirectory; + readonly IProjectFileWriter projectWriter; + public void DecompileProject(PEFile moduleDefinition, string targetDirectory, CancellationToken cancellationToken = default(CancellationToken)) { string projectFileName = Path.Combine(targetDirectory, CleanUpFileName(moduleDefinition.Name) + ".csproj"); @@ -140,197 +140,13 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler if (StrongNameKeyFile != null) { File.Copy(StrongNameKeyFile, Path.Combine(targetDirectory, Path.GetFileName(StrongNameKeyFile))); } - return WriteProjectFile(projectFileWriter, files, moduleDefinition); - } - - #region WriteProjectFile - ProjectId WriteProjectFile(TextWriter writer, IEnumerable<(string itemType, string fileName)> files, Metadata.PEFile module) - { - const string ns = "http://schemas.microsoft.com/developer/msbuild/2003"; - string platformName = GetPlatformName(module); - Guid guid = this.ProjectGuid; - var targetFramework = DetectTargetFramework(module); - - List typeGuids = new List(); - if (targetFramework.IsPortableClassLibrary) - typeGuids.Add(ProjectTypeGuids.PortableLibrary); - typeGuids.Add(ProjectTypeGuids.CSharpWindows); - // TODO: .NET core support - - using (XmlTextWriter w = new XmlTextWriter(writer)) { - w.Formatting = Formatting.Indented; - w.WriteStartDocument(); - w.WriteStartElement("Project", ns); - w.WriteAttributeString("ToolsVersion", "4.0"); - w.WriteAttributeString("DefaultTargets", "Build"); - - w.WriteStartElement("PropertyGroup"); - w.WriteElementString("ProjectGuid", guid.ToString("B").ToUpperInvariant()); - w.WriteElementString("ProjectTypeGuids", string.Join(";", typeGuids.Select(g => g.ToString("B").ToUpperInvariant()))); - - w.WriteStartElement("Configuration"); - w.WriteAttributeString("Condition", " '$(Configuration)' == '' "); - w.WriteValue("Debug"); - w.WriteEndElement(); // - - w.WriteStartElement("Platform"); - w.WriteAttributeString("Condition", " '$(Platform)' == '' "); - w.WriteValue(platformName); - w.WriteEndElement(); // - - if (module.Reader.PEHeaders.IsDll) { - w.WriteElementString("OutputType", "Library"); - } else { - switch (module.Reader.PEHeaders.PEHeader.Subsystem) { - case Subsystem.WindowsGui: - w.WriteElementString("OutputType", "WinExe"); - break; - case Subsystem.WindowsCui: - w.WriteElementString("OutputType", "Exe"); - break; - default: - w.WriteElementString("OutputType", "Library"); - break; - } - } - - w.WriteElementString("LangVersion", LanguageVersion.ToString().Replace("CSharp", "").Replace('_', '.')); - - w.WriteElementString("AssemblyName", module.Name); - if (targetFramework.TargetFrameworkIdentifier != null) - w.WriteElementString("TargetFrameworkIdentifier", targetFramework.TargetFrameworkIdentifier); - if (targetFramework.TargetFrameworkVersion != null) - w.WriteElementString("TargetFrameworkVersion", targetFramework.TargetFrameworkVersion); - if (targetFramework.TargetFrameworkProfile != null) - w.WriteElementString("TargetFrameworkProfile", targetFramework.TargetFrameworkProfile); - w.WriteElementString("WarningLevel", "4"); - w.WriteElementString("AllowUnsafeBlocks", "True"); - - if (StrongNameKeyFile != null) { - w.WriteElementString("SignAssembly", "True"); - w.WriteElementString("AssemblyOriginatorKeyFile", Path.GetFileName(StrongNameKeyFile)); - } - w.WriteEndElement(); // - - w.WriteStartElement("PropertyGroup"); // platform-specific - w.WriteAttributeString("Condition", " '$(Platform)' == '" + platformName + "' "); - w.WriteElementString("PlatformTarget", platformName); - if (targetFramework.VersionNumber > 400 && platformName == "AnyCPU" && (module.Reader.PEHeaders.CorHeader.Flags & CorFlags.Prefers32Bit) == 0) { - w.WriteElementString("Prefer32Bit", "false"); - } - w.WriteEndElement(); // (platform-specific) - - w.WriteStartElement("PropertyGroup"); // Debug - w.WriteAttributeString("Condition", " '$(Configuration)' == 'Debug' "); - w.WriteElementString("OutputPath", "bin\\Debug\\"); - w.WriteElementString("DebugSymbols", "true"); - w.WriteElementString("DebugType", "full"); - w.WriteElementString("Optimize", "false"); - w.WriteEndElement(); // (Debug) - - w.WriteStartElement("PropertyGroup"); // Release - w.WriteAttributeString("Condition", " '$(Configuration)' == 'Release' "); - w.WriteElementString("OutputPath", "bin\\Release\\"); - w.WriteElementString("DebugSymbols", "true"); - w.WriteElementString("DebugType", "pdbonly"); - w.WriteElementString("Optimize", "true"); - w.WriteEndElement(); // (Release) - - - w.WriteStartElement("ItemGroup"); // References - foreach (var r in module.AssemblyReferences) { - if (r.Name != "mscorlib") { - w.WriteStartElement("Reference"); - w.WriteAttributeString("Include", r.Name); - var asm = AssemblyResolver.Resolve(r); - if (!AssemblyResolver.IsGacAssembly(r)) { - if (asm != null) { - w.WriteElementString("HintPath", asm.FileName); - } - } - w.WriteEndElement(); - } - } - w.WriteEndElement(); // (References) - - foreach (IGrouping gr in (from f in files group f.fileName by f.itemType into g orderby g.Key select g)) { - w.WriteStartElement("ItemGroup"); - foreach (string file in gr.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)) { - w.WriteStartElement(gr.Key); - w.WriteAttributeString("Include", file); - w.WriteEndElement(); - } - w.WriteEndElement(); - } - if (targetFramework.IsPortableClassLibrary) { - w.WriteStartElement("Import"); - w.WriteAttributeString("Project", "$(MSBuildExtensionsPath32)\\Microsoft\\Portable\\$(TargetFrameworkVersion)\\Microsoft.Portable.CSharp.targets"); - w.WriteEndElement(); - } else { - w.WriteStartElement("Import"); - w.WriteAttributeString("Project", "$(MSBuildToolsPath)\\Microsoft.CSharp.targets"); - w.WriteEndElement(); - } - - w.WriteEndDocument(); - } - - return new ProjectId(platformName, guid, ProjectTypeGuids.CSharpWindows); + projectWriter.Write(projectFileWriter, this, files, moduleDefinition); + + string platformName = TargetServices.GetPlatformName(moduleDefinition); + return new ProjectId(platformName, ProjectGuid, ProjectTypeGuids.CSharpWindows); } - struct TargetFramework - { - public string TargetFrameworkIdentifier; - public string TargetFrameworkVersion; - public string TargetFrameworkProfile; - public int VersionNumber; - public bool IsPortableClassLibrary => TargetFrameworkIdentifier == ".NETPortable"; - } - - private TargetFramework DetectTargetFramework(PEFile module) - { - TargetFramework result = default; - - switch (module.GetRuntime()) { - case Metadata.TargetRuntime.Net_1_0: - result.VersionNumber = 100; - result.TargetFrameworkVersion = "v1.0"; - break; - case Metadata.TargetRuntime.Net_1_1: - result.VersionNumber = 110; - result.TargetFrameworkVersion = "v1.1"; - break; - case Metadata.TargetRuntime.Net_2_0: - result.VersionNumber = 200; - result.TargetFrameworkVersion = "v2.0"; - // TODO: Detect when .NET 3.0/3.5 is required - break; - default: - result.VersionNumber = 400; - result.TargetFrameworkVersion = "v4.0"; - break; - } - - string targetFramework = module.DetectTargetFrameworkId(); - if (!string.IsNullOrEmpty(targetFramework)) { - string[] frameworkParts = targetFramework.Split(','); - result.TargetFrameworkIdentifier = frameworkParts.FirstOrDefault(a => !a.StartsWith("Version=", StringComparison.OrdinalIgnoreCase) && !a.StartsWith("Profile=", StringComparison.OrdinalIgnoreCase)); - string frameworkVersion = frameworkParts.FirstOrDefault(a => a.StartsWith("Version=", StringComparison.OrdinalIgnoreCase)); - if (frameworkVersion != null) { - result.TargetFrameworkVersion = frameworkVersion.Substring("Version=".Length); - result.VersionNumber = int.Parse(frameworkVersion.Substring("Version=v".Length).Replace(".", "")); - if (result.VersionNumber < 100) result.VersionNumber *= 10; - } - string frameworkProfile = frameworkParts.FirstOrDefault(a => a.StartsWith("Profile=", StringComparison.OrdinalIgnoreCase)); - if (frameworkProfile != null) - result.TargetFrameworkProfile = frameworkProfile.Substring("Profile=".Length); - } - return result; - } - - #endregion - #region WriteCodeFilesInProject protected virtual bool IncludeTypeWhenDecompilingProject(PEFile module, TypeDefinitionHandle type) { @@ -571,32 +387,6 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler return false; } } - - public static string GetPlatformName(Metadata.PEFile module) - { - var headers = module.Reader.PEHeaders; - var architecture = headers.CoffHeader.Machine; - var characteristics = headers.CoffHeader.Characteristics; - var corflags = headers.CorHeader.Flags; - switch (architecture) { - case Machine.I386: - if ((corflags & CorFlags.Prefers32Bit) != 0) - return "AnyCPU"; - if ((corflags & CorFlags.Requires32Bit) != 0) - return "x86"; - // According to ECMA-335, II.25.3.3.1 CorFlags.Requires32Bit and Characteristics.Bit32Machine must be in sync - // for assemblies containing managed code. However, this is not true for C++/CLI assemblies. - if ((corflags & CorFlags.ILOnly) == 0 && (characteristics & Characteristics.Bit32Machine) != 0) - return "x86"; - return "AnyCPU"; - case Machine.Amd64: - return "x64"; - case Machine.IA64: - return "Itanium"; - default: - return architecture.ToString(); - } - } } public readonly struct DecompilationProgress diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj index a8a940e6d..1a62ad667 100644 --- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj +++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj @@ -61,9 +61,14 @@ + + + + +