Mesen2/UI/Debugger/ViewModels/DebuggerWindowViewModel.cs
Sour 8d6830a70a SNES: Add support for ST018 coprocessor
Used by a single game: Hayazashi Nidan Morita Shougi 2
2024-12-29 23:41:52 +09:00

968 lines
34 KiB
C#

using Avalonia.Controls;
using Avalonia.Media.Imaging;
using Avalonia.Threading;
using Dock.Model.Controls;
using Dock.Model.Core;
using Dock.Model.Mvvm.Controls;
using Mesen.Config;
using Mesen.Debugger.Disassembly;
using Mesen.Debugger.Integration;
using Mesen.Debugger.Labels;
using Mesen.Debugger.StatusViews;
using Mesen.Debugger.Utilities;
using Mesen.Debugger.ViewModels.DebuggerDock;
using Mesen.Debugger.Windows;
using Mesen.Interop;
using Mesen.Localization;
using Mesen.Utilities;
using Mesen.ViewModels;
using Mesen.Windows;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Reactive;
using System.Threading.Tasks;
namespace Mesen.Debugger.ViewModels
{
public class DebuggerWindowViewModel : DisposableViewModel
{
[Reactive] public string Title { get; private set; } = "Debugger";
[Reactive] public WindowIcon? Icon { get; private set; } = null;
[Reactive] public bool IsMainCpuDebugger { get; private set; } = true;
[Reactive] public DebuggerConfig Config { get; private set; }
[Reactive] public DebuggerOptionsViewModel Options { get; private set; }
[Reactive] public DisassemblyViewModel Disassembly { get; private set; }
[Reactive] public BreakpointListViewModel BreakpointList { get; private set; }
[Reactive] public WatchListViewModel WatchList { get; private set; }
[Reactive] public BaseConsoleStatusViewModel? ConsoleStatus { get; private set; }
[Reactive] public LabelListViewModel LabelList { get; private set; }
[Reactive] public FunctionListViewModel? FunctionList { get; private set; }
[Reactive] public CallStackViewModel CallStack { get; private set; }
[Reactive] public SourceViewViewModel? SourceView { get; private set; }
[Reactive] public MemoryMappingViewModel? MemoryMappings { get; private set; }
[Reactive] public FindResultListViewModel FindResultList { get; private set; }
[Reactive] public ControllerListViewModel ControllerList { get; private set; }
[Reactive] public DebuggerDockFactory DockFactory { get; private set; }
[Reactive] public IRootDock DockLayout { get; private set; }
[Reactive] public string BreakReason { get; private set; } = "";
[Reactive] public string BreakElapsedCycles { get; private set; } = "";
[Reactive] public string BreakElapsedCyclesTooltip { get; private set; } = "";
[Reactive] public string CdlStats { get; private set; } = "";
[Reactive] public List<ContextMenuAction> ToolbarItems { get; private set; } = new();
[Reactive] public List<ContextMenuAction> FileMenuItems { get; private set; } = new();
[Reactive] public List<ContextMenuAction> DebugMenuItems { get; private set; } = new();
[Reactive] public List<ContextMenuAction> SearchMenuItems { get; private set; } = new();
[Reactive] public List<ContextMenuAction> OptionMenuItems { get; private set; } = new();
public CpuType CpuType { get; private set; }
private UInt64 _masterClock = 0;
private bool _autoSwitchToSourceView = false;
private List<object> _gotoSubActions = new();
[Obsolete("For designer only")]
public DebuggerWindowViewModel() : this(null) { }
public DebuggerWindowViewModel(CpuType? cpuType)
{
if(!Design.IsDesignMode) {
DebugApi.InitializeDebugger();
}
ConsoleType consoleType;
if(Design.IsDesignMode) {
CpuType = CpuType.Snes;
consoleType = ConsoleType.Snes;
} else {
RomInfo romInfo = EmuApi.GetRomInfo();
consoleType = romInfo.ConsoleType;
if(cpuType != null) {
CpuType = cpuType.Value;
if(consoleType.GetMainCpuType() != CpuType) {
Title = ResourceHelper.GetEnumText(CpuType) + " Debugger";
Icon = new WindowIcon(ImageUtilities.BitmapFromAsset("Assets/" + CpuType.ToString() + "Debugger.png"));
IsMainCpuDebugger = false;
} else {
Icon = new WindowIcon(ImageUtilities.BitmapFromAsset("Assets/Debugger.png"));
}
} else {
CpuType = romInfo.ConsoleType.GetMainCpuType();
Icon = new WindowIcon(ImageUtilities.BitmapFromAsset("Assets/Debugger.png"));
}
}
Config = ConfigManager.Config.Debug.Debugger;
Options = new DebuggerOptionsViewModel(Config, CpuType);
Disassembly = AddDisposable(new DisassemblyViewModel(this, ConfigManager.Config.Debug, CpuType));
BreakpointList = AddDisposable(new BreakpointListViewModel(CpuType, this));
LabelList = AddDisposable(new LabelListViewModel(CpuType, this));
FindResultList = AddDisposable(new FindResultListViewModel(this));
ControllerList = new ControllerListViewModel(consoleType);
if(CpuType.SupportsFunctionList()) {
FunctionList = AddDisposable(new FunctionListViewModel(CpuType, this));
}
CallStack = AddDisposable(new CallStackViewModel(CpuType, this));
WatchList = AddDisposable(new WatchListViewModel(CpuType));
ConsoleStatus = CpuType switch {
CpuType.Snes => new SnesStatusViewModel(CpuType.Snes),
CpuType.Spc => new SpcStatusViewModel(),
CpuType.NecDsp => new NecDspStatusViewModel(),
CpuType.Sa1 => new SnesStatusViewModel(CpuType.Sa1),
CpuType.Gsu => new GsuStatusViewModel(),
CpuType.Cx4 => new Cx4StatusViewModel(),
CpuType.St018 => new St018StatusViewModel(),
CpuType.Gameboy => new GbStatusViewModel(),
CpuType.Nes => new NesStatusViewModel(),
CpuType.Pce => new PceStatusViewModel(),
CpuType.Sms => new SmsStatusViewModel(),
CpuType.Gba => new GbaStatusViewModel(),
CpuType.Ws => new WsStatusViewModel(),
_ => null
};
DockFactory = new DebuggerDockFactory(Config.SavedDockLayout);
DockFactory.BreakpointListTool.Model = BreakpointList;
DockFactory.LabelListTool.Model = LabelList;
DockFactory.FunctionListTool.Model = FunctionList;
DockFactory.CallStackTool.Model = CallStack;
DockFactory.WatchListTool.Model = WatchList;
DockFactory.FindResultListTool.Model = FindResultList;
DockFactory.ControllerListTool.Model = ControllerList;
DockFactory.DisassemblyTool.Model = Disassembly;
DockFactory.SourceViewTool.Model = null;
DockFactory.StatusTool.Model = ConsoleStatus;
DockLayout = DockFactory.CreateLayout();
InitDock();
if(Design.IsDesignMode) {
return;
}
AddDisposable(ReactiveHelper.RegisterRecursiveObserver(Config, Config_PropertyChanged));
if(CpuType.SupportsMemoryMappings()) {
MemoryMappings = new MemoryMappingViewModel(CpuType);
}
DebugWorkspaceManager.SymbolProviderChanged += DebugWorkspaceManager_SymbolProviderChanged;
LabelManager.OnLabelUpdated += LabelManager_OnLabelUpdated;
BreakpointManager.BreakpointsChanged += BreakpointManager_BreakpointsChanged;
BreakpointManager.AddCpuType(CpuType);
ConfigApi.SetDebuggerFlag(CpuType.GetDebuggerFlag(), true);
}
private void InitDock()
{
DockFactory.InitLayout(DockLayout);
if(FunctionList == null) {
DockFactory.CloseDockable(DockFactory.FunctionListTool);
}
if(Design.IsDesignMode) {
return;
}
UpdateSourceViewState();
}
public void ScrollToAddress(int address)
{
Disassembly.SetSelectedRow(address, true, true);
SourceView?.GoToRelativeAddress(address, true);
IDockable codeTool = GetActiveCodeTool();
DockFactory.SetFocusedDockable(DockLayout, codeTool);
}
private void DebugWorkspaceManager_SymbolProviderChanged(object? sender, EventArgs e)
{
UpdateSourceViewState();
}
public void Init()
{
WatchList.UpdateWatch();
CallStack.UpdateCallStack();
LabelList.UpdateLabelList();
FunctionList?.UpdateFunctionList();
BreakpointList.UpdateBreakpoints();
}
protected override void DisposeView()
{
Config.SavedDockLayout = DockFactory.ToDockDefinition(DockLayout);
DebugWorkspaceManager.SymbolProviderChanged -= DebugWorkspaceManager_SymbolProviderChanged;
LabelManager.OnLabelUpdated -= LabelManager_OnLabelUpdated;
BreakpointManager.BreakpointsChanged -= BreakpointManager_BreakpointsChanged;
BreakpointManager.RemoveCpuType(CpuType);
ConfigApi.SetDebuggerFlag(CpuType.GetDebuggerFlag(), false);
}
private void Config_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
ConfigManager.Config.Debug.ApplyConfig();
UpdateDisassembly(false);
}
private void UpdateSourceViewState()
{
ISymbolProvider? provider = DebugWorkspaceManager.SymbolProvider;
if(provider != null) {
SourceView = new SourceViewViewModel(this, provider, CpuType);
DockFactory.SourceViewTool.Model = SourceView;
SourceView.SetActiveAddress(Disassembly.ActiveAddress);
if(!IsToolVisible(DockFactory.SourceViewTool)) {
if(DockFactory.SourceViewTool.Owner is IDock dock && IsDockVisible(dock)) {
DockFactory.AddDockable(dock, DockFactory.SourceViewTool);
} else if(DockFactory.DisassemblyTool.Owner is IDock disassemblyDock) {
DockFactory.AddDockable(disassemblyDock, DockFactory.SourceViewTool);
}
}
} else {
SourceView = null;
DockFactory.SourceViewTool.Model = null;
if(IsToolVisible(DockFactory.SourceViewTool)) {
DockFactory.SourceViewTool.CanClose = true;
DockFactory.CloseDockable(DockFactory.SourceViewTool);
DockFactory.SourceViewTool.CanClose = false;
}
}
}
public void UpdateConsoleState()
{
ConsoleStatus?.UpdateConsoleState();
}
private void Manager_WatchChanged(object? sender, EventArgs e)
{
WatchList.UpdateWatch();
}
private void LabelManager_OnLabelUpdated(object? sender, EventArgs e)
{
LabelList.UpdateLabelList();
BreakpointList.RefreshBreakpointList();
CallStack.RefreshCallStack();
FunctionList?.UpdateFunctionList();
Disassembly.Refresh();
}
private void BreakpointManager_BreakpointsChanged(object? sender, EventArgs e)
{
Disassembly.InvalidateVisual();
SourceView?.InvalidateVisual();
BreakpointList.UpdateBreakpoints();
}
public void UpdateDebugger(bool forBreak = false, BreakEvent? evt = null)
{
ConsoleStatus?.UpdateUiState();
if(forBreak) {
if(ConsoleStatus?.EditAllowed == false) {
ConsoleStatus.EditAllowed = true;
}
UpdateStatusBar(evt);
}
UpdateDisassembly(forBreak);
MemoryMappings?.Refresh();
BreakpointList.RefreshBreakpointList();
LabelList.RefreshLabelList();
FunctionList?.UpdateFunctionList();
WatchList.UpdateWatch();
CallStack.UpdateCallStack();
}
public void PartialRefresh(bool refreshWatch)
{
ConsoleStatus?.UpdateUiState(true);
MemoryMappings?.Refresh();
UpdateCdlStats();
if(refreshWatch) {
WatchList.UpdateWatch();
}
}
private void UpdateStatusBar(BreakEvent? evt)
{
if(ConsoleStatus?.ElapsedCycles > 0) {
string elapsedCycles = $"{CodeTooltipHelper.FormatValue(ConsoleStatus.ElapsedCycles, 999999)} cycles elapsed";
string tooltip = $"{ConsoleStatus.ElapsedCycles:N0} cycles elapsed";
if(CpuType == CpuType.Snes) {
UInt64 prevMasterClock = _masterClock;
_masterClock = EmuApi.GetTimingInfo(CpuType).MasterClock;
if(prevMasterClock > 0 && prevMasterClock < _masterClock) {
elapsedCycles += $" ({CodeTooltipHelper.FormatValue(_masterClock - prevMasterClock, 999999)} master clocks)";
tooltip += $" ({_masterClock - prevMasterClock:N0} master clocks)";
}
}
BreakElapsedCycles = elapsedCycles;
BreakElapsedCyclesTooltip = tooltip;
} else {
BreakElapsedCycles = "";
BreakElapsedCyclesTooltip = "";
}
UpdateCdlStats();
if(evt != null) {
string breakReason = "";
BreakEvent brkEvent = evt.Value;
if(brkEvent.Source != BreakSource.Unspecified) {
breakReason = ResourceHelper.GetEnumText(brkEvent.Source);
if(brkEvent.Source == BreakSource.Breakpoint) {
Breakpoint? bp = BreakpointManager.GetBreakpointById(brkEvent.BreakpointId);
if(bp?.IsAssert == true) {
breakReason = "Assert failed: " + bp.Condition.Substring(2, bp.Condition.Length - 3);
} else {
breakReason += (
": " +
ResourceHelper.GetEnumText(brkEvent.Operation.Type) + " " +
brkEvent.Operation.MemType.GetShortName() +
" ($" + brkEvent.Operation.Address.ToString("X4") +
":$" + brkEvent.Operation.Value.ToString("X2") + ")"
);
}
}
}
BreakReason = breakReason;
} else {
BreakReason = "";
}
}
private void UpdateCdlStats()
{
string statsString = "";
if(CpuType.ToMemoryType().SupportsCdl()) {
CdlStatistics stats = DebugApi.GetCdlStatistics(CpuType.GetPrgRomMemoryType());
if(stats.TotalBytes > 0) {
statsString = $"Code: {(double)stats.CodeBytes / stats.TotalBytes * 100:0.00}% Data: {(double)stats.DataBytes / stats.TotalBytes * 100:0.00}%";
if(stats.TotalChrBytes > 0) {
statsString += $" Drawn (CHR ROM): {(double)stats.DrawnChrBytes / stats.TotalChrBytes * 100:0.00}%";
}
}
}
CdlStats = statsString;
}
public void UpdateActiveAddress(bool scrollToAddress)
{
if(scrollToAddress) {
//Scroll to the active address and highlight it
int activeAddress = (int)DebugApi.GetProgramCounter(CpuType, true);
Disassembly.SetActiveAddress(activeAddress);
if(SourceView != null) {
bool sourceMappingFound = SourceView.SetActiveAddress(activeAddress);
if(!sourceMappingFound) {
//If location is not found in source mappings, swap to disassembly view
if(IsToolVisible(DockFactory.SourceViewTool) && IsToolActive(DockFactory.SourceViewTool)) {
_autoSwitchToSourceView = true;
OpenTool(DockFactory.DisassemblyTool);
}
} else if(_autoSwitchToSourceView) {
//If we previously auto-switched to disassembly view, go back to the
//source view automatically if the current address is mapped to a source file
OpenTool(DockFactory.SourceViewTool);
_autoSwitchToSourceView = false;
}
}
if(!EmuApi.IsPaused()) {
//Clear the highlight if the emulation is still running
Disassembly.SetActiveAddress(null);
SourceView?.SetActiveAddress(null);
}
}
}
public void UpdateDisassembly(bool scrollToActiveAddress)
{
UpdateActiveAddress(scrollToActiveAddress);
Disassembly.Refresh();
SourceView?.Refresh();
}
public void ProcessResumeEvent(Window wnd)
{
if(ConsoleStatus != null) {
//Disable status fields in 50ms (if the debugger isn't paused by then)
//This improves performance when stepping through code, etc.
Task.Run(() => {
System.Threading.Thread.Sleep(50);
Dispatcher.UIThread.Post(() => {
BaseConsoleStatusViewModel status = ConsoleStatus;
if(status != null && status.EditAllowed && !EmuApi.IsPaused()) {
status.EditAllowed = false;
if(DockFactory.StatusTool.Owner is IDock parent && parent.IsActive && parent.ActiveDockable == DockFactory.StatusTool) {
//If status tool is active when it gets disabled, give window focus to avoid focus issues in Avalonia
//Disabling focused controls by setting IsEnabled=false on a parent does not appear to behave properly
//The controls keep their focus and can be modified
wnd.Focus();
}
}
});
});
}
Disassembly.SetActiveAddress(null);
Disassembly.InvalidateVisual();
SourceView?.SetActiveAddress(null);
SourceView?.InvalidateVisual();
}
private ToolDock? FindToolDock(IDock dock)
{
if(dock is ToolDock) {
return (ToolDock)dock;
}
if(dock.VisibleDockables == null) {
return null;
}
foreach(IDockable dockable in dock.VisibleDockables) {
if(dockable is IDock) {
ToolDock? result = FindToolDock((IDock)dockable);
if(result != null) {
return result;
}
}
}
return null;
}
private bool IsToolActive(Tool tool)
{
return (tool.Owner as IDock)?.ActiveDockable == tool;
}
private bool IsDockVisible(IDock? dock)
{
return dock is RootDock || (dock != null && (dock.Owner as IDock)?.VisibleDockables?.Contains(dock) == true && (dock.Owner == null || !(dock.Owner is IDock) || IsDockVisible(dock.Owner as IDock)));
}
private bool IsToolVisible(Tool tool)
{
return (tool.Owner as IDock)?.VisibleDockables?.Contains(tool) == true && IsDockVisible(tool.Owner as IDock);
}
private void ToggleTool(Tool tool)
{
if(IsToolVisible(tool)) {
DockFactory.CloseDockable(tool);
} else {
OpenTool(tool);
}
}
public void OpenTool(Tool tool)
{
if(!IsToolVisible(tool)) {
IDockable? visibleTool = DockFactory.FindDockable(DockLayout, x => x is BaseToolContainerViewModel && x != DockFactory.DisassemblyTool && x.Owner is IDock owner && owner.VisibleDockables?.Contains(x) == true);
if(visibleTool?.Owner is IDock dock) {
DockFactory.AddDockable(dock, tool);
} else {
//Couldn't find any where else to open tool, create a new section
if(DockLayout.VisibleDockables?.Count > 0 && DockLayout.VisibleDockables[0] is IDock mainDock) {
DockFactory.SplitToDock(mainDock, new ToolDock {
Proportion = 0.33,
VisibleDockables = DockFactory.CreateList<IDockable>(tool)
}, DockOperation.Bottom);
}
}
}
DockFactory.SetActiveDockable(tool);
DockFactory.SetFocusedDockable(DockLayout, tool);
}
public void InitializeMenu(Window wnd)
{
DebuggerConfig cfg = ConfigManager.Config.Debug.Debugger;
_gotoSubActions = new() {
new ContextMenuAction() {
ActionType = ActionType.GoToAddress,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.GoToAddress),
OnClick = async () => {
MemoryType memType = CpuType.ToMemoryType();
int? address = await new GoToWindow(CpuType, memType, DebugApi.GetMemorySize(memType) - 1).ShowCenteredDialog<int?>(wnd);
if(address != null) {
ScrollToAddress(address.Value);
}
}
},
new ContextMenuAction() {
ActionType = ActionType.GoToProgramCounter,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.GoToProgramCounter),
OnClick = () => {
if(Disassembly.ActiveAddress != null) {
ScrollToAddress(Disassembly.ActiveAddress.Value);
}
}
}
};
InitGoToCpuVectorActions();
ToolbarItems = AddDisposables(GetDebugMenu(wnd, true));
DebugMenuItems = AddDisposables(GetDebugMenu(wnd, false));
ToolbarItems.Add(new ContextMenuAction() {
ActionType = ActionType.GoTo,
SubActions = _gotoSubActions
});
FileMenuItems = AddDisposables(new List<ContextMenuAction>() {
SaveRomActionHelper.GetSaveRomAction(wnd),
SaveRomActionHelper.GetSaveRomAsAction(wnd),
SaveRomActionHelper.GetSaveEditsAsIpsAction(wnd),
new ContextMenuSeparator(),
GetCdlActionMenu(wnd),
GetWorkspaceActionMenu(wnd),
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.ResetLayout,
OnClick = () => {
DockLayout = DockFactory.GetDefaultLayout();
InitDock();
}
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.Exit,
OnClick = () => wnd.Close()
}
});
OptionMenuItems = AddDisposables(new List<ContextMenuAction>() {
new ContextMenuAction() {
ActionType = ActionType.ShowSettingsPanel,
IsSelected = () => cfg.ShowSettingsPanel,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ToggleSettingsPanel),
OnClick = () => cfg.ShowSettingsPanel = !cfg.ShowSettingsPanel
},
new ContextMenuAction() {
ActionType = ActionType.ShowMemoryMappings,
IsVisible = () => MemoryMappings != null,
IsSelected = () => cfg.ShowMemoryMappings,
OnClick = () => cfg.ShowMemoryMappings = !cfg.ShowMemoryMappings
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.ShowBreakpointList,
IsSelected = () => IsToolVisible(DockFactory.BreakpointListTool),
OnClick = () => ToggleTool(DockFactory.BreakpointListTool)
},
new ContextMenuAction() {
ActionType = ActionType.ShowCallStack,
IsSelected = () => IsToolVisible(DockFactory.CallStackTool),
OnClick = () => ToggleTool(DockFactory.CallStackTool)
},
new ContextMenuAction() {
ActionType = ActionType.ShowConsoleStatus,
IsSelected = () => IsToolVisible(DockFactory.StatusTool),
OnClick = () => ToggleTool(DockFactory.StatusTool)
},
new ContextMenuAction() {
ActionType = ActionType.ShowControllers,
IsVisible = () => FunctionList != null,
IsSelected = () => IsToolVisible(DockFactory.ControllerListTool),
OnClick = () => ToggleTool(DockFactory.ControllerListTool)
},
new ContextMenuAction() {
ActionType = ActionType.ShowFunctionList,
IsVisible = () => FunctionList != null,
IsSelected = () => IsToolVisible(DockFactory.FunctionListTool),
OnClick = () => ToggleTool(DockFactory.FunctionListTool)
},
new ContextMenuAction() {
ActionType = ActionType.ShowLabelList,
IsSelected = () => IsToolVisible(DockFactory.LabelListTool),
OnClick = () => ToggleTool(DockFactory.LabelListTool)
},
new ContextMenuAction() {
ActionType = ActionType.ShowWatchList,
IsSelected = () => IsToolVisible(DockFactory.WatchListTool),
OnClick = () => ToggleTool(DockFactory.WatchListTool)
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.OpenDebugSettings,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.OpenDebugSettings),
OnClick = () => DebuggerConfigWindow.Open(DebugConfigWindowTab.Debugger, wnd)
},
});
SearchMenuItems = AddDisposables(new List<ContextMenuAction>() {
new ContextMenuAction() {
ActionType = ActionType.GoTo,
SubActions = _gotoSubActions
},
new ContextMenuAction() {
ActionType = ActionType.GoToAll,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.GoToAll),
OnClick = async () => {
GoToDestination? dest = await GoToAllWindow.Open(wnd, CpuType, GoToAllOptions.ShowFilesAndConstants, DebugWorkspaceManager.SymbolProvider);
if(dest != null) {
if(GetActiveCodeTool() != DockFactory.SourceViewTool && dest.RelativeAddress?.Type == CpuType.ToMemoryType()) {
//Try to stay in disassembly view if it was the last view used
ScrollToAddress(dest.RelativeAddress.Value.Address);
} else if(dest.SourceLocation != null) {
OpenTool(DockFactory.SourceViewTool);
SourceView?.ScrollToLocation(dest.SourceLocation.Value);
} else if(dest.RelativeAddress?.Type == CpuType.ToMemoryType()) {
ScrollToAddress(dest.RelativeAddress.Value.Address);
}
}
}
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.Find,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.Find),
OnClick = () => GetActiveQuickSearch().Open()
},
new ContextMenuAction() {
ActionType = ActionType.FindPrev,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.FindPrev),
OnClick = () => GetActiveQuickSearch().FindPrev()
},
new ContextMenuAction() {
ActionType = ActionType.FindNext,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.FindNext),
OnClick = () => GetActiveQuickSearch().FindNext()
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.FindOccurrences,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.FindOccurrences),
OnClick = async () => {
FindAllOccurrencesWindow searchWnd = new();
string? search = await searchWnd.ShowCenteredDialog<string?>(wnd);
if(search!= null) {
DisassemblySearchOptions options = new() { MatchWholeWord = searchWnd.MatchWholeWord, MatchCase = searchWnd.MatchCase };
FindAllOccurrences(search, options);
}
}
},
});
DebugShortcutManager.RegisterActions(wnd, FileMenuItems);
DebugShortcutManager.RegisterActions(wnd, DebugMenuItems);
DebugShortcutManager.RegisterActions(wnd, SearchMenuItems);
DebugShortcutManager.RegisterActions(wnd, OptionMenuItems);
}
private void InitGoToCpuVectorActions()
{
DebuggerFeatures features = DebugApi.GetDebuggerFeatures(CpuType);
if(features.CpuVectorCount == 0) {
return;
}
int getAddress(CpuVectorDefinition def)
{
if(def.Type == VectorType.Indirect) {
byte[] vector = DebugApi.GetMemoryValues(CpuType.ToMemoryType(), def.Address, def.Address + 1);
return vector[0] | (vector[1] << 8);
} else if(def.Type == VectorType.Direct) {
return (int)def.Address;
} else {
byte[] vector = Array.Empty<byte>();
if(def.Type == VectorType.x86) {
vector = DebugApi.GetMemoryValues(CpuType.ToMemoryType(), def.Address, def.Address + 3);
} else if(def.Type == VectorType.x86WithOffset) {
byte irqVectorOffset = DebugApi.GetDebuggerFeatures(CpuType).IrqVectorOffset;
uint baseAddr = (irqVectorOffset + def.Address) * 4;
vector = DebugApi.GetMemoryValues(CpuType.ToMemoryType(), baseAddr, baseAddr + 3);
}
UInt16 ip = (UInt16)(vector[0] | (vector[1] << 8));
UInt16 cs = (UInt16)(vector[2] | (vector[3] << 8));
return (cs << 4) + ip;
}
}
Func<DbgShortKeys>? getShortcut(int index)
{
switch(index) {
case 0: return () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.GoToCpuVector1);
case 1: return () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.GoToCpuVector2);
case 2: return () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.GoToCpuVector3);
case 3: return () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.GoToCpuVector4);
case 4: return () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.GoToCpuVector5);
}
return null;
}
_gotoSubActions.Add(new ContextMenuSeparator());
for(int i = 0; i < features.CpuVectorCount; i++) {
CpuVectorDefinition def = features.CpuVectors[i];
string name = Utf8Utilities.GetStringFromArray(features.CpuVectors[i].Name);
_gotoSubActions.Add(new ContextMenuAction() {
ActionType = ActionType.Custom,
Shortcut = getShortcut(i),
HintText = () => $"${getAddress(def):X4}",
OnClick = () => ScrollToAddress(getAddress(def)),
CustomText = name
});
}
}
private List<ContextMenuAction> GetDebugMenu(Control wnd, bool forToolbar)
{
List<ContextMenuAction> debugMenu = new();
debugMenu.AddRange(DebugSharedActions.GetStepActions(wnd, () => CpuType));
if(!forToolbar) {
debugMenu.AddRange(new List<ContextMenuAction> {
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.Reset,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.Reset),
OnClick = () => ShortcutHandler.Reset()
},
new ContextMenuAction() {
ActionType = ActionType.PowerCycle,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.PowerCycle),
OnClick = () => ShortcutHandler.PowerCycle()
},
new ContextMenuAction() {
ActionType = ActionType.ReloadRom,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ReloadRom),
OnClick = () => ShortcutHandler.ReloadRom()
}
});
};
return debugMenu;
}
private ContextMenuAction GetCdlActionMenu(Window wnd)
{
return new ContextMenuAction() {
ActionType = ActionType.CodeDataLogger,
IsEnabled = () => CpuType.ToMemoryType().SupportsCdl(),
SubActions = new() {
new ContextMenuAction() {
ActionType = ActionType.ResetCdl,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ResetCdl),
OnClick = () => {
DebugApi.ResetCdl(CpuType.GetPrgRomMemoryType());
Disassembly.Refresh();
UpdateCdlStats();
}
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.LoadCdl,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.LoadCdl),
OnClick = async () => {
string? filename = await FileDialogHelper.OpenFile(ConfigManager.DebuggerFolder, wnd, FileDialogHelper.CdlExt);
if(filename != null) {
DebugApi.LoadCdlFile(CpuType.GetPrgRomMemoryType(), filename);
Disassembly.Refresh();
UpdateCdlStats();
}
}
},
new ContextMenuAction() {
ActionType = ActionType.SaveCdl,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.SaveCdl),
OnClick = async () => {
string? filename = await FileDialogHelper.SaveFile(ConfigManager.DebuggerFolder, EmuApi.GetRomInfo().GetRomName() + ".cdl", wnd, FileDialogHelper.CdlExt);
if(filename != null) {
DebugApi.SaveCdlFile(CpuType.GetPrgRomMemoryType(), filename);
}
}
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.GenerateRom,
SubActions = new() {
new ContextMenuAction() {
ActionType = ActionType.CdlRomStripUnused,
OnClick = () => SaveRomActionHelper.SaveRomAs(wnd, false, CdlStripOption.StripUnused)
},
new ContextMenuAction() {
ActionType = ActionType.CdlRomStripUsed,
OnClick = () => SaveRomActionHelper.SaveRomAs(wnd, false, CdlStripOption.StripUsed)
}
}
},
}
};
}
private ContextMenuAction GetWorkspaceActionMenu(Window wnd)
{
return new ContextMenuAction() {
ActionType = ActionType.Workspace,
SubActions = new() {
new ContextMenuAction() {
ActionType = ActionType.ImportLabels,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ImportLabels),
OnClick = async () => {
string? filename = await FileDialogHelper.OpenFile(null, wnd, FileDialogHelper.LabelFileExt);
if(filename != null) {
DebugWorkspaceManager.LoadSupportedFile(filename, true);
}
}
},
new ContextMenuAction() {
ActionType = ActionType.ExportLabels,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ExportLabels),
OnClick = async () => {
string initFilename = EmuApi.GetRomInfo().GetRomName() + "." + FileDialogHelper.MesenLabelExt;
string? filename = await FileDialogHelper.SaveFile(null, initFilename, wnd, FileDialogHelper.MesenLabelExt);
if(filename != null) {
MesenLabelFile.Export(filename);
}
}
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.ImportWatchEntries,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ImportWatchEntries),
OnClick = async () => {
string? filename = await FileDialogHelper.OpenFile(null, wnd, FileDialogHelper.WatchFileExt);
if(filename != null) {
WatchManager.GetWatchManager(CpuType).Import(filename);
}
}
},
new ContextMenuAction() {
ActionType = ActionType.ExportWatchEntries,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ExportWatchEntries),
OnClick = async () => {
string? filename = await FileDialogHelper.SaveFile(null, null, wnd, FileDialogHelper.WatchFileExt);
if(filename != null) {
WatchManager.GetWatchManager(CpuType).Export(filename);
}
}
},
new ContextMenuSeparator(),
new ContextMenuAction() {
ActionType = ActionType.ResetWorkspace,
Shortcut = () => ConfigManager.Config.Debug.Shortcuts.Get(DebuggerShortcut.ResetWorkspace),
OnClick = async () => {
if(await MesenMsgBox.Show(wnd, "ResetWorkspaceConfirmation", MessageBoxButtons.OKCancel, MessageBoxIcon.Warning) == DialogResult.OK) {
DebugWorkspaceManager.Workspace.Reset();
}
}
},
}
};
}
private BaseToolContainerViewModel GetActiveCodeTool()
{
if(DockLayout.ActiveDockable == DockFactory.SourceViewTool) {
return DockFactory.SourceViewTool;
} else if(DockLayout.ActiveDockable == DockFactory.DisassemblyTool) {
return DockFactory.DisassemblyTool;
} else if(IsToolActive(DockFactory.DisassemblyTool)) {
return DockFactory.DisassemblyTool;
} else if(IsToolActive(DockFactory.SourceViewTool)) {
return DockFactory.SourceViewTool;
}
return DockFactory.DisassemblyTool;
}
private QuickSearchViewModel GetActiveQuickSearch()
{
if(DockLayout.ActiveDockable == DockFactory.SourceViewTool) {
return SourceView?.QuickSearch ?? Disassembly.QuickSearch;
} else if(DockLayout.ActiveDockable == DockFactory.DisassemblyTool) {
return Disassembly.QuickSearch;
} else if(IsToolActive(DockFactory.DisassemblyTool)) {
return Disassembly.QuickSearch;
} else if(IsToolActive(DockFactory.SourceViewTool)) {
return SourceView?.QuickSearch ?? Disassembly.QuickSearch;
}
return Disassembly.QuickSearch;
}
public void Step(StepType type, int instructionCount = 1)
{
switch(type) {
case StepType.PpuStep:
case StepType.PpuScanline:
case StepType.PpuFrame:
DebugApi.Step(CpuType.GetConsoleType().GetMainCpuType(), instructionCount, type);
break;
default:
DebugApi.Step(CpuType, instructionCount, type);
break;
}
}
public void FindAllOccurrences(string search, DisassemblySearchOptions options)
{
if(!options.MatchCase) {
search = search.ToLower();
}
if(SourceView != null && GetActiveCodeTool() == DockFactory.SourceViewTool) {
FindResultList.SetResults(SourceView.FindAllOccurrences(search, options));
} else {
CodeLineData[] results = DebugApi.FindOccurrences(CpuType, search.Trim(), options);
FindResultList.SetResults(results.Select(x => new FindResultViewModel(x)));
}
}
public void RunToLocation(LocationInfo actionLocation)
{
AddressInfo? addr = actionLocation.AbsAddress ?? actionLocation.RelAddress;
if(addr == null) {
return;
}
BreakpointManager.AddTemporaryBreakpoint(new Breakpoint() {
CpuType = CpuType,
MemoryType = addr.Value.Type,
BreakOnExec = true,
Enabled = true,
StartAddress = (uint)addr.Value.Address,
EndAddress = (uint)addr.Value.Address
});
if(EmuApi.IsPaused()) {
DebugApi.ResumeExecution();
}
}
}
}