mirror of
https://github.com/SourMesen/Mesen2.git
synced 2025-04-02 10:21:44 -04:00
418 lines
12 KiB
C#
418 lines
12 KiB
C#
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Threading;
|
|
using Mesen.Config;
|
|
using Mesen.Debugger.Controls;
|
|
using Mesen.Debugger.Disassembly;
|
|
using Mesen.Debugger.Utilities;
|
|
using Mesen.Debugger.Views;
|
|
using Mesen.Interop;
|
|
using Mesen.Utilities;
|
|
using Mesen.ViewModels;
|
|
using ReactiveUI;
|
|
using ReactiveUI.Fody.Helpers;
|
|
using System;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
namespace Mesen.Debugger.ViewModels
|
|
{
|
|
public class DisassemblyViewModel : DisposableViewModel, ISelectableModel
|
|
{
|
|
public ICodeDataProvider DataProvider { get; }
|
|
public CpuType CpuType { get; }
|
|
public DebuggerWindowViewModel Debugger { get; }
|
|
public DisassemblyViewStyleProvider StyleProvider { get; }
|
|
|
|
[Reactive] public int ScrollPosition { get; set; } = 0;
|
|
[Reactive] public int MaxScrollPosition { get; private set; } = 1000000000;
|
|
[Reactive] public int TopAddress { get; private set; } = 0;
|
|
[Reactive] public CodeLineData[] Lines { get; private set; } = Array.Empty<CodeLineData>();
|
|
|
|
[Reactive] public int? ActiveAddress { get; set; }
|
|
[Reactive] public int SelectedRowAddress { get; set; }
|
|
[Reactive] public int SelectionAnchor { get; set; }
|
|
[Reactive] public int SelectionStart { get; set; }
|
|
[Reactive] public int SelectionEnd { get; set; }
|
|
|
|
public QuickSearchViewModel QuickSearch { get; } = new QuickSearchViewModel();
|
|
public NavigationHistory<int> History { get; } = new();
|
|
|
|
public bool ShowScrollBarMarkers => CpuType != CpuType.NecDsp;
|
|
|
|
public DebugConfig Config { get; private set; }
|
|
public int VisibleRowCount { get; set; } = 100;
|
|
|
|
private DisassemblyViewer? _viewer = null;
|
|
|
|
private int _ignoreScrollUpdates = 0;
|
|
private Action? _refreshScrollbar = null;
|
|
|
|
[Obsolete("For designer only")]
|
|
public DisassemblyViewModel(): this(new DebuggerWindowViewModel(), new DebugConfig(), CpuType.Snes) { }
|
|
|
|
public DisassemblyViewModel(DebuggerWindowViewModel debugger, DebugConfig config, CpuType cpuType)
|
|
{
|
|
Config = config;
|
|
CpuType = cpuType;
|
|
Debugger = debugger;
|
|
StyleProvider = new DisassemblyViewStyleProvider(cpuType, this);
|
|
DataProvider = new CodeDataProvider(cpuType);
|
|
|
|
if(Design.IsDesignMode) {
|
|
return;
|
|
}
|
|
|
|
QuickSearch.OnFind += QuickSearch_OnFind;
|
|
|
|
AddDisposable(this.WhenAnyValue(x => x.TopAddress).Subscribe(x => Refresh()));
|
|
|
|
AddDisposable(this.WhenAnyValue(x => x.QuickSearch.IsSearchBoxVisible).Subscribe(x => {
|
|
if(!QuickSearch.IsSearchBoxVisible) {
|
|
_viewer?.Focus();
|
|
}
|
|
}));
|
|
|
|
int lastValue = ScrollPosition;
|
|
AddDisposable(this.WhenAnyValue(x => x.ScrollPosition).Subscribe(scrollPos => {
|
|
if(_viewer == null) {
|
|
ScrollPosition = lastValue;
|
|
return;
|
|
}
|
|
|
|
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();
|
|
TopAddress = Math.Max(0, Math.Min(lineCount - 1, (int)((double)lineCount / MaxScrollPosition * ScrollPosition)));
|
|
}
|
|
}
|
|
}));
|
|
}
|
|
|
|
private void QuickSearch_OnFind(OnFindEventArgs e)
|
|
{
|
|
DisassemblySearchOptions options = new() { SearchBackwards = e.Direction == SearchDirection.Backward, SkipFirstLine = e.SkipCurrent };
|
|
int findAddress = DebugApi.SearchDisassembly(CpuType, e.SearchString.ToLowerInvariant(), SelectedRowAddress, options);
|
|
if(findAddress >= 0) {
|
|
Dispatcher.UIThread.Post(() => {
|
|
SetSelectedRow(findAddress, true, true);
|
|
});
|
|
e.Success = true;
|
|
} else {
|
|
e.Success = false;
|
|
}
|
|
}
|
|
|
|
public void SetViewer(DisassemblyViewer? viewer)
|
|
{
|
|
_viewer = viewer;
|
|
}
|
|
|
|
public void Scroll(int lineNumberOffset)
|
|
{
|
|
if(lineNumberOffset == 0) {
|
|
return;
|
|
}
|
|
|
|
SetTopAddress(DataProvider.GetRowAddress(TopAddress, lineNumberOffset));
|
|
}
|
|
|
|
public void ScrollToTop(bool extendSelection)
|
|
{
|
|
if(extendSelection) {
|
|
ResizeSelectionTo(0);
|
|
} else {
|
|
SetSelectedRow(0, false, true);
|
|
ScrollToAddress(0, ScrollDisplayPosition.Top);
|
|
}
|
|
}
|
|
|
|
public void ScrollToBottom(bool extendSelection)
|
|
{
|
|
if(extendSelection) {
|
|
ResizeSelectionTo(DataProvider.GetLineCount() - 1);
|
|
} else {
|
|
int address = DataProvider.GetLineCount() - 1;
|
|
SetSelectedRow(address, false, true);
|
|
ScrollToAddress((uint)address, ScrollDisplayPosition.Bottom);
|
|
}
|
|
}
|
|
|
|
public void InvalidateVisual()
|
|
{
|
|
//Force DisassemblyViewer to refresh
|
|
Lines = Lines.ToArray();
|
|
}
|
|
|
|
public void Refresh()
|
|
{
|
|
CodeLineData[] lines = DataProvider.GetCodeLines(TopAddress, VisibleRowCount);
|
|
Lines = lines;
|
|
|
|
if(lines.Length > 0 && lines[0].Address >= 0) {
|
|
SetTopAddress(lines[0].Address);
|
|
}
|
|
|
|
_refreshScrollbar?.Invoke();
|
|
}
|
|
|
|
public void SetRefreshScrollBar(Action? refreshScrollbar)
|
|
{
|
|
_refreshScrollbar = refreshScrollbar;
|
|
}
|
|
|
|
private void SetTopAddress(int address)
|
|
{
|
|
int lineCount = DataProvider.GetLineCount();
|
|
address = Math.Max(0, Math.Min(lineCount - 1, address));
|
|
|
|
_ignoreScrollUpdates++;
|
|
TopAddress = address;
|
|
ScrollPosition = (int)(TopAddress / (double)lineCount * MaxScrollPosition);
|
|
_ignoreScrollUpdates--;
|
|
}
|
|
|
|
public void SetActiveAddress(int? pc)
|
|
{
|
|
ActiveAddress = pc;
|
|
if(pc != null) {
|
|
int currentAddress = SelectedRowAddress;
|
|
SetSelectedRow((int)pc);
|
|
bool scrolled = ScrollToAddress((uint)pc, ScrollDisplayPosition.Center, Config.Debugger.KeepActiveStatementInCenter);
|
|
if(scrolled) {
|
|
if(currentAddress != 0 || History.CanGoBack()) {
|
|
History.AddHistory(currentAddress);
|
|
}
|
|
History.AddHistory((int)pc);
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool IsSelected(int address)
|
|
{
|
|
return address >= SelectionStart && address <= SelectionEnd;
|
|
}
|
|
|
|
public AddressInfo? GetSelectedRowAddress()
|
|
{
|
|
return new AddressInfo() {
|
|
Address = SelectedRowAddress,
|
|
Type = CpuType.ToMemoryType()
|
|
};
|
|
}
|
|
|
|
public void GoBack()
|
|
{
|
|
SetSelectedRow(History.GoBack(), true, false);
|
|
}
|
|
|
|
public void GoForward()
|
|
{
|
|
SetSelectedRow(History.GoForward(), true, false);
|
|
}
|
|
|
|
public void SetSelectedRow(int address)
|
|
{
|
|
SetSelectedRow(address, false);
|
|
}
|
|
|
|
public void SetSelectedRow(int address, bool scrollToRow = false, bool addToHistory = false)
|
|
{
|
|
int currentAddress = SelectedRowAddress;
|
|
SelectionStart = address;
|
|
SelectionEnd = address;
|
|
SelectedRowAddress = address;
|
|
SelectionAnchor = address;
|
|
|
|
if(addToHistory) {
|
|
if(currentAddress != 0 || History.CanGoBack()) {
|
|
History.AddHistory(currentAddress);
|
|
}
|
|
History.AddHistory(address);
|
|
}
|
|
|
|
InvalidateVisual();
|
|
|
|
if(scrollToRow) {
|
|
ScrollToAddress((uint)address);
|
|
}
|
|
}
|
|
|
|
public void MoveCursor(int rowOffset, bool extendSelection)
|
|
{
|
|
int address = DataProvider.GetRowAddress(SelectedRowAddress, rowOffset);
|
|
if(extendSelection) {
|
|
ResizeSelectionTo(address);
|
|
} else {
|
|
SetSelectedRow(address);
|
|
ScrollToAddress((uint)address, rowOffset < 0 ? ScrollDisplayPosition.Top : ScrollDisplayPosition.Bottom);
|
|
}
|
|
}
|
|
|
|
public void ResizeSelectionTo(int address)
|
|
{
|
|
if(SelectedRowAddress == address) {
|
|
return;
|
|
}
|
|
|
|
bool anchorTop = SelectionAnchor == SelectionStart;
|
|
if(anchorTop) {
|
|
if(address < SelectionStart) {
|
|
SelectionEnd = SelectionStart;
|
|
SelectionStart = address;
|
|
} else {
|
|
SelectionEnd = address;
|
|
}
|
|
} else {
|
|
if(address < SelectionEnd) {
|
|
SelectionStart = address;
|
|
} else {
|
|
SelectionStart = SelectionEnd;
|
|
SelectionEnd = address;
|
|
}
|
|
}
|
|
|
|
ScrollDisplayPosition displayPos = SelectedRowAddress < address ? ScrollDisplayPosition.Bottom : ScrollDisplayPosition.Top;
|
|
SelectedRowAddress = address;
|
|
ScrollToAddress((uint)address, displayPos);
|
|
|
|
InvalidateVisual();
|
|
}
|
|
|
|
private bool IsAddressVisible(int address)
|
|
{
|
|
for(int i = 1; i < VisibleRowCount - 2 && i < Lines.Length; i++) {
|
|
if(Lines[i].Address == address) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private bool ScrollToAddress(uint pc, ScrollDisplayPosition position = ScrollDisplayPosition.Center, bool forceScroll = false)
|
|
{
|
|
if(!forceScroll && IsAddressVisible((int)pc)) {
|
|
//Row is already visible, don't scroll
|
|
return false;
|
|
}
|
|
|
|
ICodeDataProvider dp = DataProvider;
|
|
|
|
switch(position) {
|
|
case ScrollDisplayPosition.Top: TopAddress = dp.GetRowAddress((int)pc, -1); break;
|
|
case ScrollDisplayPosition.Center: TopAddress = dp.GetRowAddress((int)pc, -VisibleRowCount / 2 + 1); break;
|
|
case ScrollDisplayPosition.Bottom: TopAddress = dp.GetRowAddress((int)pc, -VisibleRowCount + 2); break;
|
|
}
|
|
|
|
if(!IsAddressVisible((int)pc)) {
|
|
TopAddress = dp.GetRowAddress(TopAddress, TopAddress < pc ? 1 : -1);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void CopySelection()
|
|
{
|
|
DebuggerConfig cfg = Config.Debugger;
|
|
string code = GetSelection(cfg.CopyAddresses, cfg.CopyByteCode, cfg.CopyComments, cfg.CopyBlockHeaders, out _, false);
|
|
ApplicationHelper.GetMainWindow()?.Clipboard?.SetTextAsync(code);
|
|
}
|
|
|
|
public string GetSelection(bool getAddresses, bool getByteCode, bool getComments, bool getHeaders, out int byteCount, bool skipGeneratedJmpSubLabels)
|
|
{
|
|
ICodeDataProvider dp = DataProvider;
|
|
|
|
const int commentSpacingCharCount = 25;
|
|
|
|
int addrSize = dp.CpuType.GetAddressSize();
|
|
string addrFormat = "X" + addrSize;
|
|
StringBuilder sb = new StringBuilder();
|
|
int i = SelectionStart;
|
|
int endAddress = 0;
|
|
CodeLineData? prevLine = null;
|
|
AddressDisplayType addressDisplayType = Config.Debugger.AddressDisplayType;
|
|
do {
|
|
CodeLineData[] data = dp.GetCodeLines(i, 5000);
|
|
for(int j = 0; j < data.Length; j++) {
|
|
CodeLineData lineData = data[j];
|
|
if(prevLine?.Address == lineData.Address && prevLine?.Text == lineData.Text) {
|
|
continue;
|
|
}
|
|
|
|
if(lineData.Address > SelectionEnd) {
|
|
i = lineData.Address;
|
|
break;
|
|
}
|
|
|
|
string codeString = lineData.Text.Trim();
|
|
if(lineData.Flags.HasFlag(LineFlags.BlockEnd) || lineData.Flags.HasFlag(LineFlags.BlockStart)) {
|
|
if(getHeaders) {
|
|
codeString = "--------" + codeString + "--------";
|
|
} else {
|
|
if(j == data.Length - 1) {
|
|
i = lineData.Address;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
prevLine = lineData;
|
|
|
|
int padding = Math.Max(commentSpacingCharCount, codeString.Length);
|
|
if(codeString.Length == 0) {
|
|
padding = 0;
|
|
}
|
|
|
|
codeString = codeString.PadRight(padding);
|
|
|
|
bool indentText = !(lineData.Flags.HasFlag(LineFlags.ShowAsData) || lineData.Flags.HasFlag(LineFlags.BlockStart) || lineData.Flags.HasFlag(LineFlags.BlockEnd) || lineData.Flags.HasFlag(LineFlags.Label) || (lineData.Flags.HasFlag(LineFlags.Comment) && lineData.Text.Length == 0));
|
|
string line = (indentText ? " " : "") + codeString;
|
|
if(getByteCode) {
|
|
line = lineData.ByteCodeStr.PadRight(13) + line;
|
|
}
|
|
if(getAddresses) {
|
|
string addressText = lineData.GetAddressText(addressDisplayType, addrFormat);
|
|
line = addressText.PadRight(addrSize) + " " + line;
|
|
}
|
|
if(getComments && !string.IsNullOrWhiteSpace(lineData.Comment)) {
|
|
line = line + lineData.Comment;
|
|
}
|
|
|
|
//Skip lines that contain a jump/sub "label" (these aren't
|
|
bool skipLine = skipGeneratedJmpSubLabels && lineData.Flags.HasFlag(LineFlags.Label) && lineData.Text.StartsWith("$");
|
|
|
|
string result = line.TrimEnd();
|
|
if(!skipLine && result.Length > 0) {
|
|
sb.AppendLine(result);
|
|
}
|
|
|
|
i = lineData.Address;
|
|
endAddress = lineData.Address + lineData.OpSize - 1;
|
|
}
|
|
} while(i < SelectionEnd);
|
|
|
|
if(SelectionStart <= endAddress) {
|
|
byteCount = endAddress - SelectionStart + 1;
|
|
} else {
|
|
byteCount = 0;
|
|
}
|
|
|
|
return sb.ToString();
|
|
}
|
|
}
|
|
|
|
public enum ScrollDisplayPosition
|
|
{
|
|
Top,
|
|
Center,
|
|
Bottom
|
|
}
|
|
}
|