diff --git a/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs b/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs
index 74013f342..684933a25 100644
--- a/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs
+++ b/ICSharpCode.Decompiler.Console/IlspyCmdProgram.cs
@@ -13,6 +13,7 @@ using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using ICSharpCode.Decompiler.DebugInfo;
using ICSharpCode.Decompiler.PdbProvider;
+using ICSharpCode.Decompiler.CSharp.ProjectDecompiler;
// ReSharper disable All
namespace ICSharpCode.Decompiler.Console
@@ -175,14 +176,12 @@ Remarks:
int DecompileAsProject(string assemblyFileName, string outputDirectory)
{
- var decompiler = new WholeProjectDecompiler() { Settings = GetSettings() };
var module = new PEFile(assemblyFileName);
var resolver = new UniversalAssemblyResolver(assemblyFileName, false, module.Reader.DetectTargetFrameworkId());
foreach (var path in ReferencePaths) {
resolver.AddSearchDirectory(path);
}
- decompiler.AssemblyResolver = resolver;
- decompiler.DebugInfoProvider = TryLoadPDB(module);
+ var decompiler = new WholeProjectDecompiler(GetSettings(), resolver, TryLoadPDB(module));
decompiler.DecompileProject(module, outputDirectory);
return 0;
}
diff --git a/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs b/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs
index dadb0f8f4..736f59933 100644
--- a/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs
+++ b/ICSharpCode.Decompiler.PowerShell/GetDecompiledProjectCmdlet.cs
@@ -5,6 +5,7 @@ using System.Management.Automation;
using System.Threading;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.CSharp;
+using ICSharpCode.Decompiler.CSharp.ProjectDecompiler;
using ICSharpCode.Decompiler.Metadata;
namespace ICSharpCode.Decompiler.PowerShell
@@ -77,9 +78,9 @@ namespace ICSharpCode.Decompiler.PowerShell
private void DoDecompile(string path)
{
- WholeProjectDecompiler decompiler = new WholeProjectDecompiler();
PEFile module = Decompiler.TypeSystem.MainModule.PEFile;
- decompiler.AssemblyResolver = new UniversalAssemblyResolver(module.FileName, false, module.Reader.DetectTargetFrameworkId());
+ var assemblyResolver = new UniversalAssemblyResolver(module.FileName, false, module.Reader.DetectTargetFrameworkId());
+ WholeProjectDecompiler decompiler = new WholeProjectDecompiler(assemblyResolver);
decompiler.ProgressIndicator = this;
fileName = module.FileName;
completed = 0;
diff --git a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
index 6eff9af2c..546cd2ea7 100644
--- a/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
+++ b/ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
@@ -88,6 +88,8 @@
+
+
diff --git a/ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs b/ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs
new file mode 100644
index 000000000..d484714f8
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/ProjectDecompiler/TargetFrameworkTests.cs
@@ -0,0 +1,121 @@
+// 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);
+ }
+
+ [TestCase(null, 350, "net35")]
+ [TestCase(".NETFramework", 350, "net35")]
+ [TestCase(".NETFramework", 400, "net40")]
+ [TestCase(".NETFramework", 451, "net451")]
+ [TestCase(".NETCoreApp", 200, "netcoreapp2.0")]
+ [TestCase(".NETCoreApp", 310, "netcoreapp3.1")]
+ [TestCase(".NETStandard", 130, "netstandard1.3")]
+ [TestCase(".NETStandard", 200, "netstandard2.0")]
+ [TestCase("Silverlight", 400, "sl4")]
+ [TestCase("Silverlight", 550, "sl5")]
+ [TestCase(".NETCore", 450, "netcore45")]
+ [TestCase(".NETCore", 451, "netcore451")]
+ [TestCase("WindowsPhone", 700, "wp7")]
+ [TestCase("WindowsPhone", 810, "wp81")]
+ [TestCase(".NETMicroFramework", 100, "netmf")]
+ [TestCase(".NETMicroFramework", 210, "netmf")]
+ [TestCase(".NETPortable", 100, null)]
+ [TestCase("Unsupported", 100, null)]
+ public void VerifyMoniker(string identifier, int version, string expectedMoniker)
+ {
+ // Arrange - nothing
+
+ // Act
+ var targetFramework = new TargetFramework(identifier, version, profile: null);
+
+ // Assert
+ Assert.AreEqual(expectedMoniker, targetFramework.Moniker);
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs
index fa58d103e..3b369e3fc 100644
--- a/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs
+++ b/ICSharpCode.Decompiler.Tests/RoundtripAssembly.cs
@@ -24,6 +24,7 @@ using System.Reflection.PortableExecutable;
using System.Text.RegularExpressions;
using System.Threading;
using ICSharpCode.Decompiler.CSharp;
+using ICSharpCode.Decompiler.CSharp.ProjectDecompiler;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Tests.Helpers;
using Microsoft.Build.Locator;
@@ -54,7 +55,7 @@ namespace ICSharpCode.Decompiler.Tests
public void NewtonsoftJson_pcl_debug()
{
try {
- RunWithTest("Newtonsoft.Json-pcl-debug", "Newtonsoft.Json.dll", "Newtonsoft.Json.Tests.dll");
+ RunWithTest("Newtonsoft.Json-pcl-debug", "Newtonsoft.Json.dll", "Newtonsoft.Json.Tests.dll", useOldProjectFormat: true);
} catch (CompilationFailedException) {
Assert.Ignore("Cannot yet re-compile PCL projects.");
}
@@ -102,9 +103,9 @@ namespace ICSharpCode.Decompiler.Tests
RunWithOutput("Random Tests\\TestCases", "TestCase-1.exe");
}
- void RunWithTest(string dir, string fileToRoundtrip, string fileToTest, string keyFile = null)
+ void RunWithTest(string dir, string fileToRoundtrip, string fileToTest, string keyFile = null, bool useOldProjectFormat = false)
{
- RunInternal(dir, fileToRoundtrip, outputDir => RunTest(outputDir, fileToTest), keyFile);
+ RunInternal(dir, fileToRoundtrip, outputDir => RunTest(outputDir, fileToTest), keyFile, useOldProjectFormat);
}
void RunWithOutput(string dir, string fileToRoundtrip)
@@ -119,7 +120,7 @@ namespace ICSharpCode.Decompiler.Tests
RunInternal(dir, fileToRoundtrip, outputDir => { });
}
- void RunInternal(string dir, string fileToRoundtrip, Action testAction, string snkFilePath = null)
+ void RunInternal(string dir, string fileToRoundtrip, Action testAction, string snkFilePath = null, bool useOldProjectFormat = false)
{
if (!Directory.Exists(TestDir)) {
Assert.Ignore($"Assembly-roundtrip test ignored: test directory '{TestDir}' needs to be checked out separately." + Environment.NewLine +
@@ -147,16 +148,22 @@ namespace ICSharpCode.Decompiler.Tests
Stopwatch w = Stopwatch.StartNew();
using (var fileStream = new FileStream(file, FileMode.Open, FileAccess.Read)) {
PEFile module = new PEFile(file, fileStream, PEStreamOptions.PrefetchEntireImage);
- var resolver = new UniversalAssemblyResolver(file, false, module.Reader.DetectTargetFrameworkId(), PEStreamOptions.PrefetchMetadata);
+ var resolver = new TestAssemblyResolver(file, inputDir, module.Reader.DetectTargetFrameworkId());
resolver.AddSearchDirectory(inputDir);
resolver.RemoveSearchDirectory(".");
- var decompiler = new TestProjectDecompiler(inputDir);
- decompiler.AssemblyResolver = resolver;
+
+ // use a fixed GUID so that we can diff the output between different ILSpy runs without spurious changes
+ var projectGuid = Guid.Parse("{127C83E4-4587-4CF9-ADCA-799875F3DFE6}");
+
// Let's limit the roundtrip tests to C# 7.3 for now; because 8.0 is still in preview
// and the generated project doesn't build as-is.
- decompiler.Settings = new DecompilerSettings(LanguageVersion.CSharp7_3);
- // use a fixed GUID so that we can diff the output between different ILSpy runs without spurious changes
- decompiler.ProjectGuid = Guid.Parse("{127C83E4-4587-4CF9-ADCA-799875F3DFE6}");
+ var settings = new DecompilerSettings(LanguageVersion.CSharp7_3);
+ if (useOldProjectFormat) {
+ settings.UseSdkStyleProjectFormat = false;
+ }
+
+ var decompiler = new TestProjectDecompiler(projectGuid, resolver, settings);
+
if (snkFilePath != null) {
decompiler.StrongNameKeyFile = Path.Combine(inputDir, snkFilePath);
}
@@ -208,7 +215,7 @@ namespace ICSharpCode.Decompiler.Tests
static void Compile(string projectFile, string outputDir)
{
var info = new ProcessStartInfo(FindMSBuild());
- info.Arguments = $"/nologo /v:minimal /p:OutputPath=\"{outputDir}\" \"{projectFile}\"";
+ info.Arguments = $"/nologo /v:minimal /restore /p:OutputPath=\"{outputDir}\" \"{projectFile}\"";
info.CreateNoWindow = true;
info.UseShellExecute = false;
info.RedirectStandardOutput = true;
@@ -259,18 +266,9 @@ namespace ICSharpCode.Decompiler.Tests
class TestProjectDecompiler : WholeProjectDecompiler
{
- readonly string[] localAssemblies;
-
- public TestProjectDecompiler(string baseDir)
- {
- localAssemblies = new DirectoryInfo(baseDir).EnumerateFiles("*.dll").Select(f => f.FullName).ToArray();
- }
-
- protected override bool IsGacAssembly(IAssemblyReference r, PEFile asm)
+ public TestProjectDecompiler(Guid projecGuid, IAssemblyResolver resolver, DecompilerSettings settings)
+ : base(settings, projecGuid, resolver, debugInfoProvider: null)
{
- if (asm == null)
- return false;
- return !localAssemblies.Contains(asm.FileName);
}
}
diff --git a/ICSharpCode.Decompiler.Tests/TestAssemblyResolver.cs b/ICSharpCode.Decompiler.Tests/TestAssemblyResolver.cs
new file mode 100644
index 000000000..d97c6d526
--- /dev/null
+++ b/ICSharpCode.Decompiler.Tests/TestAssemblyResolver.cs
@@ -0,0 +1,28 @@
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+using ICSharpCode.Decompiler.Metadata;
+
+namespace ICSharpCode.Decompiler.Tests
+{
+ sealed class TestAssemblyResolver : UniversalAssemblyResolver
+ {
+ readonly HashSet localAssemblies = new HashSet();
+
+ public TestAssemblyResolver(string mainAssemblyFileName, string baseDir, string targetFramework)
+ : base(mainAssemblyFileName, false, targetFramework, PEStreamOptions.PrefetchMetadata, MetadataReaderOptions.ApplyWindowsRuntimeProjections)
+ {
+ var assemblyNames = new DirectoryInfo(baseDir).EnumerateFiles("*.dll").Select(f => Path.GetFileNameWithoutExtension(f.Name));
+ foreach (var name in assemblyNames) {
+ localAssemblies.Add(name);
+ }
+ }
+
+ public override bool IsGacAssembly(IAssemblyReference reference)
+ {
+ return reference != null && !localAssemblies.Contains(reference.Name);
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs
index eb8d29d05..4bcc19f1a 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ExpressionTrees.cs
@@ -1037,17 +1037,15 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
public async Task Issue1524(string str)
{
await Task.Delay(100);
- if (string.IsNullOrEmpty(str)) {
#if CS70
- if (int.TryParse(str, out int id)) {
+ if (string.IsNullOrEmpty(str) && int.TryParse(str, out int id)) {
#else
- int id;
- if (int.TryParse(str, out id)) {
+ int id;
+ if (string.IsNullOrEmpty(str) && int.TryParse(str, out id)) {
#endif
- (from a in new List().AsQueryable()
- where a == id
- select a).FirstOrDefault();
- }
+ (from a in new List().AsQueryable()
+ where a == id
+ select a).FirstOrDefault();
}
}
diff --git a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ReduceNesting.cs b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ReduceNesting.cs
index a08cd43e9..8d9b4c841 100644
--- a/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ReduceNesting.cs
+++ b/ICSharpCode.Decompiler.Tests/TestCases/Pretty/ReduceNesting.cs
@@ -194,7 +194,7 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
}
- // nesting should not be reduced as maximum nesting level is 2
+ // nesting should be reduced as maximum nesting level is 2
public void EarlyExit2()
{
if (B(0)) {
@@ -266,5 +266,202 @@ namespace ICSharpCode.Decompiler.Tests.TestCases.Pretty
}
return s;
}
+
+ public void EarlyExitBeforeTry()
+ {
+ if (B(0)) {
+ return;
+ }
+
+ try {
+ if (B(1)) {
+ Console.WriteLine();
+ }
+ } catch {
+ }
+ }
+
+ public void EarlyExitInTry()
+ {
+ try {
+ if (B(0)) {
+ return;
+ }
+
+ Console.WriteLine();
+
+ if (B(1)) {
+ for (int i = 0; i < 10; i++) {
+ Console.WriteLine(i);
+ }
+ }
+ } catch {
+ }
+ }
+
+ public void ContinueLockInLoop()
+ {
+ while (B(0)) {
+ lock (Console.Out) {
+ if (B(1)) {
+ continue;
+ }
+
+ Console.WriteLine();
+
+ if (B(2)) {
+ for (int i = 0; i < 10; i++) {
+ Console.WriteLine(i);
+ }
+ }
+ }
+ }
+ }
+
+ public void BreakLockInLoop()
+ {
+ while (B(0)) {
+ lock (Console.Out) {
+ // Before ReduceNestingTransform, the rest of the lock body is nested in if(!B(1)) with a continue;
+ // the B(1) case falls through to a break outside the lock
+ if (B(1)) {
+ break;
+ }
+
+ Console.WriteLine();
+
+ if (B(2)) {
+ for (int i = 0; i < 10; i++) {
+ Console.WriteLine(i);
+ }
+ }
+
+ // the break gets duplicated into the lock (replacing the leave) making the lock 'endpoint unreachable' and the break outside the lock is removed
+ // After the condition is inverted, ReduceNestingTransform isn't smart enough to then move the continue out of the lock
+ // Thus the redundant continue;
+ continue;
+ }
+ }
+ Console.WriteLine();
+ }
+
+ public unsafe void BreakPinnedInLoop(int[] arr)
+ {
+ while (B(0)) {
+ fixed (int* ptr = arr) {
+ if (B(1)) {
+ break;
+ }
+
+ Console.WriteLine();
+
+ if (B(2)) {
+ for (int i = 0; i < 10; i++) {
+ Console.WriteLine(ptr[i]);
+ }
+ }
+
+ // Same reason as BreakLockInLoop
+ continue;
+ }
+ }
+ Console.WriteLine();
+ }
+
+ public void CannotEarlyExitInTry()
+ {
+ try {
+ if (B(0)) {
+ Console.WriteLine();
+
+ if (B(1)) {
+ for (int i = 0; i < 10; i++) {
+ Console.WriteLine(i);
+ }
+ }
+ }
+ } catch {
+ }
+ Console.WriteLine();
+ }
+
+ public void EndpointUnreachableDueToEarlyExit()
+ {
+ using (Console.Out) {
+ if (B(0)) {
+ return;
+ }
+ do {
+ if (B(1)) {
+ return;
+ }
+ } while (B(2));
+ throw new Exception();
+ }
+ }
+
+ public void SwitchInTry()
+ {
+ try {
+ switch (I(0)) {
+ case 1:
+ Console.WriteLine(1);
+ return;
+ case 2:
+ Console.WriteLine(2);
+ return;
+ }
+ Console.WriteLine(3);
+ for (int i = 0; i < 10; i++) {
+ Console.WriteLine(i);
+ }
+ } catch {
+ throw;
+ }
+ }
+
+ public void SwitchInTryInLoopReturn()
+ {
+ for (int i = 0; i < 10; i++) {
+ try {
+ switch (I(0)) {
+ case 1:
+ Console.WriteLine(1);
+ return;
+ case 2:
+ Console.WriteLine(2);
+ return;
+ }
+ Console.WriteLine(3);
+ for (int j = 0; j < 10; j++) {
+ Console.WriteLine(j);
+ }
+ } catch {
+ throw;
+ }
+ }
+ }
+
+ public void SwitchInTryInLoopContinue()
+ {
+ for (int i = 0; i < 10; i++) {
+ try {
+ switch (I(0)) {
+ case 1:
+ Console.WriteLine(1);
+ continue;
+ case 2:
+ Console.WriteLine(2);
+ continue;
+ }
+ Console.WriteLine(3);
+ for (int j = 0; j < 10; j++) {
+ Console.WriteLine(j);
+ }
+ } catch {
+ throw;
+ }
+ }
+ }
}
}
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/ProjectFileWriterSdkStyle.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs
new file mode 100644
index 000000000..479acc419
--- /dev/null
+++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs
@@ -0,0 +1,212 @@
+// 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;
+ }
+ }
+}
diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetFramework.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetFramework.cs
new file mode 100644
index 000000000..c340578e3
--- /dev/null
+++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetFramework.cs
@@ -0,0 +1,142 @@
+// 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, withDots: true);
+ Moniker = GetTargetFrameworkMoniker(Identifier, 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 moniker. Can be null if not supported.
+ ///
+ public string Moniker { 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 GetTargetFrameworkMoniker(string frameworkIdentifier, int version)
+ {
+ // Reference: https://docs.microsoft.com/en-us/dotnet/standard/frameworks
+ switch (frameworkIdentifier) {
+ case null:
+ case ".NETFramework":
+ return "net" + GetVersionString(version, withDots: false);
+
+ case ".NETCoreApp":
+ return "netcoreapp" + GetVersionString(version, withDots: true);
+
+ case ".NETStandard":
+ return "netstandard" + GetVersionString(version, withDots: true);
+
+ case "Silverlight":
+ return "sl" + version / 100;
+
+ case ".NETCore":
+ return "netcore" + GetVersionString(version, withDots: false);
+
+ case "WindowsPhone":
+ return "wp" + GetVersionString(version, withDots: false, omitMinorWhenZero: true);
+
+ case ".NETMicroFramework":
+ return "netmf";
+
+ default:
+ return null;
+ }
+ }
+
+ static string GetVersionString(int version, bool withDots, bool omitMinorWhenZero = false)
+ {
+ int major = version / 100;
+ int minor = version % 100 / 10;
+ int patch = version % 10;
+
+ if (omitMinorWhenZero && minor == 0 && patch == 0) {
+ return major.ToString();
+ }
+
+ var versionBuilder = new StringBuilder(8);
+ versionBuilder.Append(major);
+
+ if (withDots) {
+ versionBuilder.Append('.');
+ }
+
+ versionBuilder.Append(minor);
+
+ if (patch != 0) {
+ if (withDots) {
+ 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/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
similarity index 52%
rename from ICSharpCode.Decompiler/CSharp/WholeProjectDecompiler.cs
rename to ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
index 785841255..56394502f 100644
--- a/ICSharpCode.Decompiler/CSharp/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,34 +28,24 @@ 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;
using ICSharpCode.Decompiler.DebugInfo;
-namespace ICSharpCode.Decompiler.CSharp
+namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
{
///
/// Decompiles an assembly into a visual studio project file.
///
- public class WholeProjectDecompiler
+ public class WholeProjectDecompiler : IProjectInfoProvider
{
#region Settings
- DecompilerSettings settings = new DecompilerSettings();
-
- public DecompilerSettings Settings {
- get {
- return settings;
- }
- set {
- if (value == null)
- throw new ArgumentNullException();
- settings = value;
- }
- }
+ ///
+ /// Gets the setting this instance uses for decompiling.
+ ///
+ public DecompilerSettings Settings { get; }
LanguageVersion? languageVersion;
@@ -71,15 +60,14 @@ namespace ICSharpCode.Decompiler.CSharp
}
}
- public IAssemblyResolver AssemblyResolver { get; set; }
+ public IAssemblyResolver AssemblyResolver { get; }
- public IDebugInfoProvider DebugInfoProvider { get; set; }
+ public IDebugInfoProvider DebugInfoProvider { get; }
///
/// The MSBuild ProjectGuid to use for the new project.
- /// null to automatically generate a new GUID.
///
- public Guid? ProjectGuid { get; set; }
+ public Guid ProjectGuid { get; }
///
/// Path to the snk file to use for signing.
@@ -92,6 +80,32 @@ namespace ICSharpCode.Decompiler.CSharp
public IProgress ProgressIndicator { get; set; }
#endregion
+ public WholeProjectDecompiler(IAssemblyResolver assemblyResolver)
+ : this(new DecompilerSettings(), assemblyResolver, debugInfoProvider: null)
+ {
+ }
+
+ public WholeProjectDecompiler(
+ DecompilerSettings settings,
+ IAssemblyResolver assemblyResolver,
+ IDebugInfoProvider debugInfoProvider)
+ : this(settings, Guid.NewGuid(), assemblyResolver, debugInfoProvider)
+ {
+ }
+
+ protected WholeProjectDecompiler(
+ DecompilerSettings settings,
+ Guid projectGuid,
+ IAssemblyResolver assemblyResolver,
+ IDebugInfoProvider debugInfoProvider)
+ {
+ Settings = settings ?? throw new ArgumentNullException(nameof(settings));
+ ProjectGuid = projectGuid;
+ AssemblyResolver = assemblyResolver ?? throw new ArgumentNullException(nameof(assemblyResolver));
+ DebugInfoProvider = debugInfoProvider;
+ projectWriter = Settings.UseSdkStyleProjectFormat ? ProjectFileWriterSdkStyle.Create() : ProjectFileWriterDefault.Create();
+ }
+
// per-run members
HashSet directories = new HashSet(Platform.FileNameComparer);
@@ -104,6 +118,8 @@ namespace ICSharpCode.Decompiler.CSharp
///
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");
@@ -124,207 +140,19 @@ namespace ICSharpCode.Decompiler.CSharp
if (StrongNameKeyFile != null) {
File.Copy(StrongNameKeyFile, Path.Combine(targetDirectory, Path.GetFileName(StrongNameKeyFile)));
}
- return WriteProjectFile(projectFileWriter, files, moduleDefinition);
- }
-
- #region WriteProjectFile
- ProjectId WriteProjectFile(TextWriter writer, IEnumerable> files, Metadata.PEFile module)
- {
- const string ns = "http://schemas.microsoft.com/developer/msbuild/2003";
- string platformName = GetPlatformName(module);
- Guid guid = this.ProjectGuid ?? Guid.NewGuid();
- 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 (!IsGacAssembly(r, asm)) {
- if (asm != null) {
- w.WriteElementString("HintPath", asm.FileName);
- }
- }
- w.WriteEndElement();
- }
- }
- w.WriteEndElement(); // (References)
-
- foreach (IGrouping gr in (from f in files group f.Item2 by f.Item1 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);
- }
-
- struct TargetFramework
- {
- public string TargetFrameworkIdentifier;
- public string TargetFrameworkVersion;
- public string TargetFrameworkProfile;
- public int VersionNumber;
- public bool IsPortableClassLibrary => TargetFrameworkIdentifier == ".NETPortable";
+ projectWriter.Write(projectFileWriter, this, files, moduleDefinition);
+
+ string platformName = TargetServices.GetPlatformName(moduleDefinition);
+ return new ProjectId(platformName, ProjectGuid, ProjectTypeGuids.CSharpWindows);
}
- 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;
- }
-
- protected virtual bool IsGacAssembly(Metadata.IAssemblyReference r, Metadata.PEFile asm)
- {
- return false;
- }
- #endregion
-
#region WriteCodeFilesInProject
- protected virtual bool IncludeTypeWhenDecompilingProject(Metadata.PEFile module, TypeDefinitionHandle type)
+ protected virtual bool IncludeTypeWhenDecompilingProject(PEFile module, TypeDefinitionHandle type)
{
var metadata = module.Metadata;
var typeDef = metadata.GetTypeDefinition(type);
- if (metadata.GetString(typeDef.Name) == "" || CSharpDecompiler.MemberIsHidden(module, type, settings))
+ if (metadata.GetString(typeDef.Name) == "" || CSharpDecompiler.MemberIsHidden(module, type, Settings))
return false;
if (metadata.GetString(typeDef.Namespace) == "XamlGeneratedNamespace" && metadata.GetString(typeDef.Name) == "GeneratedInternalTypeHelper")
return false;
@@ -333,14 +161,14 @@ namespace ICSharpCode.Decompiler.CSharp
CSharpDecompiler CreateDecompiler(DecompilerTypeSystem ts)
{
- var decompiler = new CSharpDecompiler(ts, settings);
+ var decompiler = new CSharpDecompiler(ts, Settings);
decompiler.DebugInfoProvider = DebugInfoProvider;
decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers());
decompiler.AstTransforms.Add(new RemoveCLSCompliantAttribute());
return decompiler;
}
- IEnumerable> WriteAssemblyInfo(DecompilerTypeSystem ts, CancellationToken cancellationToken)
+ IEnumerable<(string itemType, string fileName)> WriteAssemblyInfo(DecompilerTypeSystem ts, CancellationToken cancellationToken)
{
var decompiler = CreateDecompiler(ts);
decompiler.CancellationToken = cancellationToken;
@@ -352,12 +180,12 @@ namespace ICSharpCode.Decompiler.CSharp
Directory.CreateDirectory(Path.Combine(targetDirectory, prop));
string assemblyInfo = Path.Combine(prop, "AssemblyInfo.cs");
using (StreamWriter w = new StreamWriter(Path.Combine(targetDirectory, assemblyInfo))) {
- syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, settings.CSharpFormattingOptions));
+ syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, Settings.CSharpFormattingOptions));
}
- return new Tuple[] { Tuple.Create("Compile", assemblyInfo) };
+ return new[] { ("Compile", assemblyInfo) };
}
- IEnumerable> WriteCodeFilesInProject(Metadata.PEFile module, CancellationToken cancellationToken)
+ IEnumerable<(string itemType, string fileName)> WriteCodeFilesInProject(Metadata.PEFile module, CancellationToken cancellationToken)
{
var metadata = module.Metadata;
var files = module.Metadata.GetTopLevelTypeDefinitions().Where(td => IncludeTypeWhenDecompilingProject(module, td)).GroupBy(
@@ -374,8 +202,8 @@ namespace ICSharpCode.Decompiler.CSharp
}
}, StringComparer.OrdinalIgnoreCase).ToList();
int total = files.Count;
- var progress = this.ProgressIndicator;
- DecompilerTypeSystem ts = new DecompilerTypeSystem(module, AssemblyResolver, settings);
+ var progress = ProgressIndicator;
+ DecompilerTypeSystem ts = new DecompilerTypeSystem(module, AssemblyResolver, Settings);
Parallel.ForEach(
files,
new ParallelOptions {
@@ -388,19 +216,19 @@ namespace ICSharpCode.Decompiler.CSharp
CSharpDecompiler decompiler = CreateDecompiler(ts);
decompiler.CancellationToken = cancellationToken;
var syntaxTree = decompiler.DecompileTypes(file.ToArray());
- syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, settings.CSharpFormattingOptions));
+ syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, Settings.CSharpFormattingOptions));
} catch (Exception innerException) when (!(innerException is OperationCanceledException || innerException is DecompilerException)) {
throw new DecompilerException(module, $"Error decompiling for '{file.Key}'", innerException);
}
}
progress?.Report(new DecompilationProgress(total, file.Key));
});
- return files.Select(f => Tuple.Create("Compile", f.Key)).Concat(WriteAssemblyInfo(ts, cancellationToken));
+ return files.Select(f => ("Compile", f.Key)).Concat(WriteAssemblyInfo(ts, cancellationToken));
}
#endregion
#region WriteResourceFilesInProject
- protected virtual IEnumerable> WriteResourceFilesInProject(Metadata.PEFile module)
+ protected virtual IEnumerable<(string itemType, string fileName)> WriteResourceFilesInProject(Metadata.PEFile module)
{
foreach (var r in module.Resources.Where(r => r.ResourceType == Metadata.ResourceType.Embedded)) {
Stream stream = r.TryOpenStream();
@@ -408,7 +236,7 @@ namespace ICSharpCode.Decompiler.CSharp
if (r.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) {
bool decodedIntoIndividualFiles;
- var individualResources = new List>();
+ var individualResources = new List<(string itemType, string fileName)>();
try {
var resourcesFile = new ResourcesFile(stream);
if (resourcesFile.AllEntriesAreStreams()) {
@@ -449,12 +277,12 @@ namespace ICSharpCode.Decompiler.CSharp
stream.Position = 0;
stream.CopyTo(fs);
}
- yield return Tuple.Create("EmbeddedResource", fileName);
+ yield return ("EmbeddedResource", fileName);
}
}
}
- protected virtual IEnumerable> WriteResourceToFile(string fileName, string resourceName, Stream entryStream)
+ protected virtual IEnumerable<(string itemType, string fileName)> WriteResourceToFile(string fileName, string resourceName, Stream entryStream)
{
if (fileName.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) {
string resx = Path.ChangeExtension(fileName, ".resx");
@@ -465,7 +293,7 @@ namespace ICSharpCode.Decompiler.CSharp
writer.AddResource(entry.Key, entry.Value);
}
}
- return new[] { Tuple.Create("EmbeddedResource", resx) };
+ return new[] { ("EmbeddedResource", resx) };
} catch (BadImageFormatException) {
// if the .resources can't be decoded, just save them as-is
} catch (EndOfStreamException) {
@@ -475,7 +303,7 @@ namespace ICSharpCode.Decompiler.CSharp
using (FileStream fs = new FileStream(Path.Combine(targetDirectory, fileName), FileMode.Create, FileAccess.Write)) {
entryStream.CopyTo(fs);
}
- return new[] { Tuple.Create("EmbeddedResource", fileName) };
+ return new[] { ("EmbeddedResource", fileName) };
}
string GetFileNameForResource(string fullName)
@@ -559,32 +387,6 @@ namespace ICSharpCode.Decompiler.CSharp
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/DebugInfo/PortablePdbWriter.cs b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs
index 64eda349e..28038144a 100644
--- a/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs
+++ b/ICSharpCode.Decompiler/DebugInfo/PortablePdbWriter.cs
@@ -30,6 +30,7 @@ using System.Security.Cryptography;
using System.Text;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
+using ICSharpCode.Decompiler.CSharp.ProjectDecompiler;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Metadata;
diff --git a/ICSharpCode.Decompiler/DecompilerSettings.cs b/ICSharpCode.Decompiler/DecompilerSettings.cs
index 7743292d9..3e8349bd2 100644
--- a/ICSharpCode.Decompiler/DecompilerSettings.cs
+++ b/ICSharpCode.Decompiler/DecompilerSettings.cs
@@ -1329,6 +1329,24 @@ namespace ICSharpCode.Decompiler
}
}
+ bool useSdkStyleProjectFormat = true;
+
+ ///
+ /// Gets or sets a value indicating whether the new SDK style format
+ /// shall be used for the generated project files.
+ ///
+ [Category("DecompilerSettings.Other")]
+ [Description("DecompilerSettings.UseSdkStyleProjectFormat")]
+ public bool UseSdkStyleProjectFormat {
+ get { return useSdkStyleProjectFormat; }
+ set {
+ if (useSdkStyleProjectFormat != value) {
+ useSdkStyleProjectFormat = value;
+ OnPropertyChanged();
+ }
+ }
+ }
+
bool aggressiveScalarReplacementOfAggregates = false;
[Category("DecompilerSettings.Other")]
diff --git a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
index 3d735ec42..b207c5f6d 100644
--- a/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
+++ b/ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
@@ -61,9 +61,15 @@
+
+
+
+
+
+
@@ -277,7 +283,7 @@
-
+
diff --git a/ICSharpCode.Decompiler/IL/Transforms/ReduceNestingTransform.cs b/ICSharpCode.Decompiler/IL/Transforms/ReduceNestingTransform.cs
index 332a1a113..d73cf222f 100644
--- a/ICSharpCode.Decompiler/IL/Transforms/ReduceNestingTransform.cs
+++ b/ICSharpCode.Decompiler/IL/Transforms/ReduceNestingTransform.cs
@@ -33,7 +33,7 @@ namespace ICSharpCode.Decompiler.IL
/// This can lead to excessive indentation when the entire rest of the method/loop is included in the else block/default case.
/// When an If/SwitchInstruction is followed immediately by a keyword exit, the exit can be moved into the child blocks
/// allowing the else block or default case to be moved after the if/switch as all prior cases exit.
- /// Most importantly, this transformatino does not change the IL order of any code.
+ /// Most importantly, this transformation does not change the IL order of any code.
///
/// ConditionDetection also has a block exit priority system to assist exit point reduction which in some cases ignores IL order.
/// After HighLevelLoopTransform has run, all structures have been detected and preference can be returned to maintaining IL ordering.
@@ -99,16 +99,16 @@ namespace ICSharpCode.Decompiler.IL
// reduce nesting in switch blocks
if (container.Kind == ContainerKind.Switch &&
- CanDuplicateExit(NextInsn(), continueTarget) &&
- ReduceSwitchNesting(block, container, NextInsn())) {
+ CanDuplicateExit(NextInsn(), continueTarget, out var keywordExit1) &&
+ ReduceSwitchNesting(block, container, keywordExit1)) {
RemoveRedundantExit(block, nextInstruction);
}
break;
case IfInstruction ifInst:
- ImproveILOrdering(block, ifInst);
+ ImproveILOrdering(block, ifInst, continueTarget);
// reduce nesting in if/else blocks
- if (CanDuplicateExit(NextInsn(), continueTarget) && ReduceNesting(block, ifInst, NextInsn()))
+ if (CanDuplicateExit(NextInsn(), continueTarget, out var keywordExit2) && ReduceNesting(block, ifInst, keywordExit2))
RemoveRedundantExit(block, nextInstruction);
// visit content blocks
@@ -124,27 +124,69 @@ namespace ICSharpCode.Decompiler.IL
Visit(falseBlock, continueTarget, NextInsn());
}
break;
+ default:
+ // blocks can only exit containers via leave instructions, not fallthrough, so the only relevant context is `continueTarget`
+ VisitContainers(inst, continueTarget);
+
+ // reducing nesting inside Try/Using/Lock etc, may make the endpoint unreachable.
+ // This should only happen by replacing a Leave with the exit instruction we're about to delete, but I can't see a good way to assert this
+ // This would be better placed in ReduceNesting, but it's more difficult to find the affected instructions/blocks there than here
+ if (i == block.Instructions.Count - 2 && inst.HasFlag(InstructionFlags.EndPointUnreachable)) {
+ context.Step("Remove unreachable exit", block.Instructions.Last());
+ block.Instructions.RemoveLast();
+
+ // This would be the right place to check and fix the redundant continue; in TestCases.Pretty.ReduceNesting.BreakLockInLoop
+ // but doing so would require knowledge of what `inst` is, and how it works. (eg. to target the try block and not catch or finally blocks)
+ }
+ break;
}
}
}
+ // search for child containers to reduce nesting in
+ private void VisitContainers(ILInstruction inst, Block continueTarget)
+ {
+ switch (inst) {
+ case ILFunction _:
+ break; // assume inline ILFunctions are already transformed
+ case BlockContainer cont:
+ Visit(cont, continueTarget);
+ break;
+ default:
+ foreach (var child in inst.Children)
+ VisitContainers(child, continueTarget);
+ break;
+ }
+ }
+
///
/// For an if statement with an unreachable end point and no else block,
/// inverts to match IL order of the first statement of each branch
///
- private void ImproveILOrdering(Block block, IfInstruction ifInst)
+ private void ImproveILOrdering(Block block, IfInstruction ifInst, Block continueTarget)
{
if (!block.HasFlag(InstructionFlags.EndPointUnreachable)
- || !ifInst.TrueInst.HasFlag(InstructionFlags.EndPointUnreachable)
- || !ifInst.FalseInst.MatchNop())
+ || !ifInst.TrueInst.HasFlag(InstructionFlags.EndPointUnreachable)
+ || !ifInst.FalseInst.MatchNop())
return;
-
+
Debug.Assert(ifInst != block.Instructions.Last());
var trueRangeStart = ConditionDetection.GetStartILOffset(ifInst.TrueInst, out bool trueRangeIsEmpty);
- var falseRangeStart = ConditionDetection.GetStartILOffset(block.Instructions[block.Instructions.IndexOf(ifInst)+1], out bool falseRangeIsEmpty);
- if (!trueRangeIsEmpty && !falseRangeIsEmpty && falseRangeStart < trueRangeStart)
- ConditionDetection.InvertIf(block, ifInst, context);
+ var falseRangeStart = ConditionDetection.GetStartILOffset(block.Instructions[block.Instructions.IndexOf(ifInst) + 1], out bool falseRangeIsEmpty);
+ if (trueRangeIsEmpty || falseRangeIsEmpty || falseRangeStart >= trueRangeStart)
+ return;
+
+ if (block.Instructions.Last() is Leave leave && !leave.IsLeavingFunction && leave.TargetContainer.Kind == ContainerKind.Normal) {
+ // non-keyword leave. Can't move out of the last position in the block (fall-through) without introducing goto, unless it can be replaced with a keyword (return/continue)
+ if (!CanDuplicateExit(block.Instructions.Last(), continueTarget, out var keywordExit))
+ return;
+
+ context.Step("Replace leave with keyword exit", ifInst.TrueInst);
+ block.Instructions.Last().ReplaceWith(keywordExit.Clone());
+ }
+
+ ConditionDetection.InvertIf(block, ifInst, context);
}
///
@@ -159,16 +201,27 @@ namespace ICSharpCode.Decompiler.IL
// if (cond) { ... } exit;
if (ifInst.FalseInst.MatchNop()) {
- // a separate heuristic tp ShouldReduceNesting as there is visual balancing to be performed based on number of statments
+ // a separate heuristic to ShouldReduceNesting as there is visual balancing to be performed based on number of statments
if (maxDepth < 2)
return false;
// ->
// if (!cond) exit;
// ...; exit;
- EnsureEndPointUnreachable(ifInst.TrueInst, exitInst);
EnsureEndPointUnreachable(block, exitInst);
+ Debug.Assert(ifInst == block.Instructions.SecondToLastOrDefault());
+
+ // use the same exit the block has. If the block already has one (such as a leave from a try), keep it in place
+ EnsureEndPointUnreachable(ifInst.TrueInst, block.Instructions.Last());
ConditionDetection.InvertIf(block, ifInst, context);
+
+ // ensure the exit inst of the if instruction is a keyword
+ Debug.Assert(!(ifInst.TrueInst is Block));
+ if (!ifInst.TrueInst.Match(exitInst).Success) {
+ Debug.Assert(ifInst.TrueInst is Leave);
+ context.Step("Replace leave with keyword exit", ifInst.TrueInst);
+ ifInst.TrueInst.ReplaceWith(exitInst.Clone());
+ }
return true;
}
@@ -254,6 +307,16 @@ namespace ICSharpCode.Decompiler.IL
context.Step("Extract default case of switch", switchContainer);
+ // if the switch container is followed by an instruction, it must be a Leave from a try/pinned/etc or exitInst
+ // When it's a leave from a container, it's better to let the extracted default block 'fall through' rather than duplicating whatever
+ // instruction eventually follows the container
+ if (parentBlock.Instructions.SecondToLastOrDefault() == switchContainer) {
+ if (defaultBlock.Instructions.Last().MatchLeave(switchContainer))
+ defaultBlock.Instructions.Last().ReplaceWith(parentBlock.Instructions.Last());
+
+ parentBlock.Instructions.RemoveLast();
+ }
+
// replace all break; statements with the exitInst
var leaveInstructions = switchContainer.Descendants.Where(inst => inst.MatchLeave(switchContainer));
foreach (var leaveInst in leaveInstructions.ToArray())
@@ -267,13 +330,6 @@ namespace ICSharpCode.Decompiler.IL
foreach (var block in defaultBlocks)
switchContainer.Blocks.Remove(block);
- // replace the parent block exit with the default case instructions
- if (parentBlock.Instructions.Last() == exitInst) {
- parentBlock.Instructions.RemoveLast();
- }
- // Note: even though we don't check that the switchContainer is near the end of the block,
- // we know this must be the case because we know "exitInst" is a leave/branch and directly
- // follows the switchContainer.
Debug.Assert(parentBlock.Instructions.Last() == switchContainer);
parentBlock.Instructions.AddRange(defaultBlock.Instructions);
@@ -292,8 +348,46 @@ namespace ICSharpCode.Decompiler.IL
///
/// Checks if an exit is a duplicable keyword exit (return; break; continue;)
///
- private bool CanDuplicateExit(ILInstruction exit, Block continueTarget) =>
- exit != null && (exit is Leave leave && leave.Value.MatchNop() || exit.MatchBranch(continueTarget));
+ private bool CanDuplicateExit(ILInstruction exit, Block continueTarget, out ILInstruction keywordExit)
+ {
+ keywordExit = exit;
+ if (exit != null && exit.MatchBranch(continueTarget))
+ return true; // keyword is continue
+
+ if (!(exit is Leave leave && leave.Value.MatchNop()))
+ return false; // don't duplicate valued returns
+
+ if (leave.IsLeavingFunction || leave.TargetContainer.Kind != ContainerKind.Normal)
+ return true; // keyword is return || break
+
+ // leave from a try/pinned/lock etc, check if the target (the instruction following the target container) is duplicable, if so, set keywordExit to that
+ ILInstruction leavingInst = leave.TargetContainer;
+ Debug.Assert(!leavingInst.HasFlag(InstructionFlags.EndPointUnreachable));
+ while (!(leavingInst.Parent is Block b) || leavingInst == b.Instructions.Last()) {
+ if (leavingInst.Parent is TryFinally tryFinally) {
+ if (leavingInst.SlotInfo == TryFinally.FinallyBlockSlot) { // cannot duplicate leaves from finally containers
+ Debug.Assert(leave.TargetContainer == tryFinally.FinallyBlock); //finally cannot have control flow
+ return false;
+ }
+ if (tryFinally.HasFlag(InstructionFlags.EndPointUnreachable)) { // finally block changes return value/throws an exception? Yikes. Lets leave it alone
+ Debug.Assert(tryFinally.FinallyBlock.HasFlag(InstructionFlags.EndPointUnreachable));
+ return false;
+ }
+ }
+ else if (leavingInst.Parent is TryFault tryFault && leavingInst.SlotInfo == TryFault.FaultBlockSlot) { // cannot duplicate leaves from fault containers either
+ Debug.Assert(leave.TargetContainer == tryFault.FaultBlock);
+ return false;
+ }
+
+ leavingInst = leavingInst.Parent;
+ Debug.Assert(!leavingInst.HasFlag(InstructionFlags.EndPointUnreachable));
+ Debug.Assert(!(leavingInst is ILFunction));
+ }
+
+ var block = (Block)leavingInst.Parent;
+ var targetInst = block.Instructions[block.Instructions.IndexOf(leavingInst)+1];
+ return CanDuplicateExit(targetInst, continueTarget, out keywordExit);
+ }
///
/// Ensures the end point of a block is unreachable by duplicating and appending the [exit] instruction following the end point
@@ -353,28 +447,54 @@ namespace ICSharpCode.Decompiler.IL
///
/// Recursively computes the number of statements and maximum nested depth of an instruction
///
- private void ComputeStats(ILInstruction inst, ref int numStatements, ref int maxDepth, int currentDepth)
+ private void ComputeStats(ILInstruction inst, ref int numStatements, ref int maxDepth, int currentDepth, bool isStatement = true)
{
+ if (isStatement)
+ numStatements++;
+
+ if (currentDepth > maxDepth) {
+ Debug.Assert(isStatement);
+ maxDepth = currentDepth;
+ }
+
+ // enumerate children statements and containers
switch (inst) {
case Block block:
- foreach (var i in block.Instructions)
- ComputeStats(i, ref numStatements, ref maxDepth, currentDepth);
+ if (isStatement)
+ numStatements--; // don't count blocks as statements
+
+ // add each child as a statement (unless we're a named block)
+ foreach (var child in block.Instructions)
+ ComputeStats(child, ref numStatements, ref maxDepth, currentDepth, block.Kind != BlockKind.CallWithNamedArgs && block.Kind != BlockKind.CallInlineAssign);
+
+ // final instruction as an expression
+ ComputeStats(block.FinalInstruction, ref numStatements, ref maxDepth, currentDepth, false);
break;
case BlockContainer container:
- numStatements++; // one statement for the container head (switch/loop)
+ if (!isStatement)
+ numStatements++; //always add a statement for a container in an expression
var containerBody = container.EntryPoint;
if (container.Kind == ContainerKind.For || container.Kind == ContainerKind.While) {
+ Debug.Assert(isStatement);
+
if (!container.MatchConditionBlock(container.EntryPoint, out _, out containerBody))
throw new NotSupportedException("Invalid condition block in loop.");
}
+ // don't count implicit leave. Can't avoid counting for loop initializers but close enough, for loops can have an extra statement of visual weight
+ var lastInst = containerBody.Instructions.Last();
+ if ((container.Kind == ContainerKind.For || container.Kind == ContainerKind.DoWhile) && lastInst.MatchBranch(container.Blocks.Last()) ||
+ (container.Kind == ContainerKind.Loop || container.Kind == ContainerKind.While) && lastInst.MatchBranch(container.Blocks[0]) ||
+ container.Kind == ContainerKind.Normal && lastInst.MatchLeave(container) ||
+ container.Kind == ContainerKind.Switch) // SwitchInstructyion always counts as a statement anyway, so no need to count the container as well
+ numStatements--;
+
// add the nested body
ComputeStats(containerBody, ref numStatements, ref maxDepth, currentDepth + 1);
break;
- case IfInstruction ifInst:
- numStatements++; // one statement for the if/condition itself
-
+ case IfInstruction ifInst when ifInst.ResultType == StackType.Void:
+ Debug.Assert(isStatement);
// nested then instruction
ComputeStats(ifInst.TrueInst, ref numStatements, ref maxDepth, currentDepth + 1);
@@ -389,21 +509,36 @@ namespace ICSharpCode.Decompiler.IL
// include all nested else instruction
ComputeStats(elseInst, ref numStatements, ref maxDepth, currentDepth + 1);
break;
- case SwitchInstruction switchInst:
- // one statement per case label
- numStatements += switchInst.Sections.Count + 1;
+ case SwitchSection section:
+ Debug.Assert(!isStatement); // labels are just children of the SwitchInstruction
+ numStatements++; // add a statement for each case label
+
// add all the case blocks at the current depth
// most formatters indent switch blocks twice, but we don't want this heuristic to be based on formatting
// so we remain conservative and only include the increase in depth from the container and not the labels
- foreach (var section in switchInst.Sections)
- if (section.Body.MatchBranch(out var caseBlock) && caseBlock.Parent == switchInst.Parent.Parent)
- ComputeStats(caseBlock, ref numStatements, ref maxDepth, currentDepth);
+ if (section.Body.MatchBranch(out var caseBlock) && caseBlock.Parent == section.Parent.Parent.Parent)
+ ComputeStats(caseBlock, ref numStatements, ref maxDepth, currentDepth);
+ break;
+ case ILFunction func:
+ Debug.Assert(!isStatement);
+
+ int bodyStatements = 0;
+ int bodyMaxDepth = maxDepth;
+ ComputeStats(func.Body, ref bodyStatements, ref bodyMaxDepth, currentDepth);
+ if (bodyStatements >= 2) { // don't count inline functions
+ numStatements += bodyStatements;
+ maxDepth = bodyMaxDepth;
+ }
break;
default:
- // just a regular statement
- numStatements++;
- if (currentDepth > maxDepth)
- maxDepth = currentDepth;
+ // search each child instruction. Containers will contain statements and contribute to stats
+ int subStatements = 0;
+ foreach (var child in inst.Children)
+ ComputeStats(child, ref subStatements, ref maxDepth, currentDepth, false);
+
+ numStatements += subStatements;
+ if (isStatement && subStatements > 0)
+ numStatements--; // don't count the first container, only its contents, because this statement is already counted
break;
}
}
diff --git a/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs b/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs
index 22d154960..d066778af 100644
--- a/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs
+++ b/ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs
@@ -46,6 +46,7 @@ namespace ICSharpCode.Decompiler.Metadata
{
PEFile Resolve(IAssemblyReference reference);
PEFile ResolveModule(PEFile mainModule, string moduleName);
+ bool IsGacAssembly(IAssemblyReference reference);
}
public interface IAssemblyReference
diff --git a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs
index 22fc24105..3186fa3d3 100644
--- a/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs
+++ b/ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs
@@ -173,6 +173,11 @@ namespace ICSharpCode.Decompiler.Metadata
return new PEFile(moduleFileName, new FileStream(moduleFileName, FileMode.Open, FileAccess.Read), streamOptions, metadataOptions);
}
+ public virtual bool IsGacAssembly(IAssemblyReference reference)
+ {
+ return GetAssemblyInGac(reference) != null;
+ }
+
public string FindAssemblyFile(IAssemblyReference name)
{
if (name.IsWindowsRuntime) {
diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs
index 4f40c308d..52c7e4644 100644
--- a/ILSpy/Languages/CSharpLanguage.cs
+++ b/ILSpy/Languages/CSharpLanguage.cs
@@ -32,6 +32,7 @@ using ICSharpCode.AvalonEdit.Utils;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
+using ICSharpCode.Decompiler.CSharp.ProjectDecompiler;
using ICSharpCode.Decompiler.CSharp.Syntax;
using ICSharpCode.Decompiler.CSharp.Transforms;
using ICSharpCode.Decompiler.Metadata;
@@ -437,21 +438,19 @@ namespace ICSharpCode.ILSpy
readonly DecompilationOptions options;
public ILSpyWholeProjectDecompiler(LoadedAssembly assembly, DecompilationOptions options)
+ : base(options.DecompilerSettings, assembly.GetAssemblyResolver(), assembly.GetDebugInfoOrNull())
{
this.assembly = assembly;
this.options = options;
- base.Settings = options.DecompilerSettings;
- base.AssemblyResolver = assembly.GetAssemblyResolver();
- base.DebugInfoProvider = assembly.GetDebugInfoOrNull();
}
- protected override IEnumerable> WriteResourceToFile(string fileName, string resourceName, Stream entryStream)
+ protected override IEnumerable<(string itemType, string fileName)> WriteResourceToFile(string fileName, string resourceName, Stream entryStream)
{
foreach (var handler in App.ExportProvider.GetExportedValues()) {
if (handler.CanHandle(fileName, options)) {
entryStream.Position = 0;
fileName = handler.WriteResourceToFile(assembly, fileName, entryStream, options);
- return new[] { Tuple.Create(handler.EntryType, fileName) };
+ return new[] { (handler.EntryType, fileName) };
}
}
return base.WriteResourceToFile(fileName, resourceName, entryStream);
diff --git a/ILSpy/LoadedAssembly.cs b/ILSpy/LoadedAssembly.cs
index 4ce66d2b5..fc0e1bcdc 100644
--- a/ILSpy/LoadedAssembly.cs
+++ b/ILSpy/LoadedAssembly.cs
@@ -253,6 +253,11 @@ namespace ICSharpCode.ILSpy
this.parent = parent;
}
+ public bool IsGacAssembly(IAssemblyReference reference)
+ {
+ return parent.universalResolver?.IsGacAssembly(reference) == true;
+ }
+
public PEFile Resolve(Decompiler.Metadata.IAssemblyReference reference)
{
return parent.LookupReferencedAssembly(reference)?.GetPEFileOrNull();
diff --git a/ILSpy/Properties/Resources.Designer.cs b/ILSpy/Properties/Resources.Designer.cs
index 6e8388874..50e640b22 100644
--- a/ILSpy/Properties/Resources.Designer.cs
+++ b/ILSpy/Properties/Resources.Designer.cs
@@ -1118,6 +1118,15 @@ namespace ICSharpCode.ILSpy.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Use new SDK style format for generated project files (*.csproj).
+ ///
+ public static string DecompilerSettings_UseSdkStyleProjectFormat {
+ get {
+ return ResourceManager.GetString("DecompilerSettings.UseSdkStyleProjectFormat", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Use stackalloc initializer syntax.
///
diff --git a/ILSpy/Properties/Resources.resx b/ILSpy/Properties/Resources.resx
index fdb08a375..4fb242324 100644
--- a/ILSpy/Properties/Resources.resx
+++ b/ILSpy/Properties/Resources.resx
@@ -870,4 +870,7 @@ Do you want to continue?
New Tab
+
+ Use new SDK style format for generated project files (*.csproj)
+
\ No newline at end of file
diff --git a/ILSpy/Properties/Resources.zh-Hans.resx b/ILSpy/Properties/Resources.zh-Hans.resx
index d815abe0c..980fec4c7 100644
--- a/ILSpy/Properties/Resources.zh-Hans.resx
+++ b/ILSpy/Properties/Resources.zh-Hans.resx
@@ -732,4 +732,7 @@
关于
+
+ 使用新的 SDK 格式 (*.csproj) 生成项目文件
+
\ No newline at end of file
diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs
index e26704dde..3e4e8d0b4 100644
--- a/ILSpy/TextView/DecompilerTextView.cs
+++ b/ILSpy/TextView/DecompilerTextView.cs
@@ -45,8 +45,8 @@ using ICSharpCode.AvalonEdit.Highlighting.Xshd;
using ICSharpCode.AvalonEdit.Rendering;
using ICSharpCode.AvalonEdit.Search;
using ICSharpCode.Decompiler;
-using ICSharpCode.Decompiler.CSharp;
using ICSharpCode.Decompiler.CSharp.OutputVisitor;
+using ICSharpCode.Decompiler.CSharp.ProjectDecompiler;
using ICSharpCode.Decompiler.Documentation;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Output;