Browse Source

Merge pull request #2186 from icsharpcode/wwh1004-master

#2157 Improving csproj Export (Save Code)
pull/2193/head
Siegfried Pammer 5 years ago committed by GitHub
parent
commit
7576fb41aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj
  2. 183
      ICSharpCode.Decompiler.Tests/Util/FileUtilityTests.cs
  3. 5
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs
  4. 2
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs
  5. 123
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs
  6. 131
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetServices.cs
  7. 226
      ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs
  8. 82
      ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs
  9. 2
      ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj
  10. 1
      ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs
  11. 13
      ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs
  12. 5
      ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs
  13. 304
      ICSharpCode.Decompiler/Util/FileUtility.cs
  14. 293
      ICSharpCode.Decompiler/Util/Win32Resources.cs
  15. 6
      ILSpy/LoadedAssembly.cs

1
ICSharpCode.Decompiler.Tests/ICSharpCode.Decompiler.Tests.csproj

@ -103,6 +103,7 @@ @@ -103,6 +103,7 @@
<Compile Include="TestAssemblyResolver.cs" />
<Compile Include="TestCases\Correctness\DeconstructionTests.cs" />
<Compile Include="TestCases\Correctness\StringConcat.cs" />
<Compile Include="Util\FileUtilityTests.cs" />
<None Include="TestCases\Pretty\FunctionPointers.cs" />
<None Include="TestCases\Pretty\CS9_ExtensionGetEnumerator.cs" />
<None Include="TestCases\Pretty\UsingVariables.cs" />

183
ICSharpCode.Decompiler.Tests/Util/FileUtilityTests.cs

