780 lines
33 KiB
C#
780 lines
33 KiB
C#
/*
|
|
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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel.Composition;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using dnlib.DotNet;
|
|
using dnlib.PE;
|
|
using dnSpy.AsmEditor.Commands;
|
|
using dnSpy.AsmEditor.Properties;
|
|
using dnSpy.AsmEditor.SaveModule;
|
|
using dnSpy.AsmEditor.UndoRedo;
|
|
using dnSpy.Contracts.App;
|
|
using dnSpy.Contracts.Controls;
|
|
using dnSpy.Contracts.Documents;
|
|
using dnSpy.Contracts.Documents.Tabs;
|
|
using dnSpy.Contracts.Documents.TreeView;
|
|
using dnSpy.Contracts.Extension;
|
|
using dnSpy.Contracts.Images;
|
|
using dnSpy.Contracts.Menus;
|
|
using dnSpy.Contracts.MVVM;
|
|
|
|
namespace dnSpy.AsmEditor.Module {
|
|
[ExportAutoLoaded]
|
|
sealed class CommandLoader : IAutoLoaded {
|
|
[ImportingConstructor]
|
|
CommandLoader(IWpfCommandService wpfCommandService, IDocumentTabService documentTabService, RemoveNetModuleFromAssemblyCommand.EditMenuCommand removeCmd, ModuleSettingsCommand.EditMenuCommand settingsCmd) {
|
|
wpfCommandService.AddRemoveCommand(removeCmd);
|
|
wpfCommandService.AddSettingsCommand(documentTabService, settingsCmd, null);
|
|
}
|
|
}
|
|
|
|
[DebuggerDisplay("{Description}")]
|
|
sealed class CreateNetModuleCommand : IUndoCommand {
|
|
[ExportMenuItem(Header = "res:CreateNetModuleCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_NEW, Order = 30)]
|
|
sealed class DocumentsCommand : DocumentsContextMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
readonly IAppService appService;
|
|
|
|
[ImportingConstructor]
|
|
DocumentsCommand(Lazy<IUndoCommandService> undoCommandService, IAppService appService) {
|
|
this.undoCommandService = undoCommandService;
|
|
this.appService = appService;
|
|
}
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => CreateNetModuleCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => CreateNetModuleCommand.Execute(undoCommandService, appService, context.Nodes);
|
|
}
|
|
|
|
[ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:CreateNetModuleCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_NEW, Order = 30)]
|
|
sealed class EditMenuCommand : EditMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
readonly IAppService appService;
|
|
|
|
[ImportingConstructor]
|
|
EditMenuCommand(Lazy<IUndoCommandService> undoCommandService, IAppService appService)
|
|
: base(appService.DocumentTreeView) {
|
|
this.undoCommandService = undoCommandService;
|
|
this.appService = appService;
|
|
}
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => CreateNetModuleCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => CreateNetModuleCommand.Execute(undoCommandService, appService, context.Nodes);
|
|
}
|
|
|
|
static bool CanExecute(DocumentTreeNodeData[] nodes) => nodes is not null &&
|
|
(nodes.Length == 0 || nodes.Any(a => a is DsDocumentNode));
|
|
|
|
static void Execute(Lazy<IUndoCommandService> undoCommandService, IAppService appService, DocumentTreeNodeData[] nodes) {
|
|
if (!CanExecute(nodes))
|
|
return;
|
|
|
|
var win = new NetModuleOptionsDlg();
|
|
var data = new NetModuleOptionsVM();
|
|
win.DataContext = data;
|
|
win.Owner = appService.MainWindow;
|
|
if (win.ShowDialog() != true)
|
|
return;
|
|
|
|
var cmd = new CreateNetModuleCommand(undoCommandService, appService.DocumentTreeView, data.CreateNetModuleOptions());
|
|
undoCommandService.Value.Add(cmd);
|
|
appService.DocumentTabService.FollowReference(cmd.fileNodeCreator.DocumentNode);
|
|
}
|
|
|
|
readonly RootDocumentNodeCreator fileNodeCreator;
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
|
|
CreateNetModuleCommand(Lazy<IUndoCommandService> undoCommandService, IDocumentTreeView documentTreeView, NetModuleOptions options) {
|
|
this.undoCommandService = undoCommandService;
|
|
var module = ModuleUtils.CreateNetModule(options.Name, options.Mvid, options.ClrVersion);
|
|
var file = DsDotNetDocument.CreateModule(DsDocumentInfo.CreateDocument(string.Empty), module, true);
|
|
fileNodeCreator = RootDocumentNodeCreator.CreateModule(documentTreeView, file);
|
|
}
|
|
|
|
public string Description => dnSpy_AsmEditor_Resources.CreateNetModuleCommand2;
|
|
|
|
public void Execute() {
|
|
fileNodeCreator.Add();
|
|
undoCommandService.Value.MarkAsModified(undoCommandService.Value.GetUndoObject(fileNodeCreator.DocumentNode.Document)!);
|
|
}
|
|
|
|
public void Undo() => fileNodeCreator.Remove();
|
|
|
|
public IEnumerable<object> ModifiedObjects {
|
|
get { yield return fileNodeCreator.DocumentNode; }
|
|
}
|
|
}
|
|
|
|
[DebuggerDisplay("{Description}")]
|
|
sealed class ConvertNetModuleToAssemblyCommand : IUndoCommand {
|
|
[ExportMenuItem(Header = "res:ConvNetModuleToAssemblyCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_MISC, Order = 20)]
|
|
sealed class DocumentsCommand : DocumentsContextMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
|
|
[ImportingConstructor]
|
|
DocumentsCommand(Lazy<IUndoCommandService> undoCommandService) => this.undoCommandService = undoCommandService;
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => ConvertNetModuleToAssemblyCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => ConvertNetModuleToAssemblyCommand.Execute(undoCommandService, context.Nodes);
|
|
}
|
|
|
|
[ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:ConvNetModuleToAssemblyCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_MISC, Order = 20)]
|
|
sealed class EditMenuCommand : EditMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
|
|
[ImportingConstructor]
|
|
EditMenuCommand(Lazy<IUndoCommandService> undoCommandService, IDocumentTreeView documentTreeView)
|
|
: base(documentTreeView) => this.undoCommandService = undoCommandService;
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => ConvertNetModuleToAssemblyCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => ConvertNetModuleToAssemblyCommand.Execute(undoCommandService, context.Nodes);
|
|
}
|
|
|
|
static bool CanExecute(DocumentTreeNodeData[] nodes) =>
|
|
nodes is not null &&
|
|
nodes.Length > 0 &&
|
|
nodes.All(n => n is ModuleDocumentNode &&
|
|
n.TreeNode.Parent is not null &&
|
|
!(n.TreeNode.Parent.Data is AssemblyDocumentNode));
|
|
|
|
static void Execute(Lazy<IUndoCommandService> undoCommandService, DocumentTreeNodeData[] nodes) {
|
|
if (!CanExecute(nodes))
|
|
return;
|
|
|
|
undoCommandService.Value.Add(new ConvertNetModuleToAssemblyCommand(nodes));
|
|
}
|
|
|
|
readonly ModuleDocumentNode[] nodes;
|
|
SavedState[] savedStates;
|
|
bool hasExecuted;
|
|
sealed class SavedState {
|
|
public ModuleKind ModuleKind;
|
|
public Characteristics Characteristics;
|
|
public AssemblyDocumentNode? AssemblyFileNode;
|
|
}
|
|
|
|
ConvertNetModuleToAssemblyCommand(DocumentTreeNodeData[] nodes) {
|
|
this.nodes = nodes.Cast<ModuleDocumentNode>().ToArray();
|
|
savedStates = new SavedState[this.nodes.Length];
|
|
for (int i = 0; i < savedStates.Length; i++)
|
|
savedStates[i] = new SavedState();
|
|
}
|
|
|
|
public string Description => dnSpy_AsmEditor_Resources.ConvNetModuleToAssemblyCommand;
|
|
|
|
public void Execute() {
|
|
Debug.Assert(!hasExecuted);
|
|
if (hasExecuted)
|
|
throw new InvalidOperationException();
|
|
for (int i = 0; i < nodes.Length; i++) {
|
|
var node = nodes[i];
|
|
var savedState = savedStates[i];
|
|
|
|
var module = node.Document.ModuleDef;
|
|
bool b = module is not null && module.Assembly is null;
|
|
Debug.Assert(b);
|
|
if (!b)
|
|
throw new InvalidOperationException();
|
|
Debug2.Assert(module is not null);
|
|
|
|
savedState.ModuleKind = module.Kind;
|
|
ModuleUtils.AddToNewAssemblyDef(module, ModuleKind.Dll, out savedState.Characteristics);
|
|
|
|
if (savedState.AssemblyFileNode is null) {
|
|
var asmFile = DsDotNetDocument.CreateAssembly(node.Document);
|
|
savedState.AssemblyFileNode = node.Context.DocumentTreeView.CreateAssembly(asmFile);
|
|
}
|
|
|
|
lock (savedState.AssemblyFileNode.Document.Children.SyncRoot) {
|
|
Debug.Assert(savedState.AssemblyFileNode.Document.Children.Count == 1);
|
|
savedState.AssemblyFileNode.Document.Children.Remove(node.Document);
|
|
Debug.Assert(savedState.AssemblyFileNode.Document.Children.Count == 0);
|
|
savedState.AssemblyFileNode.TreeNode.EnsureChildrenLoaded();
|
|
Debug.Assert(savedState.AssemblyFileNode.TreeNode.Children.Count == 0);
|
|
savedState.AssemblyFileNode.Document.Children.Add(node.Document);
|
|
}
|
|
|
|
int index = node.Context.DocumentTreeView.TreeView.Root.DataChildren.ToList().IndexOf(node);
|
|
b = index >= 0;
|
|
Debug.Assert(b);
|
|
if (!b)
|
|
throw new InvalidOperationException();
|
|
node.Context.DocumentTreeView.TreeView.Root.Children[index] = savedState.AssemblyFileNode.TreeNode;
|
|
savedState.AssemblyFileNode.TreeNode.AddChild(node.TreeNode);
|
|
}
|
|
hasExecuted = true;
|
|
}
|
|
|
|
public void Undo() {
|
|
Debug.Assert(hasExecuted);
|
|
if (!hasExecuted)
|
|
throw new InvalidOperationException();
|
|
|
|
for (int i = nodes.Length - 1; i >= 0; i--) {
|
|
var node = nodes[i];
|
|
var savedState = savedStates[i];
|
|
|
|
int index = node.Context.DocumentTreeView.TreeView.Root.DataChildren.ToList().IndexOf(savedState.AssemblyFileNode!);
|
|
bool b = index >= 0;
|
|
Debug.Assert(b);
|
|
if (!b)
|
|
throw new InvalidOperationException();
|
|
savedState.AssemblyFileNode!.TreeNode.Children.Remove(node.TreeNode);
|
|
node.Context.DocumentTreeView.TreeView.Root.Children[index] = node.TreeNode;
|
|
|
|
var module = node.Document.ModuleDef;
|
|
b = module is not null && module.Assembly is not null &&
|
|
module.Assembly.Modules.Count == 1 &&
|
|
module.Assembly.ManifestModule == module;
|
|
Debug.Assert(b);
|
|
if (!b)
|
|
throw new InvalidOperationException();
|
|
module!.Assembly!.Modules.Remove(module);
|
|
module.Kind = savedState.ModuleKind;
|
|
module.Characteristics = savedState.Characteristics;
|
|
}
|
|
hasExecuted = false;
|
|
}
|
|
|
|
public IEnumerable<object> ModifiedObjects => nodes;
|
|
}
|
|
|
|
[DebuggerDisplay("{Description}")]
|
|
sealed class ConvertAssemblyToNetModuleCommand : IUndoCommand {
|
|
[ExportMenuItem(Header = "res:ConvAssemblyToNetModuleCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_MISC, Order = 30)]
|
|
sealed class DocumentsCommand : DocumentsContextMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
|
|
[ImportingConstructor]
|
|
DocumentsCommand(Lazy<IUndoCommandService> undoCommandService) => this.undoCommandService = undoCommandService;
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => ConvertAssemblyToNetModuleCommand.IsVisible(context.Nodes);
|
|
public override bool IsEnabled(AsmEditorContext context) => ConvertAssemblyToNetModuleCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => ConvertAssemblyToNetModuleCommand.Execute(undoCommandService, context.Nodes);
|
|
}
|
|
|
|
[ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:ConvAssemblyToNetModuleCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_MISC, Order = 30)]
|
|
sealed class EditMenuCommand : EditMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
|
|
[ImportingConstructor]
|
|
EditMenuCommand(Lazy<IUndoCommandService> undoCommandService, IDocumentTreeView documentTreeView)
|
|
: base(documentTreeView) => this.undoCommandService = undoCommandService;
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => ConvertAssemblyToNetModuleCommand.IsVisible(context.Nodes);
|
|
public override bool IsEnabled(AsmEditorContext context) => ConvertAssemblyToNetModuleCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => ConvertAssemblyToNetModuleCommand.Execute(undoCommandService, context.Nodes);
|
|
}
|
|
|
|
static bool IsVisible(DocumentTreeNodeData[] nodes) =>
|
|
nodes is not null &&
|
|
nodes.Length > 0 &&
|
|
nodes.All(n => n is AssemblyDocumentNode);
|
|
|
|
static bool CanExecute(DocumentTreeNodeData[] nodes) =>
|
|
nodes is not null &&
|
|
nodes.Length > 0 &&
|
|
nodes.All(n => n is AssemblyDocumentNode &&
|
|
((AssemblyDocumentNode)n).Document.AssemblyDef is not null &&
|
|
((AssemblyDocumentNode)n).Document.AssemblyDef!.Modules.Count == 1);
|
|
|
|
static void Execute(Lazy<IUndoCommandService> undoCommandService, DocumentTreeNodeData[] nodes) {
|
|
if (!CanExecute(nodes))
|
|
return;
|
|
|
|
undoCommandService.Value.Add(new ConvertAssemblyToNetModuleCommand(nodes));
|
|
}
|
|
|
|
readonly AssemblyDocumentNode[] nodes;
|
|
SavedState[]? savedStates;
|
|
sealed class SavedState {
|
|
public AssemblyDef? AssemblyDef;
|
|
public ModuleKind ModuleKind;
|
|
public Characteristics Characteristics;
|
|
public ModuleDocumentNode? ModuleNode;
|
|
}
|
|
|
|
ConvertAssemblyToNetModuleCommand(DocumentTreeNodeData[] nodes) => this.nodes = nodes.Cast<AssemblyDocumentNode>().ToArray();
|
|
|
|
public string Description => dnSpy_AsmEditor_Resources.ConvAssemblyToNetModuleCommand;
|
|
|
|
public void Execute() {
|
|
Debug2.Assert(savedStates is null);
|
|
if (savedStates is not null)
|
|
throw new InvalidOperationException();
|
|
savedStates = new SavedState[nodes.Length];
|
|
for (int i = 0; i < nodes.Length; i++) {
|
|
var node = nodes[i];
|
|
savedStates[i] = new SavedState();
|
|
var savedState = savedStates[i];
|
|
|
|
int index = node.TreeNode.Parent!.Children.IndexOf(node.TreeNode);
|
|
bool b = index >= 0;
|
|
Debug.Assert(b);
|
|
if (!b)
|
|
throw new InvalidOperationException();
|
|
savedState.ModuleNode = (ModuleDocumentNode)node.TreeNode.Children.First(a => a.Data is ModuleDocumentNode).Data;
|
|
node.TreeNode.Children.Remove(savedState.ModuleNode.TreeNode);
|
|
node.TreeNode.Parent.Children[index] = savedState.ModuleNode.TreeNode;
|
|
|
|
var module = node.Document.ModuleDef;
|
|
b = module is not null && module.Assembly is not null &&
|
|
module.Assembly.Modules.Count == 1 &&
|
|
module.Assembly.ManifestModule == module;
|
|
Debug.Assert(b);
|
|
if (!b)
|
|
throw new InvalidOperationException();
|
|
node.TreeNode.EnsureChildrenLoaded();
|
|
savedState.AssemblyDef = module!.Assembly;
|
|
module.Assembly!.Modules.Remove(module);
|
|
savedState.ModuleKind = module.Kind;
|
|
ModuleUtils.WriteNewModuleKind(module, ModuleKind.NetModule, out savedState.Characteristics);
|
|
}
|
|
}
|
|
|
|
public void Undo() {
|
|
Debug2.Assert(savedStates is not null);
|
|
if (savedStates is null)
|
|
throw new InvalidOperationException();
|
|
|
|
for (int i = nodes.Length - 1; i >= 0; i--) {
|
|
var node = nodes[i];
|
|
var savedState = savedStates[i];
|
|
|
|
var module = node.Document.ModuleDef;
|
|
bool b = module is not null && module.Assembly is null;
|
|
Debug.Assert(b);
|
|
if (!b)
|
|
throw new InvalidOperationException();
|
|
|
|
module!.Kind = savedState.ModuleKind;
|
|
module.Characteristics = savedState.Characteristics;
|
|
savedState.AssemblyDef!.Modules.Add(module);
|
|
|
|
var parent = savedState.ModuleNode!.TreeNode.Parent!;
|
|
int index = parent.Children.IndexOf(savedState.ModuleNode.TreeNode);
|
|
b = index >= 0;
|
|
Debug.Assert(b);
|
|
if (!b)
|
|
throw new InvalidOperationException();
|
|
parent.Children[index] = node.TreeNode;
|
|
node.TreeNode.AddChild(savedState.ModuleNode.TreeNode);
|
|
}
|
|
savedStates = null;
|
|
}
|
|
|
|
public IEnumerable<object> ModifiedObjects => nodes;
|
|
}
|
|
|
|
abstract class AddNetModuleToAssemblyCommand : IUndoCommand2 {
|
|
internal static bool CanExecute(DocumentTreeNodeData[] nodes) =>
|
|
nodes is not null &&
|
|
nodes.Length == 1 &&
|
|
(nodes[0] is AssemblyDocumentNode || nodes[0] is ModuleDocumentNode);
|
|
|
|
readonly IUndoCommandService undoCommandService;
|
|
readonly AssemblyDocumentNode asmNode;
|
|
internal readonly ModuleDocumentNode modNode;
|
|
readonly bool modNodeWasCreated;
|
|
|
|
protected AddNetModuleToAssemblyCommand(IUndoCommandService undoCommandService, DsDocumentNode asmNode, ModuleDocumentNode modNode, bool modNodeWasCreated) {
|
|
this.undoCommandService = undoCommandService;
|
|
if (!(asmNode is AssemblyDocumentNode))
|
|
asmNode = (AssemblyDocumentNode)asmNode.TreeNode.Parent!.Data;
|
|
this.asmNode = (AssemblyDocumentNode)asmNode;
|
|
this.modNode = modNode;
|
|
this.modNodeWasCreated = modNodeWasCreated;
|
|
}
|
|
|
|
public abstract string Description { get; }
|
|
|
|
public void Execute() {
|
|
Debug2.Assert(modNode.TreeNode.Parent is null);
|
|
if (modNode.TreeNode.Parent is not null)
|
|
throw new InvalidOperationException();
|
|
asmNode.TreeNode.EnsureChildrenLoaded();
|
|
asmNode.Document.AssemblyDef!.Modules.Add(modNode.Document.ModuleDef);
|
|
asmNode.Document.Children.Add(modNode.Document);
|
|
asmNode.TreeNode.AddChild(modNode.TreeNode);
|
|
if (modNodeWasCreated)
|
|
undoCommandService.MarkAsModified(undoCommandService.GetUndoObject(modNode.Document)!);
|
|
}
|
|
|
|
public void Undo() {
|
|
Debug2.Assert(modNode.TreeNode.Parent is not null);
|
|
if (modNode.TreeNode.Parent is null)
|
|
throw new InvalidOperationException();
|
|
asmNode.Document.AssemblyDef!.Modules.Remove(modNode.Document.ModuleDef);
|
|
asmNode.Document.Children.Remove(modNode.Document);
|
|
bool b = asmNode.TreeNode.Children.Remove(modNode.TreeNode);
|
|
Debug.Assert(b);
|
|
if (!b)
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
public IEnumerable<object> ModifiedObjects {
|
|
get { yield return asmNode; }
|
|
}
|
|
|
|
public IEnumerable<object> NonModifiedObjects {
|
|
get {
|
|
if (modNodeWasCreated)
|
|
yield return modNode;
|
|
}
|
|
}
|
|
}
|
|
|
|
sealed class AddNewNetModuleToAssemblyCommand : AddNetModuleToAssemblyCommand {
|
|
[ExportMenuItem(Header = "res:AddNewNetModuleToAssemblyCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_NEW, Order = 10)]
|
|
sealed class DocumentsCommand : DocumentsContextMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
readonly IAppService appService;
|
|
|
|
[ImportingConstructor]
|
|
DocumentsCommand(Lazy<IUndoCommandService> undoCommandService, IAppService appService) {
|
|
this.undoCommandService = undoCommandService;
|
|
this.appService = appService;
|
|
}
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => AddNewNetModuleToAssemblyCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => AddNewNetModuleToAssemblyCommand.Execute(undoCommandService, appService, context.Nodes);
|
|
}
|
|
|
|
[ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:AddNewNetModuleToAssemblyCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_NEW, Order = 10)]
|
|
sealed class EditMenuCommand : EditMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
readonly IAppService appService;
|
|
|
|
[ImportingConstructor]
|
|
EditMenuCommand(Lazy<IUndoCommandService> undoCommandService, IAppService appService)
|
|
: base(appService.DocumentTreeView) {
|
|
this.undoCommandService = undoCommandService;
|
|
this.appService = appService;
|
|
}
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => AddNewNetModuleToAssemblyCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => AddNewNetModuleToAssemblyCommand.Execute(undoCommandService, appService, context.Nodes);
|
|
}
|
|
|
|
static void Execute(Lazy<IUndoCommandService> undoCommandService, IAppService appService, DocumentTreeNodeData[] nodes) {
|
|
if (!AddNetModuleToAssemblyCommand.CanExecute(nodes))
|
|
return;
|
|
|
|
var asmNode = (DsDocumentNode)nodes[0];
|
|
if (asmNode is ModuleDocumentNode)
|
|
asmNode = (AssemblyDocumentNode)asmNode.TreeNode.Parent!.Data;
|
|
|
|
var win = new NetModuleOptionsDlg();
|
|
var data = new NetModuleOptionsVM(asmNode.Document.ModuleDef!);
|
|
win.DataContext = data;
|
|
win.Owner = appService.MainWindow;
|
|
if (win.ShowDialog() != true)
|
|
return;
|
|
|
|
var options = data.CreateNetModuleOptions();
|
|
var newModule = ModuleUtils.CreateNetModule(options.Name, options.Mvid, options.ClrVersion);
|
|
var newFile = DsDotNetDocument.CreateModule(DsDocumentInfo.CreateDocument(string.Empty), newModule, true);
|
|
var newModNode = asmNode.Context.DocumentTreeView.CreateModule(newFile);
|
|
var cmd = new AddNewNetModuleToAssemblyCommand(undoCommandService.Value, (DsDocumentNode)nodes[0], newModNode);
|
|
undoCommandService.Value.Add(cmd);
|
|
appService.DocumentTabService.FollowReference(cmd.modNode);
|
|
}
|
|
|
|
AddNewNetModuleToAssemblyCommand(IUndoCommandService undoCommandService, DsDocumentNode asmNode, ModuleDocumentNode modNode)
|
|
: base(undoCommandService, asmNode, modNode, true) {
|
|
}
|
|
|
|
public override string Description => dnSpy_AsmEditor_Resources.AddNewNetModuleToAssemblyCommand2;
|
|
}
|
|
|
|
sealed class AddExistingNetModuleToAssemblyCommand : AddNetModuleToAssemblyCommand {
|
|
[ExportMenuItem(Header = "res:AddExistingNetModuleToAssemblyCommand", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_NEW, Order = 20)]
|
|
sealed class DocumentsCommand : DocumentsContextMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
readonly IAppService appService;
|
|
|
|
[ImportingConstructor]
|
|
DocumentsCommand(Lazy<IUndoCommandService> undoCommandService, IAppService appService) {
|
|
this.undoCommandService = undoCommandService;
|
|
this.appService = appService;
|
|
}
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => AddExistingNetModuleToAssemblyCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => AddExistingNetModuleToAssemblyCommand.Execute(undoCommandService, appService, context.Nodes);
|
|
}
|
|
|
|
[ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:AddExistingNetModuleToAssemblyCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_NEW, Order = 20)]
|
|
sealed class EditMenuCommand : EditMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
readonly IAppService appService;
|
|
|
|
[ImportingConstructor]
|
|
EditMenuCommand(Lazy<IUndoCommandService> undoCommandService, IAppService appService)
|
|
: base(appService.DocumentTreeView) {
|
|
this.undoCommandService = undoCommandService;
|
|
this.appService = appService;
|
|
}
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => AddExistingNetModuleToAssemblyCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => AddExistingNetModuleToAssemblyCommand.Execute(undoCommandService, appService, context.Nodes);
|
|
}
|
|
|
|
static void Execute(Lazy<IUndoCommandService> undoCommandService, IAppService appService, DocumentTreeNodeData[] nodes) {
|
|
if (!AddNetModuleToAssemblyCommand.CanExecute(nodes))
|
|
return;
|
|
|
|
var dialog = new System.Windows.Forms.OpenFileDialog() {
|
|
Filter = PickFilenameConstants.NetModuleFilter,
|
|
RestoreDirectory = true,
|
|
};
|
|
if (dialog.ShowDialog() != System.Windows.Forms.DialogResult.OK)
|
|
return;
|
|
if (string.IsNullOrEmpty(dialog.FileName))
|
|
return;
|
|
|
|
var fm = appService.DocumentTreeView.DocumentService;
|
|
var file = fm.CreateDocument(DsDocumentInfo.CreateDocument(dialog.FileName), dialog.FileName, true);
|
|
if (file.ModuleDef is null || file.AssemblyDef is not null || !(file is IDsDotNetDocument)) {
|
|
MsgBox.Instance.Show(string.Format(dnSpy_AsmEditor_Resources.Error_NotNetModule, file.Filename), MsgBoxButton.OK);
|
|
if (file is IDisposable id)
|
|
id.Dispose();
|
|
return;
|
|
}
|
|
|
|
var node = (DsDocumentNode)nodes[0];
|
|
var newModNode = node.Context.DocumentTreeView.CreateModule((IDsDotNetDocument)file);
|
|
var cmd = new AddExistingNetModuleToAssemblyCommand(undoCommandService.Value, node, newModNode);
|
|
undoCommandService.Value.Add(cmd);
|
|
appService.DocumentTabService.FollowReference(cmd.modNode);
|
|
}
|
|
|
|
AddExistingNetModuleToAssemblyCommand(IUndoCommandService undoCommandService, DsDocumentNode asmNode, ModuleDocumentNode modNode)
|
|
: base(undoCommandService, asmNode, modNode, false) {
|
|
}
|
|
|
|
public override string Description => dnSpy_AsmEditor_Resources.AddExistingNetModuleToAssemblyCommand2;
|
|
}
|
|
|
|
sealed class RemoveNetModuleFromAssemblyCommand : IUndoCommand2 {
|
|
[ExportMenuItem(Header = "res:RemoveNetModuleCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_DELETE, Order = 10)]
|
|
sealed class DocumentsCommand : DocumentsContextMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
readonly Lazy<IDocumentSaver> documentSaver;
|
|
readonly IAppService appService;
|
|
|
|
[ImportingConstructor]
|
|
DocumentsCommand(Lazy<IUndoCommandService> undoCommandService, Lazy<IDocumentSaver> documentSaver, IAppService appService) {
|
|
this.undoCommandService = undoCommandService;
|
|
this.documentSaver = documentSaver;
|
|
this.appService = appService;
|
|
}
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => RemoveNetModuleFromAssemblyCommand.IsVisible(context.Nodes);
|
|
public override bool IsEnabled(AsmEditorContext context) => RemoveNetModuleFromAssemblyCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => RemoveNetModuleFromAssemblyCommand.Execute(undoCommandService, documentSaver, appService, context.Nodes);
|
|
}
|
|
|
|
[Export, ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:RemoveNetModuleCommand", Icon = DsImagesAttribute.Cancel, InputGestureText = "res:DeleteCommandKey", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_DELETE, Order = 10)]
|
|
internal sealed class EditMenuCommand : EditMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
readonly Lazy<IDocumentSaver> documentSaver;
|
|
readonly IAppService appService;
|
|
|
|
[ImportingConstructor]
|
|
EditMenuCommand(Lazy<IUndoCommandService> undoCommandService, Lazy<IDocumentSaver> documentSaver, IAppService appService)
|
|
: base(appService.DocumentTreeView) {
|
|
this.undoCommandService = undoCommandService;
|
|
this.documentSaver = documentSaver;
|
|
this.appService = appService;
|
|
}
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => RemoveNetModuleFromAssemblyCommand.IsVisible(context.Nodes);
|
|
public override bool IsEnabled(AsmEditorContext context) => RemoveNetModuleFromAssemblyCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => RemoveNetModuleFromAssemblyCommand.Execute(undoCommandService, documentSaver, appService, context.Nodes);
|
|
}
|
|
|
|
static bool IsVisible(DocumentTreeNodeData[] nodes) =>
|
|
nodes is not null &&
|
|
nodes.Length == 1 &&
|
|
nodes[0] is ModuleDocumentNode &&
|
|
nodes[0].TreeNode.Parent is not null &&
|
|
nodes[0].TreeNode.Parent!.Data is AssemblyDocumentNode;
|
|
|
|
static bool CanExecute(DocumentTreeNodeData[] nodes) =>
|
|
nodes is not null &&
|
|
nodes.Length == 1 &&
|
|
nodes[0] is ModuleDocumentNode &&
|
|
nodes[0].TreeNode.Parent is not null &&
|
|
nodes[0].TreeNode.Parent!.DataChildren.ToList().IndexOf(nodes[0]) > 0;
|
|
|
|
static void Execute(Lazy<IUndoCommandService> undoCommandService, Lazy<IDocumentSaver> documentSaver, IAppService appService, DocumentTreeNodeData[] nodes) {
|
|
if (!CanExecute(nodes))
|
|
return;
|
|
|
|
var modNode = (ModuleDocumentNode)nodes[0];
|
|
if (!documentSaver.Value.AskUserToSaveIfModified(new[] { modNode.Document }))
|
|
return;
|
|
|
|
undoCommandService.Value.Add(new RemoveNetModuleFromAssemblyCommand(undoCommandService, modNode));
|
|
}
|
|
|
|
readonly AssemblyDocumentNode asmNode;
|
|
readonly ModuleDocumentNode modNode;
|
|
readonly int removeIndex, removeIndexDocument;
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
|
|
RemoveNetModuleFromAssemblyCommand(Lazy<IUndoCommandService> undoCommandService, ModuleDocumentNode modNode) {
|
|
this.undoCommandService = undoCommandService;
|
|
asmNode = (AssemblyDocumentNode)modNode.TreeNode.Parent!.Data;
|
|
Debug2.Assert(asmNode is not null);
|
|
this.modNode = modNode;
|
|
removeIndex = asmNode.TreeNode.DataChildren.ToList().IndexOf(modNode);
|
|
Debug.Assert(removeIndex > 0);
|
|
Debug2.Assert(asmNode.Document.AssemblyDef is not null &&
|
|
asmNode.Document.AssemblyDef.Modules.IndexOf(modNode.Document.ModuleDef) == removeIndex);
|
|
removeIndexDocument = asmNode.Document.Children.IndexOf(modNode.Document);
|
|
Debug.Assert(removeIndexDocument >= 0);
|
|
}
|
|
|
|
public string Description => dnSpy_AsmEditor_Resources.RemoveNetModuleCommand;
|
|
|
|
public void Execute() {
|
|
Debug2.Assert(modNode.TreeNode.Parent is not null);
|
|
if (modNode.TreeNode.Parent is null)
|
|
throw new InvalidOperationException();
|
|
|
|
var children = asmNode.TreeNode.DataChildren.ToArray();
|
|
lock (asmNode.Document.Children.SyncRoot) {
|
|
bool b = removeIndex < children.Length && children[removeIndex] == modNode &&
|
|
removeIndex < asmNode.Document.AssemblyDef!.Modules.Count &&
|
|
asmNode.Document.AssemblyDef.Modules[removeIndex] == modNode.Document.ModuleDef &&
|
|
removeIndexDocument < asmNode.Document.Children.Count &&
|
|
asmNode.Document.Children[removeIndexDocument] == modNode.Document;
|
|
Debug.Assert(b);
|
|
if (!b)
|
|
throw new InvalidOperationException();
|
|
asmNode.Document.AssemblyDef!.Modules.RemoveAt(removeIndex);
|
|
asmNode.Document.Children.RemoveAt(removeIndexDocument);
|
|
asmNode.TreeNode.Children.RemoveAt(removeIndex);
|
|
}
|
|
undoCommandService.Value.MarkAsModified(undoCommandService.Value.GetUndoObject(asmNode.Document)!);
|
|
}
|
|
|
|
public void Undo() {
|
|
Debug2.Assert(modNode.TreeNode.Parent is null);
|
|
if (modNode.TreeNode.Parent is not null)
|
|
throw new InvalidOperationException();
|
|
asmNode.Document.AssemblyDef!.Modules.Insert(removeIndex, modNode.Document.ModuleDef);
|
|
asmNode.Document.Children.Insert(removeIndexDocument, modNode.Document);
|
|
asmNode.TreeNode.Children.Insert(removeIndex, modNode.TreeNode);
|
|
}
|
|
|
|
public IEnumerable<object> ModifiedObjects {
|
|
get { yield return asmNode; }
|
|
}
|
|
|
|
public IEnumerable<object> NonModifiedObjects {
|
|
get { yield return modNode; }
|
|
}
|
|
}
|
|
|
|
[DebuggerDisplay("{Description}")]
|
|
sealed class ModuleSettingsCommand : IUndoCommand {
|
|
[ExportMenuItem(Header = "res:EditModuleCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_CTX_DOCUMENTS_ASMED_SETTINGS, Order = 10)]
|
|
sealed class DocumentsCommand : DocumentsContextMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
readonly IAppService appService;
|
|
|
|
[ImportingConstructor]
|
|
DocumentsCommand(Lazy<IUndoCommandService> undoCommandService, IAppService appService) {
|
|
this.undoCommandService = undoCommandService;
|
|
this.appService = appService;
|
|
}
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => ModuleSettingsCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => ModuleSettingsCommand.Execute(undoCommandService, appService, context.Nodes);
|
|
}
|
|
|
|
[Export, ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:EditModuleCommand", Icon = DsImagesAttribute.Settings, InputGestureText = "res:ShortcutKeyAltEnter", Group = MenuConstants.GROUP_APP_MENU_EDIT_ASMED_SETTINGS, Order = 10)]
|
|
internal sealed class EditMenuCommand : EditMenuHandler {
|
|
readonly Lazy<IUndoCommandService> undoCommandService;
|
|
readonly IAppService appService;
|
|
|
|
[ImportingConstructor]
|
|
EditMenuCommand(Lazy<IUndoCommandService> undoCommandService, IAppService appService)
|
|
: base(appService.DocumentTreeView) {
|
|
this.undoCommandService = undoCommandService;
|
|
this.appService = appService;
|
|
}
|
|
|
|
public override bool IsVisible(AsmEditorContext context) => ModuleSettingsCommand.CanExecute(context.Nodes);
|
|
public override void Execute(AsmEditorContext context) => ModuleSettingsCommand.Execute(undoCommandService, appService, context.Nodes);
|
|
}
|
|
|
|
static bool CanExecute(DocumentTreeNodeData[] nodes) =>
|
|
nodes is not null &&
|
|
nodes.Length == 1 &&
|
|
nodes[0] is ModuleDocumentNode;
|
|
|
|
static void Execute(Lazy<IUndoCommandService> undoCommandService, IAppService appService, DocumentTreeNodeData[] nodes) {
|
|
if (!CanExecute(nodes))
|
|
return;
|
|
|
|
var asmNode = (ModuleDocumentNode)nodes[0];
|
|
|
|
var module = asmNode.Document.ModuleDef!;
|
|
var data = new ModuleOptionsVM(module, new ModuleOptions(module), appService.DecompilerService);
|
|
var win = new ModuleOptionsDlg();
|
|
win.DataContext = data;
|
|
win.Owner = appService.MainWindow;
|
|
if (win.ShowDialog() != true)
|
|
return;
|
|
|
|
undoCommandService.Value.Add(new ModuleSettingsCommand(asmNode, data.CreateModuleOptions()));
|
|
}
|
|
|
|
readonly ModuleDocumentNode modNode;
|
|
readonly ModuleOptions newOptions;
|
|
readonly ModuleOptions origOptions;
|
|
|
|
ModuleSettingsCommand(ModuleDocumentNode modNode, ModuleOptions newOptions) {
|
|
this.modNode = modNode;
|
|
this.newOptions = newOptions;
|
|
origOptions = new ModuleOptions(modNode.Document.ModuleDef!);
|
|
}
|
|
|
|
public string Description => dnSpy_AsmEditor_Resources.EditModuleCommand2;
|
|
|
|
void WriteModuleOptions(ModuleOptions theOptions) {
|
|
theOptions.CopyTo(modNode.Document.ModuleDef!);
|
|
modNode.TreeNode.RefreshUI();
|
|
}
|
|
|
|
public void Execute() => WriteModuleOptions(newOptions);
|
|
public void Undo() => WriteModuleOptions(origOptions);
|
|
|
|
public IEnumerable<object> ModifiedObjects {
|
|
get { yield return modNode; }
|
|
}
|
|
}
|
|
}
|