From ff0e929866b96f55b6178487f1b73dfa008adb19 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sat, 19 Nov 2022 10:48:14 +0100 Subject: [PATCH] - Introduce ProjectItemInfo instead of tuples in WholeProjectDecompiler project item generation. - Set 'LogicalName' attribute for all decompiled resources. This makes it possible to correctly recompile projects with resource names that are not valid filenames. - Set Generator and SubType properties for XAML files. --- .../ProjectDecompiler/IProjectFileWriter.cs | 5 +- .../ProjectFileWriterDefault.cs | 14 +++-- .../ProjectFileWriterSdkStyle.cs | 31 ++++++----- .../WholeProjectDecompiler.cs | 53 +++++++++++++------ .../BamlResourceNodeFactory.cs | 2 + ILSpy/Languages/CSharpLanguage.cs | 4 +- ILSpy/Languages/IResourceFileHandler.cs | 3 ++ 7 files changed, 74 insertions(+), 38 deletions(-) diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs index d5542fb6e..26c1a8a58 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/IProjectFileWriter.cs @@ -34,9 +34,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler /// /// 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. + /// A collection of source files to be included into the project. /// The module being decompiled. - void Write(TextWriter target, IProjectInfoProvider project, IEnumerable<(string itemType, string fileName)> files, PEFile module); + void Write(TextWriter target, IProjectInfoProvider project, IEnumerable files, PEFile module); } } diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs index af98d6045..eb383ba75 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterDefault.cs @@ -25,6 +25,7 @@ using System.Xml; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.Solution; +using ICSharpCode.Decompiler.Util; namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler { @@ -43,7 +44,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler public void Write( TextWriter target, IProjectInfoProvider project, - IEnumerable<(string itemType, string fileName)> files, + IEnumerable files, PEFile module) { const string ns = "http://schemas.microsoft.com/developer/msbuild/2003"; @@ -162,13 +163,18 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler } w.WriteEndElement(); // (References) - foreach (IGrouping gr in from f in files group f.fileName by f.itemType into g orderby g.Key select g) + foreach (IGrouping gr in files.GroupBy(f => f.ItemType).OrderBy(g => g.Key)) { w.WriteStartElement("ItemGroup"); - foreach (string file in gr.OrderBy(f => f, StringComparer.OrdinalIgnoreCase)) + foreach (var item in gr.OrderBy(f => f.FileName, StringComparer.OrdinalIgnoreCase)) { w.WriteStartElement(gr.Key); - w.WriteAttributeString("Include", file); + w.WriteAttributeString("Include", item.FileName); + if (item.AdditionalProperties != null) + { + foreach (var (key, value) in item.AdditionalProperties) + w.WriteAttributeString(key, value); + } w.WriteEndElement(); } w.WriteEndElement(); diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs index 61bb6d21f..b5273f1d5 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/ProjectFileWriterSdkStyle.cs @@ -66,7 +66,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler public void Write( TextWriter target, IProjectInfoProvider project, - IEnumerable<(string itemType, string fileName)> files, + IEnumerable files, PEFile module) { using (XmlTextWriter xmlWriter = new XmlTextWriter(target)) @@ -76,7 +76,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler } } - static void Write(XmlTextWriter xml, IProjectInfoProvider project, IEnumerable<(string itemType, string fileName)> files, PEFile module) + static void Write(XmlTextWriter xml, IProjectInfoProvider project, IEnumerable files, PEFile module) { xml.WriteStartElement("Project"); @@ -188,27 +188,27 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler } } - static void WriteMiscellaneousPropertyGroup(XmlTextWriter xml, IEnumerable<(string itemType, string fileName)> files) + static void WriteMiscellaneousPropertyGroup(XmlTextWriter xml, IEnumerable files) { - var (itemType, fileName) = files.FirstOrDefault(t => t.itemType == "ApplicationIcon"); + var (itemType, fileName) = files.FirstOrDefault(t => t.ItemType == "ApplicationIcon"); if (fileName != null) xml.WriteElementString("ApplicationIcon", fileName); - (itemType, fileName) = files.FirstOrDefault(t => t.itemType == "ApplicationManifest"); + (itemType, fileName) = files.FirstOrDefault(t => t.ItemType == "ApplicationManifest"); if (fileName != null) xml.WriteElementString("ApplicationManifest", fileName); - if (files.Any(t => t.itemType == "EmbeddedResource")) + 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) + static void WriteResources(XmlTextWriter xml, IEnumerable files) { // remove phase - foreach (var (itemType, fileName) in files.Where(t => t.itemType == "EmbeddedResource")) + foreach (var item in files.Where(t => t.ItemType == "EmbeddedResource")) { - string buildAction = Path.GetExtension(fileName).ToUpperInvariant() switch { + string buildAction = Path.GetExtension(item.FileName).ToUpperInvariant() switch { ".CS" => "Compile", ".RESX" => "EmbeddedResource", _ => "None" @@ -217,18 +217,23 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler continue; xml.WriteStartElement(buildAction); - xml.WriteAttributeString("Remove", fileName); + xml.WriteAttributeString("Remove", item.FileName); xml.WriteEndElement(); } // include phase - foreach (var (itemType, fileName) in files.Where(t => t.itemType == "EmbeddedResource")) + foreach (var item in files.Where(t => t.ItemType == "EmbeddedResource")) { - if (Path.GetExtension(fileName) == ".resx") + if (Path.GetExtension(item.FileName) == ".resx") continue; xml.WriteStartElement("EmbeddedResource"); - xml.WriteAttributeString("Include", fileName); + xml.WriteAttributeString("Include", item.FileName); + if (item.AdditionalProperties != null) + { + foreach (var (key, value) in item.AdditionalProperties) + xml.WriteAttributeString(key, value); + } xml.WriteEndElement(); } } diff --git a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs index e342df4a6..852de74a7 100644 --- a/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs +++ b/ICSharpCode.Decompiler/CSharp/ProjectDecompiler/WholeProjectDecompiler.cs @@ -151,8 +151,8 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler TargetDirectory = targetDirectory; directories.Clear(); var resources = WriteResourceFilesInProject(moduleDefinition).ToList(); - var files = WriteCodeFilesInProject(moduleDefinition, resources.SelectMany(r => r.partialTypes ?? Enumerable.Empty()).ToList(), cancellationToken).ToList(); - files.AddRange(resources.Select(r => (r.itemType, r.fileName))); + var files = WriteCodeFilesInProject(moduleDefinition, resources.SelectMany(r => r.PartialTypes ?? Enumerable.Empty()).ToList(), cancellationToken).ToList(); + files.AddRange(resources); files.AddRange(WriteMiscellaneousFilesInProject(moduleDefinition)); if (StrongNameKeyFile != null) { @@ -190,7 +190,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler return decompiler; } - IEnumerable<(string itemType, string fileName)> WriteAssemblyInfo(DecompilerTypeSystem ts, CancellationToken cancellationToken) + IEnumerable WriteAssemblyInfo(DecompilerTypeSystem ts, CancellationToken cancellationToken) { var decompiler = CreateDecompiler(ts); decompiler.CancellationToken = cancellationToken; @@ -205,10 +205,10 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler { syntaxTree.AcceptVisitor(new CSharpOutputVisitor(w, Settings.CSharpFormattingOptions)); } - return new[] { ("Compile", assemblyInfo) }; + return new[] { new ProjectItemInfo("Compile", assemblyInfo) }; } - IEnumerable<(string itemType, string fileName)> WriteCodeFilesInProject(Metadata.PEFile module, IList partialTypes, CancellationToken cancellationToken) + IEnumerable WriteCodeFilesInProject(Metadata.PEFile module, IList partialTypes, CancellationToken cancellationToken) { var metadata = module.Metadata; var files = module.Metadata.GetTopLevelTypeDefinitions().Where(td => IncludeTypeWhenDecompilingProject(module, td)) @@ -229,7 +229,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler progress.TotalUnits = files.Count; } - return files.Select(f => ("Compile", f.Key)).Concat(WriteAssemblyInfo(ts, cancellationToken)); + return files.Select(f => new ProjectItemInfo("Compile", f.Key)).Concat(WriteAssemblyInfo(ts, cancellationToken)); string GetFileFileNameForHandle(TypeDefinitionHandle h) { @@ -308,7 +308,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler #endregion #region WriteResourceFilesInProject - protected virtual IEnumerable<(string itemType, string fileName, List partialTypes)> WriteResourceFilesInProject(Metadata.PEFile module) + protected virtual IEnumerable WriteResourceFilesInProject(Metadata.PEFile module) { foreach (var r in module.Resources.Where(r => r.ResourceType == ResourceType.Embedded)) { @@ -318,7 +318,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler if (r.Name.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) { bool decodedIntoIndividualFiles; - var individualResources = new List<(string itemType, string fileName, List partialTypes)>(); + var individualResources = new List(); try { var resourcesFile = new ResourcesFile(stream); @@ -378,12 +378,12 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler stream.Position = 0; stream.CopyTo(fs); } - yield return ("EmbeddedResource", fileName, null); + yield return new ProjectItemInfo("EmbeddedResource", fileName).With("LogicalName", r.Name); } } } - protected virtual IEnumerable<(string itemType, string fileName, List partialTypes)> WriteResourceToFile(string fileName, string resourceName, Stream entryStream) + protected virtual IEnumerable WriteResourceToFile(string fileName, string resourceName, Stream entryStream) { if (fileName.EndsWith(".resources", StringComparison.OrdinalIgnoreCase)) { @@ -398,7 +398,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler writer.AddResource(entry.Key, entry.Value); } } - return new[] { ("EmbeddedResource", resx, (List)null) }; + return new[] { new ProjectItemInfo("EmbeddedResource", resx).With("LogicalName", resourceName) }; } catch (BadImageFormatException) { @@ -413,7 +413,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler { entryStream.CopyTo(fs); } - return new[] { ("EmbeddedResource", fileName, (List)null) }; + return new[] { new ProjectItemInfo("EmbeddedResource", fileName).With("LogicalName", resourceName) }; } string GetFileNameForResource(string fullName) @@ -444,7 +444,7 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler #endregion #region WriteMiscellaneousFilesInProject - protected virtual IEnumerable<(string itemType, string fileName)> WriteMiscellaneousFilesInProject(PEFile module) + protected virtual IEnumerable WriteMiscellaneousFilesInProject(PEFile module) { var resources = module.Reader.ReadWin32Resources(); if (resources == null) @@ -454,21 +454,21 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler if (appIcon != null) { File.WriteAllBytes(Path.Combine(TargetDirectory, "app.ico"), appIcon); - yield return ("ApplicationIcon", "app.ico"); + yield return new ProjectItemInfo("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"); + yield return new ProjectItemInfo("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)); + yield return new ProjectItemInfo("ApplicationConfig", Path.GetFileName(appConfig)); } } @@ -753,4 +753,25 @@ namespace ICSharpCode.Decompiler.CSharp.ProjectDecompiler return TargetServices.DetectTargetFramework(module).Moniker != null; } } + + public record struct ProjectItemInfo(string ItemType, string FileName) + { + public List PartialTypes { get; set; } = null; + + public Dictionary AdditionalProperties { get; set; } = null; + + public ProjectItemInfo With(string name, string value) + { + AdditionalProperties ??= new Dictionary(); + AdditionalProperties.Add(name, value); + return this; + } + + public ProjectItemInfo With(IEnumerable> pairs) + { + AdditionalProperties ??= new Dictionary(); + AdditionalProperties.AddRange(pairs); + return this; + } + } } diff --git a/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs b/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs index be0a6ae1c..4ac408de5 100644 --- a/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs +++ b/ILSpy.BamlDecompiler/BamlResourceNodeFactory.cs @@ -72,6 +72,8 @@ namespace ILSpy.BamlDecompiler { fileName = Path.ChangeExtension(fileName, ".xaml"); } + context.AdditionalProperties.Add("Generator", "MSBuild:Compile"); + context.AdditionalProperties.Add("SubType", "Designer"); string saveFileName = Path.Combine(context.DecompilationOptions.SaveAsProjectDirectory, fileName); Directory.CreateDirectory(Path.GetDirectoryName(saveFileName)); result.Xaml.Save(saveFileName); diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index 703fcd3a6..a5cd1e1a0 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -517,7 +517,7 @@ namespace ICSharpCode.ILSpy this.options = options; } - protected override IEnumerable<(string itemType, string fileName, List partialTypes)> WriteResourceToFile(string fileName, string resourceName, Stream entryStream) + protected override IEnumerable WriteResourceToFile(string fileName, string resourceName, Stream entryStream) { var context = new ResourceFileHandlerContext(options); foreach (var handler in App.ExportProvider.GetExportedValues()) @@ -527,7 +527,7 @@ namespace ICSharpCode.ILSpy entryStream.Position = 0; fileName = handler.WriteResourceToFile(assembly, fileName, entryStream, context); - return new[] { (handler.EntryType, fileName, context.PartialTypes) }; + return new[] { new ProjectItemInfo(handler.EntryType, fileName) { PartialTypes = context.PartialTypes }.With(context.AdditionalProperties) }; } } return base.WriteResourceToFile(fileName, resourceName, entryStream); diff --git a/ILSpy/Languages/IResourceFileHandler.cs b/ILSpy/Languages/IResourceFileHandler.cs index 8fe336f06..19b977ac5 100644 --- a/ILSpy/Languages/IResourceFileHandler.cs +++ b/ILSpy/Languages/IResourceFileHandler.cs @@ -36,6 +36,9 @@ namespace ICSharpCode.ILSpy readonly List partialTypes = new(); internal List PartialTypes => partialTypes; + readonly Dictionary additionalProperties = new(); + public Dictionary AdditionalProperties => additionalProperties; + public DecompilationOptions DecompilationOptions { get; } public ResourceFileHandlerContext(DecompilationOptions options)