@ -0,0 +1,183 @@ @@ -0,0 +1,183 @@
// 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 NUnit.Framework;
namespace ICSharpCode.Decompiler.Util
{
[TestFixture]
public class FileUtilityTests
{
#region NormalizePath
[Test]
public void NormalizePath()
{
Assert.AreEqual(@"c:\temp\test.txt", FileUtility.NormalizePath(@"c:\temp\project\..\test.txt"));
Assert.AreEqual(@"c:\temp\test.txt", FileUtility.NormalizePath(@"c:\temp\project\.\..\test.txt"));
Assert.AreEqual(@"c:\temp\test.txt", FileUtility.NormalizePath(@"c:\temp\\test.txt")); // normalize double backslash
Assert.AreEqual(@"c:\temp", FileUtility.NormalizePath(@"c:\temp\."));
Assert.AreEqual(@"c:\temp", FileUtility.NormalizePath(@"c:\temp\subdir\.."));
}
[Test]
public void NormalizePath_DriveRoot()
{
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:\"));
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:/"));
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:"));
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:/."));
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:/.."));
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:/./"));
Assert.AreEqual(@"C:\", FileUtility.NormalizePath(@"C:/..\"));
}
[Test]
public void NormalizePath_UNC()
{
Assert.AreEqual(@"\\server\share", FileUtility.NormalizePath(@"\\server\share"));
Assert.AreEqual(@"\\server\share", FileUtility.NormalizePath(@"\\server\share\"));
Assert.AreEqual(@"\\server\share", FileUtility.NormalizePath(@"//server/share/"));
Assert.AreEqual(@"\\server\share\otherdir", FileUtility.NormalizePath(@"//server/share/dir/..\otherdir"));
}
[Test]
public void NormalizePath_Web()
{
Assert.AreEqual(@"http://danielgrunwald.de/path/", FileUtility.NormalizePath(@"http://danielgrunwald.de/path/"));
Assert.AreEqual(@"browser://http://danielgrunwald.de/path/", FileUtility.NormalizePath(@"browser://http://danielgrunwald.de/wrongpath/../path/"));
}
[Test]
public void NormalizePath_Relative()
{
Assert.AreEqual(@"../b", FileUtility.NormalizePath(@"..\a\..\b"));
Assert.AreEqual(@".", FileUtility.NormalizePath(@"."));
Assert.AreEqual(@".", FileUtility.NormalizePath(@"a\.."));
}
[Test]
public void NormalizePath_UnixStyle()
{
Assert.AreEqual("/", FileUtility.NormalizePath("/"));
Assert.AreEqual("/a/b", FileUtility.NormalizePath("/a/b"));
Assert.AreEqual("/a/b", FileUtility.NormalizePath("/c/../a/./b"));
Assert.AreEqual("/a/b", FileUtility.NormalizePath("/c/../../a/./b"));
}
#endregion
[Test]
public void TestIsBaseDirectory()
{
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a", @"C:\A\b\hello"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a", @"C:\a"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a\", @"C:\a\"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a\", @"C:\a"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a", @"C:\a\"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\A", @"C:\a"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a", @"C:\A"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\a\x\fWufhweoe", @"C:\a\x\fwuFHweoe\a\b\hello"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\b\..\A", @"C:\a"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\HELLO\..\B\..\a", @"C:\b\..\a"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\.\B\..\.\.\a", @"C:\.\.\.\.\.\.\.\a"));
Assert.IsFalse(FileUtility.IsBaseDirectory(@"C:\b", @"C:\a\b\hello"));
Assert.IsFalse(FileUtility.IsBaseDirectory(@"C:\a\b\hello", @"C:\b"));
Assert.IsFalse(FileUtility.IsBaseDirectory(@"C:\a\x\fwufhweoe", @"C:\a\x\fwuFHweoex\a\b\hello"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\", @"C:\"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"C:\", @"C:\a\b\hello"));
Assert.IsFalse(FileUtility.IsBaseDirectory(@"C:\", @"D:\a\b\hello"));
}
[Test]
public void TestIsBaseDirectoryRelative()
{
Assert.IsTrue(FileUtility.IsBaseDirectory(@".", @"a\b"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@".", @"a"));
Assert.IsFalse(FileUtility.IsBaseDirectory(@".", @"c:\"));
Assert.IsFalse(FileUtility.IsBaseDirectory(@".", @"/"));
}
[Test]
public void TestIsBaseDirectoryUnixStyle()
{
Assert.IsTrue(FileUtility.IsBaseDirectory(@"/", @"/"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"/", @"/a"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"/", @"/a/subdir"));
}
[Test]
public void TestIsBaseDirectoryUNC()
{
Assert.IsTrue(FileUtility.IsBaseDirectory(@"\\server\share", @"\\server\share\dir\subdir"));
Assert.IsTrue(FileUtility.IsBaseDirectory(@"\\server\share", @"\\server\share\dir\subdir"));
Assert.IsFalse(FileUtility.IsBaseDirectory(@"\\server2\share", @"\\server\share\dir\subdir"));
}
[Test]
public void TestGetRelativePath()
{
Assert.AreEqual(@"blub", FileUtility.GetRelativePath(@"C:\hello\.\..\a", @"C:\.\a\blub"));
Assert.AreEqual(@"..\a\blub", FileUtility.GetRelativePath(@"C:\.\.\.\.\hello", @"C:\.\blub\.\..\.\a\.\blub"));
Assert.AreEqual(@"..\a\blub", FileUtility.GetRelativePath(@"C:\.\.\.\.\hello\", @"C:\.\blub\.\..\.\a\.\blub"));
Assert.AreEqual(@".", FileUtility.GetRelativePath(@"C:\hello", @"C:\.\hello"));
Assert.AreEqual(@".", FileUtility.GetRelativePath(@"C:\", @"C:\"));
Assert.AreEqual(@"blub", FileUtility.GetRelativePath(@"C:\", @"C:\blub"));
Assert.AreEqual(@"D:\", FileUtility.GetRelativePath(@"C:\", @"D:\"));
Assert.AreEqual(@"D:\def", FileUtility.GetRelativePath(@"C:\abc", @"D:\def"));
// casing troubles
Assert.AreEqual(@"blub", FileUtility.GetRelativePath(@"C:\hello\.\..\A", @"C:\.\a\blub"));
Assert.AreEqual(@"..\a\blub", FileUtility.GetRelativePath(@"C:\.\.\.\.\HELlo", @"C:\.\blub\.\..\.\a\.\blub"));
Assert.AreEqual(@"..\a\blub", FileUtility.GetRelativePath(@"C:\.\.\.\.\heLLo\A\..", @"C:\.\blub\.\..\.\a\.\blub"));
}
[Test]
public void RelativeGetRelativePath()
{
// Relative path
Assert.AreEqual(@"a", FileUtility.GetRelativePath(@".", @"a"));
Assert.AreEqual(@"..", FileUtility.GetRelativePath(@"a", @"."));
Assert.AreEqual(@"..\b", FileUtility.GetRelativePath(@"a", @"b"));
Assert.AreEqual(@"..\..", FileUtility.GetRelativePath(@"a", @".."));
// Getting a path from an absolute path to a relative path isn't really possible;
// so we just keep the existing relative path (don't introduce incorrect '..\').
Assert.AreEqual(@"def", FileUtility.GetRelativePath(@"C:\abc", @"def"));
}
[Test]
public void GetRelativePath_Unix()
{
Assert.AreEqual(@"a", FileUtility.GetRelativePath("/", "/a"));
Assert.AreEqual(@"a\b", FileUtility.GetRelativePath("/", "/a/b"));
Assert.AreEqual(@"b", FileUtility.GetRelativePath("/a", "/a/b"));
}
[Test]
public void TestIsEqualFile()
{
Assert.IsTrue(FileUtility.IsEqualFileName(@"C:\.\Hello World.Exe", @"C:\HELLO WOrld.exe"));
Assert.IsTrue(FileUtility.IsEqualFileName(@"C:\bla\..\a\my.file.is.this", @"C:\gg\..\.\.\.\.\a\..\a\MY.FILE.IS.THIS"));
Assert.IsFalse(FileUtility.IsEqualFileName(@"C:\.\Hello World.Exe", @"C:\HELLO_WOrld.exe"));
Assert.IsFalse(FileUtility.IsEqualFileName(@"C:\a\my.file.is.this", @"C:\gg\..\.\.\.\.\a\..\b\MY.FILE.IS.THIS"));
}
}
}

5
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectInfoProvider.cs

@ -42,6 +42,11 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -42,6 +42,11 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// </summary>
Guid ProjectGuid { get; }
/// <summary>
/// Gets the target directory of the project
/// </summary>
string TargetDirectory { get; }
/// <summary>
/// Gets the name of the key file being used for strong name signing. Can be null if no file is available.
/// </summary>

2
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs

@ -49,6 +49,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -49,6 +49,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
const string ns = "http://schemas.microsoft.com/developer/msbuild/2003";
string platformName = TargetServices.GetPlatformName(module);
var targetFramework = TargetServices.DetectTargetFramework(module);
if (targetFramework.Identifier == ".NETFramework" && targetFramework.VersionNumber == 200)
targetFramework = TargetServices.DetectTargetFrameworkNET20(module, project.AssemblyResolver, targetFramework);
List<Guid> typeGuids = new List<Guid>();
if (targetFramework.IsPortableClassLibrary)

123
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs

@ -24,6 +24,7 @@ using System.Reflection.PortableExecutable; @@ -24,6 +24,7 @@ using System.Reflection.PortableExecutable;
using System.Xml;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Util;
namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
{
@ -71,20 +72,22 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -71,20 +72,22 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
using (XmlTextWriter xmlWriter = new XmlTextWriter(target))
{
xmlWriter.Formatting = Formatting.Indented;
Write(xmlWriter, project, module);
Write(xmlWriter, project, files, module);
}
}
static void Write(XmlTextWriter xml, IProjectInfoProvider project, PEFile module)
static void Write(XmlTextWriter xml, IProjectInfoProvider project, IEnumerable<(string itemType, string fileName)> files, PEFile module)
{
xml.WriteStartElement("Project");
var projectType = GetProjectType(module);
xml.WriteAttributeString("Sdk", GetSdkString(projectType));
PlaceIntoTag("PropertyGroup", xml, () => WriteAssemblyInfo(xml, module, projectType));
PlaceIntoTag("PropertyGroup", xml, () => WriteAssemblyInfo(xml, module, project, projectType));
PlaceIntoTag("PropertyGroup", xml, () => WriteProjectInfo(xml, project));
PlaceIntoTag("ItemGroup", xml, () => WriteReferences(xml, module, project));
PlaceIntoTag("PropertyGroup", xml, () => WriteMiscellaneousPropertyGroup(xml, files));
PlaceIntoTag("ItemGroup", xml, () => WriteResources(xml, files));
PlaceIntoTag("ItemGroup", xml, () => WriteReferences(xml, module, project, projectType));
xml.WriteEndElement();
}
@ -102,23 +105,21 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -102,23 +105,21 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
}
}
static void WriteAssemblyInfo(XmlTextWriter xml, PEFile module, ProjectType projectType)
static void WriteAssemblyInfo(XmlTextWriter xml, PEFile module, IProjectInfoProvider project, 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);
}
WriteOutputType(xml, module.Reader.PEHeaders.IsDll, module.Reader.PEHeaders.PEHeader.Subsystem, projectType);
WriteDesktopExtensions(xml, projectType);
string platformName = TargetServices.GetPlatformName(module);
var targetFramework = TargetServices.DetectTargetFramework(module);
if (targetFramework.Identifier == ".NETFramework" && targetFramework.VersionNumber == 200)
targetFramework = TargetServices.DetectTargetFrameworkNET20(module, project.AssemblyResolver, targetFramework);
if (targetFramework.Moniker == null)
{
@ -139,16 +140,27 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -139,16 +140,27 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
}
}
static void WriteOutputType(XmlTextWriter xml, Subsystem moduleSubsystem)
static void WriteOutputType(XmlTextWriter xml, bool isDll, Subsystem moduleSubsystem, ProjectType projectType)
{
switch (moduleSubsystem)
if (!isDll)
{
case Subsystem.WindowsGui:
xml.WriteElementString("OutputType", "WinExe");
break;
case Subsystem.WindowsCui:
xml.WriteElementString("OutputType", "Exe");
break;
switch (moduleSubsystem)
{
case Subsystem.WindowsGui:
xml.WriteElementString("OutputType", "WinExe");
break;
case Subsystem.WindowsCui:
xml.WriteElementString("OutputType", "Exe");
break;
}
}
else
{
// 'Library' is default, so only need to specify output type for executables (excludes ProjectType.Web)
if (projectType == ProjectType.Web)
{
xml.WriteElementString("OutputType", "Library");
}
}
}
@ -176,17 +188,86 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -176,17 +188,86 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
}
}
static void WriteReferences(XmlTextWriter xml, PEFile module, IProjectInfoProvider project)
static void WriteMiscellaneousPropertyGroup(XmlTextWriter xml, IEnumerable<(string itemType, string fileName)> files)
{
var (itemType, fileName) = files.FirstOrDefault(t => t.itemType == "ApplicationIcon");
if (fileName != null)
xml.WriteElementString("ApplicationIcon", fileName);
(itemType, fileName) = files.FirstOrDefault(t => t.itemType == "ApplicationManifest");
if (fileName != null)
xml.WriteElementString("ApplicationManifest", fileName);
if (files.Any(t => t.itemType == "EmbeddedResource"))
xml.WriteElementString("RootNamespace", string.Empty);
// TODO: We should add CustomToolNamespace for resources, otherwise we should add empty RootNamespace
}
static void WriteResources(XmlTextWriter xml, IEnumerable<(string itemType, string fileName)> files)
{
// remove phase
foreach (var (itemType, fileName) in files.Where(t => t.itemType == "EmbeddedResource"))
{
string buildAction = Path.GetExtension(fileName).ToUpperInvariant() switch
{
".CS" => "Compile",
".RESX" => "EmbeddedResource",
_ => "None"
};
if (buildAction == "EmbeddedResource")
continue;
xml.WriteStartElement(buildAction);
xml.WriteAttributeString("Remove", fileName);
xml.WriteEndElement();
}
// include phase
foreach (var (itemType, fileName) in files.Where(t => t.itemType == "EmbeddedResource"))
{
if (Path.GetExtension(fileName) == ".resx")
continue;
xml.WriteStartElement("EmbeddedResource");
xml.WriteAttributeString("Include", fileName);
xml.WriteEndElement();
}
}
static void WriteReferences(XmlTextWriter xml, PEFile module, IProjectInfoProvider project, ProjectType projectType)
{
bool isNetCoreApp = TargetServices.DetectTargetFramework(module).Identifier == ".NETCoreApp";
var targetPacks = new HashSet<string>();
if (isNetCoreApp)
{
targetPacks.Add("Microsoft.NETCore.App");
switch (projectType)
{
case ProjectType.WinForms:
case ProjectType.Wpf:
targetPacks.Add("Microsoft.WindowsDesktop.App");
break;
case ProjectType.Web:
targetPacks.Add("Microsoft.AspNetCore.App");
targetPacks.Add("Microsoft.AspNetCore.All");
break;
}
}
foreach (var reference in module.AssemblyReferences.Where(r => !ImplicitReferences.Contains(r.Name)))
{
if (isNetCoreApp && project.AssemblyResolver.IsSharedAssembly(reference, out string runtimePack) && targetPacks.Contains(runtimePack))
{
continue;
}
xml.WriteStartElement("Reference");
xml.WriteAttributeString("Include", reference.Name);
var asembly = project.AssemblyResolver.Resolve(reference);
if (asembly != null)
if (asembly != null && !project.AssemblyResolver.IsGacAssembly(reference))
{
xml.WriteElementString("HintPath", asembly.FileName);
xml.WriteElementString("HintPath", FileUtility.GetRelativePath(project.TargetDirectory, asembly.FileName));
}
xml.WriteEndElement();

131
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/TargetServices.cs

@ -17,6 +17,7 @@ @@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.PortableExecutable;
@ -58,7 +59,6 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -58,7 +59,6 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
case TargetRuntime.Net_2_0:
versionNumber = 200;
// TODO: Detect when .NET 3.0/3.5 is required
break;
default:
@ -133,5 +133,134 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -133,5 +133,134 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
return architecture.ToString();
}
}
static HashSet<string> dotNet30Assemblies = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
"ComSvcConfig, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"infocard, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"Microsoft.Transactions.Bridge, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"Microsoft.Transactions.Bridge.Dtc, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"PresentationBuildTasks, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"PresentationCFFRasterizer, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"PresentationFramework.Aero, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"PresentationFramework.Classic, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"PresentationFramework.Luna, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"PresentationFramework.Royale, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"PresentationUI, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"ReachFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"ServiceModelReg, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"SMSvcHost, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.IdentityModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.IdentityModel.Selectors, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.IO.Log, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.Printing, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"System.Runtime.Serialization, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.ServiceModel.Install, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.ServiceModel.WasHosting, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Speech, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"System.Workflow.Activities, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"System.Workflow.ComponentModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"System.Workflow.Runtime, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"UIAutomationClient, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"UIAutomationClientsideProviders, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"UIAutomationProvider, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"UIAutomationTypes, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"WindowsBase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"WindowsFormsIntegration, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"WsatConfig, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
};
static HashSet<string> dotNet35Assemblies = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
"AddInProcess, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"AddInProcess32, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"AddInUtil, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"DataSvcUtil, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"EdmGen, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"Microsoft.Build.Conversion.v3.5, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"Microsoft.Build.Engine, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"Microsoft.Build.Framework, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"Microsoft.Build.Tasks.v3.5, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"Microsoft.Build.Utilities.v3.5, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"Microsoft.Data.Entity.Build.Tasks, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"Microsoft.VisualC.STLCLR, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"MSBuild, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"Sentinel.v3.5Client, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.AddIn, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.AddIn.Contract, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.ComponentModel.DataAnnotations, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Data.DataSetExtensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Data.Entity, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Data.Entity.Design, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Data.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Data.Services, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Data.Services.Client, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Data.Services.Design, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.DirectoryServices.AccountManagement, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Management.Instrumentation, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Net, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a",
"System.ServiceModel.Web, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"System.Web.Abstractions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"System.Web.DynamicData, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"System.Web.DynamicData.Design, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"System.Web.Entity, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Web.Entity.Design, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"System.Web.Extensions.Design, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"System.Web.Routing, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"System.Windows.Presentation, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
"System.WorkflowServices, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35",
"System.Xml.Linq, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089",
};
/// <summary>
/// Gets exact <see cref="TargetFramework"/> if <see cref="PEFile.GetRuntime"/> is <see cref="TargetRuntime.Net_2_0"/>
/// </summary>
public static TargetFramework DetectTargetFrameworkNET20(PEFile module, IAssemblyResolver assemblyResolver, TargetFramework targetFramework)
{
var resolvedAssemblies = new HashSet<string>();
int version = 200;
GetFrameworkVersionNET20(module, assemblyResolver, resolvedAssemblies, ref version);
return new TargetFramework(targetFramework.Identifier, version, targetFramework.Profile);
}
static void GetFrameworkVersionNET20(PEFile module, IAssemblyResolver assemblyResolver, HashSet<string> resolvedAssemblies, ref int version)
{
foreach (var r in module.Metadata.AssemblyReferences)
{
var reference = new AssemblyReference(module, r);
if (!resolvedAssemblies.Add(reference.FullName))
continue;
if (dotNet30Assemblies.Contains(reference.FullName))
{
version = 300;
continue;
}
else if (dotNet35Assemblies.Contains(reference.FullName))
{
version = 350;
break;
}
PEFile resolvedReference;
try
{
resolvedReference = assemblyResolver.Resolve(reference);
}
catch (AssemblyResolutionException)
{
resolvedReference = null;
}
if (resolvedReference == null)
continue;
resolvedAssemblies.Add(resolvedReference.FullName);
GetFrameworkVersionNET20(resolvedReference, assemblyResolver, resolvedAssemblies, ref version);
if (version == 350)
return;
}
}
}
}

226
ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs

@ -22,6 +22,7 @@ using System.Collections.Generic; @@ -22,6 +22,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -72,6 +73,15 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -72,6 +73,15 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
/// </summary>
public Guid ProjectGuid { get; }
/// <summary>
/// The target directory that the decompiled files are written to.
/// </summary>
/// <remarks>
/// This property is set by DecompileProject() and protected so that overridden protected members
/// can access it.
/// </remarks>
public string TargetDirectory { get; protected set; }
/// <summary>
/// Path to the snk file to use for signing.
/// <c>null</c> to not sign.
@ -112,15 +122,6 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -112,15 +122,6 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
// per-run members
HashSet<string> directories = new HashSet<string>(Platform.FileNameComparer);
/// <summary>
/// The target directory that the decompiled files are written to.
/// </summary>
/// <remarks>
/// This field is set by DecompileProject() and protected so that overridden protected members
/// can access it.
/// </remarks>
protected string targetDirectory;
readonly IProjectFileWriter projectWriter;
public void DecompileProject(PEFile moduleDefinition, string targetDirectory, CancellationToken cancellationToken = default(CancellationToken))
@ -138,13 +139,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -138,13 +139,14 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
{
throw new InvalidOperationException("Must set TargetDirectory");
}
this.targetDirectory = targetDirectory;
TargetDirectory = targetDirectory;
directories.Clear();
var files = WriteCodeFilesInProject(moduleDefinition, cancellationToken).ToList();
files.AddRange(WriteResourceFilesInProject(moduleDefinition));
files.AddRange(WriteMiscellaneousFilesInProject(moduleDefinition));
if (StrongNameKeyFile != null)
{
File.Copy(StrongNameKeyFile, Path.Combine(targetDirectory, Path.GetFileName(StrongNameKeyFile)));
File.Copy(StrongNameKeyFile, Path.Combine(targetDirectory, Path.GetFileName(StrongNameKeyFile)), overwrite: true);
}
projectWriter.Write(projectFileWriter, this, files, moduleDefinition);
@ -183,9 +185,9 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -183,9 +185,9 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
const string prop = "Properties";
if (directories.Add(prop))
Directory.CreateDirectory(Path.Combine(targetDirectory, prop));
Directory.CreateDirectory(Path.Combine(TargetDirectory, prop));
string assemblyInfo = Path.Combine(prop, "AssemblyInfo.cs");
using (StreamWriter w = new StreamWriter(Path.Combine(targetDirectory, assemblyInfo)))
using (StreamWriter w = new StreamWriter(Path.Combine(TargetDirectory, assemblyInfo)))
{
syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, Settings.CSharpFormattingOptions));
}
@ -205,9 +207,9 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -205,9 +207,9 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
}
else
{
string dir = CleanUpFileName(metadata.GetString(type.Namespace));
string dir = CleanUpDirectoryName(metadata.GetString(type.Namespace));
if (directories.Add(dir))
Directory.CreateDirectory(Path.Combine(targetDirectory, dir));
Directory.CreateDirectory(Path.Combine(TargetDirectory, dir));
return Path.Combine(dir, file);
}
}, StringComparer.OrdinalIgnoreCase).ToList();
@ -221,7 +223,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -221,7 +223,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
CancellationToken = cancellationToken
},
delegate (IGrouping<string, TypeDefinitionHandle> file) {
using (StreamWriter w = new StreamWriter(Path.Combine(targetDirectory, file.Key)))
using (StreamWriter w = new StreamWriter(Path.Combine(TargetDirectory, file.Key)))
{
try
{
@ -264,7 +266,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -264,7 +266,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
string dirName = Path.GetDirectoryName(fileName);
if (!string.IsNullOrEmpty(dirName) && directories.Add(dirName))
{
Directory.CreateDirectory(Path.Combine(targetDirectory, dirName));
Directory.CreateDirectory(Path.Combine(TargetDirectory, dirName));
}
Stream entryStream = (Stream)value;
entryStream.Position = 0;
@ -306,7 +308,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -306,7 +308,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
else
{
string fileName = GetFileNameForResource(r.Name);
using (FileStream fs = new FileStream(Path.Combine(targetDirectory, fileName), FileMode.Create, FileAccess.Write))
using (FileStream fs = new FileStream(Path.Combine(TargetDirectory, fileName), FileMode.Create, FileAccess.Write))
{
stream.Position = 0;
stream.CopyTo(fs);
@ -323,7 +325,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -323,7 +325,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
string resx = Path.ChangeExtension(fileName, ".resx");
try
{
using (FileStream fs = new FileStream(Path.Combine(targetDirectory, resx), FileMode.Create, FileAccess.Write))
using (FileStream fs = new FileStream(Path.Combine(TargetDirectory, resx), FileMode.Create, FileAccess.Write))
using (ResXResourceWriter writer = new ResXResourceWriter(fs))
{
foreach (var entry in new ResourcesFile(entryStream))
@ -342,7 +344,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -342,7 +344,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
// if the .resources can't be decoded, just save them as-is
}
}
using (FileStream fs = new FileStream(Path.Combine(targetDirectory, fileName), FileMode.Create, FileAccess.Write))
using (FileStream fs = new FileStream(Path.Combine(TargetDirectory, fileName), FileMode.Create, FileAccess.Write))
{
entryStream.CopyTo(fs);
}
@ -367,6 +369,156 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -367,6 +369,156 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
}
#endregion
#region WriteMiscellaneousFilesInProject
protected virtual IEnumerable<(string itemType, string fileName)> WriteMiscellaneousFilesInProject(PEFile module)
{
var resources = module.Reader.ReadWin32Resources();
if (resources == null)
yield break;
byte[] appIcon = CreateApplicationIcon(resources);
if (appIcon != null)
{
File.WriteAllBytes(Path.Combine(TargetDirectory, "app.ico"), appIcon);
yield return ("ApplicationIcon", "app.ico");
}
byte[] appManifest = CreateApplicationManifest(resources);
if (appManifest != null && !IsDefaultApplicationManifest(appManifest))
{
File.WriteAllBytes(Path.Combine(TargetDirectory, "app.manifest"), appManifest);
yield return ("ApplicationManifest", "app.manifest");
}
var appConfig = module.FileName + ".config";
if (File.Exists(appConfig))
{
File.Copy(appConfig, Path.Combine(TargetDirectory, "app.config"), overwrite: true);
yield return ("ApplicationConfig", Path.GetFileName(appConfig));
}
}
const int RT_ICON = 3;
const int RT_GROUP_ICON = 14;
unsafe static byte[] CreateApplicationIcon(Win32ResourceDirectory resources)
{
var iconGroup = resources.Find(new Win32ResourceName(RT_GROUP_ICON))?.FirstDirectory()?.FirstData()?.Data;
if (iconGroup == null)
return null;
var iconDir = resources.Find(new Win32ResourceName(RT_ICON));
if (iconDir == null)
return null;
using var outStream = new MemoryStream();
using var writer = new BinaryWriter(outStream);
fixed (byte* pIconGroupData = iconGroup)
{
var pIconGroup = (GRPICONDIR*)pIconGroupData;
writer.Write(pIconGroup->idReserved);
writer.Write(pIconGroup->idType);
writer.Write(pIconGroup->idCount);
int iconCount = pIconGroup->idCount;
uint offset = (2 * 3) + ((uint)iconCount * 0x10);
for (int i = 0; i < iconCount; i++)
{
var pIconEntry = pIconGroup->idEntries + i;
writer.Write(pIconEntry->bWidth);
writer.Write(pIconEntry->bHeight);
writer.Write(pIconEntry->bColorCount);
writer.Write(pIconEntry->bReserved);
writer.Write(pIconEntry->wPlanes);
writer.Write(pIconEntry->wBitCount);
writer.Write(pIconEntry->dwBytesInRes);
writer.Write(offset);
offset += pIconEntry->dwBytesInRes;
}
for (int i = 0; i < iconCount; i++)
{
var icon = iconDir.FindDirectory(new Win32ResourceName(pIconGroup->idEntries[i].nID))?.FirstData()?.Data;
if (icon == null)
return null;
writer.Write(icon);
}
}
return outStream.ToArray();
}
[StructLayout(LayoutKind.Sequential, Pack = 2)]
unsafe struct GRPICONDIR
{
public ushort idReserved;
public ushort idType;
public ushort idCount;
private fixed byte _idEntries[1];
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "<Pending>")]
public GRPICONDIRENTRY* idEntries {
get {
fixed (byte* p = _idEntries)
return (GRPICONDIRENTRY*)p;
}
}
};
[StructLayout(LayoutKind.Sequential, Pack = 2)]
struct GRPICONDIRENTRY
{
public byte bWidth;
public byte bHeight;
public byte bColorCount;
public byte bReserved;
public ushort wPlanes;
public ushort wBitCount;
public uint dwBytesInRes;
public short nID;
};
const int RT_MANIFEST = 24;
unsafe static byte[] CreateApplicationManifest(Win32ResourceDirectory resources)
{
return resources.Find(new Win32ResourceName(RT_MANIFEST))?.FirstDirectory()?.FirstData()?.Data;
}
static bool IsDefaultApplicationManifest(byte[] appManifest)
{
const string DEFAULT_APPMANIFEST =
"<?xmlversion=\"1.0\"encoding=\"UTF-8\"standalone=\"yes\"?><assemblyxmlns=\"urn:schemas-microsoft-com" +
":asm.v1\"manifestVersion=\"1.0\"><assemblyIdentityversion=\"1.0.0.0\"name=\"MyApplication.app\"/><tr" +
"ustInfoxmlns=\"urn:schemas-microsoft-com:asm.v2\"><security><requestedPrivilegesxmlns=\"urn:schemas-" +
"microsoft-com:asm.v3\"><requestedExecutionLevellevel=\"asInvoker\"uiAccess=\"false\"/></requestedPri" +
"vileges></security></trustInfo></assembly>";
string s = CleanUpApplicationManifest(appManifest);
return s == DEFAULT_APPMANIFEST;
}
static string CleanUpApplicationManifest(byte[] appManifest)
{
bool bom = appManifest.Length >= 3 && appManifest[0] == 0xEF && appManifest[1] == 0xBB && appManifest[2] == 0xBF;
string s = Encoding.UTF8.GetString(appManifest, bom ? 3 : 0, appManifest.Length - (bom ? 3 : 0));
var sb = new StringBuilder(s.Length);
for (int i = 0; i < s.Length; i++)
{
char c = s[i];
switch (c)
{
case '\t':
case '\n':
case '\r':
case ' ':
continue;
}
sb.Append(c);
}
return sb.ToString();
}
#endregion
/// <summary>
/// Cleans up a node name for use as a file name.
/// </summary>
@ -403,6 +555,40 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler @@ -403,6 +555,40 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler
return name;
}
/// <summary>
/// Cleans up a node name for use as a directory name.
/// </summary>
public static string CleanUpDirectoryName(string text)
{
int pos = text.IndexOf(':');
if (pos > 0)
text = text.Substring(0, pos);
pos = text.IndexOf('`');
if (pos > 0)
text = text.Substring(0, pos);
text = text.Trim();
// Whitelist allowed characters, replace everything else:
StringBuilder b = new StringBuilder(text.Length);
foreach (var c in text)
{
if (char.IsLetterOrDigit(c) || c == '-' || c == '_' || c == '\\')
b.Append(c);
else if (c == '.' && b.Length > 0 && b[b.Length - 1] != '.')
b.Append('\\'); // allow dot, but never two in a row
else
b.Append('-');
if (b.Length >= 200)
break; // limit to 200 chars
}
if (b.Length == 0)
b.Append('-');
string name = b.ToString();
if (name == ".")
return "_";
else
return name;
}
static bool IsReservedFileSystemName(string name)
{
switch (name.ToUpperInvariant())

82
ICSharpCode.Decompiler/CSharp/Transforms/EscapeInvalidIdentifiers.cs

@ -68,16 +68,86 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms @@ -68,16 +68,86 @@ namespace ICSharpCode.Decompiler.CSharp.Transforms
{
foreach (var section in rootNode.Children.OfType<AttributeSection>())
{
if (section.AttributeTarget != "assembly")
continue;
foreach (var attribute in section.Attributes)
if (section.AttributeTarget == "assembly")
{
foreach (var attribute in section.Attributes)
{
var trr = attribute.Type.Annotation<TypeResolveResult>();
if (trr == null)
continue;
string fullName = trr.Type.FullName;
var arguments = attribute.Arguments;
switch (fullName)
{
case "System.Diagnostics.DebuggableAttribute":
{
attribute.Remove();
break;
}
case "System.Runtime.CompilerServices.CompilationRelaxationsAttribute":
{
if (arguments.Count == 1 && arguments.First() is PrimitiveExpression expr && expr.Value is int value && value == 8)
attribute.Remove();
break;
}
case "System.Runtime.CompilerServices.RuntimeCompatibilityAttribute":
{
if (arguments.Count != 1)
break;
if (!(arguments.First() is NamedExpression expr1) || expr1.Name != "WrapNonExceptionThrows")
break;
if (!(expr1.Expression is PrimitiveExpression expr2) || !(expr2.Value is bool value) || value != true)
break;
attribute.Remove();
break;
}
case "System.Runtime.Versioning.TargetFrameworkAttribute":
{
attribute.Remove();
break;
}
case "System.Security.Permissions.SecurityPermissionAttribute":
{
if (arguments.Count != 2)
break;
if (!(arguments.First() is MemberReferenceExpression expr1) || expr1.MemberName != "RequestMinimum")
break;
if (!(expr1.NextSibling is NamedExpression expr2) || expr2.Name != "SkipVerification")
break;
if (!(expr2.Expression is PrimitiveExpression expr3) || !(expr3.Value is bool value2) || value2 != true)
break;
attribute.Remove();
break;
}
}
}
}
else if (section.AttributeTarget == "module")
{
var trr = attribute.Type.Annotation<TypeResolveResult>();
if (trr != null && trr.Type.FullName == "System.Runtime.Versioning.TargetFrameworkAttribute")
attribute.Remove();
foreach (var attribute in section.Attributes)
{
var trr = attribute.Type.Annotation<TypeResolveResult>();
if (trr == null)
continue;
switch (trr.Type.FullName)
{
case "System.Security.UnverifiableCodeAttribute":
attribute.Remove();
break;
}
}
}
else
{
continue;
}
if (section.Attributes.Count == 0)
{
section.Remove();
}
}
}
}

2
ICSharpCode.Decompiler/ICSharpCode.Decompiler.csproj

@ -413,6 +413,7 @@ @@ -413,6 +413,7 @@
<Compile Include="TypeSystem\Implementation\NullabilityAnnotatedType.cs" />
<Compile Include="TypeSystem\TupleType.cs" />
<Compile Include="TypeSystem\TypeProvider.cs" />
<Compile Include="Util\FileUtility.cs" />
<Compile Include="Util\GraphVizGraph.cs" />
<Compile Include="Util\KeyComparer.cs" />
<Compile Include="Util\LongDict.cs" />
@ -611,6 +612,7 @@ @@ -611,6 +612,7 @@
<Compile Include="Util\ReferenceComparer.cs" />
<Compile Include="Util\TreeTraversal.cs" />
<Compile Include="Util\UnionFind.cs" />
<Compile Include="Util\Win32Resources.cs" />
<None Include="ICSharpCode.Decompiler.nuspec" DependentUpon="ICSharpCode.Decompiler.nuspec.template" />
<None Include="ICSharpCode.Decompiler.nuspec.template" />
<None Include="ICSharpCode.Decompiler.ruleset" />

1
ICSharpCode.Decompiler/Metadata/AssemblyReferences.cs

@ -49,6 +49,7 @@ namespace ICSharpCode.Decompiler.Metadata @@ -49,6 +49,7 @@ namespace ICSharpCode.Decompiler.Metadata
PEFile ResolveModule(PEFile mainModule, string moduleName);
#endif
bool IsGacAssembly(IAssemblyReference reference);
bool IsSharedAssembly(IAssemblyReference reference, out string runtimePack);
}
public interface IAssemblyReference

