From fdef5d11c615cc61e738849156508669c495a7a9 Mon Sep 17 00:00:00 2001
From: dymanoid <9433345+dymanoid@users.noreply.github.com>
Date: Sat, 6 Jun 2020 23:55:14 +0200
Subject: [PATCH] Rearrange project writing logic
The WholeProjectDecompiler shall not have too many responsibilities.
---
.../ICSharpCode.Decompiler.Tests.csproj | 1 +
.../ProjectDecompiler/TargetFrameworkTests.cs | 92 +++++++
.../ProjectDecompiler/IProjectFileWriter.cs | 41 ++++
.../ProjectDecompiler/IProjectInfoProvider.cs | 49 ++++
.../ProjectFileWriterDefault.cs | 175 ++++++++++++++
.../ProjectDecompiler/TargetFramework.cs | 94 ++++++++
.../ProjectDecompiler/TargetServices.cs | 129 ++++++++++
.../WholeProjectDecompiler.cs | 226 +-----------------
.../ICSharpCode.Decompiler.csproj | 5 +
9 files changed, 594 insertions(+), 218 deletions(-)
create mode 100644 ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs
create mode 100644 ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs
create mode 100644 ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs
create mode 100644 ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs
create mode 100644 ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetFramework.cs
create mode 100644 ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetServices.cs
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 @@
+
+
+
+
+