mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
Debugger: Improved scrolling navigation for disassembly & trace log
This commit is contained in:
parent
a0b9413946
commit
bb2b08fffc
17 changed files with 392 additions and 177 deletions
|
@ -128,8 +128,7 @@ vector<DisassemblyResult> Disassembler::Disassemble(CpuType cpuType, uint8_t ban
|
|||
AddressInfo relAddress = {};
|
||||
relAddress.Type = DebugUtilities::GetCpuMemoryType(cpuType);
|
||||
|
||||
int32_t maxBank = (_memoryDumper->GetMemorySize(relAddress.Type) - 1) >> 16;
|
||||
if(bank > maxBank) {
|
||||
if(bank > GetMaxBank(cpuType)) {
|
||||
return results;
|
||||
}
|
||||
|
||||
|
@ -494,11 +493,8 @@ CodeLineData Disassembler::GetLineData(DisassemblyResult& row, CpuType type, Sne
|
|||
return data;
|
||||
}
|
||||
|
||||
uint32_t Disassembler::GetDisassemblyOutput(CpuType type, uint32_t address, CodeLineData output[], uint32_t rowCount)
|
||||
int32_t Disassembler::GetMatchingRow(vector<DisassemblyResult>& rows, uint32_t address)
|
||||
{
|
||||
uint8_t bank = address >> 16;
|
||||
vector<DisassemblyResult> rows = Disassemble(type, bank);
|
||||
|
||||
int32_t i;
|
||||
for(i = 0; i < (int32_t)rows.size(); i++) {
|
||||
if(rows[i].CpuAddress == (int32_t)address) {
|
||||
|
@ -510,8 +506,16 @@ uint32_t Disassembler::GetDisassemblyOutput(CpuType type, uint32_t address, Code
|
|||
break;
|
||||
}
|
||||
}
|
||||
return std::max(0, i);
|
||||
}
|
||||
|
||||
i = std::max(0, i);
|
||||
uint32_t Disassembler::GetDisassemblyOutput(CpuType type, uint32_t address, CodeLineData output[], uint32_t rowCount)
|
||||
{
|
||||
uint16_t bank = address >> 16;
|
||||
Timer timer;
|
||||
vector<DisassemblyResult> rows = Disassemble(type, bank);
|
||||
|
||||
int32_t i = GetMatchingRow(rows, address);
|
||||
|
||||
if(i >= (int32_t)rows.size()) {
|
||||
return 0;
|
||||
|
@ -541,6 +545,70 @@ uint32_t Disassembler::GetDisassemblyOutput(CpuType type, uint32_t address, Code
|
|||
return row;
|
||||
}
|
||||
|
||||
uint16_t Disassembler::GetMaxBank(CpuType cpuType)
|
||||
{
|
||||
AddressInfo relAddress = {};
|
||||
relAddress.Type = DebugUtilities::GetCpuMemoryType(cpuType);
|
||||
return (_memoryDumper->GetMemorySize(relAddress.Type) - 1) >> 16;
|
||||
}
|
||||
|
||||
int32_t Disassembler::GetDisassemblyRowAddress(CpuType cpuType, uint32_t address, int32_t rowOffset)
|
||||
{
|
||||
uint16_t bank = address >> 16;
|
||||
vector<DisassemblyResult> rows = Disassemble(cpuType, bank);
|
||||
int32_t len = (int32_t)rows.size();
|
||||
if(len == 0) {
|
||||
return address;
|
||||
}
|
||||
|
||||
uint16_t maxBank = GetMaxBank(cpuType);
|
||||
int32_t i = GetMatchingRow(rows, address);
|
||||
|
||||
if(rowOffset > 0) {
|
||||
while(len > 0) {
|
||||
for(; i < len; i++) {
|
||||
rowOffset--;
|
||||
if(rowOffset <= 0 && rows[i].CpuAddress >= 0) {
|
||||
return rows[i].CpuAddress;
|
||||
}
|
||||
}
|
||||
|
||||
//End of bank, didn't find an appropriate row to jump to, try the next bank
|
||||
if(bank == maxBank) {
|
||||
//Reached bottom of last bank, return the bottom row
|
||||
return rows[len - 1].CpuAddress >= 0 ? rows[len - 1].CpuAddress : address;
|
||||
}
|
||||
|
||||
bank++;
|
||||
rows = Disassemble(cpuType, bank);
|
||||
len = (int32_t)rows.size();
|
||||
i = 0;
|
||||
}
|
||||
} else if(rowOffset < 0) {
|
||||
while(len > 0) {
|
||||
for(; i >= 0; i--) {
|
||||
rowOffset++;
|
||||
if(rowOffset >= 0 && rows[i].CpuAddress >= 0) {
|
||||
return rows[i].CpuAddress;
|
||||
}
|
||||
}
|
||||
|
||||
//Start of bank, didn't find an appropriate row to jump to, try the previous bank
|
||||
if(bank == 0) {
|
||||
//Reached top of first bank, return the top row
|
||||
return rows[0].CpuAddress >= 0 ? rows[0].CpuAddress : address;
|
||||
}
|
||||
|
||||
bank--;
|
||||
rows = Disassemble(cpuType, bank);
|
||||
len = (int32_t)rows.size();
|
||||
i = len - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
int32_t Disassembler::SearchDisassembly(CpuType type, const char *searchString, int32_t startPosition, int32_t endPosition, bool searchBackwards)
|
||||
{
|
||||
//TODO
|
||||
|
|
|
@ -36,7 +36,9 @@ private:
|
|||
DisassemblerSource& GetSource(SnesMemoryType type);
|
||||
|
||||
CodeLineData GetLineData(DisassemblyResult& result, CpuType type, SnesMemoryType memType);
|
||||
int32_t GetMatchingRow(vector<DisassemblyResult>& rows, uint32_t address);
|
||||
vector<DisassemblyResult> Disassemble(CpuType cpuType, uint8_t bank);
|
||||
uint16_t GetMaxBank(CpuType cpuType);
|
||||
|
||||
public:
|
||||
Disassembler(IConsole* console, Debugger* debugger);
|
||||
|
@ -55,5 +57,6 @@ public:
|
|||
}
|
||||
|
||||
uint32_t GetDisassemblyOutput(CpuType type, uint32_t address, CodeLineData output[], uint32_t rowCount);
|
||||
int32_t GetDisassemblyRowAddress(CpuType type, uint32_t address, int32_t rowOffset);
|
||||
int32_t SearchDisassembly(CpuType type, const char* searchString, int32_t startPosition, int32_t endPosition, bool searchBackwards);
|
||||
};
|
|
@ -50,6 +50,7 @@ extern "C"
|
|||
DllExport void __stdcall Step(CpuType cpuType, uint32_t count, StepType type) { GetDebugger()->Step(cpuType, count, type); }
|
||||
|
||||
DllExport uint32_t __stdcall GetDisassemblyOutput(CpuType type, uint32_t lineIndex, CodeLineData output[], uint32_t rowCount) { return GetDebugger()->GetDisassembler()->GetDisassemblyOutput(type, lineIndex, output, rowCount); }
|
||||
DllExport uint32_t __stdcall GetDisassemblyRowAddress(CpuType type, uint32_t address, int32_t rowOffset) { return GetDebugger()->GetDisassembler()->GetDisassemblyRowAddress(type, address, rowOffset); }
|
||||
DllExport int32_t __stdcall SearchDisassembly(CpuType type, const char* searchString, int32_t startPosition, int32_t endPosition, bool searchBackwards) { return GetDebugger()->GetDisassembler()->SearchDisassembly(type, searchString, startPosition, endPosition, searchBackwards); }
|
||||
|
||||
DllExport void __stdcall SetTraceOptions(CpuType type, TraceLoggerOptions options) { GetDebugger()->GetTraceLogger(type)->SetOptions(options); }
|
||||
|
|
|
@ -117,8 +117,8 @@ namespace Mesen.Debugger
|
|||
EndAddress = (UInt32)info.Address
|
||||
};
|
||||
|
||||
if(info.Type != SnesMemoryType.PrgRom) {
|
||||
//TODO
|
||||
if(!info.Type.SupportsCdl() || info.Type.IsRelativeMemory()) {
|
||||
//Turn on break on read/write for non-ROM types only
|
||||
breakpoint.BreakOnRead = true;
|
||||
breakpoint.BreakOnWrite = true;
|
||||
}
|
||||
|
|
|
@ -12,9 +12,8 @@ namespace Mesen.Debugger.Controls
|
|||
{
|
||||
public class DisassemblyViewer : Control
|
||||
{
|
||||
public static readonly StyledProperty<ICodeDataProvider> DataProviderProperty = AvaloniaProperty.Register<DisassemblyViewer, ICodeDataProvider>(nameof(DataProvider));
|
||||
public static readonly StyledProperty<CodeLineData[]> LinesProperty = AvaloniaProperty.Register<DisassemblyViewer, CodeLineData[]>(nameof(Lines));
|
||||
public static readonly StyledProperty<ILineStyleProvider> StyleProviderProperty = AvaloniaProperty.Register<DisassemblyViewer, ILineStyleProvider>(nameof(StyleProviderProperty));
|
||||
public static readonly StyledProperty<int> ScrollPositionProperty = AvaloniaProperty.Register<DisassemblyViewer, int>(nameof(ScrollPosition), 0, false, Avalonia.Data.BindingMode.TwoWay);
|
||||
|
||||
public static readonly StyledProperty<string> FontFamilyProperty = AvaloniaProperty.Register<DisassemblyViewer, string>(nameof(FontFamily), DebuggerConfig.MonospaceFontFamily);
|
||||
public static readonly StyledProperty<float> FontSizeProperty = AvaloniaProperty.Register<DisassemblyViewer, float>(nameof(FontSize), DebuggerConfig.DefaultFontSize);
|
||||
|
@ -24,10 +23,10 @@ namespace Mesen.Debugger.Controls
|
|||
new Point(0, 5), new Point(8, 5), new Point(8, 0), new Point(15, 7), new Point(15, 8), new Point(8, 15), new Point(8, 10), new Point(0, 10),
|
||||
}, true);
|
||||
|
||||
public ICodeDataProvider DataProvider
|
||||
public CodeLineData[] Lines
|
||||
{
|
||||
get { return GetValue(DataProviderProperty); }
|
||||
set { SetValue(DataProviderProperty, value); }
|
||||
get { return GetValue(LinesProperty); }
|
||||
set { SetValue(LinesProperty, value); }
|
||||
}
|
||||
|
||||
public ILineStyleProvider StyleProvider
|
||||
|
@ -36,12 +35,6 @@ namespace Mesen.Debugger.Controls
|
|||
set { SetValue(StyleProviderProperty, value); }
|
||||
}
|
||||
|
||||
public int ScrollPosition
|
||||
{
|
||||
get { return GetValue(ScrollPositionProperty); }
|
||||
set { SetValue(ScrollPositionProperty, value); }
|
||||
}
|
||||
|
||||
public string FontFamily
|
||||
{
|
||||
get { return GetValue(FontFamilyProperty); }
|
||||
|
@ -63,42 +56,19 @@ namespace Mesen.Debugger.Controls
|
|||
private Typeface Font { get; set; }
|
||||
private Size LetterSize { get; set; }
|
||||
private double RowHeight => this.LetterSize.Height;
|
||||
private int VisibleRows => (int)(Bounds.Height / RowHeight) - 1;
|
||||
|
||||
public int GetVisibleRowCount()
|
||||
{
|
||||
InitFontAndLetterSize();
|
||||
return (int)Math.Ceiling(Bounds.Height / RowHeight);
|
||||
}
|
||||
|
||||
private bool _scrollByAddress = false;
|
||||
private bool _updatingScroll = false;
|
||||
public delegate void RowClickedEventHandler(DisassemblyViewer sender, RowClickedEventArgs args);
|
||||
public event RowClickedEventHandler? RowClicked;
|
||||
|
||||
static DisassemblyViewer()
|
||||
{
|
||||
AffectsRender<DisassemblyViewer>(FontFamilyProperty, FontSizeProperty);
|
||||
|
||||
DataProviderProperty.Changed.AddClassHandler<DisassemblyViewer>((x, e) => {
|
||||
x.Refresh();
|
||||
x.InvalidateVisual();
|
||||
});
|
||||
|
||||
ScrollPositionProperty.Changed.AddClassHandler<DisassemblyViewer>((x, e) => {
|
||||
if(x._updatingScroll) {
|
||||
return;
|
||||
}
|
||||
|
||||
x.Refresh();
|
||||
|
||||
if(x._scrollByAddress) {
|
||||
x._updatingScroll = true;
|
||||
CodeLineData[] lines = x._lines;
|
||||
if(e.OldValue is int oldValue && e.NewValue is int newValue && oldValue < newValue) {
|
||||
foreach(CodeLineData line in lines) {
|
||||
if(line.Address >= 0 && newValue < line.Address) {
|
||||
x.ScrollPosition = line.Address;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
x._updatingScroll = false;
|
||||
}
|
||||
x.InvalidateVisual();
|
||||
});
|
||||
AffectsRender<DisassemblyViewer>(FontFamilyProperty, FontSizeProperty, StyleProviderProperty, ShowByteCodeProperty, LinesProperty);
|
||||
}
|
||||
|
||||
public DisassemblyViewer()
|
||||
|
@ -110,30 +80,15 @@ namespace Mesen.Debugger.Controls
|
|||
{
|
||||
base.OnPointerPressed(e);
|
||||
PointerPoint p = e.GetCurrentPoint(this);
|
||||
if(p.Position.X < 20) {
|
||||
int rowNumber = (int)(p.Position.Y / LetterSize.Height);
|
||||
CodeLineData[] lines = _lines;
|
||||
if(rowNumber < lines.Length) {
|
||||
CpuType cpuType = lines[rowNumber].CpuType;
|
||||
AddressInfo relAddress = new AddressInfo() {
|
||||
Address = lines[rowNumber].Address,
|
||||
Type = cpuType.ToMemoryType()
|
||||
};
|
||||
|
||||
AddressInfo absAddress = DebugApi.GetAbsoluteAddress(relAddress);
|
||||
BreakpointManager.ToggleBreakpoint(absAddress.Address < 0 ? relAddress : absAddress, cpuType);
|
||||
}
|
||||
int rowNumber = (int)(p.Position.Y / LetterSize.Height);
|
||||
bool marginClicked = p.Position.X < 20;
|
||||
if(rowNumber < Lines.Length) {
|
||||
RowClicked?.Invoke(this, new RowClickedEventArgs(Lines[rowNumber], rowNumber, marginClicked, e.GetCurrentPoint(this).Properties));
|
||||
}
|
||||
|
||||
InvalidateVisual();
|
||||
}
|
||||
|
||||
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
||||
{
|
||||
base.OnPointerWheelChanged(e);
|
||||
ScrollPosition = Math.Max(0, Math.Min(ScrollPosition - (int)(e.Delta.Y * 3), DataProvider.GetLineCount() - 1));
|
||||
}
|
||||
|
||||
private void InitFontAndLetterSize()
|
||||
{
|
||||
this.Font = new Typeface(new FontFamily(this.FontFamily));
|
||||
|
@ -141,38 +96,17 @@ namespace Mesen.Debugger.Controls
|
|||
this.LetterSize = text.Bounds.Size;
|
||||
}
|
||||
|
||||
private CodeLineData[] _lines = new CodeLineData[0];
|
||||
public void Refresh()
|
||||
{
|
||||
ICodeDataProvider dp = this.DataProvider;
|
||||
int scrollPosition = ScrollPosition;
|
||||
|
||||
InitFontAndLetterSize();
|
||||
|
||||
_lines = dp?.GetCodeLines(scrollPosition, VisibleRows + 3) ?? new CodeLineData[0];
|
||||
if(_scrollByAddress) {
|
||||
foreach(CodeLineData line in _lines) {
|
||||
if(line.Address >= 0) {
|
||||
if(ScrollPosition != line.Address) {
|
||||
_updatingScroll = true;
|
||||
ScrollPosition = line.Address;
|
||||
_updatingScroll = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Render(DrawingContext context)
|
||||
{
|
||||
if(DataProvider == null) {
|
||||
CodeLineData[] lines = Lines;
|
||||
if(lines.Length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
CpuType cpuType = lines[0].CpuType;
|
||||
|
||||
InitFontAndLetterSize();
|
||||
|
||||
CodeLineData[] lines = _lines;
|
||||
double y = 0;
|
||||
var text = new FormattedText("", this.Font, this.FontSize, TextAlignment.Left, TextWrapping.NoWrap, Size.Empty);
|
||||
var smallText = new FormattedText("", this.Font, this.FontSize - 2, TextAlignment.Left, TextWrapping.NoWrap, Size.Empty);
|
||||
|
@ -181,10 +115,10 @@ namespace Mesen.Debugger.Controls
|
|||
|
||||
ILineStyleProvider styleProvider = this.StyleProvider;
|
||||
|
||||
string addrFormat = "X" + DataProvider.CpuType.GetAddressSize();
|
||||
string addrFormat = "X" + cpuType.GetAddressSize();
|
||||
double symbolMargin = 20;
|
||||
double addressMargin = Math.Floor(LetterSize.Width * DataProvider.CpuType.GetAddressSize() + symbolMargin) + 0.5;
|
||||
double byteCodeMargin = Math.Floor(LetterSize.Width * (3 * DataProvider.CpuType.GetByteCodeSize()));
|
||||
double addressMargin = Math.Floor(LetterSize.Width * cpuType.GetAddressSize() + symbolMargin) + 0.5;
|
||||
double byteCodeMargin = Math.Floor(LetterSize.Width * (3 * cpuType.GetByteCodeSize()));
|
||||
double codeIndent = Math.Floor(LetterSize.Width * 2) + 0.5;
|
||||
|
||||
//Draw margin (address)
|
||||
|
@ -295,12 +229,30 @@ namespace Mesen.Debugger.Controls
|
|||
}
|
||||
}
|
||||
|
||||
public class RowClickedEventArgs
|
||||
{
|
||||
public CodeLineData CodeLineData { get; private set; }
|
||||
public int RowNumber { get; private set; }
|
||||
public bool MarginClicked { get; private set; }
|
||||
public PointerPointProperties Properties { get; private set; }
|
||||
|
||||
public RowClickedEventArgs(CodeLineData codeLineData, int rowNumber, bool marginClicked, PointerPointProperties properties)
|
||||
{
|
||||
this.CodeLineData = codeLineData;
|
||||
this.RowNumber = rowNumber;
|
||||
this.MarginClicked = marginClicked;
|
||||
this.Properties = properties;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ICodeDataProvider
|
||||
{
|
||||
CpuType CpuType { get; }
|
||||
|
||||
CodeLineData[] GetCodeLines(int address, int rowCount);
|
||||
|
||||
int GetRowAddress(int address, int rowOffset);
|
||||
|
||||
int GetLineCount();
|
||||
int GetNextResult(string searchString, int startPosition, int endPosition, bool searchBackwards);
|
||||
bool UseOptimizedSearch { get; }
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
using Mesen.Debugger.Controls;
|
||||
using Mesen.Interop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Mesen.Debugger.Disassembly
|
||||
{
|
||||
|
@ -28,6 +23,11 @@ namespace Mesen.Debugger.Disassembly
|
|||
return DebugApi.GetDisassemblyOutput(_type, (uint)startPosition, (uint)rowCount);
|
||||
}
|
||||
|
||||
public int GetRowAddress(int address, int rowOffset)
|
||||
{
|
||||
return DebugApi.GetDisassemblyRowAddress(_type, (uint)address, rowOffset);
|
||||
}
|
||||
|
||||
public bool UseOptimizedSearch { get { return true; } }
|
||||
|
||||
public int GetNextResult(string searchString, int startPosition, int endPosition, bool searchBackwards)
|
||||
|
|
|
@ -110,5 +110,10 @@ namespace Mesen.Debugger.Disassembly
|
|||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public int GetRowAddress(int address, int rowOffset)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ namespace Mesen.Debugger.ViewModels.DebuggerDock
|
|||
{
|
||||
public class DisassemblyToolViewModel : Tool
|
||||
{
|
||||
public DisassemblyViewerViewModel DisassemblyViewerViewModel { get; set; }
|
||||
public DisassemblyViewModel DisassemblyViewerViewModel { get; set; }
|
||||
|
||||
public DisassemblyToolViewModel(DisassemblyViewerViewModel disassemblyViewerViewModel)
|
||||
public DisassemblyToolViewModel(DisassemblyViewModel disassemblyViewerViewModel)
|
||||
{
|
||||
Id = "DiassemblyTool";
|
||||
Title = "Disassembly";
|
||||
|
|
|
@ -24,7 +24,7 @@ namespace Mesen.Debugger.ViewModels
|
|||
|
||||
[Reactive] public DebuggerOptionsViewModel Options { get; private set; }
|
||||
|
||||
[Reactive] public DisassemblyViewerViewModel Disassembly { 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 LabelListViewModel LabelList { get; private set; }
|
||||
|
@ -48,7 +48,7 @@ namespace Mesen.Debugger.ViewModels
|
|||
|
||||
Options = new DebuggerOptionsViewModel(Config, CpuType);
|
||||
|
||||
Disassembly = new DisassemblyViewerViewModel(ConfigManager.Config.Debug);
|
||||
Disassembly = new DisassemblyViewModel(ConfigManager.Config.Debug);
|
||||
BreakpointList = new BreakpointListViewModel();
|
||||
|
||||
DockFactory = new DebuggerDockFactory(this);
|
||||
|
@ -101,10 +101,8 @@ namespace Mesen.Debugger.ViewModels
|
|||
|
||||
internal void UpdateDisassembly()
|
||||
{
|
||||
//TODO
|
||||
Disassembly.DataProvider = new CodeDataProvider(CpuType);
|
||||
Disassembly.UpdateMaxScroll();
|
||||
Disassembly.ScrollPosition = (Disassembly.StyleProvider?.ActiveAddress ?? 0);
|
||||
Disassembly.SetActiveAddress(DebugUtilities.GetProgramCounter(CpuType));
|
||||
}
|
||||
|
||||
public void UpdateCpuPpuState()
|
||||
|
@ -114,9 +112,6 @@ namespace Mesen.Debugger.ViewModels
|
|||
if(DockFactory.CpuStatusTool.StatusViewModel is SnesCpuViewModel snesCpuModel) {
|
||||
CpuState state = DebugApi.GetCpuState<CpuState>(CpuType);
|
||||
snesCpuModel.UpdateState(state);
|
||||
if(Disassembly.StyleProvider != null) {
|
||||
Disassembly.StyleProvider.ActiveAddress = (state.K << 16) | state.PC;
|
||||
}
|
||||
}
|
||||
|
||||
if(DockFactory.PpuStatusTool.StatusViewModel is SnesPpuViewModel snesPpuModel) {
|
||||
|
@ -128,9 +123,6 @@ namespace Mesen.Debugger.ViewModels
|
|||
if(DockFactory.CpuStatusTool.StatusViewModel is NesCpuViewModel nesCpuModel) {
|
||||
NesCpuState state = DebugApi.GetCpuState<NesCpuState>(CpuType);
|
||||
nesCpuModel.UpdateState(state);
|
||||
if(Disassembly.StyleProvider != null) {
|
||||
Disassembly.StyleProvider.ActiveAddress = state.PC;
|
||||
}
|
||||
}
|
||||
|
||||
if(DockFactory.PpuStatusTool.StatusViewModel is NesPpuViewModel nesPpuModel) {
|
||||
|
|
119
NewUI/Debugger/ViewModels/DisassemblyViewModel.cs
Normal file
119
NewUI/Debugger/ViewModels/DisassemblyViewModel.cs
Normal file
|
@ -0,0 +1,119 @@
|
|||
using Mesen.Config;
|
||||
using Mesen.Debugger.Controls;
|
||||
using Mesen.Debugger.Disassembly;
|
||||
using Mesen.ViewModels;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
using System;
|
||||
using System.Reactive;
|
||||
|
||||
namespace Mesen.Debugger.ViewModels
|
||||
{
|
||||
public class DisassemblyViewModel : ViewModelBase
|
||||
{
|
||||
[Reactive] public ICodeDataProvider? DataProvider { get; set; } = null;
|
||||
[Reactive] public BaseStyleProvider StyleProvider { get; set; } = new BaseStyleProvider();
|
||||
[Reactive] public int ScrollPosition { get; set; } = 0;
|
||||
[Reactive] public int MaxScrollPosition { get; private set; } = 10000;
|
||||
[Reactive] public int TopAddress { get; private set; } = 0;
|
||||
[Reactive] public CodeLineData[] Lines { get; private set; } = Array.Empty<CodeLineData>();
|
||||
|
||||
public DebugConfig Config { get; private set; }
|
||||
public int VisibleRowCount { get; set; } = 100;
|
||||
|
||||
private int _ignoreScrollUpdates = 0;
|
||||
|
||||
[Obsolete("For designer only")]
|
||||
public DisassemblyViewModel(): this(new DebugConfig()) { }
|
||||
|
||||
public DisassemblyViewModel(DebugConfig config)
|
||||
{
|
||||
Config = config;
|
||||
|
||||
this.WhenAnyValue(x => x.DataProvider).Subscribe(x => Refresh());
|
||||
this.WhenAnyValue(x => x.TopAddress).Subscribe(x => Refresh());
|
||||
|
||||
int lastValue = ScrollPosition;
|
||||
this.WhenAnyValue(x => x.ScrollPosition).Subscribe(scrollPos => {
|
||||
int gap = scrollPos - lastValue;
|
||||
lastValue = scrollPos;
|
||||
if(_ignoreScrollUpdates > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(gap != 0) {
|
||||
if(Math.Abs(gap) <= 10) {
|
||||
Scroll(gap);
|
||||
} else {
|
||||
int lineCount = DataProvider?.GetLineCount() ?? 0;
|
||||
TopAddress = Math.Max(0, Math.Min(lineCount - 1, (int)((double)lineCount / MaxScrollPosition * ScrollPosition)));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Scroll(int lineNumberOffset)
|
||||
{
|
||||
ICodeDataProvider? dp = DataProvider;
|
||||
if(dp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SetTopAddress(dp.GetRowAddress(TopAddress, lineNumberOffset));
|
||||
}
|
||||
|
||||
public void ScrollToTop()
|
||||
{
|
||||
SetTopAddress(0);
|
||||
}
|
||||
|
||||
public void ScrollToBottom()
|
||||
{
|
||||
SetTopAddress((DataProvider?.GetLineCount() ?? 0) - 1);
|
||||
}
|
||||
|
||||
public void Refresh()
|
||||
{
|
||||
ICodeDataProvider? dp = DataProvider;
|
||||
if(dp == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
CodeLineData[] lines = dp.GetCodeLines(TopAddress, VisibleRowCount);
|
||||
Lines = lines;
|
||||
|
||||
if(lines.Length > 0 && lines[0].Address >= 0) {
|
||||
SetTopAddress(lines[0].Address);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetTopAddress(int address)
|
||||
{
|
||||
int lineCount = DataProvider?.GetLineCount() ?? 0;
|
||||
address = Math.Max(0, Math.Min(lineCount - 1, address));
|
||||
|
||||
_ignoreScrollUpdates++;
|
||||
TopAddress = address;
|
||||
ScrollPosition = (int)(TopAddress / (double)lineCount * MaxScrollPosition);
|
||||
_ignoreScrollUpdates--;
|
||||
}
|
||||
|
||||
public void SetActiveAddress(uint pc)
|
||||
{
|
||||
StyleProvider.ActiveAddress = (int)pc;
|
||||
|
||||
for(int i = 1; i < VisibleRowCount - 2 && i < Lines.Length; i++) {
|
||||
if(Lines[i].Address == (int)pc) {
|
||||
//Row is already visible, don't scroll
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ICodeDataProvider? dp = DataProvider;
|
||||
if(dp == null) {
|
||||
return;
|
||||
}
|
||||
TopAddress = dp.GetRowAddress((int)pc, -VisibleRowCount / 2 + 1);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
using Mesen.Config;
|
||||
using Mesen.Debugger.Controls;
|
||||
using Mesen.Debugger.Disassembly;
|
||||
using Mesen.ViewModels;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.Fody.Helpers;
|
||||
|
||||
namespace Mesen.Debugger.ViewModels
|
||||
{
|
||||
public class DisassemblyViewerViewModel : ViewModelBase
|
||||
{
|
||||
[Reactive] public ICodeDataProvider? DataProvider { get; set; } = null;
|
||||
[Reactive] public BaseStyleProvider? StyleProvider { get; set; } = null;
|
||||
[Reactive] public int ScrollPosition { get; set; }
|
||||
[Reactive] public int MaxScrollPosition { get; set; } = 0;
|
||||
|
||||
public DebugConfig Config { get; private set; }
|
||||
|
||||
//For designer
|
||||
public DisassemblyViewerViewModel(): this(new DebugConfig()) { }
|
||||
|
||||
public DisassemblyViewerViewModel(DebugConfig config)
|
||||
{
|
||||
Config = config;
|
||||
}
|
||||
|
||||
public void UpdateMaxScroll()
|
||||
{
|
||||
MaxScrollPosition = DataProvider?.GetLineCount() - 1 ?? 0;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -16,10 +16,14 @@ namespace Mesen.Debugger.ViewModels
|
|||
{
|
||||
public class TraceLoggerViewModel : ViewModelBase
|
||||
{
|
||||
public const int TraceLogBufferSize = 30000;
|
||||
|
||||
[Reactive] public TraceLoggerCodeDataProvider? DataProvider { get; set; }
|
||||
[Reactive] public TraceLoggerStyleProvider StyleProvider { get; set; } = new TraceLoggerStyleProvider();
|
||||
[Reactive] public CodeLineData[] TraceLogLines { get; set; } = Array.Empty<CodeLineData>();
|
||||
[Reactive] public int VisibleRowCount { get; set; } = 100;
|
||||
[Reactive] public int ScrollPosition { get; set; } = 0;
|
||||
[Reactive] public int MaxScrollPosition { get; set; } = 0;
|
||||
[Reactive] public int MaxScrollPosition { get; set; } = TraceLoggerViewModel.TraceLogBufferSize;
|
||||
[Reactive] public bool IsLoggingToFile { get; set; } = false;
|
||||
|
||||
[Reactive] public List<TraceLoggerOptionTab> Tabs { get; set; } = new List<TraceLoggerOptionTab>();
|
||||
|
@ -32,6 +36,14 @@ namespace Mesen.Debugger.ViewModels
|
|||
|
||||
DataProvider = new TraceLoggerCodeDataProvider();
|
||||
UpdateAvailableTabs();
|
||||
|
||||
this.WhenAnyValue(x => x.ScrollPosition).Subscribe(x => {
|
||||
ScrollPosition = Math.Max(0, Math.Min(x, MaxScrollPosition));
|
||||
UpdateTraceLogLines();
|
||||
});
|
||||
this.WhenAnyValue(x => x.MaxScrollPosition).Subscribe(x => {
|
||||
ScrollPosition = Math.Min(ScrollPosition, MaxScrollPosition);
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateAvailableTabs()
|
||||
|
@ -77,9 +89,30 @@ namespace Mesen.Debugger.ViewModels
|
|||
DebugApi.SetTraceOptions(tab.CpuType, options);
|
||||
}
|
||||
|
||||
DataProvider = new TraceLoggerCodeDataProvider();
|
||||
MaxScrollPosition = DataProvider.GetLineCount();
|
||||
ScrollPosition = MaxScrollPosition - 0x20;
|
||||
UpdateTraceLogLines();
|
||||
ScrollToBottom();
|
||||
}
|
||||
|
||||
private void UpdateTraceLogLines()
|
||||
{
|
||||
if(DataProvider != null) {
|
||||
TraceLogLines = DataProvider.GetCodeLines(ScrollPosition, VisibleRowCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void Scroll(int offset)
|
||||
{
|
||||
ScrollPosition += offset;
|
||||
}
|
||||
|
||||
public void ScrollToTop()
|
||||
{
|
||||
ScrollPosition = 0;
|
||||
}
|
||||
|
||||
public void ScrollToBottom()
|
||||
{
|
||||
ScrollPosition = MaxScrollPosition;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,7 +136,7 @@ namespace Mesen.Debugger.ViewModels
|
|||
|
||||
public int GetLineCount()
|
||||
{
|
||||
return 30000;
|
||||
return TraceLoggerViewModel.TraceLogBufferSize;
|
||||
}
|
||||
|
||||
public int GetNextResult(string searchString, int startPosition, int endPosition, bool searchBackwards)
|
||||
|
@ -113,7 +146,7 @@ namespace Mesen.Debugger.ViewModels
|
|||
|
||||
public CodeLineData[] GetCodeLines(int startIndex, int rowCount)
|
||||
{
|
||||
TraceRow[] rows = DebugApi.GetExecutionTrace((uint)(30000 - startIndex), (uint)rowCount);
|
||||
TraceRow[] rows = DebugApi.GetExecutionTrace((uint)(TraceLoggerViewModel.TraceLogBufferSize - startIndex - rowCount), (uint)rowCount);
|
||||
|
||||
List<CodeLineData> lines = new(rowCount);
|
||||
for(int i = 0; i < rows.Length; i++) {
|
||||
|
@ -131,6 +164,11 @@ namespace Mesen.Debugger.ViewModels
|
|||
|
||||
return lines.ToArray();
|
||||
}
|
||||
|
||||
public int GetRowAddress(int address, int rowOffset)
|
||||
{
|
||||
return Math.Max(0, Math.Min(GetLineCount() - 1, address + rowOffset));
|
||||
}
|
||||
}
|
||||
|
||||
public class TraceLoggerStyleProvider : ILineStyleProvider
|
||||
|
|
|
@ -8,32 +8,35 @@
|
|||
mc:Ignorable="d" d:DesignWidth="500" d:DesignHeight="450"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
x:DataType="vm:DisassemblyViewerViewModel"
|
||||
x:DataType="vm:DisassemblyViewModel"
|
||||
x:Class="Mesen.Debugger.Views.DisassemblyView"
|
||||
>
|
||||
<Design.DataContext>
|
||||
<vm:DisassemblyViewerViewModel />
|
||||
<vm:DisassemblyViewModel />
|
||||
</Design.DataContext>
|
||||
|
||||
<DockPanel>
|
||||
<ScrollBar
|
||||
DockPanel.Dock="Right"
|
||||
Orientation="Vertical"
|
||||
Minimum="0"
|
||||
Maximum="{CompiledBinding MaxScrollPosition}"
|
||||
VerticalAlignment="Stretch"
|
||||
AllowAutoHide="False"
|
||||
SmallChange="1"
|
||||
LargeChange="10"
|
||||
Minimum="0"
|
||||
Maximum="{CompiledBinding MaxScrollPosition}"
|
||||
Value="{CompiledBinding ScrollPosition}"
|
||||
/>
|
||||
|
||||
<dc:DisassemblyViewer
|
||||
DockPanel.Dock="Left"
|
||||
DataProvider="{CompiledBinding DataProvider}"
|
||||
Name="disViewer"
|
||||
Lines="{CompiledBinding Lines}"
|
||||
StyleProvider="{CompiledBinding StyleProvider}"
|
||||
ScrollPosition="{CompiledBinding ScrollPosition}"
|
||||
FontFamily="{CompiledBinding Config.Font.FontFamily}"
|
||||
FontSize="{CompiledBinding Config.Font.FontSize}"
|
||||
ShowByteCode="{CompiledBinding Config.Debugger.ShowByteCode}"
|
||||
RowClicked="Diassembly_RowClicked"
|
||||
PointerWheelChanged="Disassembly_PointerWheelChanged"
|
||||
/>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
|
|
|
@ -1,19 +1,63 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Mesen.Debugger.Controls;
|
||||
using Mesen.Debugger.ViewModels;
|
||||
using Mesen.Interop;
|
||||
|
||||
namespace Mesen.Debugger.Views
|
||||
{
|
||||
public class DisassemblyView : UserControl
|
||||
{
|
||||
private DisassemblyViewModel Model => (DisassemblyViewModel)DataContext!;
|
||||
|
||||
static DisassemblyView()
|
||||
{
|
||||
BoundsProperty.Changed.AddClassHandler<DisassemblyView>((x, e) => {
|
||||
DisassemblyViewer viewer = x.FindControl<DisassemblyViewer>("disViewer");
|
||||
x.Model.VisibleRowCount = viewer.GetVisibleRowCount();
|
||||
});
|
||||
}
|
||||
|
||||
public DisassemblyView()
|
||||
{
|
||||
InitializeComponent();
|
||||
Focusable = true;
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public void Diassembly_RowClicked(DisassemblyViewer sender, RowClickedEventArgs e)
|
||||
{
|
||||
if(e.MarginClicked && e.Properties.IsLeftButtonPressed) {
|
||||
CpuType cpuType = e.CodeLineData.CpuType;
|
||||
AddressInfo relAddress = new AddressInfo() {
|
||||
Address = e.CodeLineData.Address,
|
||||
Type = cpuType.ToMemoryType()
|
||||
};
|
||||
|
||||
AddressInfo absAddress = DebugApi.GetAbsoluteAddress(relAddress);
|
||||
BreakpointManager.ToggleBreakpoint(absAddress.Address < 0 ? relAddress : absAddress, cpuType);
|
||||
}
|
||||
}
|
||||
|
||||
public void Disassembly_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
|
||||
{
|
||||
Model.Scroll((int)(-e.Delta.Y * 3));
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
switch(e.Key) {
|
||||
case Key.PageDown: Model.Scroll(Model.VisibleRowCount - 2); e.Handled = true; break;
|
||||
case Key.PageUp: Model.Scroll(-(Model.VisibleRowCount - 2)); e.Handled = true; break;
|
||||
case Key.Home: Model.ScrollToTop(); e.Handled = true; break;
|
||||
case Key.End: Model.ScrollToBottom(); e.Handled = true; break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,17 +65,18 @@
|
|||
<ScrollBar
|
||||
DockPanel.Dock="Right"
|
||||
Orientation="Vertical"
|
||||
Minimum="0"
|
||||
Maximum="{CompiledBinding MaxScrollPosition}"
|
||||
VerticalAlignment="Stretch"
|
||||
AllowAutoHide="False"
|
||||
Minimum="0"
|
||||
Maximum="{CompiledBinding MaxScrollPosition}"
|
||||
Value="{CompiledBinding ScrollPosition}"
|
||||
/>
|
||||
|
||||
<dc:DisassemblyViewer
|
||||
DataProvider="{CompiledBinding DataProvider}"
|
||||
Name="disViewer"
|
||||
Lines="{CompiledBinding TraceLogLines}"
|
||||
StyleProvider="{CompiledBinding StyleProvider}"
|
||||
ScrollPosition="{CompiledBinding ScrollPosition}"
|
||||
PointerWheelChanged="Disassembly_PointerWheelChanged"
|
||||
/>
|
||||
</DockPanel>
|
||||
</Window>
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Avalonia.Threading;
|
||||
using Mesen.Config;
|
||||
using Mesen.Debugger.Controls;
|
||||
using Mesen.Debugger.ViewModels;
|
||||
using Mesen.Interop;
|
||||
using Mesen.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace Mesen.Debugger.Windows
|
||||
|
@ -19,11 +20,20 @@ namespace Mesen.Debugger.Windows
|
|||
private TraceLoggerViewModel Model => ((TraceLoggerViewModel)DataContext!);
|
||||
private int _refreshCounter = 0;
|
||||
|
||||
static TraceLoggerWindow()
|
||||
{
|
||||
BoundsProperty.Changed.AddClassHandler<TraceLoggerWindow>((x, e) => {
|
||||
DisassemblyViewer viewer = x.FindControl<DisassemblyViewer>("disViewer");
|
||||
x.Model.VisibleRowCount = viewer.GetVisibleRowCount() - 1;
|
||||
x.Model.MaxScrollPosition = TraceLoggerViewModel.TraceLogBufferSize - x.Model.VisibleRowCount;
|
||||
});
|
||||
}
|
||||
|
||||
public TraceLoggerWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -43,6 +53,8 @@ namespace Mesen.Debugger.Windows
|
|||
{
|
||||
base.OnClosing(e);
|
||||
_listener?.Dispose();
|
||||
DebugApi.StopLogTraceToFile();
|
||||
Model.SaveConfig();
|
||||
DataContext = null;
|
||||
}
|
||||
|
||||
|
@ -55,17 +67,17 @@ namespace Mesen.Debugger.Windows
|
|||
|
||||
case ConsoleNotificationType.CodeBreak: {
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
Model.UpdateLog();
|
||||
Model?.UpdateLog();
|
||||
});
|
||||
break;
|
||||
}
|
||||
|
||||
case ConsoleNotificationType.PpuFrameDone: {
|
||||
_refreshCounter++;
|
||||
if(_refreshCounter == 9) {
|
||||
//Refresh every 9 frames, ~7fps
|
||||
if(_refreshCounter == 6) {
|
||||
//Refresh every 6 frames, 10fps
|
||||
Dispatcher.UIThread.Post(() => {
|
||||
Model.UpdateLog();
|
||||
Model?.UpdateLog();
|
||||
});
|
||||
_refreshCounter = 0;
|
||||
}
|
||||
|
@ -89,11 +101,19 @@ namespace Mesen.Debugger.Windows
|
|||
DebugApi.StopLogTraceToFile();
|
||||
}
|
||||
|
||||
protected override void OnClosed(EventArgs e)
|
||||
public void Disassembly_PointerWheelChanged(object? sender, PointerWheelEventArgs e)
|
||||
{
|
||||
base.OnClosed(e);
|
||||
DebugApi.StopLogTraceToFile();
|
||||
Model.SaveConfig();
|
||||
Model.Scroll((int)(-e.Delta.Y * 3));
|
||||
}
|
||||
|
||||
protected override void OnKeyDown(KeyEventArgs e)
|
||||
{
|
||||
switch(e.Key) {
|
||||
case Key.PageDown: Model.Scroll(Model.VisibleRowCount - 2); e.Handled = true; break;
|
||||
case Key.PageUp: Model.Scroll(-(Model.VisibleRowCount - 2)); e.Handled = true; break;
|
||||
case Key.Home: Model.ScrollToTop(); e.Handled = true; break;
|
||||
case Key.End: Model.ScrollToBottom(); e.Handled = true; break;
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
|
|
|
@ -63,6 +63,7 @@ namespace Mesen.Interop
|
|||
return result;
|
||||
}
|
||||
|
||||
[DllImport(DllPath)] public static extern int GetDisassemblyRowAddress(CpuType type, UInt32 address, int rowOffset);
|
||||
[DllImport(DllPath)] public static extern int SearchDisassembly(CpuType type, [MarshalAs(UnmanagedType.LPUTF8Str)]string searchString, int startPosition, int endPosition, [MarshalAs(UnmanagedType.I1)]bool searchBackwards);
|
||||
|
||||
[DllImport(DllPath)] private static extern void GetCpuState(IntPtr state, CpuType cpuType);
|
||||
|
@ -505,7 +506,7 @@ namespace Mesen.Interop
|
|||
switch(memType) {
|
||||
case SnesMemoryType.CpuMemory:
|
||||
case SnesMemoryType.GameboyMemory:
|
||||
case SnesMemoryType.NesMemory:
|
||||
case SnesMemoryType.NesMemory:
|
||||
case SnesMemoryType.PrgRom:
|
||||
case SnesMemoryType.GbPrgRom:
|
||||
case SnesMemoryType.NesPrgRom:
|
||||
|
|
Loading…
Add table
Reference in a new issue