13
ICSharpCode.Decompiler/Metadata/DotNetCorePathFinder.cs

@ -139,7 +139,7 @@ namespace ICSharpCode.Decompiler.Metadata @@ -139,7 +139,7 @@ namespace ICSharpCode.Decompiler.Metadata
}
}
return FallbackToDotNetSharedDirectory(name);
return TryResolveDotNetCoreShared(name, out _);
}
internal string GetReferenceAssemblyPath(string targetFramework)
@ -188,13 +188,17 @@ namespace ICSharpCode.Decompiler.Metadata @@ -188,13 +188,17 @@ namespace ICSharpCode.Decompiler.Metadata
}
}
string FallbackToDotNetSharedDirectory(IAssemblyReference name)
public string TryResolveDotNetCoreShared(IAssemblyReference name, out string runtimePack)
{
if (dotnetBasePath == null)
{
runtimePack = null;
return null;
var basePaths = RuntimePacks.Select(pack => Path.Combine(dotnetBasePath, "shared", pack));
foreach (var basePath in basePaths)
}
foreach (string pack in RuntimePacks)
{
runtimePack = pack;
string basePath = Path.Combine(dotnetBasePath, "shared", pack);
if (!Directory.Exists(basePath))
continue;
var closestVersion = GetClosestVersionFolder(basePath, targetFrameworkVersion);
@ -207,6 +211,7 @@ namespace ICSharpCode.Decompiler.Metadata @@ -207,6 +211,7 @@ namespace ICSharpCode.Decompiler.Metadata
return Path.Combine(basePath, closestVersion, name.Name + ".exe");
}
}
runtimePack = null;
return null;
}

