/* 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 System.Windows.Controls; using System.Windows.Input; using dnlib.DotNet; using dnSpy.AsmEditor.Hex.Nodes; using dnSpy.AsmEditor.Properties; using dnSpy.AsmEditor.Utilities; using dnSpy.Contracts.App; using dnSpy.Contracts.Command; using dnSpy.Contracts.Controls; using dnSpy.Contracts.Decompiler; using dnSpy.Contracts.Documents; 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.Hex; using dnSpy.Contracts.Images; using dnSpy.Contracts.Menus; using dnSpy.Contracts.Text; using dnSpy.Contracts.Text.Editor; using dnSpy.Contracts.TreeView; using dnSpy.Contracts.Utilities; using Microsoft.VisualStudio.Text.Editor; namespace dnSpy.AsmEditor.Hex { [ExportAutoLoaded] sealed class HexCommandLoader : IAutoLoaded { [ImportingConstructor] HexCommandLoader(IWpfCommandService wpfCommandService, IDocumentTabService documentTabService, Lazy methodAnnotations) { OpenHexEditorCommand.Initialize(wpfCommandService, documentTabService, methodAnnotations); GoToMDTableRowHexEditorCommand.Initialize(wpfCommandService, documentTabService); GoToMDTableRowUIHexEditorCommand.Initialize(wpfCommandService, documentTabService); } } sealed class HexContext { public TreeNodeData[]? Nodes { get; } public bool IsDefinition { get; } public object? Reference { get; } public int? TextPosition { get; } public GuidObject CreatorObject { get; } public HexContext() { } public HexContext(GuidObject creatorObject, TreeNodeData[] nodes) { Nodes = nodes; CreatorObject = creatorObject; } public HexContext(IDocumentViewer documentViewer, int? textPosition, object? @ref, bool isDefinition) { Reference = @ref; IsDefinition = isDefinition; TextPosition = textPosition; CreatorObject = new GuidObject(MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID, documentViewer); } } abstract class HexTextEditorCommand : MenuItemBase { protected sealed override object CachedContextKey => ContextKey; static readonly object ContextKey = new object(); protected sealed override HexContext? CreateContext(IMenuItemContext context) { if (context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID)) { var textRef = context.Find(); bool isDefinition = false; object? @ref = null; if (textRef is not null) { @ref = textRef.Reference; isDefinition = textRef.IsDefinition; } var pos = context.Find(); return new HexContext(context.Find(), pos is null ? (int?)null : pos.Position, @ref, isDefinition); } if (context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_DOCUMENTS_TREEVIEW_GUID)) { var nodes = context.Find(); if (nodes is null) return null; return new HexContext(context.CreatorObject, nodes); } return null; } public override bool IsEnabled(HexContext context) => true; } abstract class HexMenuCommand : MenuItemBase { protected sealed override object CachedContextKey => ContextKey; static readonly object ContextKey = new object(); protected readonly IDocumentTabService documentTabService; protected HexMenuCommand(IDocumentTabService documentTabService) => this.documentTabService = documentTabService; protected sealed override HexContext? CreateContext(IMenuItemContext context) { if (context.CreatorObject.Guid != new Guid(MenuConstants.APP_MENU_EDIT_GUID)) return null; return CreateContext(documentTabService); } internal static HexContext CreateContext(IDocumentTabService documentTabService) { var documentViewer = documentTabService.ActiveTab.TryGetDocumentViewer(); if (documentViewer is not null && documentViewer.UIObject.IsKeyboardFocusWithin) return CreateContext(documentViewer); if (documentTabService.DocumentTreeView.TreeView.UIObject.IsKeyboardFocusWithin) return CreateContext(documentTabService.DocumentTreeView); if (documentTabService.DocumentTreeView.TreeView.SelectedItems.Length != 0) { if (documentViewer is not null) return CreateContext(documentViewer); if (UIUtils.HasSelectedChildrenFocus(documentTabService.DocumentTreeView.TreeView.UIObject as ListBox)) return CreateContext(documentTabService.DocumentTreeView); } return new HexContext(); } static HexContext CreateContext(IDocumentViewer documentViewer) { var refInfo = documentViewer.SelectedReference; bool isDefinition = false; object? @ref = null; if (refInfo is not null) { @ref = refInfo.Value.Data.Reference; isDefinition = refInfo.Value.Data.IsDefinition; } return new HexContext(documentViewer, documentViewer.Caret.Position.BufferPosition, @ref, isDefinition); } static HexContext CreateContext(IDocumentTreeView documentTreeView) => new HexContext(new GuidObject(MenuConstants.GUIDOBJ_DOCUMENTS_TREEVIEW_GUID, documentTreeView), documentTreeView.TreeView.TopLevelSelection); public override bool IsEnabled(HexContext context) => true; } [ExportCommandTargetFilterProvider(CommandTargetFilterOrder.TextEditor - 1)] sealed class HexCommandTargetFilterProvider : ICommandTargetFilterProvider { readonly IDocumentTabService documentTabService; readonly Lazy methodAnnotations; [ImportingConstructor] HexCommandTargetFilterProvider(IDocumentTabService documentTabService, Lazy methodAnnotations) { this.documentTabService = documentTabService; this.methodAnnotations = methodAnnotations; } public ICommandTargetFilter? Create(object target) { if ((target as ITextView)?.Roles.Contains(PredefinedDsTextViewRoles.DocumentViewer) == true) return new HexCommandTargetFilter(documentTabService, methodAnnotations); return null; } } sealed class HexCommandTargetFilter : ICommandTargetFilter { readonly IDocumentTabService documentTabService; readonly Lazy methodAnnotations; public HexCommandTargetFilter(IDocumentTabService documentTabService, Lazy methodAnnotations) { this.documentTabService = documentTabService; this.methodAnnotations = methodAnnotations; } public CommandTargetStatus CanExecute(Guid group, int cmdId) { if (group == CommandConstants.StandardGroup) { switch ((StandardIds)cmdId) { case StandardIds.Cut: return CommandTargetStatus.Handled; } } return CommandTargetStatus.NotHandled; } public CommandTargetStatus Execute(Guid group, int cmdId, object? args = null) { object? result = null; return Execute(group, cmdId, args, ref result); } public CommandTargetStatus Execute(Guid group, int cmdId, object? args, ref object? result) { if (group == CommandConstants.StandardGroup) { switch ((StandardIds)cmdId) { case StandardIds.Cut: OpenHexEditorCommand.ExecuteCommand(documentTabService, methodAnnotations); return CommandTargetStatus.Handled; } } return CommandTargetStatus.NotHandled; } public void SetNextCommandTarget(ICommandTarget commandTarget) { } public void Dispose() { } } static class OpenHexEditorCommand { static readonly RoutedCommand OpenHexEditor = new RoutedCommand("OpenHexEditor", typeof(OpenHexEditorCommand)); internal static void Initialize(IWpfCommandService wpfCommandService, IDocumentTabService documentTabService, Lazy methodAnnotations) { var cmds = wpfCommandService.GetCommands(ControlConstants.GUID_MAINWINDOW); cmds.Add(OpenHexEditor, (s, e) => ExecuteCommand(documentTabService, methodAnnotations), (s, e) => e.CanExecute = CanExecuteCommand(documentTabService, methodAnnotations), ModifierKeys.Control, Key.X); } [ExportMenuItem(Header = "res:OpenHexEditorCommand", Icon = DsImagesAttribute.Binary, InputGestureText = "res:ShortCutKeyCtrlX", Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 0)] sealed class TheHexTextEditorCommand : HexTextEditorCommand { readonly IDocumentTabService documentTabService; readonly Lazy methodAnnotations; [ImportingConstructor] TheHexTextEditorCommand(IDocumentTabService documentTabService, Lazy methodAnnotations) { this.documentTabService = documentTabService; this.methodAnnotations = methodAnnotations; } public override void Execute(HexContext context) => ExecuteInternal(documentTabService, methodAnnotations, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(documentTabService, methodAnnotations, context); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:OpenHexEditorCommand", Icon = DsImagesAttribute.Binary, InputGestureText = "res:ShortCutKeyCtrlX", Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 0)] sealed class TheHexMenuCommand : HexMenuCommand { readonly Lazy methodAnnotations; [ImportingConstructor] TheHexMenuCommand(IDocumentTabService documentTabService, Lazy methodAnnotations) : base(documentTabService) => this.methodAnnotations = methodAnnotations; public override void Execute(HexContext context) => ExecuteInternal(documentTabService, methodAnnotations, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(documentTabService, methodAnnotations, context); } internal static void ExecuteCommand(IDocumentTabService documentTabService, Lazy methodAnnotations) { var context = HexMenuCommand.CreateContext(documentTabService); if (ShowAddressReferenceInHexEditorCommand.IsVisibleInternal(context)) ShowAddressReferenceInHexEditorCommand.ExecuteInternal(documentTabService, context); else if (ShowILSpanInHexEditorCommand.IsVisibleInternal(methodAnnotations, context)) ShowILSpanInHexEditorCommand.ExecuteInternal(documentTabService, methodAnnotations, context); else if (ShowHexNodeInHexEditorCommand.IsVisibleInternal(methodAnnotations, context)) ShowHexNodeInHexEditorCommand.ExecuteInternal(documentTabService, methodAnnotations, context); else if (IsVisibleInternal(documentTabService, methodAnnotations, context)) ExecuteInternal(documentTabService, methodAnnotations, context); } static bool CanExecuteCommand(IDocumentTabService documentTabService, Lazy methodAnnotations) { var context = HexMenuCommand.CreateContext(documentTabService); return ShowAddressReferenceInHexEditorCommand.IsVisibleInternal(context) || ShowILSpanInHexEditorCommand.IsVisibleInternal(methodAnnotations, context) || ShowHexNodeInHexEditorCommand.IsVisibleInternal(methodAnnotations, context) || IsVisibleInternal(documentTabService, methodAnnotations, context); } internal static void ExecuteInternal(IDocumentTabService documentTabService, Lazy methodAnnotations, HexContext context) { var node = GetNode(documentTabService, methodAnnotations, context); if (node is not null) { var tab = documentTabService.ActiveTab; var uiContext = tab?.UIContext as HexViewDocumentTabUIContext; if (uiContext is null) documentTabService.FollowReference(new AddressReference(node.Document.Filename, false, 0, 0)); } } static bool IsVisibleInternal(IDocumentTabService documentTabService, Lazy methodAnnotations, HexContext context) { var node = GetNode(documentTabService, methodAnnotations, context); return node is not null && !string.IsNullOrEmpty(node.Document.Filename); } static DsDocumentNode? GetDocumentNode(IDocumentTabService documentTabService, Lazy methodAnnotations, HexContext context) { if (ShowAddressReferenceInHexEditorCommand.IsVisibleInternal(context)) return null; if (ShowILSpanInHexEditorCommand.IsVisibleInternal(methodAnnotations, context)) return null; if (ShowHexNodeInHexEditorCommand.IsVisibleInternal(methodAnnotations, context)) return null; if (context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID)) return GetActiveAssemblyTreeNode(documentTabService); if (context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_DOCUMENTS_TREEVIEW_GUID)) { return context.Nodes is not null && context.Nodes.Length == 1 ? context.Nodes[0] as DsDocumentNode : null; } return null; } static DsDocumentNode? GetActiveAssemblyTreeNode(IDocumentTabService documentTabService) { var tab = documentTabService.ActiveTab; if (tab is null) return null; var node = tab.Content.Nodes.FirstOrDefault(); return node.GetDocumentNode(); } static DsDocumentNode? GetNode(IDocumentTabService documentTabService, Lazy methodAnnotations, HexContext context) => GetDocumentNode(documentTabService, methodAnnotations, context); } static class ShowAddressReferenceInHexEditorCommand { [ExportMenuItem(Header = "res:ShowInHexEditorCommand", Icon = DsImagesAttribute.Binary, InputGestureText = "res:ShortCutKeyCtrlX", Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 10)] sealed class TheHexTextEditorCommand : HexTextEditorCommand { readonly IDocumentTabService documentTabService; [ImportingConstructor] TheHexTextEditorCommand(IDocumentTabService documentTabService) => this.documentTabService = documentTabService; public override void Execute(HexContext context) => ExecuteInternal(documentTabService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(context); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:ShowInHexEditorCommand", Icon = DsImagesAttribute.Binary, InputGestureText = "res:ShortCutKeyCtrlX", Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 10)] sealed class TheHexMenuCommand : HexMenuCommand { [ImportingConstructor] TheHexMenuCommand(IDocumentTabService documentTabService) : base(documentTabService) { } public override void Execute(HexContext context) => ExecuteInternal(documentTabService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(context); } internal static void ExecuteInternal(IDocumentTabService documentTabService, HexContext context) { var @ref = GetAddressReference(context); if (@ref is not null) documentTabService.FollowReference(@ref); } internal static bool IsVisibleInternal(HexContext context) => GetAddressReference(context) is not null; static AddressReference? GetAddressReference(HexContext context) { if (context.Reference is null) return null; if (context.Reference is AddressReference addr && File.Exists(addr.Filename)) return addr; if (context.Reference is DocumentTreeNodeData node && ResourceDataProviderUtils.GetResourceDataProvider(node) is IResourceDataProvider rsrc && rsrc.FileOffset != 0) { var name = GetFilename((DocumentTreeNodeData)node.TreeNode.Parent!.Data); if (!string.IsNullOrEmpty(name)) return new AddressReference(name, false, rsrc.FileOffset, rsrc.Length); } return null; } internal static string? GetFilename(DocumentTreeNodeData node) { var fileNode = node.GetDocumentNode(); if (fileNode is null) return null; var mod = fileNode.Document.ModuleDef; if (mod is not null && File.Exists(mod.Location)) return mod.Location; var peImage = fileNode.Document.PEImage; if (peImage is not null && File.Exists(peImage.Filename)) return peImage.Filename; return null; } } static class ShowILSpanInHexEditorCommand { [ExportMenuItem(Header = "res:ShowInstrsInHexEditorCommand", Icon = DsImagesAttribute.Binary, InputGestureText = "res:ShortCutKeyCtrlX", Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 20)] sealed class TheHexTextEditorCommand : HexTextEditorCommand { readonly IDocumentTabService documentTabService; readonly Lazy methodAnnotations; [ImportingConstructor] TheHexTextEditorCommand(IDocumentTabService documentTabService, Lazy methodAnnotations) { this.documentTabService = documentTabService; this.methodAnnotations = methodAnnotations; } public override void Execute(HexContext context) => ExecuteInternal(documentTabService, methodAnnotations, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(methodAnnotations, context); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:ShowInstrsInHexEditorCommand", Icon = DsImagesAttribute.Binary, InputGestureText = "res:ShortCutKeyCtrlX", Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 20)] sealed class TheHexMenuCommand : HexMenuCommand { readonly Lazy methodAnnotations; [ImportingConstructor] TheHexMenuCommand(IDocumentTabService documentTabService, Lazy methodAnnotations) : base(documentTabService) => this.methodAnnotations = methodAnnotations; public override void Execute(HexContext context) => ExecuteInternal(documentTabService, methodAnnotations, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(methodAnnotations, context); } internal static void ExecuteInternal(IDocumentTabService documentTabService, Lazy methodAnnotations, HexContext context) { var @ref = GetAddressReference(methodAnnotations, context); if (@ref is not null) documentTabService.FollowReference(@ref); } internal static bool IsVisibleInternal(Lazy methodAnnotations, HexContext context) => GetAddressReference(methodAnnotations, context) is not null; static AddressReference? GetAddressReference(Lazy methodAnnotations, HexContext context) { if (ShowAddressReferenceInHexEditorCommand.IsVisibleInternal(context)) return null; if (TVShowMethodInstructionsInHexEditorCommand.IsVisibleInternal(methodAnnotations, context)) return null; var methodStatements = GetStatements(context); if (methodStatements is null || methodStatements.Count == 0) return null; var method = methodStatements[0].Method; var mod = methodStatements[0].Method.Module as ModuleDefMD; if (mod is null || string.IsNullOrEmpty(mod.Location)) return null; ulong addr = (ulong)method.RVA; ulong len; if (methodAnnotations.Value.IsBodyModified(method)) len = 0; else if (methodStatements.Count == 1) { addr += (ulong)method.Body.HeaderSize + methodStatements[0].Statement.ILSpan.Start; len = methodStatements[0].Statement.ILSpan.End - methodStatements[0].Statement.ILSpan.Start; } else { addr += (ulong)method.Body.HeaderSize + methodStatements[0].Statement.ILSpan.Start; len = 0; } return new AddressReference(mod.Location, true, addr, len); } static IList? GetStatements(HexContext context) { if (context.TextPosition is null) return null; return MethodBody.BodyCommandUtils.GetStatements(context.CreatorObject.Object as IDocumentViewer, context.TextPosition.Value, FindByTextPositionOptions.None); } } static class ShowHexNodeInHexEditorCommand { [ExportMenuItem(Header = "res:ShowInHexEditorCommand", Icon = DsImagesAttribute.Binary, InputGestureText = "res:ShortCutKeyCtrlX", Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 30)] sealed class TheHexTextEditorCommand : HexTextEditorCommand { readonly IDocumentTabService documentTabService; readonly Lazy methodAnnotations; [ImportingConstructor] TheHexTextEditorCommand(IDocumentTabService documentTabService, Lazy methodAnnotations) { this.documentTabService = documentTabService; this.methodAnnotations = methodAnnotations; } public override void Execute(HexContext context) => ExecuteInternal(documentTabService, methodAnnotations, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(methodAnnotations, context); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:ShowInHexEditorCommand", Icon = DsImagesAttribute.Binary, InputGestureText = "res:ShortCutKeyCtrlX", Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 30)] sealed class TheHexMenuCommand : HexMenuCommand { readonly Lazy methodAnnotations; [ImportingConstructor] TheHexMenuCommand(IDocumentTabService documentTabService, Lazy methodAnnotations) : base(documentTabService) => this.methodAnnotations = methodAnnotations; public override void Execute(HexContext context) => ExecuteInternal(documentTabService, methodAnnotations, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(methodAnnotations, context); } internal static void ExecuteInternal(IDocumentTabService documentTabService, Lazy methodAnnotations, HexContext context) { var @ref = GetAddressReference(methodAnnotations, context); if (@ref is not null) documentTabService.FollowReference(@ref); } internal static bool IsVisibleInternal(Lazy methodAnnotations, HexContext context) => GetAddressReference(methodAnnotations, context) is not null; static AddressReference? GetAddressReference(Lazy methodAnnotations, HexContext context) { if (ShowAddressReferenceInHexEditorCommand.IsVisibleInternal(context)) return null; if (ShowILSpanInHexEditorCommand.IsVisibleInternal(methodAnnotations, context)) return null; if (context.Nodes is null || context.Nodes.Length != 1) return null; var hexNode = context.Nodes[0] as HexNode; if (hexNode is null) return null; var name = ShowAddressReferenceInHexEditorCommand.GetFilename(hexNode); if (string.IsNullOrEmpty(name)) return null; return new AddressReference(name, false, hexNode.Span.Start.ToUInt64(), hexNode.Span.Start == 0 && hexNode.Span.End == new HexPosition(ulong.MaxValue) + 1 ? ulong.MaxValue : hexNode.Span.Length.ToUInt64()); } } static class ShowStorageStreamDataInHexEditorCommand { [ExportMenuItem(Header = "res:ShowDataInHexEditorCommand", Icon = DsImagesAttribute.Binary, Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 40)] sealed class TheHexTextEditorCommand : HexTextEditorCommand { readonly IDocumentTabService documentTabService; readonly Lazy methodAnnotations; [ImportingConstructor] TheHexTextEditorCommand(IDocumentTabService documentTabService, Lazy methodAnnotations) { this.documentTabService = documentTabService; this.methodAnnotations = methodAnnotations; } public override void Execute(HexContext context) => ExecuteInternal(documentTabService, methodAnnotations, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(methodAnnotations, context); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:ShowDataInHexEditorCommand", Icon = DsImagesAttribute.Binary, Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 40)] sealed class TheHexMenuCommand : HexMenuCommand { readonly Lazy methodAnnotations; [ImportingConstructor] TheHexMenuCommand(IDocumentTabService documentTabService, Lazy methodAnnotations) : base(documentTabService) => this.methodAnnotations = methodAnnotations; public override void Execute(HexContext context) => ExecuteInternal(documentTabService, methodAnnotations, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(methodAnnotations, context); } internal static void ExecuteInternal(IDocumentTabService documentTabService, Lazy methodAnnotations, HexContext context) { var @ref = GetAddressReference(methodAnnotations, context); if (@ref is not null) documentTabService.FollowReference(@ref); } internal static bool IsVisibleInternal(Lazy methodAnnotations, HexContext context) => GetAddressReference(methodAnnotations, context) is not null; static AddressReference? GetAddressReference(Lazy methodAnnotations, HexContext context) { if (ShowAddressReferenceInHexEditorCommand.IsVisibleInternal(context)) return null; if (ShowILSpanInHexEditorCommand.IsVisibleInternal(methodAnnotations, context)) return null; if (context.Nodes is null || context.Nodes.Length != 1) return null; if (!(context.Nodes[0] is HexNode)) return null; var mod = context.Nodes[0].GetModule() as ModuleDefMD; if (mod is null) return null; var pe = mod.Metadata.PEImage; if (context.Nodes[0] is ImageSectionHeaderNode sectNode) { if (sectNode.SectionNumber >= pe.ImageSectionHeaders.Count) return null; var sect = pe.ImageSectionHeaders[sectNode.SectionNumber]; return new AddressReference(mod.Location, false, sect.PointerToRawData, sect.SizeOfRawData); } if (context.Nodes[0] is StorageStreamNode stgNode) { if (stgNode.StreamNumber >= mod.Metadata.MetadataHeader.StreamHeaders.Count) return null; var sh = mod.Metadata.MetadataHeader.StreamHeaders[stgNode.StreamNumber]; return new AddressReference(mod.Location, false, (ulong)mod.Metadata.MetadataHeader.StartOffset + sh.Offset, sh.StreamSize); } return null; } } static class TVShowMethodInstructionsInHexEditorCommand { [ExportMenuItem(Header = "res:ShowInstrsInHexEditorCommand", Icon = DsImagesAttribute.Binary, Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 50)] sealed class TheHexTextEditorCommand : HexTextEditorCommand { readonly IDocumentTabService documentTabService; readonly Lazy methodAnnotations; [ImportingConstructor] TheHexTextEditorCommand(IDocumentTabService documentTabService, Lazy methodAnnotations) { this.documentTabService = documentTabService; this.methodAnnotations = methodAnnotations; } public override void Execute(HexContext context) => ExecuteInternal(documentTabService, methodAnnotations, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(methodAnnotations, context); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:ShowInstrsInHexEditorCommand", Icon = DsImagesAttribute.Binary, Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 50)] sealed class TheHexMenuCommand : HexMenuCommand { readonly Lazy methodAnnotations; [ImportingConstructor] TheHexMenuCommand(IDocumentTabService documentTabService, Lazy methodAnnotations) : base(documentTabService) => this.methodAnnotations = methodAnnotations; public override void Execute(HexContext context) => ExecuteInternal(documentTabService, methodAnnotations, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(methodAnnotations, context); } static void ExecuteInternal(IDocumentTabService documentTabService, Lazy methodAnnotations, HexContext context) { var @ref = GetAddressReference(methodAnnotations, context); if (@ref is not null) documentTabService.FollowReference(@ref); } internal static bool IsVisibleInternal(Lazy methodAnnotations, HexContext context) => GetAddressReference(methodAnnotations, context) is not null; static IMemberDef? ResolveDef(object? mr) { if (mr is ITypeDefOrRef) return ((ITypeDefOrRef)mr).ResolveTypeDef(); if (mr is IMethod && ((IMethod)mr).IsMethod) return ((IMethod)mr).ResolveMethodDef(); if (mr is IField) return ((IField)mr).ResolveFieldDef(); return mr as IMemberDef; } internal static IMemberDef? GetMemberDef(HexContext context) { IMemberDef? def = null; if (context.Nodes is not null && context.Nodes.Length == 1 && context.Nodes[0] is IMDTokenNode) def = ResolveDef(((IMDTokenNode)context.Nodes[0]).Reference); else { // Only allow declarations of the defs, i.e., right-clicking a method call with a method // def as reference should return null, not the method def. if (context.Reference is not null && context.IsDefinition && context.Reference is IMemberRef) { // Don't resolve it. It's confusing if we show the method body of a called method // instead of the current method. def = context.Reference as IMemberDef; } } var mod = def?.Module; return mod is ModuleDefMD ? def : null; } static AddressReference? GetAddressReference(Lazy methodAnnotations, HexContext context) { var md = GetMemberDef(context) as MethodDef; if (md is null) return null; var body = md.Body; if (body is null) return null; var mod = md.Module; bool modified = methodAnnotations.Value.IsBodyModified(md); return new AddressReference(mod?.Location, true, (ulong)md.RVA + body.HeaderSize, modified ? 0 : (ulong)body.GetCodeSize()); } } static class TVShowMethodHeaderInHexEditorCommand { [ExportMenuItem(Header = "res:ShowMethodBodyInHexEditorCommand", Icon = DsImagesAttribute.Binary, Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 60)] sealed class TheHexTextEditorCommand : HexTextEditorCommand { readonly IDocumentTabService documentTabService; [ImportingConstructor] TheHexTextEditorCommand(IDocumentTabService documentTabService) => this.documentTabService = documentTabService; public override void Execute(HexContext context) => ExecuteInternal(documentTabService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(context); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:ShowMethodBodyInHexEditorCommand", Icon = DsImagesAttribute.Binary, Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 60)] sealed class TheHexMenuCommand : HexMenuCommand { [ImportingConstructor] TheHexMenuCommand(IDocumentTabService documentTabService) : base(documentTabService) { } public override void Execute(HexContext context) => ExecuteInternal(documentTabService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(context); } static void ExecuteInternal(IDocumentTabService documentTabService, HexContext context) { var @ref = GetAddressReference(context); if (@ref is not null) documentTabService.FollowReference(@ref); } static bool IsVisibleInternal(HexContext context) => GetAddressReference(context) is not null; static AddressReference? GetAddressReference(HexContext context) { var info = TVChangeBodyHexEditorCommand.GetMethodLengthAndOffset(context); if (info is not null) return new AddressReference(info.Value.Filename, false, info.Value.Offset, info.Value.Size); return null; } } static class TVShowFieldInitialValueInHexEditorCommand { [ExportMenuItem(Header = "res:ShowInitialValueInHexEditorCommand", Icon = DsImagesAttribute.Binary, Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 70)] sealed class TheHexTextEditorCommand : HexTextEditorCommand { readonly IDocumentTabService documentTabService; [ImportingConstructor] TheHexTextEditorCommand(IDocumentTabService documentTabService) => this.documentTabService = documentTabService; public override void Execute(HexContext context) => ExecuteInternal(documentTabService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(context); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:ShowInitialValueInHexEditorCommand", Icon = DsImagesAttribute.Binary, Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 70)] sealed class TheHexMenuCommand : HexMenuCommand { [ImportingConstructor] TheHexMenuCommand(IDocumentTabService documentTabService) : base(documentTabService) { } public override void Execute(HexContext context) => ExecuteInternal(documentTabService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(context); } static void ExecuteInternal(IDocumentTabService documentTabService, HexContext context) { var @ref = GetAddressReference(context); if (@ref is not null) documentTabService.FollowReference(@ref); } static bool IsVisibleInternal(HexContext context) => GetAddressReference(context) is not null; static AddressReference? GetAddressReference(HexContext context) { var fd = TVShowMethodInstructionsInHexEditorCommand.GetMemberDef(context) as FieldDef; if (fd is null || fd.RVA == 0) return null; var iv = fd.InitialValue; if (iv is null) return null; var mod = fd.Module; return new AddressReference(mod?.Location, true, (ulong)fd.RVA, (ulong)iv.Length); } } static class TVShowResourceInHexEditorCommand { [ExportMenuItem(Header = "res:ShowInHexEditorCommand2", Icon = DsImagesAttribute.Binary, Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 80)] sealed class TheHexTextEditorCommand : HexTextEditorCommand { readonly IDocumentTabService documentTabService; [ImportingConstructor] TheHexTextEditorCommand(IDocumentTabService documentTabService) => this.documentTabService = documentTabService; public override void Execute(HexContext context) => ExecuteInternal(documentTabService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(context); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:ShowInHexEditorCommand2", Icon = DsImagesAttribute.Binary, Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 80)] sealed class TheHexMenuCommand : HexMenuCommand { [ImportingConstructor] TheHexMenuCommand(IDocumentTabService documentTabService) : base(documentTabService) { } public override void Execute(HexContext context) => ExecuteInternal(documentTabService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(context); } static void ExecuteInternal(IDocumentTabService documentTabService, HexContext context) { var @ref = GetAddressReference(context); if (@ref is not null) documentTabService.FollowReference(@ref); } static bool IsVisibleInternal(HexContext context) => GetAddressReference(context) is not null; static AddressReference? GetAddressReference(HexContext context) { if (context.Nodes is null || context.Nodes.Length != 1) return null; if (context.Nodes[0] is DocumentTreeNodeData node && ResourceDataProviderUtils.GetResourceDataProvider(node) is IResourceDataProvider rsrc && rsrc.FileOffset != 0) { var mod = node.GetParentModule(); if (mod is not null && File.Exists(mod.Location)) return new AddressReference(mod.Location, false, rsrc.FileOffset, rsrc.Length); } return null; } } readonly struct LengthAndOffset { public readonly string Filename; public readonly ulong Offset; public readonly ulong Size; public LengthAndOffset(string filename, ulong offs, ulong size) { Filename = filename; Offset = offs; Size = size; } } interface ITVChangeBodyHexEditorCommand { byte[]? GetData(MethodDef method); } static class TVChangeBodyHexEditorCommand { internal abstract class TheHexTextEditorCommand : HexTextEditorCommand, ITVChangeBodyHexEditorCommand { public abstract byte[]? GetData(MethodDef method); } internal abstract class TheHexMenuCommand : HexMenuCommand, ITVChangeBodyHexEditorCommand { public abstract byte[]? GetData(MethodDef method); protected TheHexMenuCommand(IDocumentTabService documentTabService) : base(documentTabService) { } } internal static void ExecuteInternal(Lazy hexBufferService, ITVChangeBodyHexEditorCommand cmd, HexContext context) { var data = GetData(cmd, context); if (data is null) return; var info = GetMethodLengthAndOffset(context); if (info is null || info.Value.Size < (ulong)data.Length) return; HexBufferWriterHelper.Write(hexBufferService.Value, info.Value.Filename, info.Value.Offset, data); } internal static bool IsVisibleInternal(ITVChangeBodyHexEditorCommand cmd, HexContext context) { var data = GetData(cmd, context); if (data is null) return false; var info = GetMethodLengthAndOffset(context); return info is not null && info.Value.Size >= (ulong)data.Length; } static byte[]? GetData(ITVChangeBodyHexEditorCommand cmd, HexContext context) { var md = TVShowMethodInstructionsInHexEditorCommand.GetMemberDef(context) as MethodDef; if (md is null) return null; return cmd.GetData(md); } internal static LengthAndOffset? GetMethodLengthAndOffset(HexContext context) { var md = TVShowMethodInstructionsInHexEditorCommand.GetMemberDef(context) as MethodDef; if (md is null) return null; var mod = md.Module; if (mod is null || !File.Exists(mod.Location)) return null; if (!md.GetRVA(out uint rva, out long fileOffset)) return null; return new LengthAndOffset(mod.Location, (ulong)fileOffset, InstructionUtils.GetTotalMethodBodyLength(md)); } } static class TVChangeBodyToReturnTrueHexEditorCommand { [ExportMenuItem(Header = "res:HexWriteReturnTrueBodyCommand", Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 90)] sealed class TheHexTextEditorCommand : TVChangeBodyHexEditorCommand.TheHexTextEditorCommand { readonly Lazy hexBufferService; [ImportingConstructor] TheHexTextEditorCommand(Lazy hexBufferService) => this.hexBufferService = hexBufferService; public override void Execute(HexContext context) => TVChangeBodyHexEditorCommand.ExecuteInternal(hexBufferService, this, context); public override bool IsVisible(HexContext context) => TVChangeBodyHexEditorCommand.IsVisibleInternal(this, context); public override byte[]? GetData(MethodDef method) => TVChangeBodyToReturnTrueHexEditorCommand.GetData(method); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:HexWriteReturnTrueBodyCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 90)] sealed class TheHexMenuCommand : TVChangeBodyHexEditorCommand.TheHexMenuCommand { readonly Lazy hexBufferService; [ImportingConstructor] TheHexMenuCommand(Lazy hexBufferService, IDocumentTabService documentTabService) : base(documentTabService) => this.hexBufferService = hexBufferService; public override void Execute(HexContext context) => TVChangeBodyHexEditorCommand.ExecuteInternal(hexBufferService, this, context); public override bool IsVisible(HexContext context) => TVChangeBodyHexEditorCommand.IsVisibleInternal(this, context); public override byte[]? GetData(MethodDef method) => TVChangeBodyToReturnTrueHexEditorCommand.GetData(method); } static byte[]? GetData(MethodDef method) { if (method.MethodSig.GetRetType().RemovePinnedAndModifiers().GetElementType() != ElementType.Boolean) return null; return data; } static readonly byte[] data = new byte[] { 0x0A, 0x17, 0x2A }; } static class TVChangeBodyToReturnFalseHexEditorCommand { [ExportMenuItem(Header = "res:HexWriteReturnFalseBodyCommand", Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 100)] sealed class TheHexTextEditorCommand : TVChangeBodyHexEditorCommand.TheHexTextEditorCommand { readonly Lazy hexBufferService; [ImportingConstructor] TheHexTextEditorCommand(Lazy hexBufferService) => this.hexBufferService = hexBufferService; public override void Execute(HexContext context) => TVChangeBodyHexEditorCommand.ExecuteInternal(hexBufferService, this, context); public override bool IsVisible(HexContext context) => TVChangeBodyHexEditorCommand.IsVisibleInternal(this, context); public override byte[]? GetData(MethodDef method) => TVChangeBodyToReturnFalseHexEditorCommand.GetData(method); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:HexWriteReturnFalseBodyCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 100)] sealed class TheHexMenuCommand : TVChangeBodyHexEditorCommand.TheHexMenuCommand { readonly Lazy hexBufferService; [ImportingConstructor] TheHexMenuCommand(Lazy hexBufferService, IDocumentTabService documentTabService) : base(documentTabService) => this.hexBufferService = hexBufferService; public override void Execute(HexContext context) => TVChangeBodyHexEditorCommand.ExecuteInternal(hexBufferService, this, context); public override bool IsVisible(HexContext context) => TVChangeBodyHexEditorCommand.IsVisibleInternal(this, context); public override byte[]? GetData(MethodDef method) => TVChangeBodyToReturnFalseHexEditorCommand.GetData(method); } static byte[]? GetData(MethodDef method) { if (method.MethodSig.GetRetType().RemovePinnedAndModifiers().GetElementType() != ElementType.Boolean) return null; return data; } static readonly byte[] data = new byte[] { 0x0A, 0x16, 0x2A }; } static class TVWriteEmptyBodyHexEditorCommand { [ExportMenuItem(Header = "res:HexWriteEmptyMethodBodyCommand", Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 110)] sealed class TheHexTextEditorCommand : TVChangeBodyHexEditorCommand.TheHexTextEditorCommand { readonly Lazy hexBufferService; [ImportingConstructor] TheHexTextEditorCommand(Lazy hexBufferService) => this.hexBufferService = hexBufferService; public override void Execute(HexContext context) => TVChangeBodyHexEditorCommand.ExecuteInternal(hexBufferService, this, context); public override bool IsVisible(HexContext context) => TVChangeBodyHexEditorCommand.IsVisibleInternal(this, context); public override byte[]? GetData(MethodDef method) => TVWriteEmptyBodyHexEditorCommand.GetData(method); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:HexWriteEmptyMethodBodyCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 110)] sealed class TheHexMenuCommand : TVChangeBodyHexEditorCommand.TheHexMenuCommand { readonly Lazy hexBufferService; [ImportingConstructor] TheHexMenuCommand(Lazy hexBufferService, IDocumentTabService documentTabService) : base(documentTabService) => this.hexBufferService = hexBufferService; public override void Execute(HexContext context) => TVChangeBodyHexEditorCommand.ExecuteInternal(hexBufferService, this, context); public override bool IsVisible(HexContext context) => TVChangeBodyHexEditorCommand.IsVisibleInternal(this, context); public override byte[]? GetData(MethodDef method) => TVWriteEmptyBodyHexEditorCommand.GetData(method); } static byte[]? GetData(MethodDef method) { var sig = method.MethodSig.GetRetType().RemovePinnedAndModifiers(); // This is taken care of by the write 'return true/false' commands if (sig.GetElementType() == ElementType.Boolean) return null; return GetData(sig, 0); } static byte[]? GetData(TypeSig typeSig, int level) { if (level >= 10) return null; var retType = typeSig.RemovePinnedAndModifiers(); if (retType is null) return null; switch (retType.ElementType) { case ElementType.Void: return dataVoidReturnType; case ElementType.Boolean: case ElementType.Char: case ElementType.I1: case ElementType.U1: case ElementType.I2: case ElementType.U2: case ElementType.I4: case ElementType.U4: return dataInt32ReturnType; case ElementType.I8: case ElementType.U8: return dataInt64ReturnType; case ElementType.R4: return dataSingleReturnType; case ElementType.R8: return dataDoubleReturnType; case ElementType.I: return dataIntPtrReturnType; case ElementType.U: case ElementType.Ptr: case ElementType.FnPtr: return dataUIntPtrReturnType; case ElementType.ValueType: var td = ((ValueTypeSig)retType).TypeDefOrRef.ResolveTypeDef(); if (td is not null && td.IsEnum) { var undType = td.GetEnumUnderlyingType().RemovePinnedAndModifiers(); var et = undType.GetElementType(); if ((ElementType.Boolean <= et && et <= ElementType.R8) || et == ElementType.I || et == ElementType.U) return GetData(undType, level + 1); } goto case ElementType.TypedByRef; case ElementType.TypedByRef: case ElementType.Var: case ElementType.MVar: // Need ldloca, initobj, ldloc and a local variable return null; case ElementType.GenericInst: if (((GenericInstSig)retType).GenericType is ValueTypeSig) goto case ElementType.TypedByRef; goto case ElementType.Class; case ElementType.End: case ElementType.String: case ElementType.ByRef: case ElementType.Class: case ElementType.Array: case ElementType.ValueArray: case ElementType.R: case ElementType.Object: case ElementType.SZArray: case ElementType.CModReqd: case ElementType.CModOpt: case ElementType.Internal: case ElementType.Module: case ElementType.Sentinel: case ElementType.Pinned: default: return dataRefTypeReturnType; } } static readonly byte[] dataVoidReturnType = new byte[] { 0x06, 0x2A }; // ret static readonly byte[] dataInt32ReturnType = new byte[] { 0x0A, 0x16, 0x2A }; // ldc.i4.0, ret static readonly byte[] dataInt64ReturnType = new byte[] { 0x0E, 0x16, 0x6A, 0x2A }; // ldc.i4.0, conv.i8, ret static readonly byte[] dataSingleReturnType = new byte[] { 0x0E, 0x16, 0x6B, 0x2A }; // ldc.i4.0, conv.r4, ret static readonly byte[] dataDoubleReturnType = new byte[] { 0x0E, 0x16, 0x6C, 0x2A }; // ldc.i4.0, conv.r8, ret static readonly byte[] dataIntPtrReturnType = new byte[] { 0x0E, 0x16, 0xD3, 0x2A }; // ldc.i4.0, conv.i, ret static readonly byte[] dataUIntPtrReturnType = new byte[] { 0x0E, 0x16, 0xE0, 0x2A }; // ldc.i4.0, conv.u, ret static readonly byte[] dataRefTypeReturnType = new byte[] { 0x0A, 0x14, 0x2A }; // ldnull, ret } static class TVCopyMethodBodyHexEditorCommand { [ExportMenuItem(Header = "res:HexCopyMethodBodyCommand", Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 120)] sealed class TheHexTextEditorCommand : HexTextEditorCommand { readonly Lazy hexBufferService; [ImportingConstructor] TheHexTextEditorCommand(Lazy hexBufferService) => this.hexBufferService = hexBufferService; public override void Execute(HexContext context) => ExecuteInternal(hexBufferService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(context); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:HexCopyMethodBodyCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 120)] sealed class TheHexMenuCommand : HexMenuCommand { readonly Lazy hexBufferService; [ImportingConstructor] TheHexMenuCommand(Lazy hexBufferService, IDocumentTabService documentTabService) : base(documentTabService) => this.hexBufferService = hexBufferService; public override void Execute(HexContext context) => ExecuteInternal(hexBufferService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(context); } static void ExecuteInternal(Lazy hexBufferService, HexContext context) { var data = GetMethodBodyBytes(hexBufferService, context); if (data is null) return; ClipboardUtils.SetText(ClipboardUtils.ToHexString(data)); } static bool IsVisibleInternal(HexContext context) => TVChangeBodyHexEditorCommand.GetMethodLengthAndOffset(context) is not null; static byte[]? GetMethodBodyBytes(Lazy hexBufferService, HexContext context) { var info = TVChangeBodyHexEditorCommand.GetMethodLengthAndOffset(context); if (info is null || info.Value.Size > int.MaxValue) return null; var buffer = hexBufferService.Value.GetOrCreate(info.Value.Filename); if (buffer is null) return null; return buffer.ReadBytes(info.Value.Offset, info.Value.Size); } } static class TVPasteMethodBodyHexEditorCommand { [ExportMenuItem(Header = "res:HexPasteMethodBodyCommand", Group = MenuConstants.GROUP_CTX_DOCVIEWER_HEX, Order = 130)] sealed class TheHexTextEditorCommand : TVChangeBodyHexEditorCommand.TheHexTextEditorCommand { readonly Lazy hexBufferService; [ImportingConstructor] TheHexTextEditorCommand(Lazy hexBufferService) => this.hexBufferService = hexBufferService; public override void Execute(HexContext context) => TVChangeBodyHexEditorCommand.ExecuteInternal(hexBufferService, this, context); public override bool IsVisible(HexContext context) => TVChangeBodyHexEditorCommand.IsVisibleInternal(this, context); public override byte[]? GetData(MethodDef method) => TVPasteMethodBodyHexEditorCommand.GetData(method); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:HexPasteMethodBodyCommand", Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX, Order = 130)] sealed class TheHexMenuCommand : TVChangeBodyHexEditorCommand.TheHexMenuCommand { readonly Lazy hexBufferService; [ImportingConstructor] TheHexMenuCommand(Lazy hexBufferService, IDocumentTabService documentTabService) : base(documentTabService) => this.hexBufferService = hexBufferService; public override void Execute(HexContext context) => TVChangeBodyHexEditorCommand.ExecuteInternal(hexBufferService, this, context); public override bool IsVisible(HexContext context) => TVChangeBodyHexEditorCommand.IsVisibleInternal(this, context); public override byte[]? GetData(MethodDef method) => TVPasteMethodBodyHexEditorCommand.GetData(method); } static byte[]? GetData(MethodDef method) => ClipboardUtils.GetData(canBeEmpty: false); } static class GoToMDTableRowHexEditorCommand { static readonly RoutedCommand GoToMDTableRow = new RoutedCommand("GoToMDTableRow", typeof(GoToMDTableRowHexEditorCommand)); internal static void Initialize(IWpfCommandService wpfCommandService, IDocumentTabService documentTabService) { var cmds = wpfCommandService.GetCommands(ControlConstants.GUID_MAINWINDOW); cmds.Add(GoToMDTableRow, (s, e) => Execute(documentTabService), (s, e) => e.CanExecute = CanExecute(documentTabService), ModifierKeys.Shift | ModifierKeys.Alt, Key.R); } [ExportMenuItem(Group = MenuConstants.GROUP_CTX_DOCVIEWER_TOKENS, Order = 40)] sealed class TheHexTextEditorCommand : HexTextEditorCommand { readonly IDocumentTabService documentTabService; [ImportingConstructor] TheHexTextEditorCommand(IDocumentTabService documentTabService) => this.documentTabService = documentTabService; public override void Execute(HexContext context) => ExecuteInternal(documentTabService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(documentTabService, context); public override string? GetHeader(HexContext context) => GetHeaderInternal(documentTabService, context); public override string? GetInputGestureText(HexContext context) => GetInputGestureTextInternal(context); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX_GOTO_MD, Order = 10)] sealed class TheHexMenuCommand : HexMenuCommand { [ImportingConstructor] TheHexMenuCommand(IDocumentTabService documentTabService) : base(documentTabService) { } public override void Execute(HexContext context) => ExecuteInternal(documentTabService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(documentTabService, context); public override string? GetHeader(HexContext context) => GetHeaderInternal(documentTabService, context); public override string? GetInputGestureText(HexContext context) => GetInputGestureTextInternal(context); } static void Execute(IDocumentTabService documentTabService) => ExecuteInternal(documentTabService, HexMenuCommand.CreateContext(documentTabService)); static bool CanExecute(IDocumentTabService documentTabService) => IsVisibleInternal(documentTabService, HexMenuCommand.CreateContext(documentTabService)); static string GetHeaderInternal(IDocumentTabService documentTabService, HexContext context) { var tokRef = GetTokenReference(documentTabService, context); Debug2.Assert(tokRef is not null); return string.Format(dnSpy_AsmEditor_Resources.GoToMetaDataTableRowCommand, tokRef.Token); } static string? GetInputGestureTextInternal(HexContext context) { if (context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_DOCUMENTS_TREEVIEW_GUID) || context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_DOCUMENTVIEWERCONTROL_GUID)) return dnSpy_AsmEditor_Resources.ShortCutKeyShiftAltR; return null; } internal static void ExecuteInternal(IDocumentTabService documentTabService, HexContext context) { var @ref = GetTokenReference(documentTabService, context); if (@ref is not null) documentTabService.FollowReference(@ref); } internal static bool IsVisibleInternal(IDocumentTabService documentTabService, HexContext context) => GetTokenReference(documentTabService, context) is not null; static TokenReference? GetTokenReference(IDocumentTabService documentTabService, HexContext context) { var @ref = GetTokenReference2(context); if (@ref is null) return null; var node = documentTabService.DocumentTreeView.FindNode(@ref.ModuleDef); return HasPENode(node) ? @ref : null; } internal static bool HasPENode(ModuleDocumentNode? node) { if (node is null) return false; return PETreeNodeDataProviderBase.HasPENode(node); } static TokenReference? GetTokenReference2(HexContext context) { if (context is null) return null; if (context.Reference is not null) { if (context.Reference is TokenReference tokRef) return tokRef; if (context.Reference is IMemberRef mr) return CreateTokenReference(mr.Module, mr); if (context.Reference is Parameter p) { var pd = p.ParamDef; if (pd is not null && pd.DeclaringMethod is not null) return CreateTokenReference(pd.DeclaringMethod.Module, pd); } } if (context.Nodes is not null && context.Nodes.Length == 1) { if (context.Nodes[0] is IMDTokenNode node && node.Reference is not null) { var mod = (node as TreeNodeData).GetModule(); if (mod is not null) return new TokenReference(mod, node.Reference.MDToken.Raw); } } return null; } static TokenReference? CreateTokenReference(ModuleDef module, IMDTokenProvider @ref) { if (module is null || @ref is null) return null; // Make sure it's not a created method/field/etc var res = module.ResolveToken(@ref.MDToken.Raw); if (res is null) return null; return new TokenReference(module, @ref.MDToken.Raw); } } static class GoToMDTableRowUIHexEditorCommand { static readonly RoutedCommand GoToMDTableRowUI = new RoutedCommand("GoToMDTableRowUI", typeof(GoToMDTableRowUIHexEditorCommand)); internal static void Initialize(IWpfCommandService wpfCommandService, IDocumentTabService documentTabService) { var cmds = wpfCommandService.GetCommands(ControlConstants.GUID_MAINWINDOW); cmds.Add(GoToMDTableRowUI, (s, e) => Execute(documentTabService), (s, e) => e.CanExecute = CanExecute(documentTabService), ModifierKeys.Control | ModifierKeys.Shift, Key.D); } [ExportMenuItem(Header = "res:GoToMetaDataTableRowCommand2", InputGestureText = "res:ShortCutKeyCtrlShiftD", Group = MenuConstants.GROUP_CTX_DOCVIEWER_TOKENS, Order = 30)] sealed class TheHexTextEditorCommand : HexTextEditorCommand { readonly IDocumentTabService documentTabService; [ImportingConstructor] TheHexTextEditorCommand(IDocumentTabService documentTabService) => this.documentTabService = documentTabService; public override void Execute(HexContext context) => ExecuteInternal(documentTabService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(context); } [ExportMenuItem(OwnerGuid = MenuConstants.APP_MENU_EDIT_GUID, Header = "res:GoToMetaDataTableRowCommand2", InputGestureText = "res:ShortCutKeyCtrlShiftD", Group = MenuConstants.GROUP_APP_MENU_EDIT_HEX_GOTO_MD, Order = 0)] sealed class TheHexMenuCommand : HexMenuCommand { [ImportingConstructor] TheHexMenuCommand(IDocumentTabService documentTabService) : base(documentTabService) { } public override void Execute(HexContext context) => ExecuteInternal(documentTabService, context); public override bool IsVisible(HexContext context) => IsVisibleInternal(context); } static void Execute(IDocumentTabService documentTabService) => Execute2(documentTabService, HexMenuCommand.CreateContext(documentTabService)); static bool CanExecute(IDocumentTabService documentTabService) => CanExecute(HexMenuCommand.CreateContext(documentTabService)); static void ExecuteInternal(IDocumentTabService documentTabService, HexContext context) => Execute2(documentTabService, context); static bool IsVisibleInternal(HexContext context) => CanExecute(context); static bool CanExecute(HexContext context) => GetModule(context, out var tab) is not null; static ModuleDef? GetModule(HexContext context, out IDocumentTab? tab) { tab = null; if (context is null) return null; if (context.CreatorObject.Object is IDocumentViewer uiContext) { tab = uiContext.DocumentTab!; var content = tab.Content; var node = content.Nodes.FirstOrDefault(); if (node is not null) return GetModule(GetModuleNode(node)); } if (context.Nodes is not null && context.Nodes.Length == 1) return GetModule(GetModuleNode(context.Nodes[0])); return null; } static ModuleDocumentNode? GetModuleNode(TreeNodeData node) { var modNode = node.GetModuleNode(); if (modNode is not null) return modNode; if (node is AssemblyDocumentNode asmNode) { asmNode.TreeNode.EnsureChildrenLoaded(); return (ModuleDocumentNode?)asmNode.TreeNode.DataChildren.FirstOrDefault(a => a is ModuleDocumentNode); } return null; } static ModuleDef? GetModule(ModuleDocumentNode? node) => GoToMDTableRowHexEditorCommand.HasPENode(node) ? node!.Document.ModuleDef : null; static void Execute2(IDocumentTabService documentTabService, HexContext context) { var module = GetModule(context, out var tab); if (module is null) return; uint? token = AskForDef(dnSpy_AsmEditor_Resources.GoToMetaDataTableRowTitle, module); if (token is null) return; var tokRef = new TokenReference(module, token.Value); if (HexDocumentTreeNodeDataFinder.FindNode(documentTabService.DocumentTreeView, tokRef) is null) { MsgBox.Instance.Show(string.Format(dnSpy_AsmEditor_Resources.GoToMetaDataTableRow_TokenDoesNotExist, token.Value)); return; } if (tab is not null) tab.FollowReference(tokRef, false); else documentTabService.FollowReference(tokRef); } static uint? AskForDef(string title, ModuleDef module) => MsgBox.Instance.Ask(dnSpy_AsmEditor_Resources.GoToMetaDataTableRow_MetadataToken, null, title, s => { uint token = SimpleTypeConverter.ParseUInt32(s, uint.MinValue, uint.MaxValue, out var error); return string.IsNullOrEmpty(error) ? token : (uint?)null; }, s => { uint token = SimpleTypeConverter.ParseUInt32(s, uint.MinValue, uint.MaxValue, out var error); if (!string.IsNullOrEmpty(error)) return error; var memberRef = module.ResolveToken(token); if (memberRef is not null) return string.Empty; if (module is ModuleDefMD md) { var mdToken = new MDToken(token); var table = md.Metadata.TablesStream.Get(mdToken.Table); if (table?.IsValidRID(mdToken.Rid) == true) return string.Empty; } return string.Format(dnSpy_AsmEditor_Resources.GoToMetaDataTableRow_InvalidMetadataToken, token); }); } }