Hex Editor: Implemented more hex editor actions

This commit is contained in:
Sour 2021-12-21 22:59:55 -05:00
parent d4a72589b1
commit 64e9e69a61
13 changed files with 338 additions and 76 deletions

View file

@ -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);
}

View file

@ -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) {

View file

@ -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<ContextMenuAction>? _subActions;
public List<ContextMenuAction>? 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<string>? HintText { get; set; }
public Func<bool>? IsEnabled { get; set; }
public Func<DbgShortKeys>? Shortcut { get; set; }
public string ShortcutText => Shortcut?.Invoke().ToString() ?? "";
[Reactive] public bool Enabled { get; set; }
private ReactiveCommand<Unit, Unit>? _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
}
}

View file

@ -33,6 +33,10 @@ namespace Mesen.Debugger.Utilities
WeakReference<ContextMenuAction> weakAction = new WeakReference<ContextMenuAction>(action);
WeakReference<Window> weakWnd = new WeakReference<Window>(wnd);
if(action.SubActions != null) {
RegisterActions(wnd, focusParent, action.SubActions);
}
EventHandler<KeyEventArgs>? handler = null;
handler = (s, e) => {
if(weakFocusParent.TryGetTarget(out IInputElement? elem) && weakAction.TryGetTarget(out ContextMenuAction? act)) {

View file

@ -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<bool>(this);
if(result) {
BreakpointManager.AddBreakpoint(bp);
}
BreakpointEditWindow.EditBreakpoint(bp, this);
}
private void mnuEditBreakpoint_Click(object sender, RoutedEventArgs e)
{
DataGrid grid = this.FindControl<DataGrid>("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<bool>(parent);
if(result) {
bp.CopyFrom(copy);
BreakpointManager.RefreshBreakpoints();
}
}
}
}

View file

@ -44,7 +44,7 @@ namespace Mesen.Debugger.Views
DataGrid grid = this.FindControl<DataGrid>("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<bool>(this);
if(result) {
label.CopyFrom(copy);
LabelManager.SetLabel(label, true);
LabelEditWindow.EditLabel(this, label);
}
}
}

View file

@ -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<bool>(parent);
if(result) {
bp.CopyFrom(copy);
BreakpointManager.AddBreakpoint(bp);
}
}
}
}

View file

@ -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<bool>(parent);
if(result) {
label.CopyFrom(copy);
LabelManager.SetLabel(label, true);
}
}
private void Ok_OnClick(object sender, RoutedEventArgs e)
{
Close(true);

View file

@ -97,10 +97,12 @@
<ContextMenu Items="{CompiledBinding Actions}" >
<ContextMenu.Styles>
<Style Selector="MenuItem">
<Setter Property="Header" Value="{Binding Name}"/>
<Setter Property="Icon" Value="{Binding Icon}"/>
<Setter Property="Tag" Value="{Binding ShortcutText}"/>
<Setter Property="Command" Value="{Binding ClickCommand}"/>
<Setter Property="Header" Value="{Binding Name}" />
<Setter Property="Icon" Value="{Binding Icon}" />
<Setter Property="IsEnabled" Value="{Binding Enabled}" />
<Setter Property="Tag" Value="{Binding ShortcutText}" />
<Setter Property="Items" Value="{Binding SubActions}" />
<Setter Property="Command" Value="{Binding ClickCommand}" />
</Style>
</ContextMenu.Styles>
</ContextMenu>

View file

@ -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<ContextMenuAction>() {
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) {

View file

@ -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 {

View file

@ -1469,6 +1469,14 @@ x == [$150] || y == [10]
<Value ID="Copy">Copy</Value>
<Value ID="Paste">Paste</Value>
<Value ID="SelectAll">Select All</Value>
<Value ID="EditLabel">Edit Label</Value>
<Value ID="AddWatch">Add to Watch</Value>
<Value ID="EditBreakpoint">Edit Breakpoint</Value>
<Value ID="MarkSelectionAs">Mark as...</Value>
<Value ID="MarkAsCode">Code</Value>
<Value ID="MarkAsData">Data</Value>
<Value ID="MarkAsUnidentified">Unknown</Value>
</Enum>
</Enums>
</Resources>

View file

@ -504,6 +504,6 @@
<EmbeddedResource Include="Localization\resources.en.xml" WithCulture="false" Type="Non-Resx" />
</ItemGroup>
<Target Name="PreBuild" BeforeTargets="PreBuildEvent">
<Exec Command="cd $(OutDir)&#xD;&#xA;md Dependencies&#xD;&#xA;copy libHarfBuzzSharp.dll Dependencies&#xD;&#xA;copy libSkiaSharp.dll Dependencies&#xD;&#xA;copy MesenSCore.dll Dependencies&#xD;&#xA;DependencyPacker.exe&#xD;&#xA;copy Dependencies.zip $(ProjectDir)" />
<Exec Command="cd $(OutDir)&#xD;&#xA;rd Dependencies /s /q&#xD;&#xA;md Dependencies&#xD;&#xA;copy libHarfBuzzSharp.dll Dependencies&#xD;&#xA;copy libSkiaSharp.dll Dependencies&#xD;&#xA;copy MesenSCore.dll Dependencies&#xD;&#xA;DependencyPacker.exe&#xD;&#xA;copy Dependencies.zip $(ProjectDir)" />
</Target>
</Project>