5
ICSharpCode.Decompiler/Metadata/UniversalAssemblyResolver.cs

@ -189,6 +189,11 @@ namespace ICSharpCode.Decompiler.Metadata @@ -189,6 +189,11 @@ namespace ICSharpCode.Decompiler.Metadata
return GetAssemblyInGac(reference) != null;
}
public virtual bool IsSharedAssembly(IAssemblyReference reference, out string runtimePack)
{
return dotNetCorePathFinder.TryResolveDotNetCoreShared(reference, out runtimePack) != null;
}
public string FindAssemblyFile(IAssemblyReference name)
{
if (name.IsWindowsRuntime)

304
ICSharpCode.Decompiler/Util/FileUtility.cs

@ -0,0 +1,304 @@ @@ -0,0 +1,304 @@
// 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 System.IO;
using System.Text;
namespace ICSharpCode.Decompiler.Util
{
static class FileUtility
{
/// <summary>
/// Gets the normalized version of fileName.
/// Slashes are replaced with backslashes, backreferences "." and ".." are 'evaluated'.
/// </summary>
public static string NormalizePath(string fileName)
{
if (string.IsNullOrEmpty(fileName))
return fileName;
int i;
bool isWeb = false;
for (i = 0; i < fileName.Length; i++)
{
if (fileName[i] == '/' || fileName[i] == '\\')
break;
if (fileName[i] == ':')
{
if (i > 1)
isWeb = true;
break;
}
}
char outputSeparator = '/';
bool isRelative;
bool isAbsoluteUnixPath = false;
StringBuilder result = new StringBuilder();
if (isWeb == false && IsUNCPath(fileName))
{
// UNC path
i = 2;
outputSeparator = '\\';
result.Append(outputSeparator);
isRelative = false;
}
else
{
i = 0;
isAbsoluteUnixPath = fileName[0] == '/';
isRelative = !isWeb && !isAbsoluteUnixPath && (fileName.Length < 2 || fileName[1] != ':');
if (fileName.Length >= 2 && fileName[1] == ':')
{
outputSeparator = '\\';
}
}
int levelsBack = 0;
int segmentStartPos = i;
for (; i <= fileName.Length; i++)
{
if (i == fileName.Length || fileName[i] == '/' || fileName[i] == '\\')
{
int segmentLength = i - segmentStartPos;
switch (segmentLength)
{
case 0:
// ignore empty segment (if not in web mode)
if (isWeb)
{
result.Append(outputSeparator);
}
break;
case 1:
// ignore /./ segment, but append other one-letter segments
if (fileName[segmentStartPos] != '.')
{
if (result.Length > 0)
result.Append(outputSeparator);
result.Append(fileName[segmentStartPos]);
}
break;
case 2:
if (fileName[segmentStartPos] == '.' && fileName[segmentStartPos + 1] == '.')
{
// remove previous segment
int j;
for (j = result.Length - 1; j >= 0 && result[j] != outputSeparator; j--)
{
}
if (j > 0)
{
result.Length = j;
}
else if (isAbsoluteUnixPath)
{
result.Length = 0;
}
else if (isRelative)
{
if (result.Length == 0)
levelsBack++;
else
result.Length = 0;
}
break;
}
else
{
// append normal segment
goto default;
}
default:
if (result.Length > 0)
result.Append(outputSeparator);
result.Append(fileName, segmentStartPos, segmentLength);
break;
}
segmentStartPos = i + 1; // remember start position for next segment
}
}
if (isWeb == false)
{
if (isRelative)
{
for (int j = 0; j < levelsBack; j++)
{
result.Insert(0, ".." + outputSeparator);
}
}
if (result.Length > 0 && result[result.Length - 1] == outputSeparator)
{
result.Length -= 1;
}
if (isAbsoluteUnixPath)
{
result.Insert(0, '/');
}
if (result.Length == 2 && result[1] == ':')
{
result.Append(outputSeparator);
}
if (result.Length == 0)
return ".";
}
return result.ToString();
}
static bool IsUNCPath(string fileName)
{
return fileName.Length > 2
&& (fileName[0] == '\\' || fileName[0] == '/')
&& (fileName[1] == '\\' || fileName[1] == '/');
}
public static bool IsEqualFileName(string fileName1, string fileName2)
{
return string.Equals(NormalizePath(fileName1),
NormalizePath(fileName2),
StringComparison.OrdinalIgnoreCase);
}
public static bool IsBaseDirectory(string baseDirectory, string testDirectory)
{
if (baseDirectory == null || testDirectory == null)
return false;
baseDirectory = NormalizePath(baseDirectory);
if (baseDirectory == "." || baseDirectory == "")
return !Path.IsPathRooted(testDirectory);
baseDirectory = AddTrailingSeparator(baseDirectory);
testDirectory = AddTrailingSeparator(NormalizePath(testDirectory));
return testDirectory.StartsWith(baseDirectory, StringComparison.OrdinalIgnoreCase);
}
static string AddTrailingSeparator(string input)
{
if (string.IsNullOrEmpty(input))
return input;
if (input[input.Length - 1] == Path.DirectorySeparatorChar || input[input.Length - 1] == Path.AltDirectorySeparatorChar)
return input;
else
return input + GetSeparatorForPath(input).ToString();
}
static char GetSeparatorForPath(string input)
{
if (input.Length > 2 && input[1] == ':' || IsUNCPath(input))
return '\\';
return '/';
}
readonly static char[] separators = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar };
/// <summary>
/// Converts a given absolute path and a given base path to a path that leads
/// from the base path to the absoulte path. (as a relative path)
/// </summary>
public static string GetRelativePath(string baseDirectoryPath, string absPath)
{
if (string.IsNullOrEmpty(baseDirectoryPath))
{
return absPath;
}
baseDirectoryPath = NormalizePath(baseDirectoryPath);
absPath = NormalizePath(absPath);
string[] bPath = baseDirectoryPath != "." ? baseDirectoryPath.TrimEnd(separators).Split(separators) : new string[0];
string[] aPath = absPath != "." ? absPath.TrimEnd(separators).Split(separators) : new string[0];
int indx = 0;
for (; indx < Math.Min(bPath.Length, aPath.Length); ++indx)
{
if (!bPath[indx].Equals(aPath[indx], StringComparison.OrdinalIgnoreCase))
break;
}
if (indx == 0 && (Path.IsPathRooted(baseDirectoryPath) || Path.IsPathRooted(absPath)))
{
return absPath;
}
if (indx == bPath.Length && indx == aPath.Length)
{
return ".";
}
StringBuilder erg = new StringBuilder();
for (int i = indx; i < bPath.Length; ++i)
{
erg.Append("..");
erg.Append(Path.DirectorySeparatorChar);
}
erg.Append(String.Join(Path.DirectorySeparatorChar.ToString(), aPath, indx, aPath.Length - indx));
if (erg[erg.Length - 1] == Path.DirectorySeparatorChar)
erg.Length -= 1;
return erg.ToString();
}
public static string TrimPath(string path, int max_chars)
{
const char ellipsis = '\u2026'; // HORIZONTAL ELLIPSIS
const int ellipsisLength = 2;
if (path == null || path.Length <= max_chars)
return path;
char sep = Path.DirectorySeparatorChar;
if (path.IndexOf(Path.AltDirectorySeparatorChar) >= 0 && path.IndexOf(Path.DirectorySeparatorChar) < 0)
{
sep = Path.AltDirectorySeparatorChar;
}
string[] parts = path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
int len = ellipsisLength; // For initial ellipsis
int index = parts.Length;
// From the end of the path, fit as many parts as possible:
while (index > 1 && len + parts[index - 1].Length < max_chars)
{
len += parts[index - 1].Length + 1;
index--;
}
StringBuilder result = new StringBuilder();
result.Append(ellipsis);
// If there's 5 chars left, partially fit another part:
if (index > 1 && len + 5 <= max_chars)
{
if (index == 2 && parts[0].Length <= ellipsisLength)
{
// If the partial part is part #1,
// and part #0 is as short as the ellipsis
// (e.g. just a drive letter), use part #0
// instead of the ellipsis.
result.Clear();
result.Append(parts[0]);
}
result.Append(sep);
result.Append(parts[index - 1], 0, max_chars - len - 3);
result.Append(ellipsis);
}
while (index < parts.Length)
{
result.Append(sep);
result.Append(parts[index]);
index++;
}
return result.ToString();
}
}
}

