1356 lines
60 KiB
C#
Raw Permalink Normal View History

2021-09-20 18:20:01 +02:00
/*
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.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<IMethodAnnotations> 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<HexContext> {
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<TextReference>();
bool isDefinition = false;
object? @ref = null;
if (textRef is not null) {
@ref = textRef.Reference;
isDefinition = textRef.IsDefinition;
}
var pos = context.Find<TextEditorPosition>();
return new HexContext(context.Find<IDocumentViewer>(), pos is null ? (int?)null : pos.Position, @ref, isDefinition);
}
if (context.CreatorObject.Guid == new Guid(MenuConstants.GUIDOBJ_DOCUMENTS_TREEVIEW_GUID)) {
var nodes = context.Find<TreeNodeData[]>();
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<HexContext> {
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<IMethodAnnotations> methodAnnotations;
[ImportingConstructor]
HexCommandTargetFilterProvider(IDocumentTabService documentTabService, Lazy<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations;
public HexCommandTargetFilter(IDocumentTabService documentTabService, Lazy<IMethodAnnotations> 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<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations;
[ImportingConstructor]
TheHexTextEditorCommand(IDocumentTabService documentTabService, Lazy<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations;
[ImportingConstructor]
TheHexMenuCommand(IDocumentTabService documentTabService, Lazy<IMethodAnnotations> 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<IMethodAnnotations> 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<IMethodAnnotations> 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<IMethodAnnotations> 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<IMethodAnnotations> 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<IMethodAnnotations> 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<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations;
[ImportingConstructor]
TheHexTextEditorCommand(IDocumentTabService documentTabService, Lazy<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations;
[ImportingConstructor]
TheHexMenuCommand(IDocumentTabService documentTabService, Lazy<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations, HexContext context) {
var @ref = GetAddressReference(methodAnnotations, context);
if (@ref is not null)
documentTabService.FollowReference(@ref);
}
internal static bool IsVisibleInternal(Lazy<IMethodAnnotations> methodAnnotations, HexContext context) => GetAddressReference(methodAnnotations, context) is not null;
static AddressReference? GetAddressReference(Lazy<IMethodAnnotations> 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<MethodSourceStatement>? 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<IMethodAnnotations> methodAnnotations;
[ImportingConstructor]
TheHexTextEditorCommand(IDocumentTabService documentTabService, Lazy<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations;
[ImportingConstructor]
TheHexMenuCommand(IDocumentTabService documentTabService, Lazy<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations, HexContext context) {
var @ref = GetAddressReference(methodAnnotations, context);
if (@ref is not null)
documentTabService.FollowReference(@ref);
}
internal static bool IsVisibleInternal(Lazy<IMethodAnnotations> methodAnnotations, HexContext context) => GetAddressReference(methodAnnotations, context) is not null;
static AddressReference? GetAddressReference(Lazy<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations;
[ImportingConstructor]
TheHexTextEditorCommand(IDocumentTabService documentTabService, Lazy<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations;
[ImportingConstructor]
TheHexMenuCommand(IDocumentTabService documentTabService, Lazy<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations, HexContext context) {
var @ref = GetAddressReference(methodAnnotations, context);
if (@ref is not null)
documentTabService.FollowReference(@ref);
}
internal static bool IsVisibleInternal(Lazy<IMethodAnnotations> methodAnnotations, HexContext context) => GetAddressReference(methodAnnotations, context) is not null;
static AddressReference? GetAddressReference(Lazy<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations;
[ImportingConstructor]
TheHexTextEditorCommand(IDocumentTabService documentTabService, Lazy<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations;
[ImportingConstructor]
TheHexMenuCommand(IDocumentTabService documentTabService, Lazy<IMethodAnnotations> 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<IMethodAnnotations> methodAnnotations, HexContext context) {
var @ref = GetAddressReference(methodAnnotations, context);
if (@ref is not null)
documentTabService.FollowReference(@ref);
}
internal static bool IsVisibleInternal(Lazy<IMethodAnnotations> 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<IMethodAnnotations> 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<IHexBufferService> 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<IHexBufferService> hexBufferService;
[ImportingConstructor]
TheHexTextEditorCommand(Lazy<IHexBufferService> 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<IHexBufferService> hexBufferService;
[ImportingConstructor]
TheHexMenuCommand(Lazy<IHexBufferService> 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<IHexBufferService> hexBufferService;
[ImportingConstructor]
TheHexTextEditorCommand(Lazy<IHexBufferService> 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<IHexBufferService> hexBufferService;
[ImportingConstructor]
TheHexMenuCommand(Lazy<IHexBufferService> 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<IHexBufferService> hexBufferService;
[ImportingConstructor]
TheHexTextEditorCommand(Lazy<IHexBufferService> 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<IHexBufferService> hexBufferService;
[ImportingConstructor]
TheHexMenuCommand(Lazy<IHexBufferService> 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<IHexBufferService> hexBufferService;
[ImportingConstructor]
TheHexTextEditorCommand(Lazy<IHexBufferService> 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<IHexBufferService> hexBufferService;
[ImportingConstructor]
TheHexMenuCommand(Lazy<IHexBufferService> 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<IHexBufferService> 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<IHexBufferService> 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<IHexBufferService> hexBufferService;
[ImportingConstructor]
TheHexTextEditorCommand(Lazy<IHexBufferService> 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<IHexBufferService> hexBufferService;
[ImportingConstructor]
TheHexMenuCommand(Lazy<IHexBufferService> 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);
});
}
}