/* Copyright (C) 2014-2019 de4dot@gmail.com This file is part of dnSpy dnSpy is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. dnSpy is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with dnSpy. If not, see . */ using System; using System.Collections.Generic; using System.ComponentModel.Composition; using System.Diagnostics; using System.IO; using System.Linq; using dnlib.DotNet; using dnlib.DotNet.Resources; using dnSpy.AsmEditor.Commands; using dnSpy.AsmEditor.Properties; using dnSpy.AsmEditor.UndoRedo; using dnSpy.Contracts.App; using dnSpy.Contracts.Controls; using dnSpy.Contracts.Documents.Tabs; using dnSpy.Contracts.Documents.Tabs.DocViewer; using dnSpy.Contracts.Documents.TreeView; using dnSpy.Contracts.Documents.TreeView.Resources; using dnSpy.Contracts.Extension; using dnSpy.Contracts.Images; using dnSpy.Contracts.Menus; using dnSpy.Contracts.MVVM; using dnSpy.Contracts.TreeView; using dnSpy.Contracts.Utilities; using WF = System.Windows.Forms; namespace dnSpy.AsmEditor.Resources { [ExportAutoLoaded] sealed class CommandLoader : IAutoLoaded { [ImportingConstructor] CommandLoader(IWpfCommandService wpfCommandService, IDocumentTabService documentTabService, DeleteResourceCommand.EditMenuCommand removeCmd1, DeleteResourceElementCommand.EditMenuCommand removeCmd2, DeleteResourceCommand.CodeCommand removeCmd3, DeleteResourceElementCommand.CodeCommand removeCmd4, ResourceSettingsCommand.EditMenuCommand settingsCmd10, ResourceSettingsCommand.CodeCommand settingsCmd11, ResourceElementSettingsCommand.EditMenuCommand settingsCmd20, ResourceElementSettingsCommand.CodeCommand settingsCmd21, ImageResourceElementSettingsCommand.EditMenuCommand settingsCmd30, ImageResourceElementSettingsCommand.CodeCommand settingsCmd31, SerializedImageResourceElementSettingsCommand.EditMenuCommand settingsCmd40, SerializedImageResourceElementSettingsCommand.CodeCommand settingsCmd41, SerializedImageListStreamerResourceElementSettingsCommand.EditMenuCommand settingsCmd50, SerializedImageListStreamerResourceElementSettingsCommand.CodeCommand settingsCmd51) { wpfCommandService.AddRemoveCommand(removeCmd1); wpfCommandService.AddRemoveCommand(removeCmd2); wpfCommandService.AddRemoveCommand(removeCmd3, documentTabService); wpfCommandService.AddRemoveCommand(removeCmd4, documentTabService); wpfCommandService.AddSettingsCommand(documentTabService, settingsCmd10, settingsCmd11); wpfCommandService.AddSettingsCommand(documentTabService, settingsCmd20, settingsCmd21); wpfCommandService.AddSettingsCommand(documentTabService, settingsCmd30, settingsCmd31); wpfCommandService.AddSettingsCommand(documentTabService, settingsCmd40, settingsCmd41); wpfCommandService.AddSettingsCommand(documentTabService, settingsCmd50, settingsCmd51); } } [DebuggerDisplay("{Description}")] sealed class DeleteResourceCommand : IUndoCommand { [ExportMenuItem(Header = "res:DeleteResourceCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_DELETE, Order = 80)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService) => this.undoCommandService = undoCommandService; public override bool IsVisible(AsmEditorContext context) => DeleteResourceCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => DeleteResourceCommand.Execute(undoCommandService, context.Nodes); public override string? GetHeader(AsmEditorContext context) => DeleteResourceCommand.GetHeader(context.Nodes); } [Export, ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:DeleteResourceCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_DELETE, Order = 80)] internal sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IDocumentTreeView documentTreeView) : base(documentTreeView) => this.undoCommandService = undoCommandService; public override bool IsVisible(AsmEditorContext context) => DeleteResourceCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => DeleteResourceCommand.Execute(undoCommandService, context.Nodes); public override string? GetHeader(AsmEditorContext context) => DeleteResourceCommand.GetHeader(context.Nodes); } [Export, ExportMenuItem(Header = "res:DeleteResourceCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_DELETE, Order = 80)] internal sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IDocumentTreeView documentTreeView) : base(documentTreeView) => this.undoCommandService = undoCommandService; public override bool IsEnabled(CodeContext context) => context.IsDefinition && DeleteResourceCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => DeleteResourceCommand.Execute(undoCommandService, context.Nodes); public override string? GetHeader(CodeContext context) => DeleteResourceCommand.GetHeader(context.Nodes); } static string GetHeader(DocumentTreeNodeData[] nodes) { if (nodes.Length == 1) return string.Format(dnSpy_AsmEditor_Resources.DeleteX, UIUtilities.EscapeMenuItemHeader(nodes[0].ToString())); return string.Format(dnSpy_AsmEditor_Resources.DeleteResourcesCommand, nodes.Length); } static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes.Length > 0 && nodes.All(n => ResourceNode.GetResource(n) is not null); static void Execute(Lazy undoCommandService, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; undoCommandService.Value.Add(new DeleteResourceCommand(nodes)); } struct DeleteModelNodes { ModelInfo[]? infos; readonly struct ModelInfo { public readonly ModuleDef OwnerModule; public readonly int Index; public ModelInfo(ModuleDef module, Resource rsrc) { OwnerModule = module; Index = module.Resources.IndexOf(rsrc); Debug.Assert(Index >= 0); if (Index < 0) throw new InvalidOperationException(); } } public void Delete(DocumentTreeNodeData[] nodes, DocumentTreeNodeData[] parents) { Debug2.Assert(infos is null); if (infos is not null) throw new InvalidOperationException(); infos = new ModelInfo[nodes.Length]; for (int i = 0; i < infos.Length; i++) { var node = nodes[i]; var module = parents[i].GetModule(); Debug2.Assert(module is not null); if (module is null) throw new InvalidOperationException(); var resource = ResourceNode.GetResource(node) ?? throw new InvalidOperationException(); var info = new ModelInfo(module, resource); infos[i] = info; info.OwnerModule.Resources.RemoveAt(info.Index); } } public void Restore(DocumentTreeNodeData[] nodes) { Debug2.Assert(infos is not null); if (infos is null) throw new InvalidOperationException(); Debug.Assert(infos.Length == nodes.Length); if (infos.Length != nodes.Length) throw new InvalidOperationException(); for (int i = infos.Length - 1; i >= 0; i--) { var node = nodes[i]; ref readonly var info = ref infos[i]; var resource = ResourceNode.GetResource(node) ?? throw new InvalidOperationException(); info.OwnerModule.Resources.Insert(info.Index, resource); } infos = null; } } DeletableNodes nodes; DeleteModelNodes modelNodes; DeleteResourceCommand(DocumentTreeNodeData[] rsrcNodes) => nodes = new DeletableNodes(rsrcNodes); public string Description => dnSpy_AsmEditor_Resources.DeleteResourceCommand; public void Execute() { nodes.Delete(); modelNodes.Delete(nodes.Nodes, nodes.Parents!); } public void Undo() { modelNodes.Restore(nodes.Nodes); nodes.Restore(); } public IEnumerable ModifiedObjects => nodes.Nodes; } [DebuggerDisplay("{Description}")] sealed class DeleteResourceElementCommand : IUndoCommand { [ExportMenuItem(Header = "res:DeleteResourceCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_DELETE, Order = 90)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService) => this.undoCommandService = undoCommandService; public override bool IsVisible(AsmEditorContext context) => DeleteResourceElementCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => DeleteResourceElementCommand.Execute(undoCommandService, context.Nodes); public override string? GetHeader(AsmEditorContext context) => DeleteResourceElementCommand.GetHeader(context.Nodes); } [Export, ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:DeleteResourceCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_DELETE, Order = 90)] internal sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IDocumentTreeView documentTreeView) : base(documentTreeView) => this.undoCommandService = undoCommandService; public override bool IsVisible(AsmEditorContext context) => DeleteResourceElementCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => DeleteResourceElementCommand.Execute(undoCommandService, context.Nodes); public override string? GetHeader(AsmEditorContext context) => DeleteResourceElementCommand.GetHeader(context.Nodes); } [Export, ExportMenuItem(Header = "res:DeleteResourceCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_DELETE, Order = 90)] internal sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IDocumentTreeView documentTreeView) : base(documentTreeView) => this.undoCommandService = undoCommandService; public override bool IsEnabled(CodeContext context) => context.IsDefinition && DeleteResourceElementCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => DeleteResourceElementCommand.Execute(undoCommandService, context.Nodes); public override string? GetHeader(CodeContext context) => DeleteResourceElementCommand.GetHeader(context.Nodes); } static string GetHeader(DocumentTreeNodeData[] nodes) { if (nodes.Length == 1) return string.Format(dnSpy_AsmEditor_Resources.DeleteX, UIUtilities.EscapeMenuItemHeader(nodes[0].ToString())); return string.Format(dnSpy_AsmEditor_Resources.DeleteResourcesCommand, nodes.Length); } static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes.Length > 0 && nodes.All(n => ResourceElementNode.GetResourceElement(n) is not null); static void Execute(Lazy undoCommandService, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; undoCommandService.Value.Add(new DeleteResourceElementCommand(nodes)); } sealed class ModuleInfo { public readonly ResourceElementSetNode Node; readonly ModuleDef Module; readonly int Index; readonly Resource Resource; Resource? NewResource; public ModuleInfo(ResourceElementSetNode node) { Node = node ?? throw new InvalidOperationException(); var module = node.GetModule(); Debug2.Assert(module is not null); Module = module ?? throw new InvalidOperationException(); Index = module.Resources.IndexOf(node.Resource); Debug.Assert(Index >= 0); if (Index < 0) throw new InvalidOperationException(); Resource = node.Resource; } public void Replace() { Debug.Assert(Index < Module.Resources.Count && Module.Resources[Index] == Node.Resource); if (NewResource is null) { Node.RegenerateEmbeddedResource(); NewResource = Node.Resource; } else Node.Resource = NewResource; Module.Resources[Index] = NewResource; } public void Restore() { Debug.Assert(Index < Module.Resources.Count && Module.Resources[Index] == Node.Resource); Node.Resource = Resource; Module.Resources[Index] = Resource; } } DeletableNodes nodes; readonly List savedResources = new List(); DeleteResourceElementCommand(DocumentTreeNodeData[] rsrcNodes) => nodes = new DeletableNodes(rsrcNodes); public string Description => dnSpy_AsmEditor_Resources.DeleteResourceCommand; public void Execute() { Debug.Assert(savedResources.Count == 0); savedResources.AddRange(nodes.Nodes.Select(a => a.GetAncestorOrSelf()!).Distinct().Select(a => new ModuleInfo(a))); nodes.Delete(); for (int i = 0; i < savedResources.Count; i++) savedResources[i].Replace(); } public void Undo() { Debug.Assert(savedResources.Count > 0); for (int i = savedResources.Count - 1; i >= 0; i--) savedResources[i].Restore(); savedResources.Clear(); nodes.Restore(); } public IEnumerable ModifiedObjects => nodes.Nodes; } abstract class SaveResourcesCommandBase : MenuItemBase { readonly bool useSubDirs; readonly ResourceDataType resourceDataType; protected SaveResourcesCommandBase(bool useSubDirs, ResourceDataType resourceDataType) { this.useSubDirs = useSubDirs; this.resourceDataType = resourceDataType; } protected abstract IResourceDataProvider[]? GetResourceNodes(IMenuItemContext context); IResourceDataProvider[]? Filter(IResourceDataProvider[] nodes) { nodes = nodes.Where(a => a.GetResourceData(resourceDataType).Any()).ToArray(); return nodes.Length == 0 ? null : nodes; } protected IResourceDataProvider[]? CodeGetResourceNodes(IMenuItemContext context) { if (context.CreatorObject.Guid != new Guid(MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID)) return null; var @ref = context.Find(); if (@ref is null) return null; if (!(@ref.Reference is DocumentTreeNodeData node && ResourceDataProviderUtils.GetResourceDataProvider(node) is IResourceDataProvider rsrcNode)) return null; return Filter(new[] { rsrcNode }); } protected IResourceDataProvider[]? FilesGetResourceNodes(IMenuItemContext context) { if (context.CreatorObject.Guid != new Guid(MenuConstants.GUIDOBJ_DOCUMENTS_TREEVIEW_GUID)) return null; var selNodes = context.Find(); if (selNodes is null) return null; if (selNodes.Length == 1 && selNodes[0] is ResourcesFolderNode) { var rlist = (ResourcesFolderNode)selNodes[0]; rlist.TreeNode.EnsureChildrenLoaded(); selNodes = rlist.TreeNode.DataChildren.Cast().ToArray(); } return Filter(selNodes.Select(a => ResourceDataProviderUtils.GetResourceDataProvider((DocumentTreeNodeData)a)).OfType().ToArray()); } protected ResourceData[] GetResourceData(IResourceDataProvider[]? nodes) => SaveResources.GetResourceData(nodes, resourceDataType); public override bool IsVisible(IMenuItemContext context) => GetResourceNodes(context) is not null; public override void Execute(IMenuItemContext context) => SaveResources.Save(GetResourceNodes(context), useSubDirs, resourceDataType); } static class SaveResourcesCommand { [ExportMenuItem(Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_SAVE, Order = 0)] sealed class CodeCommand : SaveResourcesCommandBase { public CodeCommand() : base(false, ResourceDataType.Deserialized) { } public override string? GetHeader(IMenuItemContext context) => GetHeaderInternal(GetResourceData(GetResourceNodes(context))); protected override IResourceDataProvider[]? GetResourceNodes(IMenuItemContext context) => CodeGetResourceNodes(context); } [ExportMenuItem(Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_SAVE, Order = 0)] sealed class DocumentsCommand : SaveResourcesCommandBase { public DocumentsCommand() : base(false, ResourceDataType.Deserialized) { } public override string? GetHeader(IMenuItemContext context) => GetHeaderInternal(GetResourceData(GetResourceNodes(context))); protected override IResourceDataProvider[]? GetResourceNodes(IMenuItemContext context) => FilesGetResourceNodes(context); } static string GetHeaderInternal(ResourceData[] infos) { if (infos.Length == 1) return string.Format(dnSpy_AsmEditor_Resources.SaveResourceCommand, UIUtilities.EscapeMenuItemHeader(infos[0].Name)); return string.Format(dnSpy_AsmEditor_Resources.SaveResourcesCommand, infos.Length); } } static class SaveWithPathResourcesCommand { [ExportMenuItem(Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_SAVE, Order = 10)] sealed class CodeCommand : SaveResourcesCommandBase { public CodeCommand() : base(true, ResourceDataType.Deserialized) { } public override string? GetHeader(IMenuItemContext context) => GetHeaderInternal(GetResourceData(GetResourceNodes(context))); protected override IResourceDataProvider[]? GetResourceNodes(IMenuItemContext context) => CodeGetResourceNodes(context); public override bool IsVisible(IMenuItemContext context) => IsVisibleInternal(GetResourceData(GetResourceNodes(context))); } [ExportMenuItem(Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_SAVE, Order = 10)] sealed class DocumentsCommand : SaveResourcesCommandBase { public DocumentsCommand() : base(true, ResourceDataType.Deserialized) { } public override string? GetHeader(IMenuItemContext context) => GetHeaderInternal(GetResourceData(GetResourceNodes(context))); protected override IResourceDataProvider[]? GetResourceNodes(IMenuItemContext context) => FilesGetResourceNodes(context); public override bool IsVisible(IMenuItemContext context) => IsVisibleInternal(GetResourceData(GetResourceNodes(context))); } static string GetHeaderInternal(ResourceData[] infos) => string.Format(dnSpy_AsmEditor_Resources.SaveResourcesSubDirectoriesCommand, infos.Length); internal static bool IsVisibleInternal(ResourceData[] infos) => infos.Length > 1 && infos.Any(a => a.Name.Contains('/') || a.Name.Contains('\\')); } static class SaveRawResourcesCommand { [ExportMenuItem(Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_SAVE, Order = 20)] sealed class CodeCommand : SaveResourcesCommandBase { public CodeCommand() : base(false, ResourceDataType.Serialized) { } public override string? GetHeader(IMenuItemContext context) => GetHeaderInternal(GetResourceData(GetResourceNodes(context))); protected override IResourceDataProvider[]? GetResourceNodes(IMenuItemContext context) => CodeGetResourceNodes(context); } [ExportMenuItem(Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_SAVE, Order = 20)] sealed class DocumentsCommand : SaveResourcesCommandBase { public DocumentsCommand() : base(false, ResourceDataType.Serialized) { } public override string? GetHeader(IMenuItemContext context) => GetHeaderInternal(GetResourceData(GetResourceNodes(context))); protected override IResourceDataProvider[]? GetResourceNodes(IMenuItemContext context) => FilesGetResourceNodes(context); } static string GetHeaderInternal(ResourceData[] infos) { if (infos.Length == 1) return string.Format(dnSpy_AsmEditor_Resources.RawSaveResourceCommand, UIUtilities.EscapeMenuItemHeader(infos[0].Name)); return string.Format(dnSpy_AsmEditor_Resources.RawSaveResourcesCommand, infos.Length); } } static class SaveRawWithPathResourcesCommand { [ExportMenuItem(Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_SAVE, Order = 30)] sealed class CodeCommand : SaveResourcesCommandBase { public CodeCommand() : base(true, ResourceDataType.Serialized) { } public override string? GetHeader(IMenuItemContext context) => GetHeaderInternal(GetResourceData(GetResourceNodes(context))); protected override IResourceDataProvider[]? GetResourceNodes(IMenuItemContext context) => CodeGetResourceNodes(context); public override bool IsVisible(IMenuItemContext context) => IsVisibleInternal(GetResourceData(GetResourceNodes(context))); } [ExportMenuItem(Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_SAVE, Order = 30)] sealed class DocumentsCommand : SaveResourcesCommandBase { public DocumentsCommand() : base(true, ResourceDataType.Serialized) { } public override string? GetHeader(IMenuItemContext context) => GetHeaderInternal(GetResourceData(GetResourceNodes(context))); protected override IResourceDataProvider[]? GetResourceNodes(IMenuItemContext context) => FilesGetResourceNodes(context); public override bool IsVisible(IMenuItemContext context) => IsVisibleInternal(GetResourceData(GetResourceNodes(context))); } static string GetHeaderInternal(ResourceData[] infos) => string.Format(dnSpy_AsmEditor_Resources.RawSaveResourcesSubDirectoriesCommand, infos.Length); static bool IsVisibleInternal(ResourceData[] infos) => SaveWithPathResourcesCommand.IsVisibleInternal(infos); } static class ResUtils { public static bool CanExecuteResourceListCommand(DocumentTreeNodeData[] nodes) => GetResourceListTreeNode(nodes) is not null; public static ResourcesFolderNode? GetResourceListTreeNode(DocumentTreeNodeData[] nodes) { if (nodes.Length != 1) return null; ResourcesFolderNode? rsrcListNode; if ((rsrcListNode = nodes[0] as ResourcesFolderNode) is not null) return rsrcListNode; rsrcListNode = nodes[0].TreeNode.Parent?.Data as ResourcesFolderNode; if (rsrcListNode is not null) return rsrcListNode; var modNode = nodes[0] as ModuleDocumentNode; if (modNode is null) return null; modNode.TreeNode.EnsureChildrenLoaded(); rsrcListNode = (ResourcesFolderNode?)modNode.TreeNode.DataChildren.FirstOrDefault(a => a is ResourcesFolderNode); if (rsrcListNode is null) // If not a module node return null; rsrcListNode.TreeNode.EnsureChildrenLoaded(); if (rsrcListNode.TreeNode.Children.Count == 0) return rsrcListNode; return null; } } [DebuggerDisplay("{Description}")] sealed class CreateFileResourceCommand : IUndoCommand { [ExportMenuItem(Header = "res:CreateFileResourceCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_NEW, Order = 100)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateFileResourceCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateFileResourceCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:CreateFileResourceCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_NEW, Order = 100)] sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateFileResourceCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateFileResourceCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(Header = "res:CreateFileResourceCommand", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_NEW, Order = 100)] sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsEnabled(CodeContext context) => context.IsDefinition && CreateFileResourceCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => CreateFileResourceCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => ResUtils.CanExecuteResourceListCommand(nodes); static void Execute(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; var rsrcListNode = ResUtils.GetResourceListTreeNode(nodes)!; var module = rsrcListNode.GetModule(); Debug2.Assert(module is not null); if (module is null) throw new InvalidOperationException(); var dlg = new WF.OpenFileDialog { RestoreDirectory = true, Multiselect = true, }; if (dlg.ShowDialog() != WF.DialogResult.OK) return; var fnames = dlg.FileNames; if (fnames.Length == 0) return; var newNodes = new NodeAndResource[fnames.Length]; var treeView = appService.DocumentTreeView.TreeView; var treeNodeGroup = appService.DocumentTreeView.DocumentTreeNodeGroups.GetGroup(DocumentTreeNodeGroupType.ResourceTreeNodeGroup); for (int i = 0; i < fnames.Length; i++) { var fn = fnames[i]; try { var rsrc = new EmbeddedResource(Path.GetFileName(fn), File.ReadAllBytes(fn), ManifestResourceAttributes.Public); var node = (DocumentTreeNodeData)treeView.Create(resourceNodeFactory.Create(module, rsrc, treeNodeGroup)).Data; newNodes[i] = new NodeAndResource(node); } catch (Exception ex) { MsgBox.Instance.Show(string.Format(dnSpy_AsmEditor_Resources.Error_ReadingFiles, ex.Message)); return; } } undoCommandService.Value.Add(new CreateFileResourceCommand(rsrcListNode, newNodes)); appService.DocumentTabService.FollowReference(newNodes[0].Node); } readonly ModuleDef module; readonly ResourcesFolderNode rsrcListNode; readonly NodeAndResource[] nodes; CreateFileResourceCommand(ResourcesFolderNode rsrcListNode, NodeAndResource[] nodes) { module = rsrcListNode.GetModule()!; Debug2.Assert(module is not null); this.rsrcListNode = rsrcListNode; this.nodes = nodes; } public string Description => dnSpy_AsmEditor_Resources.CreateFileResourceCommand2; public void Execute() { foreach (var info in nodes) { module.Resources.Add(info.Resource); rsrcListNode.TreeNode.AddChild(info.Node.TreeNode); } } public void Undo() { for (int i = nodes.Length - 1; i >= 0; i--) { var info = nodes[i]; bool b = rsrcListNode.TreeNode.Children.Remove(info.Node.TreeNode); Debug.Assert(b); if (!b) throw new InvalidOperationException(); b = module.Resources.Remove(info.Resource); Debug.Assert(b); if (!b) throw new InvalidOperationException(); } } public IEnumerable ModifiedObjects { get { yield return rsrcListNode; } } } [DebuggerDisplay("{Description}")] abstract class CreateResourceTreeNodeCommand : IUndoCommand { readonly ModuleDef module; readonly ResourcesFolderNode rsrcListNode; readonly NodeAndResource resTreeNode; protected CreateResourceTreeNodeCommand(ResourcesFolderNode rsrcListNode, NodeAndResource resTreeNode) { module = rsrcListNode.GetModule()!; Debug2.Assert(module is not null); this.rsrcListNode = rsrcListNode; this.resTreeNode = resTreeNode; } public abstract string Description { get; } public void Execute() { module.Resources.Add(resTreeNode.Resource); rsrcListNode.TreeNode.AddChild(resTreeNode.Node.TreeNode); } public void Undo() { bool b = rsrcListNode.TreeNode.Children.Remove(resTreeNode.Node.TreeNode); Debug.Assert(b); if (!b) throw new InvalidOperationException(); b = module.Resources.Remove(resTreeNode.Resource); Debug.Assert(b); if (!b) throw new InvalidOperationException(); } public IEnumerable ModifiedObjects { get { yield return rsrcListNode; } } } sealed class CreateMultiFileResourceCommand : CreateResourceTreeNodeCommand { [ExportMenuItem(Header = "res:CreateMultiFileResourceCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_NEW, Order = 110)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateMultiFileResourceCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateMultiFileResourceCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:CreateMultiFileResourceCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_NEW, Order = 110)] sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateMultiFileResourceCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateMultiFileResourceCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(Header = "res:CreateMultiFileResourceCommand", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_NEW, Order = 110)] sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsEnabled(CodeContext context) => context.IsDefinition && CreateMultiFileResourceCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => CreateMultiFileResourceCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => ResUtils.CanExecuteResourceListCommand(nodes); static void Execute(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; var rsrcListNode = ResUtils.GetResourceListTreeNode(nodes)!; var module = rsrcListNode.GetModule(); Debug2.Assert(module is not null); if (module is null) throw new InvalidOperationException(); var options = new ResourceOptions { ResourceType = ResourceType.Embedded, Name = "my.resources", Attributes = ManifestResourceAttributes.Public, }; var data = new ResourceVM(options, module); var win = new ResourceDlg(); win.Title = dnSpy_AsmEditor_Resources.CreateMultiFileResourceCommand2; win.DataContext = data; win.Owner = appService.MainWindow; if (win.ShowDialog() != true) return; var outStream = new MemoryStream(); ResourceWriter.Write(module, outStream, new ResourceElementSet()); var er = new EmbeddedResource(data.Name, outStream.ToArray(), data.Attributes); var treeView = appService.DocumentTreeView.TreeView; var treeNodeGroup = appService.DocumentTreeView.DocumentTreeNodeGroups.GetGroup(DocumentTreeNodeGroupType.ResourceTreeNodeGroup); var node = (DocumentTreeNodeData)treeView.Create(resourceNodeFactory.Create(module, er, treeNodeGroup)).Data; undoCommandService.Value.Add(new CreateMultiFileResourceCommand(rsrcListNode, new NodeAndResource(node))); appService.DocumentTabService.FollowReference(node); } CreateMultiFileResourceCommand(ResourcesFolderNode rsrcListNode, NodeAndResource resTreeNode) : base(rsrcListNode, resTreeNode) { } public override string Description => dnSpy_AsmEditor_Resources.CreateMultiFileResourceCommand2; } sealed class CreateAssemblyLinkedResourceCommand : CreateResourceTreeNodeCommand { [ExportMenuItem(Header = "res:CreateAssemblyLinkedResourceCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_NEW, Order = 120)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateAssemblyLinkedResourceCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateAssemblyLinkedResourceCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:CreateAssemblyLinkedResourceCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_NEW, Order = 120)] sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateAssemblyLinkedResourceCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateAssemblyLinkedResourceCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(Header = "res:CreateAssemblyLinkedResourceCommand", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_NEW, Order = 120)] sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsEnabled(CodeContext context) => context.IsDefinition && CreateAssemblyLinkedResourceCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => CreateAssemblyLinkedResourceCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => ResUtils.CanExecuteResourceListCommand(nodes); static void Execute(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; var rsrcListNode = ResUtils.GetResourceListTreeNode(nodes)!; var module = rsrcListNode.GetModule(); Debug2.Assert(module is not null); if (module is null) throw new InvalidOperationException(); var options = new ResourceOptions { ResourceType = ResourceType.AssemblyLinked, Name = "asmlinked", Attributes = ManifestResourceAttributes.Public, Assembly = module.CorLibTypes.AssemblyRef, }; var data = new ResourceVM(options, module); var win = new ResourceDlg(); win.Title = dnSpy_AsmEditor_Resources.CreateAssemblyLinkedResourceCommand2; win.DataContext = data; win.Owner = appService.MainWindow; if (win.ShowDialog() != true) return; var treeView = appService.DocumentTreeView.TreeView; var treeNodeGroup = appService.DocumentTreeView.DocumentTreeNodeGroups.GetGroup(DocumentTreeNodeGroupType.ResourceTreeNodeGroup); var node = (DocumentTreeNodeData)treeView.Create(resourceNodeFactory.Create(module, new AssemblyLinkedResource(data.Name, data.Assembly, data.Attributes), treeNodeGroup)).Data; undoCommandService.Value.Add(new CreateAssemblyLinkedResourceCommand(rsrcListNode, new NodeAndResource(node))); appService.DocumentTabService.FollowReference(node); } CreateAssemblyLinkedResourceCommand(ResourcesFolderNode rsrcListNode, NodeAndResource resTreeNode) : base(rsrcListNode, resTreeNode) { } public override string Description => dnSpy_AsmEditor_Resources.CreateAssemblyLinkedResourceCommand2; } sealed class CreateFileLinkedResourceCommand : CreateResourceTreeNodeCommand { [ExportMenuItem(Header = "res:CreateFileLinkedResourceCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_NEW, Order = 130)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateFileLinkedResourceCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateFileLinkedResourceCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:CreateFileLinkedResourceCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_NEW, Order = 130)] sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateFileLinkedResourceCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateFileLinkedResourceCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(Header = "res:CreateFileLinkedResourceCommand", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_NEW, Order = 130)] sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsEnabled(CodeContext context) => context.IsDefinition && CreateFileLinkedResourceCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => CreateFileLinkedResourceCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => ResUtils.CanExecuteResourceListCommand(nodes); static void Execute(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; var rsrcListNode = ResUtils.GetResourceListTreeNode(nodes)!; var module = rsrcListNode.GetModule(); Debug2.Assert(module is not null); if (module is null) throw new InvalidOperationException(); var options = new ResourceOptions { ResourceType = ResourceType.Linked, Name = "filelinked", Attributes = ManifestResourceAttributes.Public, File = new FileDefUser("somefile", dnlib.DotNet.FileAttributes.ContainsNoMetadata, Array.Empty()), }; var data = new ResourceVM(options, module); var win = new ResourceDlg(); win.Title = dnSpy_AsmEditor_Resources.CreateFileLinkedResourceCommand2; win.DataContext = data; win.Owner = appService.MainWindow; if (win.ShowDialog() != true) return; var opts = data.CreateResourceOptions(); var treeView = appService.DocumentTreeView.TreeView; var treeNodeGroup = appService.DocumentTreeView.DocumentTreeNodeGroups.GetGroup(DocumentTreeNodeGroupType.ResourceTreeNodeGroup); var node = (DocumentTreeNodeData)treeView.Create(resourceNodeFactory.Create(module, new LinkedResource(opts.Name, opts.File, opts.Attributes), treeNodeGroup)).Data; undoCommandService.Value.Add(new CreateFileLinkedResourceCommand(rsrcListNode, new NodeAndResource(node))); appService.DocumentTabService.FollowReference(node); } CreateFileLinkedResourceCommand(ResourcesFolderNode rsrcListNode, NodeAndResource resTreeNode) : base(rsrcListNode, resTreeNode) { } public override string Description => dnSpy_AsmEditor_Resources.CreateFileLinkedResourceCommand2; } [DebuggerDisplay("{Description}")] sealed class ResourceSettingsCommand : IUndoCommand { [ExportMenuItem(Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_SETTINGS, Order = 80)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService) { this.undoCommandService = undoCommandService; this.appService = appService; } public override bool IsVisible(AsmEditorContext context) => ResourceSettingsCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => ResourceSettingsCommand.Execute(undoCommandService, appService, context.Nodes); } [Export, ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_SETTINGS, Order = 90)] internal sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; } public override bool IsVisible(AsmEditorContext context) => ResourceSettingsCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => ResourceSettingsCommand.Execute(undoCommandService, appService, context.Nodes); } [Export, ExportMenuItem(Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_SETTINGS, Order = 90)] internal sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; } public override bool IsEnabled(CodeContext context) => ResourceSettingsCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => ResourceSettingsCommand.Execute(undoCommandService, appService, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes.Length == 1 && ResourceNode.GetResource(nodes[0]) is not null; static void Execute(Lazy undoCommandService, IAppService appService, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; var rsrcNode = new NodeAndResource(nodes[0]); var module = nodes[0].GetParentModule(); Debug2.Assert(module is not null); if (module is null) throw new InvalidOperationException(); var data = new ResourceVM(new ResourceOptions(rsrcNode.Resource), module); var win = new ResourceDlg(); win.DataContext = data; win.Owner = appService.MainWindow; if (win.ShowDialog() != true) return; undoCommandService.Value.Add(new ResourceSettingsCommand(rsrcNode, data.CreateResourceOptions())); } readonly NodeAndResource rsrcNode; readonly ResourceOptions newOptions; readonly ResourceOptions origOptions; readonly DocumentTreeNodeData origParentNode; readonly int origParentChildIndex; readonly bool nameChanged; ResourceSettingsCommand(NodeAndResource rsrcNode, ResourceOptions options) { this.rsrcNode = rsrcNode; newOptions = options; origOptions = new ResourceOptions(rsrcNode.Resource); origParentNode = (DocumentTreeNodeData)rsrcNode.Node.TreeNode.Parent!.Data; origParentChildIndex = origParentNode.TreeNode.Children.IndexOf(rsrcNode.Node.TreeNode); Debug.Assert(origParentChildIndex >= 0); if (origParentChildIndex < 0) throw new InvalidOperationException(); nameChanged = origOptions.Name != newOptions.Name; } public string Description => dnSpy_AsmEditor_Resources.EditResourceCommand2; public void Execute() { if (nameChanged) { bool b = origParentChildIndex < origParentNode.TreeNode.Children.Count && origParentNode.TreeNode.Children[origParentChildIndex] == rsrcNode.Node.TreeNode; Debug.Assert(b); if (!b) throw new InvalidOperationException(); var isNodeSelected = rsrcNode.Node.TreeNode.TreeView.SelectedItem == rsrcNode.Node; origParentNode.TreeNode.Children.RemoveAt(origParentChildIndex); newOptions.CopyTo(rsrcNode.Resource); origParentNode.TreeNode.AddChild(rsrcNode.Node.TreeNode); if (isNodeSelected) origParentNode.TreeNode.TreeView.SelectItems(new[] { rsrcNode.Node }); } else newOptions.CopyTo(rsrcNode.Resource); rsrcNode.Node.TreeNode.RefreshUI(); } public void Undo() { if (nameChanged) { var isNodeSelected = rsrcNode.Node.TreeNode.TreeView.SelectedItem == rsrcNode.Node; bool b = origParentNode.TreeNode.Children.Remove(rsrcNode.Node.TreeNode); Debug.Assert(b); if (!b) throw new InvalidOperationException(); origOptions.CopyTo(rsrcNode.Resource); origParentNode.TreeNode.Children.Insert(origParentChildIndex, rsrcNode.Node.TreeNode); if (isNodeSelected) origParentNode.TreeNode.TreeView.SelectItems(new[] { rsrcNode.Node }); } else origOptions.CopyTo(rsrcNode.Resource); rsrcNode.Node.TreeNode.RefreshUI(); } public IEnumerable ModifiedObjects { get { yield return rsrcNode.Node; } } } [DebuggerDisplay("{Description}")] abstract class CreateResourceElementCommandBase : IUndoCommand { readonly ModuleDef module; readonly ResourceElementSetNode rsrcSetNode; readonly NodeAndResourceElement[] nodes; readonly Resource resource; readonly int resourceIndex; Resource? newResource; protected CreateResourceElementCommandBase(ResourceElementSetNode rsrcSetNode, NodeAndResourceElement[] nodes) { module = rsrcSetNode.GetModule()!; Debug2.Assert(module is not null); this.rsrcSetNode = rsrcSetNode; this.nodes = nodes; resource = rsrcSetNode.Resource; resourceIndex = module.Resources.IndexOf(resource); Debug.Assert(resourceIndex >= 0); if (resourceIndex < 0) throw new InvalidOperationException(); } public abstract string Description { get; } public void Execute() { Debug.Assert(resource == rsrcSetNode.Resource); Debug.Assert(module.Resources[resourceIndex] == resource); foreach (var node in nodes) rsrcSetNode.TreeNode.AddChild(node.Node.TreeNode); if (newResource is null) { rsrcSetNode.RegenerateEmbeddedResource(); newResource = rsrcSetNode.Resource; } else rsrcSetNode.Resource = newResource; module.Resources[resourceIndex] = newResource; } public void Undo() { for (int i = nodes.Length - 1; i >= 0; i--) { var node = nodes[i]; bool b = rsrcSetNode.TreeNode.Children.Remove(node.Node.TreeNode); Debug.Assert(b); if (!b) throw new InvalidOperationException(); } rsrcSetNode.Resource = resource; module.Resources[resourceIndex] = resource; } public IEnumerable ModifiedObjects { get { yield return rsrcSetNode; } } } sealed class CreateImageResourceElementCommand : CreateResourceElementCommandBase { [ExportMenuItem(Header = "res:CreateBitMapIconResourceCommand", Icon = DsImagesAttribute.NewImage, Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_NEW, Order = 140)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateImageResourceElementCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateImageResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:CreateBitMapIconResourceCommand", Icon = DsImagesAttribute.NewImage, Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_NEW, Order = 140)] sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateImageResourceElementCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateImageResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(Header = "res:CreateBitMapIconResourceCommand", Icon = DsImagesAttribute.NewImage, Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_NEW, Order = 140)] sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsEnabled(CodeContext context) => context.IsDefinition && CreateImageResourceElementCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => CreateImageResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes.Length == 1 && (nodes[0] is ResourceElementSetNode || nodes[0].TreeNode.Parent?.Data is ResourceElementSetNode); static void Execute(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; var rsrcSetNode = nodes[0] as ResourceElementSetNode; if (rsrcSetNode is null) rsrcSetNode = nodes[0].TreeNode.Parent!.Data as ResourceElementSetNode; Debug2.Assert(rsrcSetNode is not null); var module = rsrcSetNode.GetModule(); Debug2.Assert(module is not null); if (module is null) throw new InvalidOperationException(); var dlg = new WF.OpenFileDialog { RestoreDirectory = true, Multiselect = true, Filter = PickFilenameConstants.ImagesFilter, }; if (dlg.ShowDialog() != WF.DialogResult.OK) return; var fnames = dlg.FileNames; if (fnames.Length == 0) return; var newNodes = new List(fnames.Length); var treeView = appService.DocumentTreeView.TreeView; var treeNodeGroup = appService.DocumentTreeView.DocumentTreeNodeGroups.GetGroup(DocumentTreeNodeGroupType.ResourceElementTreeNodeGroup); string? error = null; for (int i = 0; i < fnames.Length; i++) { var fn = fnames[i]; try { var node = (DocumentTreeNodeData)treeView.Create(resourceNodeFactory.Create(module, SerializationUtilities.CreateSerializedImage(fn), treeNodeGroup)).Data; newNodes.Add(new NodeAndResourceElement(node)); } catch (Exception ex) { if (error is null) error = string.Format(dnSpy_AsmEditor_Resources.Error_ReadingFiles, ex.Message); } } if (error is not null) MsgBox.Instance.Show(error); if (newNodes.Count == 0) return; undoCommandService.Value.Add(new CreateImageResourceElementCommand(rsrcSetNode, newNodes.ToArray())); appService.DocumentTabService.FollowReference(newNodes[0].Node); } CreateImageResourceElementCommand(ResourceElementSetNode rsrcSetNode, NodeAndResourceElement[] nodes) : base(rsrcSetNode, nodes) { } public override string Description => dnSpy_AsmEditor_Resources.CreateBitMapIconResourceCommand2; } sealed class CreateImageListResourceElementCommand : CreateResourceElementCommandBase { [ExportMenuItem(Header = "res:CreateImageListStreamerResourceCommand", Icon = DsImagesAttribute.NewImage, Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_NEW, Order = 150)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateImageListResourceElementCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateImageListResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:CreateImageListStreamerResourceCommand", Icon = DsImagesAttribute.NewImage, Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_NEW, Order = 150)] sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateImageListResourceElementCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateImageListResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(Header = "res:CreateImageListStreamerResourceCommand", Icon = DsImagesAttribute.NewImage, Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_NEW, Order = 150)] sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsEnabled(CodeContext context) => context.IsDefinition && CreateImageListResourceElementCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => CreateImageListResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes.Length == 1 && (nodes[0] is ResourceElementSetNode || nodes[0].TreeNode.Parent?.Data is ResourceElementSetNode); static void Execute(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; var rsrcSetNode = nodes[0] as ResourceElementSetNode; if (rsrcSetNode is null) rsrcSetNode = nodes[0].TreeNode.Parent!.Data as ResourceElementSetNode; Debug2.Assert(rsrcSetNode is not null); var module = rsrcSetNode.GetModule(); Debug2.Assert(module is not null); if (module is null) throw new InvalidOperationException(); var data = new ImageListVM(new ImageListOptions() { Name = "my.ImageStream" }); var win = new ImageListDlg(); win.Title = dnSpy_AsmEditor_Resources.CreateImageListStreamerResourceCommand2; win.DataContext = data; win.Owner = appService.MainWindow; if (win.ShowDialog() != true) return; if (data.ImageListStreamerVM.Collection.Count == 0) { MsgBox.Instance.Show(dnSpy_AsmEditor_Resources.Error_EmptyImageList); return; } var listOpts = data.CreateImageListOptions(); ResourceElementOptions? opts = null; string error; try { opts = new ResourceElementOptions(SerializedImageListStreamerUtilities.Serialize(listOpts)); error = SerializedImageListStreamerUtilities.CheckCanUpdateData(module, opts.Create()); } catch (Exception ex) { error = string.Format(dnSpy_AsmEditor_Resources.Error_CouldNotSerializeImages, ex.Message); } if (!string.IsNullOrEmpty(error)) { MsgBox.Instance.Show(error); return; } Debug2.Assert(opts is not null); var treeView = appService.DocumentTreeView.TreeView; var treeNodeGroup = appService.DocumentTreeView.DocumentTreeNodeGroups.GetGroup(DocumentTreeNodeGroupType.ResourceElementTreeNodeGroup); var newNode = (DocumentTreeNodeData)treeView.Create(resourceNodeFactory.Create(module, opts.Create(), treeNodeGroup)).Data; undoCommandService.Value.Add(new CreateImageListResourceElementCommand(rsrcSetNode, newNode)); appService.DocumentTabService.FollowReference(newNode); } CreateImageListResourceElementCommand(ResourceElementSetNode rsrcSetNode, DocumentTreeNodeData node) : base(rsrcSetNode, new[] { new NodeAndResourceElement(node) }) { } public override string Description => dnSpy_AsmEditor_Resources.CreateImageListStreamerResourceCommand2; } sealed class CreateByteArrayResourceElementCommand : CreateResourceElementCommandBase { [ExportMenuItem(Header = "res:CreateByteArrayResourceCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_NEW, Order = 160)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateByteArrayResourceElementCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateByteArrayResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:CreateByteArrayResourceCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_NEW, Order = 170)] sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateByteArrayResourceElementCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateByteArrayResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(Header = "res:CreateByteArrayResourceCommand", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_NEW, Order = 170)] sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsEnabled(CodeContext context) => context.IsDefinition && CreateByteArrayResourceElementCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => CreateByteArrayResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes.Length == 1 && (nodes[0] is ResourceElementSetNode || nodes[0].TreeNode.Parent?.Data is ResourceElementSetNode); static void Execute(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; Execute(undoCommandService, appService, resourceNodeFactory, nodes, ResourceTypeCode.ByteArray, (a, b) => new CreateByteArrayResourceElementCommand(a, b)); } internal static void Execute(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory, DocumentTreeNodeData[] nodes, ResourceTypeCode typeCode, Func createCommand) { var rsrcSetNode = nodes[0] as ResourceElementSetNode; if (rsrcSetNode is null) rsrcSetNode = nodes[0].TreeNode.Parent!.Data as ResourceElementSetNode; Debug2.Assert(rsrcSetNode is not null); var module = rsrcSetNode.GetModule(); Debug2.Assert(module is not null); if (module is null) throw new InvalidOperationException(); var dlg = new WF.OpenFileDialog { RestoreDirectory = true, Multiselect = true, Filter = PickFilenameConstants.AnyFilenameFilter, }; if (dlg.ShowDialog() != WF.DialogResult.OK) return; var fnames = dlg.FileNames; if (fnames.Length == 0) return; var newNodes = new NodeAndResourceElement[fnames.Length]; var treeView = appService.DocumentTreeView.TreeView; var treeNodeGroup = appService.DocumentTreeView.DocumentTreeNodeGroups.GetGroup(DocumentTreeNodeGroupType.ResourceElementTreeNodeGroup); for (int i = 0; i < fnames.Length; i++) { var fn = fnames[i]; try { var rsrcElem = new ResourceElement { Name = Path.GetFileName(fn), ResourceData = new BuiltInResourceData(typeCode, File.ReadAllBytes(fn)), }; var node = (DocumentTreeNodeData)treeView.Create(resourceNodeFactory.Create(module, rsrcElem, treeNodeGroup)).Data; newNodes[i] = new NodeAndResourceElement(node); } catch (Exception ex) { MsgBox.Instance.Show(string.Format(dnSpy_AsmEditor_Resources.Error_ReadingFiles, ex.Message)); return; } } undoCommandService.Value.Add(createCommand(rsrcSetNode, newNodes.ToArray())); appService.DocumentTabService.FollowReference(newNodes[0].Node); } CreateByteArrayResourceElementCommand(ResourceElementSetNode rsrcSetNode, NodeAndResourceElement[] nodes) : base(rsrcSetNode, nodes) { } public override string Description => dnSpy_AsmEditor_Resources.CreateByteArrayResourceCommand2; } sealed class CreateStreamResourceElementCommand : CreateResourceElementCommandBase { [ExportMenuItem(Header = "res:CreateStreamResourceCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_NEW, Order = 170)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateStreamResourceElementCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateStreamResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:CreateStreamResourceCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_NEW, Order = 180)] sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsVisible(AsmEditorContext context) => CreateStreamResourceElementCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateStreamResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } [ExportMenuItem(Header = "res:CreateStreamResourceCommand", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_NEW, Order = 180)] sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; } public override bool IsEnabled(CodeContext context) => context.IsDefinition && CreateStreamResourceElementCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => CreateStreamResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes.Length == 1 && (nodes[0] is ResourceElementSetNode || nodes[0].TreeNode.Parent?.Data is ResourceElementSetNode); static void Execute(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; CreateByteArrayResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, nodes, ResourceTypeCode.Stream, (a, b) => new CreateStreamResourceElementCommand(a, b)); } CreateStreamResourceElementCommand(ResourceElementSetNode rsrcSetNode, NodeAndResourceElement[] nodes) : base(rsrcSetNode, nodes) { } public override string Description => dnSpy_AsmEditor_Resources.CreateStreamResourceCommand2; } sealed class CreateResourceElementCommand : CreateResourceElementCommandBase { [ExportMenuItem(Header = "res:CreateResourceCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_NEW, Order = 190)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; readonly IDocumentTreeViewSettings documentTreeViewSettings; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory, IDocumentTreeViewSettings documentTreeViewSettings) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; this.documentTreeViewSettings = documentTreeViewSettings; } public override bool IsVisible(AsmEditorContext context) => CreateResourceElementCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, documentTreeViewSettings, context.Nodes); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:CreateResourceCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_NEW, Order = 190)] sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; readonly IDocumentTreeViewSettings documentTreeViewSettings; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory, IDocumentTreeViewSettings documentTreeViewSettings) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; this.documentTreeViewSettings = documentTreeViewSettings; } public override bool IsVisible(AsmEditorContext context) => CreateResourceElementCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => CreateResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, documentTreeViewSettings, context.Nodes); } [ExportMenuItem(Header = "res:CreateResourceCommand", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_NEW, Order = 190)] sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IResourceNodeFactory resourceNodeFactory; readonly IDocumentTreeViewSettings documentTreeViewSettings; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory, IDocumentTreeViewSettings documentTreeViewSettings) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.resourceNodeFactory = resourceNodeFactory; this.documentTreeViewSettings = documentTreeViewSettings; } public override bool IsEnabled(CodeContext context) => context.IsDefinition && CreateResourceElementCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => CreateResourceElementCommand.Execute(undoCommandService, appService, resourceNodeFactory, documentTreeViewSettings, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes.Length == 1 && (nodes[0] is ResourceElementSetNode || nodes[0].TreeNode.Parent?.Data is ResourceElementSetNode); static void Execute(Lazy undoCommandService, IAppService appService, IResourceNodeFactory resourceNodeFactory, IDocumentTreeViewSettings documentTreeViewSettings, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; var rsrcSetNode = nodes[0] as ResourceElementSetNode; if (rsrcSetNode is null) rsrcSetNode = nodes[0].TreeNode.Parent!.Data as ResourceElementSetNode; Debug2.Assert(rsrcSetNode is not null); var module = rsrcSetNode.GetModule(); Debug2.Assert(module is not null); if (module is null) throw new InvalidOperationException(); var options = new ResourceElementOptions(new ResourceElement { Name = string.Empty, ResourceData = new BuiltInResourceData(ResourceTypeCode.String, string.Empty), }); var data = new ResourceElementVM(options, module, documentTreeViewSettings.DeserializeResources); var win = new ResourceElementDlg(); win.Title = dnSpy_AsmEditor_Resources.CreateResourceCommand2; win.DataContext = data; win.Owner = appService.MainWindow; if (win.ShowDialog() != true) return; var opts = data.CreateResourceElementOptions(); var treeView = appService.DocumentTreeView.TreeView; var treeNodeGroup = appService.DocumentTreeView.DocumentTreeNodeGroups.GetGroup(DocumentTreeNodeGroupType.ResourceElementTreeNodeGroup); var node = (DocumentTreeNodeData)treeView.Create(resourceNodeFactory.Create(module, opts.Create(), treeNodeGroup)).Data; undoCommandService.Value.Add(new CreateResourceElementCommand(rsrcSetNode, node)); appService.DocumentTabService.FollowReference(node); } CreateResourceElementCommand(ResourceElementSetNode rsrcSetNode, DocumentTreeNodeData node) : base(rsrcSetNode, new[] { new NodeAndResourceElement(node) }) { } public override string Description => dnSpy_AsmEditor_Resources.CreateResourceCommand2; } [DebuggerDisplay("{Description}")] abstract class ResourceElementSettingsBaseCommand : IUndoCommand { readonly ModuleDef module; readonly Resource resource; readonly int resourceIndex; Resource? newResource; readonly ResourceElementSetNode rsrcSetNode; readonly ResourceElementNode rsrcElNode; readonly ResourceElement newOptions; readonly ResourceElement origOptions; readonly int origParentChildIndex; readonly bool nameChanged; protected ResourceElementSettingsBaseCommand(ResourceElementNode rsrcElNode, ResourceElementOptions options) { rsrcSetNode = (ResourceElementSetNode)rsrcElNode.TreeNode.Parent!.Data; this.rsrcElNode = rsrcElNode; newOptions = options.Create(); origOptions = rsrcElNode.ResourceElement; module = rsrcSetNode.GetModule()!; Debug2.Assert(module is not null); resource = rsrcSetNode.Resource; resourceIndex = module.Resources.IndexOf(resource); Debug.Assert(resourceIndex >= 0); if (resourceIndex < 0) throw new InvalidOperationException(); origParentChildIndex = rsrcSetNode.TreeNode.Children.IndexOf(rsrcElNode.TreeNode); Debug.Assert(origParentChildIndex >= 0); if (origParentChildIndex < 0) throw new InvalidOperationException(); nameChanged = origOptions.Name != newOptions.Name; } public abstract string Description { get; } public void Execute() { Debug.Assert(resource == rsrcSetNode.Resource); Debug.Assert(module.Resources[resourceIndex] == resource); if (nameChanged) { bool b = origParentChildIndex < rsrcSetNode.TreeNode.Children.Count && rsrcSetNode.TreeNode.Children[origParentChildIndex] == rsrcElNode.TreeNode; Debug.Assert(b); if (!b) throw new InvalidOperationException(); rsrcSetNode.TreeNode.Children.RemoveAt(origParentChildIndex); rsrcElNode.UpdateData(newOptions); rsrcSetNode.TreeNode.AddChild(rsrcElNode.TreeNode); } else rsrcElNode.UpdateData(newOptions); if (newResource is null) { rsrcSetNode.RegenerateEmbeddedResource(); newResource = rsrcSetNode.Resource; } else rsrcSetNode.Resource = newResource; module.Resources[resourceIndex] = newResource; rsrcElNode.TreeNode.RefreshUI(); } public void Undo() { if (nameChanged) { bool b = rsrcSetNode.TreeNode.Children.Remove(rsrcElNode.TreeNode); Debug.Assert(b); if (!b) throw new InvalidOperationException(); rsrcElNode.UpdateData(origOptions); rsrcSetNode.TreeNode.Children.Insert(origParentChildIndex, rsrcElNode.TreeNode); } else rsrcElNode.UpdateData(origOptions); rsrcSetNode.Resource = resource; module.Resources[resourceIndex] = resource; rsrcElNode.TreeNode.RefreshUI(); } public IEnumerable ModifiedObjects { get { yield return rsrcElNode; } } } sealed class ResourceElementSettingsCommand : ResourceElementSettingsBaseCommand { [ExportMenuItem(Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_SETTINGS, Order = 90)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IDocumentTreeViewSettings documentTreeViewSettings; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService, IDocumentTreeViewSettings documentTreeViewSettings) { this.undoCommandService = undoCommandService; this.appService = appService; this.documentTreeViewSettings = documentTreeViewSettings; } public override bool IsVisible(AsmEditorContext context) => ResourceElementSettingsCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => ResourceElementSettingsCommand.Execute(undoCommandService, appService, documentTreeViewSettings, context.Nodes); } [Export, ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_SETTINGS, Order = 100)] internal sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IDocumentTreeViewSettings documentTreeViewSettings; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService, IDocumentTreeViewSettings documentTreeViewSettings) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.documentTreeViewSettings = documentTreeViewSettings; } public override bool IsVisible(AsmEditorContext context) => ResourceElementSettingsCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => ResourceElementSettingsCommand.Execute(undoCommandService, appService, documentTreeViewSettings, context.Nodes); } [Export, ExportMenuItem(Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_SETTINGS, Order = 100)] internal sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; readonly IDocumentTreeViewSettings documentTreeViewSettings; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService, IDocumentTreeViewSettings documentTreeViewSettings) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; this.documentTreeViewSettings = documentTreeViewSettings; } public override bool IsEnabled(CodeContext context) => ResourceElementSettingsCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => ResourceElementSettingsCommand.Execute(undoCommandService, appService, documentTreeViewSettings, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes.Length == 1 && (nodes[0] is BuiltInResourceElementNode || nodes[0] is UnknownSerializedResourceElementNode); static void Execute(Lazy undoCommandService, IAppService appService, IDocumentTreeViewSettings documentTreeViewSettings, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; var rsrcElNode = (ResourceElementNode)nodes[0]; var module = nodes[0].GetParentModule(); Debug2.Assert(module is not null); if (module is null) throw new InvalidOperationException(); var options = new ResourceElementOptions(rsrcElNode.ResourceElement); var data = new ResourceElementVM(options, module, documentTreeViewSettings.DeserializeResources); data.CanChangeType = false; var win = new ResourceElementDlg(); win.Title = dnSpy_AsmEditor_Resources.EditResourceCommand2; win.DataContext = data; win.Owner = appService.MainWindow; if (win.ShowDialog() != true) return; var opts = data.CreateResourceElementOptions(); string? error; try { error = rsrcElNode.CheckCanUpdateData(opts.Create()); } catch (Exception ex) { error = string.Format(dnSpy_AsmEditor_Resources.Error_InvalidResourceData, ex.Message); } if (!string2.IsNullOrEmpty(error)) { MsgBox.Instance.Show(error); return; } undoCommandService.Value.Add(new ResourceElementSettingsCommand(rsrcElNode, opts)); } ResourceElementSettingsCommand(ResourceElementNode rsrcElNode, ResourceElementOptions options) : base(rsrcElNode, options) { } public override string Description => dnSpy_AsmEditor_Resources.EditResourceCommand2; } sealed class ImageResourceElementSettingsCommand : ResourceElementSettingsBaseCommand { [ExportMenuItem(Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_SETTINGS, Order = 100)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService) { this.undoCommandService = undoCommandService; this.appService = appService; } public override bool IsVisible(AsmEditorContext context) => ImageResourceElementSettingsCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => ImageResourceElementSettingsCommand.Execute(undoCommandService, appService, context.Nodes); } [Export, ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_SETTINGS, Order = 110)] internal sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; } public override bool IsVisible(AsmEditorContext context) => ImageResourceElementSettingsCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => ImageResourceElementSettingsCommand.Execute(undoCommandService, appService, context.Nodes); } [Export, ExportMenuItem(Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_SETTINGS, Order = 110)] internal sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; } public override bool IsEnabled(CodeContext context) => ImageResourceElementSettingsCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => ImageResourceElementSettingsCommand.Execute(undoCommandService, appService, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes.Length == 1 && nodes[0] is ImageResourceElementNode; static void Execute(Lazy undoCommandService, IAppService appService, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; var imgRsrcElNode = (ImageResourceElementNode)nodes[0]; var options = new ResourceElementOptions(imgRsrcElNode.ResourceElement); var data = new ImageResourceElementVM(options); var win = new ImageResourceElementDlg(); win.Title = dnSpy_AsmEditor_Resources.EditResourceCommand2; win.DataContext = data; win.Owner = appService.MainWindow; if (win.ShowDialog() != true) return; var opts = data.CreateResourceElementOptions(); string? error; try { error = imgRsrcElNode.CheckCanUpdateData(opts.Create()); } catch (Exception ex) { error = string.Format(dnSpy_AsmEditor_Resources.Error_NewResourceDataMustBeImage, ex.Message); } if (!string2.IsNullOrEmpty(error)) { MsgBox.Instance.Show(error); return; } undoCommandService.Value.Add(new ImageResourceElementSettingsCommand(imgRsrcElNode, opts)); } ImageResourceElementSettingsCommand(ResourceElementNode rsrcElNode, ResourceElementOptions options) : base(rsrcElNode, options) { } public override string Description => dnSpy_AsmEditor_Resources.EditResourceCommand2; } sealed class SerializedImageResourceElementSettingsCommand : ResourceElementSettingsBaseCommand { [ExportMenuItem(Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_SETTINGS, Order = 110)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService) { this.undoCommandService = undoCommandService; this.appService = appService; } public override bool IsVisible(AsmEditorContext context) => SerializedImageResourceElementSettingsCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => SerializedImageResourceElementSettingsCommand.Execute(undoCommandService, appService, context.Nodes); } [Export, ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_SETTINGS, Order = 120)] internal sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; } public override bool IsVisible(AsmEditorContext context) => SerializedImageResourceElementSettingsCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => SerializedImageResourceElementSettingsCommand.Execute(undoCommandService, appService, context.Nodes); } [Export, ExportMenuItem(Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_SETTINGS, Order = 120)] internal sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; } public override bool IsEnabled(CodeContext context) => SerializedImageResourceElementSettingsCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => SerializedImageResourceElementSettingsCommand.Execute(undoCommandService, appService, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes.Length == 1 && nodes[0] is SerializedImageResourceElementNode; static void Execute(Lazy undoCommandService, IAppService appService, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; var imgRsrcElNode = (SerializedImageResourceElementNode)nodes[0]; var options = new ResourceElementOptions(imgRsrcElNode.GetAsRawImage()); var data = new ImageResourceElementVM(options); var win = new ImageResourceElementDlg(); win.Title = dnSpy_AsmEditor_Resources.EditResourceCommand2; win.DataContext = data; win.Owner = appService.MainWindow; if (win.ShowDialog() != true) return; var opts = data.CreateResourceElementOptions(); string? error; try { opts = new ResourceElementOptions(SerializedImageUtilities.Serialize(opts.Create())); error = imgRsrcElNode.CheckCanUpdateData(opts.Create()); } catch (Exception ex) { error = string.Format(dnSpy_AsmEditor_Resources.Error_NewResourceDataMustBeImage, ex.Message); } if (!string2.IsNullOrEmpty(error)) { MsgBox.Instance.Show(error); return; } undoCommandService.Value.Add(new SerializedImageResourceElementSettingsCommand(imgRsrcElNode, opts)); } SerializedImageResourceElementSettingsCommand(ResourceElementNode rsrcElNode, ResourceElementOptions options) : base(rsrcElNode, options) { } public override string Description => dnSpy_AsmEditor_Resources.EditResourceCommand2; } sealed class SerializedImageListStreamerResourceElementSettingsCommand : ResourceElementSettingsBaseCommand { [ExportMenuItem(Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_SETTINGS, Order = 120)] sealed class DocumentsCommand : DocumentsContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; [ImportingConstructor] DocumentsCommand(Lazy undoCommandService, IAppService appService) { this.undoCommandService = undoCommandService; this.appService = appService; } public override bool IsVisible(AsmEditorContext context) => SerializedImageListStreamerResourceElementSettingsCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => SerializedImageListStreamerResourceElementSettingsCommand.Execute(undoCommandService, appService, context.Nodes); } [Export, ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_SETTINGS, Order = 130)] internal sealed class EditMenuCommand : EditMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; [ImportingConstructor] EditMenuCommand(Lazy undoCommandService, IAppService appService) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; } public override bool IsVisible(AsmEditorContext context) => SerializedImageListStreamerResourceElementSettingsCommand.CanExecute(context.Nodes); public override void Execute(AsmEditorContext context) => SerializedImageListStreamerResourceElementSettingsCommand.Execute(undoCommandService, appService, context.Nodes); } [Export, ExportMenuItem(Header = "res:EditResourceCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_CTX_DOCVIEWER_ASMED_SETTINGS, Order = 130)] internal sealed class CodeCommand : CodeContextMenuHandler { readonly Lazy undoCommandService; readonly IAppService appService; [ImportingConstructor] CodeCommand(Lazy undoCommandService, IAppService appService) : base(appService.DocumentTreeView) { this.undoCommandService = undoCommandService; this.appService = appService; } public override bool IsEnabled(CodeContext context) => SerializedImageListStreamerResourceElementSettingsCommand.CanExecute(context.Nodes); public override void Execute(CodeContext context) => SerializedImageListStreamerResourceElementSettingsCommand.Execute(undoCommandService, appService, context.Nodes); } static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes.Length == 1 && nodes[0] is SerializedImageListStreamerResourceElementNode; static void Execute(Lazy undoCommandService, IAppService appService, DocumentTreeNodeData[] nodes) { if (!CanExecute(nodes)) return; var imgNode = (SerializedImageListStreamerResourceElementNode)nodes[0]; var options = new ImageListOptions(imgNode.ImageListOptions); var data = new ImageListVM(options); var win = new ImageListDlg(); win.Title = dnSpy_AsmEditor_Resources.EditResourceCommand2; win.DataContext = data; win.Owner = appService.MainWindow; if (win.ShowDialog() != true) return; var listOpts = data.CreateImageListOptions(); if (listOpts.ImageSources.Count == 0) { MsgBox.Instance.Show(dnSpy_AsmEditor_Resources.Error_EmptyImageList); return; } ResourceElementOptions? opts = null; string? error; try { opts = new ResourceElementOptions(SerializedImageListStreamerUtilities.Serialize(listOpts)); error = imgNode.CheckCanUpdateData(opts.Create()); } catch (Exception ex) { error = string.Format(dnSpy_AsmEditor_Resources.Error_CouldNotSerializeImages, ex.Message); } if (!string2.IsNullOrEmpty(error)) { MsgBox.Instance.Show(error); return; } undoCommandService.Value.Add(new SerializedImageListStreamerResourceElementSettingsCommand(imgNode, opts!)); } SerializedImageListStreamerResourceElementSettingsCommand(ResourceElementNode rsrcElNode, ResourceElementOptions options) : base(rsrcElNode, options) { } public override string Description => dnSpy_AsmEditor_Resources.EditResourceCommand2; } }