293
ICSharpCode.Decompiler/Util/Win32Resources.cs

@ -0,0 +1,293 @@ @@ -0,0 +1,293 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection.PortableExecutable;
namespace ICSharpCode.Decompiler.Util
{
/// <summary>
/// Represents win32 resources
/// </summary>
public static class Win32Resources
{
/// <summary>
/// Reads win32 resource root directory
/// </summary>
/// <param name="pe"></param>
/// <returns></returns>
public static unsafe Win32ResourceDirectory ReadWin32Resources(this PEReader pe)
{
if (pe == null)
{
throw new ArgumentNullException(nameof(pe));
}
int rva = pe.PEHeaders.PEHeader.ResourceTableDirectory.RelativeVirtualAddress;
if (rva == 0)
return null;
byte* pRoot = pe.GetSectionData(rva).Pointer;
return new Win32ResourceDirectory(pe, pRoot, 0, new Win32ResourceName("Root"));
}
public static Win32ResourceDirectory Find(this Win32ResourceDirectory root, Win32ResourceName type)
{
if (root is null)
throw new ArgumentNullException(nameof(root));
if (!root.Name.HasName || root.Name.Name != "Root")
throw new ArgumentOutOfRangeException(nameof(root));
if (type is null)
throw new ArgumentNullException(nameof(type));
return root.FindDirectory(type);
}
public static Win32ResourceDirectory Find(this Win32ResourceDirectory root, Win32ResourceName type, Win32ResourceName name)
{
if (root is null)
throw new ArgumentNullException(nameof(root));
if (!root.Name.HasName || root.Name.Name != "Root")
throw new ArgumentOutOfRangeException(nameof(root));
if (type is null)
throw new ArgumentNullException(nameof(type));
if (name is null)
throw new ArgumentNullException(nameof(name));
return root.FindDirectory(type)?.FindDirectory(name);
}
public static Win32ResourceData Find(this Win32ResourceDirectory root, Win32ResourceName type, Win32ResourceName name, Win32ResourceName langId)
{
if (root is null)
throw new ArgumentNullException(nameof(root));
if (!root.Name.HasName || root.Name.Name != "Root")
throw new ArgumentOutOfRangeException(nameof(root));
if (type is null)
throw new ArgumentNullException(nameof(type));
if (name is null)
throw new ArgumentNullException(nameof(name));
if (langId is null)
throw new ArgumentNullException(nameof(langId));
return root.FindDirectory(type)?.FindDirectory(name)?.FindData(langId);
}
}
[DebuggerDisplay("Directory: {Name}")]
public sealed class Win32ResourceDirectory
{
#region Structure
public uint Characteristics { get; }
public uint TimeDateStamp { get; }
public ushort MajorVersion { get; }
public ushort MinorVersion { get; }
public ushort NumberOfNamedEntries { get; }
public ushort NumberOfIdEntries { get; }
#endregion
public Win32ResourceName Name { get; }
public IList<Win32ResourceDirectory> Directories { get; }
public IList<Win32ResourceData> Datas { get; }
internal unsafe Win32ResourceDirectory(PEReader pe, byte* pRoot, int offset, Win32ResourceName name)
{
var p = (IMAGE_RESOURCE_DIRECTORY*)(pRoot + offset);
Characteristics = p->Characteristics;
TimeDateStamp = p->TimeDateStamp;
MajorVersion = p->MajorVersion;
MinorVersion = p->MinorVersion;
NumberOfNamedEntries = p->NumberOfNamedEntries;
NumberOfIdEntries = p->NumberOfIdEntries;
Name = name;
Directories = new List<Win32ResourceDirectory>();
Datas = new List<Win32ResourceData>();
var pEntries = (IMAGE_RESOURCE_DIRECTORY_ENTRY*)(p + 1);
int total = NumberOfNamedEntries + NumberOfIdEntries;
for (int i = 0; i < total; i++)
{
var pEntry = pEntries + i;
name = new Win32ResourceName(pRoot, pEntry);
if ((pEntry->OffsetToData & 0x80000000) == 0)
Datas.Add(new Win32ResourceData(pe, pRoot, (int)pEntry->OffsetToData, name));
else
Directories.Add(new Win32ResourceDirectory(pe, pRoot, (int)(pEntry->OffsetToData & 0x7FFFFFFF), name));
}
}
static unsafe string ReadString(byte* pRoot, int offset)
{
var pString = (IMAGE_RESOURCE_DIRECTORY_STRING*)(pRoot + offset);
return new string(pString->NameString, 0, pString->Length);
}
public Win32ResourceDirectory FindDirectory(Win32ResourceName name)
{
foreach (var directory in Directories)
{
if (directory.Name == name)
return directory;
}
return null;
}
public Win32ResourceData FindData(Win32ResourceName name)
{
foreach (var data in Datas)
{
if (data.Name == name)
return data;
}
return null;
}
public Win32ResourceDirectory FirstDirectory()
{
return Directories.Count != 0 ? Directories[0] : null;
}
public Win32ResourceData FirstData()
{
return Datas.Count != 0 ? Datas[0] : null;
}
}
[DebuggerDisplay("Data: {Name}")]
public sealed unsafe class Win32ResourceData
{
#region Structure
public uint OffsetToData { get; }
public uint Size { get; }
public uint CodePage { get; }
public uint Reserved { get; }
#endregion
private readonly void* _pointer;
public Win32ResourceName Name { get; }
public byte[] Data {
get {
byte[] data = new byte[Size];
fixed (void* pData = data)
Buffer.MemoryCopy(_pointer, pData, Size, Size);
return data;
}
}
internal Win32ResourceData(PEReader pe, byte* pRoot, int offset, Win32ResourceName name)
{
var p = (IMAGE_RESOURCE_DATA_ENTRY*)(pRoot + offset);
OffsetToData = p->OffsetToData;
Size = p->Size;
CodePage = p->CodePage;
Reserved = p->Reserved;
_pointer = pe.GetSectionData((int)OffsetToData).Pointer;
Name = name;
}
}
public sealed class Win32ResourceName
{
private readonly object _name;
public bool HasName => _name is string;
public bool HasId => _name is ushort;
public string Name => (string)_name;
public ushort Id => (ushort)_name;
public Win32ResourceName(string name)
{
_name = name ?? throw new ArgumentNullException(nameof(name));
}
public Win32ResourceName(int id) : this(checked((ushort)id))
{
}
public Win32ResourceName(ushort id)
{
_name = id;
}
internal unsafe Win32ResourceName(byte* pRoot, IMAGE_RESOURCE_DIRECTORY_ENTRY* pEntry)
{
_name = (pEntry->Name & 0x80000000) == 0 ? (object)(ushort)pEntry->Name : ReadString(pRoot, (int)(pEntry->Name & 0x7FFFFFFF));
static string ReadString(byte* pRoot, int offset)
{
var pString = (IMAGE_RESOURCE_DIRECTORY_STRING*)(pRoot + offset);
return new string(pString->NameString, 0, pString->Length);
}
}
public static bool operator ==(Win32ResourceName x, Win32ResourceName y)
{
if (x.HasName)
{
return y.HasName ? string.Compare(x.Name, y.Name, StringComparison.OrdinalIgnoreCase) == 0 : false;
}
else
{
return y.HasId ? x.Id == y.Id : false;
}
}
public static bool operator !=(Win32ResourceName x, Win32ResourceName y)
{
return !(x == y);
}
public override int GetHashCode()
{
return _name.GetHashCode();
}
public override bool Equals(object obj)
{
if (!(obj is Win32ResourceName name))
return false;
return this == name;
}
public override string ToString()
{
return HasName ? $"Name: {Name}" : $"Id: {Id}";
}
}
internal struct IMAGE_RESOURCE_DIRECTORY
{
public uint Characteristics;
public uint TimeDateStamp;
public ushort MajorVersion;
public ushort MinorVersion;
public ushort NumberOfNamedEntries;
public ushort NumberOfIdEntries;
}
internal struct IMAGE_RESOURCE_DIRECTORY_ENTRY
{
public uint Name;
public uint OffsetToData;
}
internal unsafe struct IMAGE_RESOURCE_DIRECTORY_STRING
{
public ushort Length;
public fixed char NameString[1];
}
internal struct IMAGE_RESOURCE_DATA_ENTRY
{
public uint OffsetToData;
public uint Size;
public uint CodePage;
public uint Reserved;
}
}

6
ILSpy/LoadedAssembly.cs

@ -288,6 +288,12 @@ namespace ICSharpCode.ILSpy @@ -288,6 +288,12 @@ namespace ICSharpCode.ILSpy
return parent.universalResolver?.IsGacAssembly(reference) == true;
}
public bool IsSharedAssembly(IAssemblyReference reference, out string runtimePack)
{
runtimePack = null;
return parent.universalResolver?.IsSharedAssembly(reference, out runtimePack) == true;
}
public PEFile Resolve(Decompiler.Metadata.IAssemblyReference reference)
{
return parent.LookupReferencedAssembly(reference)?.GetPEFileOrNull();

Loading…
Cancel
Save