From 64e9e69a61cae540ad7f65f76a2a2f31234390b7 Mon Sep 17 00:00:00 2001 From: Sour Date: Tue, 21 Dec 2021 22:59:55 -0500 Subject: [PATCH] Hex Editor: Implemented more hex editor actions --- .../Debugger/Breakpoints/BreakpointManager.cs | 4 +- NewUI/Debugger/Controls/HexEditor.cs | 11 + NewUI/Debugger/Utilities/ContextMenuAction.cs | 59 ++++- .../Utilities/DebugShortcutManager.cs | 4 + .../Views/BreakpointListView.axaml.cs | 33 +-- NewUI/Debugger/Views/LabelListView.axaml.cs | 18 +- .../Windows/BreakpointEditWindow.axaml.cs | 16 ++ .../Debugger/Windows/LabelEditWindow.axaml.cs | 17 ++ .../Debugger/Windows/MemoryToolsWindow.axaml | 10 +- .../Windows/MemoryToolsWindow.axaml.cs | 217 ++++++++++++++++-- NewUI/Interop/DebugApi.cs | 15 ++ NewUI/Localization/resources.en.xml | 8 + NewUI/NewUI.csproj | 2 +- 13 files changed, 338 insertions(+), 76 deletions(-) diff --git a/NewUI/Debugger/Breakpoints/BreakpointManager.cs b/NewUI/Debugger/Breakpoints/BreakpointManager.cs index cb737590..2b2f32fc 100644 --- a/NewUI/Debugger/Breakpoints/BreakpointManager.cs +++ b/NewUI/Debugger/Breakpoints/BreakpointManager.cs @@ -52,7 +52,9 @@ namespace Mesen.Debugger public static void AddBreakpoint(Breakpoint bp) { - _breakpoints.Add(bp); + if(!_breakpoints.Contains(bp)) { + _breakpoints.Add(bp); + } RefreshBreakpoints(bp); } diff --git a/NewUI/Debugger/Controls/HexEditor.cs b/NewUI/Debugger/Controls/HexEditor.cs index 2130aa2d..dcf192d0 100644 --- a/NewUI/Debugger/Controls/HexEditor.cs +++ b/NewUI/Debugger/Controls/HexEditor.cs @@ -141,9 +141,20 @@ namespace Mesen.Debugger.Controls public HexEditor() { Focusable = true; + } + + protected override void OnPointerEnter(PointerEventArgs e) + { + base.OnPointerEnter(e); Cursor = new Cursor(StandardCursorType.Ibeam); } + protected override void OnPointerLeave(PointerEventArgs e) + { + base.OnPointerLeave(e); + Cursor = null; + } + private void MoveCursor(int offset, bool nibbleMode = false, bool keepNibble = false) { if(nibbleMode) { diff --git a/NewUI/Debugger/Utilities/ContextMenuAction.cs b/NewUI/Debugger/Utilities/ContextMenuAction.cs index 64992096..93e5d619 100644 --- a/NewUI/Debugger/Utilities/ContextMenuAction.cs +++ b/NewUI/Debugger/Utilities/ContextMenuAction.cs @@ -7,6 +7,7 @@ using Mesen.ViewModels; using ReactiveUI; using ReactiveUI.Fody.Helpers; using System; +using System.Collections.Generic; using System.Reactive; namespace Mesen.Debugger.Utilities @@ -15,7 +16,18 @@ namespace Mesen.Debugger.Utilities { public ActionType ActionType; - public string Name => ResourceHelper.GetEnumText(ActionType); + public string Name + { + get + { + string label = ResourceHelper.GetEnumText(ActionType); + if(HintText != null) { + label += " (" + HintText() + ")"; + } + return label; + } + } + public Image? Icon { get @@ -28,12 +40,33 @@ namespace Mesen.Debugger.Utilities } } + List? _subActions; + public List? SubActions + { + get => _subActions; + set + { + _subActions = value; + + if(_subActions != null) { + IsEnabled = () => { + foreach(ContextMenuAction subAction in _subActions) { + if(subAction.IsEnabled == null || subAction.IsEnabled()) { + return true; + } + } + return false; + }; + } + } + } + + public Func? HintText { get; set; } public Func? IsEnabled { get; set; } public Func? Shortcut { get; set; } public string ShortcutText => Shortcut?.Invoke().ToString() ?? ""; - [Reactive] public bool Enabled { get; set; } private ReactiveCommand? _clickCommand; @@ -70,6 +103,26 @@ namespace Mesen.Debugger.Utilities Paste, [IconFile("SelectAll")] - SelectAll + SelectAll, + + [IconFile("EditLabel")] + EditLabel, + + [IconFile("Add")] + AddWatch, + + [IconFile("BreakpointEnableDisable")] + EditBreakpoint, + + MarkSelectionAs, + + [IconFile("Accept")] + MarkAsCode, + + [IconFile("CheatCode")] + MarkAsData, + + [IconFile("Help")] + MarkAsUnidentified } } diff --git a/NewUI/Debugger/Utilities/DebugShortcutManager.cs b/NewUI/Debugger/Utilities/DebugShortcutManager.cs index ede8771f..b55bee67 100644 --- a/NewUI/Debugger/Utilities/DebugShortcutManager.cs +++ b/NewUI/Debugger/Utilities/DebugShortcutManager.cs @@ -33,6 +33,10 @@ namespace Mesen.Debugger.Utilities WeakReference weakAction = new WeakReference(action); WeakReference weakWnd = new WeakReference(wnd); + if(action.SubActions != null) { + RegisterActions(wnd, focusParent, action.SubActions); + } + EventHandler? handler = null; handler = (s, e) => { if(weakFocusParent.TryGetTarget(out IInputElement? elem) && weakAction.TryGetTarget(out ContextMenuAction? act)) { diff --git a/NewUI/Debugger/Views/BreakpointListView.axaml.cs b/NewUI/Debugger/Views/BreakpointListView.axaml.cs index 4d8b36d8..acd76d60 100644 --- a/NewUI/Debugger/Views/BreakpointListView.axaml.cs +++ b/NewUI/Debugger/Views/BreakpointListView.axaml.cs @@ -44,29 +44,22 @@ namespace Mesen.Debugger.Views DataGrid grid = (DataGrid)sender; Breakpoint? bp = grid.SelectedItem as Breakpoint; if(bp != null && grid != null) { - EditBreakpoint(bp, this); + BreakpointEditWindow.EditBreakpoint(bp, this); } } - private async void mnuAddBreakpoint_Click(object sender, RoutedEventArgs e) + private void mnuAddBreakpoint_Click(object sender, RoutedEventArgs e) { Breakpoint bp = new Breakpoint() { BreakOnRead = true, BreakOnWrite = true, BreakOnExec = true }; - BreakpointEditWindow wnd = new BreakpointEditWindow() { - DataContext = new BreakpointEditViewModel(bp) - }; - - bool result = await wnd.ShowCenteredDialog(this); - if(result) { - BreakpointManager.AddBreakpoint(bp); - } + BreakpointEditWindow.EditBreakpoint(bp, this); } private void mnuEditBreakpoint_Click(object sender, RoutedEventArgs e) { DataGrid grid = this.FindControl("DataGrid"); - Breakpoint? label = grid.SelectedItem as Breakpoint; - if(label != null && grid != null) { - EditBreakpoint(label, this); + Breakpoint? bp = grid.SelectedItem as Breakpoint; + if(bp != null && grid != null) { + BreakpointEditWindow.EditBreakpoint(bp, this); } } @@ -79,19 +72,5 @@ namespace Mesen.Debugger.Views } } } - - public async static void EditBreakpoint(Breakpoint bp, Control parent) - { - Breakpoint copy = bp.Clone(); - BreakpointEditWindow wnd = new BreakpointEditWindow() { - DataContext = new BreakpointEditViewModel(copy) - }; - - bool result = await wnd.ShowCenteredDialog(parent); - if(result) { - bp.CopyFrom(copy); - BreakpointManager.RefreshBreakpoints(); - } - } } } diff --git a/NewUI/Debugger/Views/LabelListView.axaml.cs b/NewUI/Debugger/Views/LabelListView.axaml.cs index 64686eac..a1c6fe4b 100644 --- a/NewUI/Debugger/Views/LabelListView.axaml.cs +++ b/NewUI/Debugger/Views/LabelListView.axaml.cs @@ -44,7 +44,7 @@ namespace Mesen.Debugger.Views DataGrid grid = this.FindControl("DataGrid"); CodeLabel ? label = grid.SelectedItem as CodeLabel; if(label != null && grid != null) { - EditLabel(label); + LabelEditWindow.EditLabel(this, label); } } @@ -64,21 +64,7 @@ namespace Mesen.Debugger.Views DataGrid grid = (DataGrid)sender; CodeLabel? label = grid.SelectedItem as CodeLabel; if(label != null && grid != null) { - EditLabel(label); - } - } - - private async void EditLabel(CodeLabel label) - { - CodeLabel copy = label.Clone(); - LabelEditWindow wnd = new LabelEditWindow() { - DataContext = new LabelEditViewModel(copy, label) - }; - - bool result = await wnd.ShowCenteredDialog(this); - if(result) { - label.CopyFrom(copy); - LabelManager.SetLabel(label, true); + LabelEditWindow.EditLabel(this, label); } } } diff --git a/NewUI/Debugger/Windows/BreakpointEditWindow.axaml.cs b/NewUI/Debugger/Windows/BreakpointEditWindow.axaml.cs index d42a95dc..f09e60c6 100644 --- a/NewUI/Debugger/Windows/BreakpointEditWindow.axaml.cs +++ b/NewUI/Debugger/Windows/BreakpointEditWindow.axaml.cs @@ -2,6 +2,8 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; +using Mesen.Debugger.ViewModels; +using Mesen.Utilities; namespace Mesen.Debugger.Windows { @@ -29,5 +31,19 @@ namespace Mesen.Debugger.Windows { Close(false); } + + public static async void EditBreakpoint(Breakpoint bp, Control parent) + { + Breakpoint copy = bp.Clone(); + BreakpointEditWindow wnd = new BreakpointEditWindow() { + DataContext = new BreakpointEditViewModel(copy) + }; + + bool result = await wnd.ShowCenteredDialog(parent); + if(result) { + bp.CopyFrom(copy); + BreakpointManager.AddBreakpoint(bp); + } + } } } diff --git a/NewUI/Debugger/Windows/LabelEditWindow.axaml.cs b/NewUI/Debugger/Windows/LabelEditWindow.axaml.cs index adee3c53..5dfcfcc9 100644 --- a/NewUI/Debugger/Windows/LabelEditWindow.axaml.cs +++ b/NewUI/Debugger/Windows/LabelEditWindow.axaml.cs @@ -2,6 +2,9 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; +using Mesen.Debugger.Labels; +using Mesen.Debugger.ViewModels; +using Mesen.Utilities; namespace Mesen.Debugger.Windows { @@ -20,6 +23,20 @@ namespace Mesen.Debugger.Windows AvaloniaXamlLoader.Load(this); } + public static async void EditLabel(Control parent, CodeLabel label) + { + CodeLabel copy = label.Clone(); + LabelEditWindow wnd = new LabelEditWindow() { + DataContext = new LabelEditViewModel(copy, label) + }; + + bool result = await wnd.ShowCenteredDialog(parent); + if(result) { + label.CopyFrom(copy); + LabelManager.SetLabel(label, true); + } + } + private void Ok_OnClick(object sender, RoutedEventArgs e) { Close(true); diff --git a/NewUI/Debugger/Windows/MemoryToolsWindow.axaml b/NewUI/Debugger/Windows/MemoryToolsWindow.axaml index d4a2712c..f47afe83 100644 --- a/NewUI/Debugger/Windows/MemoryToolsWindow.axaml +++ b/NewUI/Debugger/Windows/MemoryToolsWindow.axaml @@ -97,10 +97,12 @@ diff --git a/NewUI/Debugger/Windows/MemoryToolsWindow.axaml.cs b/NewUI/Debugger/Windows/MemoryToolsWindow.axaml.cs index ad4c99b3..87f634c9 100644 --- a/NewUI/Debugger/Windows/MemoryToolsWindow.axaml.cs +++ b/NewUI/Debugger/Windows/MemoryToolsWindow.axaml.cs @@ -20,6 +20,8 @@ using System.IO; using Mesen.Utilities; using Mesen.Localization; using Mesen.Config; +using Mesen.Debugger.Labels; +using System.Linq; namespace Mesen.Debugger.Windows { @@ -83,35 +85,202 @@ namespace Mesen.Debugger.Windows if(this.DataContext is MemoryToolsViewModel model) { _model = model; _model.Config.LoadWindowSettings(this); - - DebugConfig cfg = ConfigManager.Config.Debug; - object[] actions = new object[] { - new ContextMenuAction() { - ActionType = ActionType.Copy, - IsEnabled = () => _editor.SelectionLength > 0, - OnClick = () => _editor.CopySelection(), - Shortcut = () => cfg.Shortcuts.Copy - }, - new ContextMenuAction() { - ActionType = ActionType.Paste, - OnClick = () => _editor.PasteSelection(), - Shortcut = () => cfg.Shortcuts.Paste - }, - new Separator(), - new ContextMenuAction() { - ActionType = ActionType.SelectAll, - OnClick = () => _editor.SelectAll(), - Shortcut = () => cfg.Shortcuts.SelectAll - }, - }; - - DebugShortcutManager.RegisterActions(this, _editor, actions); - _model.SetActions(actions); + InitializeActions(); } else { throw new Exception("Invalid model"); } } + private void InitializeActions() + { + DebugConfig cfg = ConfigManager.Config.Debug; + object[] actions = new object[] { + GetMarkSelectionAction(), + new Separator(), + GetAddWatchAction(), + GetEditBreakpointAction(), + GetEditLabelAction(), + new Separator(), + new ContextMenuAction() { + ActionType = ActionType.Copy, + IsEnabled = () => _editor.SelectionLength > 0, + OnClick = () => _editor.CopySelection(), + Shortcut = () => cfg.Shortcuts.Copy + }, + new ContextMenuAction() { + ActionType = ActionType.Paste, + OnClick = () => _editor.PasteSelection(), + Shortcut = () => cfg.Shortcuts.Paste + }, + new Separator(), + new ContextMenuAction() { + ActionType = ActionType.SelectAll, + OnClick = () => _editor.SelectAll(), + Shortcut = () => cfg.Shortcuts.SelectAll + }, + }; + + DebugShortcutManager.RegisterActions(this, _editor, actions); + _model.SetActions(actions); + } + + private ContextMenuAction GetEditLabelAction() + { + AddressInfo? GetAddress(SnesMemoryType memType, int address) + { + if(memType.IsRelativeMemory()) { + AddressInfo relAddress = new AddressInfo() { + Address = address, + Type = memType + }; + + AddressInfo absAddress = DebugApi.GetAbsoluteAddress(relAddress); + return absAddress.Address >= 0 && absAddress.Type.SupportsLabels() ? absAddress : null; + } else { + return memType.SupportsLabels() ? new AddressInfo() { Address = address, Type = memType } : null; + } + } + + return new ContextMenuAction() { + ActionType = ActionType.EditLabel, + HintText = () => "$" + _editor.SelectionStart.ToString("X2"), + Shortcut = () => ConfigManager.Config.Debug.Shortcuts.MemoryViewer_EditLabel, + + IsEnabled = () => GetAddress(_model.Config.MemoryType, _editor.SelectionStart) != null, + + OnClick = () => { + AddressInfo? addr = GetAddress(_model.Config.MemoryType, _editor.SelectionStart); + if(addr == null) { + return; + } + + CodeLabel? label = LabelManager.GetLabel((uint)addr.Value.Address, addr.Value.Type); + if(label == null) { + label = new CodeLabel() { + Address = (uint)addr.Value.Address, + MemoryType = addr.Value.Type + }; + } + + LabelEditWindow.EditLabel(this, label); + } + }; + } + + private ContextMenuAction GetAddWatchAction() + { + return new ContextMenuAction() { + ActionType = ActionType.AddWatch, + HintText = () => GetAddressRange(), + Shortcut = () => ConfigManager.Config.Debug.Shortcuts.MemoryViewer_AddToWatch, + + IsEnabled = () => _model.Config.MemoryType.SupportsWatch(), + + OnClick = () => { + string[] toAdd = Enumerable.Range(_editor.SelectionStart, Math.Max(1, _editor.SelectionLength)).Select((num) => $"[${num.ToString("X2")}]").ToArray(); + WatchManager.GetWatchManager(_model.Config.MemoryType.ToCpuType()).AddWatch(toAdd); + } + }; + } + + private ContextMenuAction GetEditBreakpointAction() + { + return new ContextMenuAction() { + ActionType = ActionType.EditBreakpoint, + HintText = () => GetAddressRange(), + Shortcut = () => ConfigManager.Config.Debug.Shortcuts.MemoryViewer_EditBreakpoint, + + OnClick = () => { + uint startAddress = (uint)_editor.SelectionStart; + uint endAddress = (uint)(_editor.SelectionStart + Math.Max(1, _editor.SelectionLength) - 1); + + SnesMemoryType memType = _model.Config.MemoryType; + Breakpoint? bp = BreakpointManager.GetMatchingBreakpoint(startAddress, endAddress, memType); + if(bp == null) { + bp = new Breakpoint() { + MemoryType = memType, + CpuType = memType.ToCpuType(), + StartAddress = startAddress, + EndAddress = endAddress, + BreakOnWrite = true, + BreakOnRead = true + }; + if(bp.IsCpuBreakpoint) { + bp.BreakOnExec = true; + } + } + + BreakpointEditWindow.EditBreakpoint(bp, this); + } + }; + } + + private ContextMenuAction GetMarkSelectionAction() + { + return new ContextMenuAction() { + ActionType = ActionType.MarkSelectionAs, + HintText = () => GetAddressRange(), + SubActions = new List() { + new ContextMenuAction() { + ActionType = ActionType.MarkAsCode, + IsEnabled = () => GetMarkStartEnd(out _, out _), + Shortcut = () => ConfigManager.Config.Debug.Shortcuts.MarkAsCode, + OnClick = () => MarkSelectionAs(CdlFlags.Code) + }, + new ContextMenuAction() { + ActionType = ActionType.MarkAsData, + IsEnabled = () => GetMarkStartEnd(out _, out _), + Shortcut = () => ConfigManager.Config.Debug.Shortcuts.MarkAsData, + OnClick = () => MarkSelectionAs(CdlFlags.Data) + }, + new ContextMenuAction() { + ActionType = ActionType.MarkAsUnidentified, + IsEnabled = () => GetMarkStartEnd(out _, out _), + Shortcut = () => ConfigManager.Config.Debug.Shortcuts.MarkAsUnidentified, + OnClick = () => MarkSelectionAs(CdlFlags.None) + } + } + }; + } + + private bool GetMarkStartEnd(out int start, out int end) + { + SnesMemoryType memType = _model.Config.MemoryType; + start = _editor.SelectionStart; + end = start + Math.Max(1, _editor.SelectionLength) - 1; + + if(memType.IsRelativeMemory()) { + AddressInfo startAddr = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = start, Type = memType }); + AddressInfo endAddr = DebugApi.GetAbsoluteAddress(new AddressInfo() { Address = end, Type = memType }); + if(startAddr.Type == endAddr.Type && startAddr.Type.SupportsCdl()) { + start = startAddr.Address; + end = endAddr.Address; + } else { + return false; + } + } else if(!memType.SupportsCdl()) { + return false; + } + + return start >= 0 && end >= 0 && start <= end; + } + + private void MarkSelectionAs(CdlFlags type) + { + if(GetMarkStartEnd(out int start, out int end)) { + DebugApi.MarkBytesAs(_model.Config.MemoryType.ToCpuType(), (UInt32)start, (UInt32)end, type); + } + } + + private string GetAddressRange() + { + string address = "$" + _editor.SelectionStart.ToString("X2"); + if(_editor.SelectionLength > 1) { + address += "-$" + (_editor.SelectionStart + _editor.SelectionLength - 1).ToString("X2"); + } + return address; + } + private void listener_OnNotification(NotificationEventArgs e) { if(e.NotificationType == ConsoleNotificationType.PpuFrameDone || e.NotificationType == ConsoleNotificationType.CodeBreak) { diff --git a/NewUI/Interop/DebugApi.cs b/NewUI/Interop/DebugApi.cs index dd2d7a3f..efb15507 100644 --- a/NewUI/Interop/DebugApi.cs +++ b/NewUI/Interop/DebugApi.cs @@ -454,6 +454,21 @@ namespace Mesen.Interop return false; } + public static bool SupportsCdl(this SnesMemoryType memType) + { + switch(memType) { + case SnesMemoryType.CpuMemory: + case SnesMemoryType.GameboyMemory: + case SnesMemoryType.NesMemory: + case SnesMemoryType.PrgRom: + case SnesMemoryType.GbPrgRom: + case SnesMemoryType.NesPrgRom: + return true; + } + + return false; + } + public static string GetShortName(this SnesMemoryType memType) { return memType switch { diff --git a/NewUI/Localization/resources.en.xml b/NewUI/Localization/resources.en.xml index c409e666..12025d01 100644 --- a/NewUI/Localization/resources.en.xml +++ b/NewUI/Localization/resources.en.xml @@ -1469,6 +1469,14 @@ x == [$150] || y == [10] Copy Paste Select All + Edit Label + Add to Watch + Edit Breakpoint + + Mark as... + Code + Data + Unknown \ No newline at end of file diff --git a/NewUI/NewUI.csproj b/NewUI/NewUI.csproj index c594ea8e..3e4a5c56 100644 --- a/NewUI/NewUI.csproj +++ b/NewUI/NewUI.csproj @@ -504,6 +504,6 @@ - +