Label list, call stack

This commit is contained in:
Sour 2021-05-28 21:39:18 -04:00
parent ba2c574f9e
commit 329021c63c
26 changed files with 753 additions and 135 deletions

View file

@ -582,7 +582,7 @@ shared_ptr<ScriptManager> Debugger::GetScriptManager()
shared_ptr<CallstackManager> Debugger::GetCallstackManager(CpuType cpuType)
{
if(_debuggers[(int)cpuType].Debugger) {
_debuggers[(int)cpuType].Debugger->GetCallstackManager();
return _debuggers[(int)cpuType].Debugger->GetCallstackManager();
}
throw std::runtime_error("GetCallstackManager() - Unsupported CPU type");
}
@ -600,7 +600,7 @@ Emulator* Debugger::GetEmulator()
shared_ptr<IAssembler> Debugger::GetAssembler(CpuType cpuType)
{
if(_debuggers[(int)cpuType].Debugger) {
_debuggers[(int)cpuType].Debugger->GetAssembler();
return _debuggers[(int)cpuType].Debugger->GetAssembler();
}
throw std::runtime_error("GetAssembler() - Unsupported CPU type");
}

View file

@ -293,13 +293,22 @@ CodeLineData Disassembler::GetLineData(DisassemblyResult& row, CpuType type, Sne
switch(row.Address.Type) {
default: break;
case SnesMemoryType::GbPrgRom:
case SnesMemoryType::PrgRom: data.Flags |= (uint8_t)LineFlags::PrgRom; break;
case SnesMemoryType::PrgRom:
case SnesMemoryType::NesPrgRom:
data.Flags |= (uint8_t)LineFlags::PrgRom;
break;
case SnesMemoryType::GbWorkRam:
case SnesMemoryType::WorkRam: data.Flags |= (uint8_t)LineFlags::WorkRam; break;
case SnesMemoryType::WorkRam:
case SnesMemoryType::NesWorkRam:
data.Flags |= (uint8_t)LineFlags::WorkRam;
break;
case SnesMemoryType::GbCartRam:
case SnesMemoryType::SaveRam: data.Flags |= (uint8_t)LineFlags::SaveRam; break;
case SnesMemoryType::SaveRam:
case SnesMemoryType::NesSaveRam:
data.Flags |= (uint8_t)LineFlags::SaveRam;
break;
}
bool isBlockStartEnd = (data.Flags & (LineFlags::BlockStart | LineFlags::BlockEnd)) != 0;
@ -511,8 +520,8 @@ uint32_t Disassembler::GetDisassemblyOutput(CpuType type, uint32_t address, Code
SnesMemoryType memType = DebugUtilities::GetCpuMemoryType(type);
uint32_t maxBank = (_memoryDumper->GetMemorySize(memType) - 1) >> 16;
uint32_t row;
for(row = 0; row < rowCount; row++){
int32_t row;
for(row = 0; row < (int32_t)rowCount; row++){
if(row + i >= rows.size()) {
if(bank < maxBank) {
bank++;

View file

@ -44,51 +44,12 @@ void LabelManager::SetLabel(uint32_t address, SnesMemoryType memType, string lab
int64_t LabelManager::GetLabelKey(uint32_t absoluteAddr, SnesMemoryType memType)
{
switch(memType) {
case SnesMemoryType::PrgRom: return absoluteAddr | ((uint64_t)1 << 32);
case SnesMemoryType::WorkRam: return absoluteAddr | ((uint64_t)2 << 32);
case SnesMemoryType::SaveRam: return absoluteAddr | ((uint64_t)3 << 32);
case SnesMemoryType::Register: return absoluteAddr | ((uint64_t)4 << 32);
case SnesMemoryType::SpcRam: return absoluteAddr | ((uint64_t)5 << 32);
case SnesMemoryType::SpcRom: return absoluteAddr | ((uint64_t)6 << 32);
case SnesMemoryType::Sa1InternalRam: return absoluteAddr | ((uint64_t)7 << 32);
case SnesMemoryType::GsuWorkRam: return absoluteAddr | ((uint64_t)8 << 32);
case SnesMemoryType::BsxPsRam: return absoluteAddr | ((uint64_t)9 << 32);
case SnesMemoryType::BsxMemoryPack: return absoluteAddr | ((uint64_t)10 << 32);
case SnesMemoryType::DspProgramRom: return absoluteAddr | ((uint64_t)11 << 32);
case SnesMemoryType::GbPrgRom: return absoluteAddr | ((uint64_t)12 << 32);
case SnesMemoryType::GbWorkRam: return absoluteAddr | ((uint64_t)13 << 32);
case SnesMemoryType::GbCartRam: return absoluteAddr | ((uint64_t)14 << 32);
case SnesMemoryType::GbHighRam: return absoluteAddr | ((uint64_t)15 << 32);
case SnesMemoryType::GbBootRom: return absoluteAddr | ((uint64_t)16 << 32);
case SnesMemoryType::GameboyMemory: return absoluteAddr | ((uint64_t)17 << 32);
default: return -1;
}
return absoluteAddr | ((uint64_t)memType << 32);
}
SnesMemoryType LabelManager::GetKeyMemoryType(uint64_t key)
{
switch(key & ~(uint64_t)0xFFFFFFFF) {
case ((uint64_t)1 << 32): return SnesMemoryType::PrgRom; break;
case ((uint64_t)2 << 32): return SnesMemoryType::WorkRam; break;
case ((uint64_t)3 << 32): return SnesMemoryType::SaveRam; break;
case ((uint64_t)4 << 32): return SnesMemoryType::Register; break;
case ((uint64_t)5 << 32): return SnesMemoryType::SpcRam; break;
case ((uint64_t)6 << 32): return SnesMemoryType::SpcRom; break;
case ((uint64_t)7 << 32): return SnesMemoryType::Sa1InternalRam; break;
case ((uint64_t)8 << 32): return SnesMemoryType::GsuWorkRam; break;
case ((uint64_t)9 << 32): return SnesMemoryType::BsxPsRam; break;
case ((uint64_t)10 << 32): return SnesMemoryType::BsxMemoryPack; break;
case ((uint64_t)11 << 32): return SnesMemoryType::DspProgramRom; break;
case ((uint64_t)12 << 32): return SnesMemoryType::GbPrgRom; break;
case ((uint64_t)13 << 32): return SnesMemoryType::GbWorkRam; break;
case ((uint64_t)14 << 32): return SnesMemoryType::GbCartRam; break;
case ((uint64_t)15 << 32): return SnesMemoryType::GbHighRam; break;
case ((uint64_t)16 << 32): return SnesMemoryType::GbBootRom; break;
case ((uint64_t)17 << 32): return SnesMemoryType::GameboyMemory; break;
}
throw std::runtime_error("Invalid label key");
return (SnesMemoryType)(key >> 32);
}
string LabelManager::GetLabel(AddressInfo address)

View file

@ -73,6 +73,7 @@ uint32_t MemoryDumper::GetMemorySize(SnesMemoryType type)
case SnesMemoryType::Cx4Memory: return 0x1000000;
case SnesMemoryType::GameboyMemory: return 0x10000;
case SnesMemoryType::NesMemory: return 0x10000;
case SnesMemoryType::Register: return 0x10000;
default: return _emu->GetMemory(type).Size;
}
}

View file

@ -0,0 +1,41 @@
using Mesen.Interop;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Mesen.Debugger
{
public static class DebugUtilities
{
public static uint GetProgramCounter(CpuType cpuType)
{
switch(cpuType) {
case CpuType.Cpu:
case CpuType.Sa1: {
CpuState state = DebugApi.GetState<CpuState>(cpuType);
return (uint)(state.K << 16) | state.PC;
}
case CpuType.Spc: {
SpcState state = DebugApi.GetState<SpcState>(cpuType);
return (uint)state.PC;
}
case CpuType.Gameboy: {
GbCpuState state = DebugApi.GetState<GbCpuState>(cpuType);
return (uint)state.PC;
}
case CpuType.Nes: {
NesCpuState state = DebugApi.GetState<NesCpuState>(cpuType);
return (uint)state.PC;
}
default: throw new Exception("Invalid cpu type");
}
}
}
}

View file

@ -64,7 +64,7 @@ namespace Mesen.Debugger
new SplitterDockable(),
new ToolDock {
Proportion = 0.33,
VisibleDockables = CreateList<IDockable>(new DummyTool() { Id = "Labels", Title = "Labels" })
VisibleDockables = CreateList<IDockable>(_context.LabelList)
}
)
}
@ -87,7 +87,7 @@ namespace Mesen.Debugger
new SplitterDockable(),
new ToolDock {
Proportion = 0.33,
VisibleDockables = CreateList<IDockable>(new DummyTool() { Id = "CallStack", Title = "Call Stack" })
VisibleDockables = CreateList<IDockable>(_context.CallStack)
}
)
}

View file

@ -1,4 +1,5 @@
using Mesen.Interop;
using Mesen.Debugger.Labels;
using Mesen.Interop;
using System;
using System.Collections.Generic;
using System.Linq;
@ -33,7 +34,7 @@ namespace Mesen.Debugger
{
if(EffectiveAddress >= 0) {
AddressInfo relAddress = new AddressInfo() { Address = EffectiveAddress, Type = _cpuType.ToMemoryType() };
/*CodeLabel label = LabelManager.GetLabel(relAddress);
CodeLabel? label = LabelManager.GetLabel(relAddress);
if(label != null) {
if(label.Length > 1) {
int gap = DebugApi.GetAbsoluteAddress(relAddress).Address - label.GetAbsoluteAddress().Address;
@ -42,9 +43,9 @@ namespace Mesen.Debugger
}
}
return "[" + label.Label + "]";
} else {*/
} else {
return "[$" + EffectiveAddress.ToString(format) + "]";
//}
}
} else {
return "";
}

View file

@ -1,18 +1,20 @@
using Mesen.Interop;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System;
using System.Globalization;
using System.Text;
namespace Mesen.Debugger.Labels
{
public class CodeLabel
public class CodeLabel : ReactiveObject
{
public UInt32 Address;
public SnesMemoryType MemoryType;
public string Label = "";
public string Comment = "";
public CodeLabelFlags Flags;
public UInt32 Length = 1;
[Reactive] public UInt32 Address { get; set; }
[Reactive] public SnesMemoryType MemoryType { get; set; }
[Reactive] public string Label { get; set; } = "";
[Reactive] public string Comment { get; set; } = "";
[Reactive] public CodeLabelFlags Flags { get; set; }
[Reactive] public UInt32 Length { get; set; } = 1;
public override string ToString()
{
@ -49,10 +51,10 @@ namespace Mesen.Debugger.Labels
return sb.ToString();
}
private static char[] _separatar = new char[1] { ':' };
private static char[] _separator = new char[1] { ':' };
public static CodeLabel? FromString(string data)
{
string[] rowData = data.Split(_separatar, 4);
string[] rowData = data.Split(_separator, 4);
if(rowData.Length < 3) {
//Invalid row
return null;
@ -151,5 +153,15 @@ namespace Mesen.Debugger.Labels
{
return (CodeLabel)this.MemberwiseClone();
}
public void CopyFrom(CodeLabel copy)
{
Address = copy.Address;
MemoryType = copy.MemoryType;
Label = copy.Label;
Comment = copy.Comment;
Flags = copy.Flags;
Length = copy.Length;
}
}
}

View file

@ -77,26 +77,7 @@ namespace Mesen.Debugger.Labels
private static UInt64 GetKey(UInt32 address, SnesMemoryType memType)
{
switch(memType) {
case SnesMemoryType.PrgRom: return address | ((ulong)1 << 32);
case SnesMemoryType.WorkRam: return address | ((ulong)2 << 32);
case SnesMemoryType.SaveRam: return address | ((ulong)3 << 32);
case SnesMemoryType.Register: return address | ((ulong)4 << 32);
case SnesMemoryType.SpcRam: return address | ((ulong)5 << 32);
case SnesMemoryType.SpcRom: return address | ((ulong)6 << 32);
case SnesMemoryType.Sa1InternalRam: return address | ((ulong)7 << 32);
case SnesMemoryType.GsuWorkRam: return address | ((ulong)8 << 32);
case SnesMemoryType.BsxPsRam: return address | ((ulong)9 << 32);
case SnesMemoryType.BsxMemoryPack: return address | ((ulong)10 << 32);
case SnesMemoryType.DspProgramRom: return address | ((ulong)11 << 32);
case SnesMemoryType.GbPrgRom: return address | ((ulong)12 << 32);
case SnesMemoryType.GbWorkRam: return address | ((ulong)13 << 32);
case SnesMemoryType.GbCartRam: return address | ((ulong)14 << 32);
case SnesMemoryType.GbHighRam: return address | ((ulong)15 << 32);
case SnesMemoryType.GbBootRom: return address | ((ulong)16 << 32);
case SnesMemoryType.GameboyMemory: return address | ((ulong)17 << 32);
}
throw new Exception("Invalid type");
return address | ((UInt64)memType << 32);
}
private static void SetLabel(uint address, SnesMemoryType memType, string label, string comment)
@ -234,6 +215,7 @@ namespace Mesen.Debugger.Labels
public static void SetDefaultLabels()
{
SetSnesDefaultLabels();
//TODO
/*CoprocessorType coproc = EmuApi.GetRomInfo().CoprocessorType;
if(coproc == CoprocessorType.SGB) {

View file

@ -0,0 +1,86 @@
using Dock.Model.ReactiveUI.Controls;
using Mesen.Debugger.Labels;
using Mesen.Interop;
using Mesen.ViewModels;
using ReactiveUI.Fody.Helpers;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Mesen.Debugger.ViewModels
{
public class CallStackViewModel : Tool
{
private CpuType _cpuType;
[Reactive] public List<StackInfo> StackFrames { get; private set; } = new List<StackInfo>();
//For designer
public CallStackViewModel() : this(CpuType.Cpu) { }
public CallStackViewModel(CpuType cpuType)
{
_cpuType = cpuType;
Id = "CallStack";
Title = "Call Stack";
UpdateCallStack();
}
public void UpdateCallStack()
{
StackFrames = GetStackInfo();
}
private List<StackInfo> GetStackInfo()
{
StackFrameInfo[] stackFrames = DebugApi.GetCallstack(_cpuType);
int relDestinationAddr = -1;
List<StackInfo> stack = new List<StackInfo>();
for(int i = 0, len = stackFrames.Length; i < len; i++) {
int relSubEntryAddr = i == 0 ? -1 : (int)stackFrames[i - 1].Target;
stack.Insert(0, new StackInfo() {
SubName = this.GetFunctionName(relSubEntryAddr, i == 0 ? StackFrameFlags.None : stackFrames[i - 1].Flags),
Address = stackFrames[i].Source,
});
relDestinationAddr = (int)stackFrames[i].Target;
}
//Add current location
stack.Insert(0, new StackInfo() {
SubName = this.GetFunctionName(relDestinationAddr, stackFrames.Length == 0 ? StackFrameFlags.None : stackFrames[^1].Flags),
Address = DebugUtilities.GetProgramCounter(_cpuType),
});
return stack;
}
private string GetFunctionName(int relSubEntryAddr, StackFrameFlags flags)
{
if(relSubEntryAddr < 0) {
return "[bottom of stack]";
}
string format = "X" + _cpuType.GetAddressSize();
CodeLabel? label = relSubEntryAddr >= 0 ? LabelManager.GetLabel(new AddressInfo() { Address = relSubEntryAddr, Type = _cpuType.ToMemoryType() }) : null;
if(label != null) {
return label.Label + " ($" + relSubEntryAddr.ToString(format) + ")";
} else if(flags == StackFrameFlags.Nmi) {
return "[nmi] $" + relSubEntryAddr.ToString(format);
} else if(flags == StackFrameFlags.Irq) {
return "[irq] $" + relSubEntryAddr.ToString(format);
}
return "$" + relSubEntryAddr.ToString(format);
}
public class StackInfo
{
public string SubName { get; set; }
public UInt32 Address { get; set; }
}
}
}

View file

@ -2,6 +2,7 @@
using Dock.Model.Core;
using Dock.Model.ReactiveUI.Controls;
using Mesen.Debugger.Disassembly;
using Mesen.Debugger.Labels;
using Mesen.Interop;
using Mesen.ViewModels;
using ReactiveUI;
@ -15,6 +16,8 @@ namespace Mesen.Debugger.ViewModels
{
[Reactive] public DisassemblyViewerViewModel Disassembly { get; private set; }
[Reactive] public BreakpointListViewModel BreakpointList { get; private set; }
[Reactive] public LabelListViewModel LabelList { get; private set; }
[Reactive] public CallStackViewModel CallStack { get; private set; }
[Reactive] public DebuggerDockFactory DockFactory { get; private set; }
[Reactive] public IRootDock DockLayout { get; private set; }
@ -33,13 +36,8 @@ namespace Mesen.Debugger.ViewModels
Disassembly = new DisassemblyViewerViewModel();
BreakpointList = new BreakpointListViewModel();
var factory = new DebuggerDockFactory(this);
var layout = factory.CreateLayout();
factory.InitLayout(layout);
DockFactory = factory;
DockLayout = layout;
DockFactory = new DebuggerDockFactory(this);
RomInfo romInfo = EmuApi.GetRomInfo();
@ -64,6 +62,14 @@ namespace Mesen.Debugger.ViewModels
ConfigApi.SetDebuggerFlag(DebuggerFlags.GbDebuggerEnabled, true);
break;
}
LabelManager.SetDefaultLabels();
LabelList = new LabelListViewModel(CpuType);
CallStack = new CallStackViewModel(CpuType);
DockLayout = DockFactory.CreateLayout();
DockFactory.InitLayout(DockLayout);
}
internal void UpdateDisassembly()

View file

@ -0,0 +1,66 @@
using Dock.Model.ReactiveUI.Controls;
using Mesen.Debugger.Labels;
using Mesen.Interop;
using Mesen.ViewModels;
using ReactiveUI;
using ReactiveUI.Fody.Helpers;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reactive.Linq;
namespace Mesen.Debugger.ViewModels
{
public class LabelEditViewModel : ViewModelBase
{
[Reactive] public CodeLabel Label { get; set; }
[ObservableAsProperty] public bool OkEnabled { get; }
[ObservableAsProperty] public string MaxAddress { get; }
//For designer
public LabelEditViewModel() : this(new CodeLabel()) { }
public LabelEditViewModel(CodeLabel label, CodeLabel? originalLabel = null)
{
Label = label;
this.WhenAnyValue(x => x.Label.MemoryType, (memoryType) => {
int maxAddress = DebugApi.GetMemorySize(memoryType) - 1;
if(maxAddress <= 0) {
return "(unavailable)";
} else {
return "(Max: $" + maxAddress.ToString("X4") + ")";
}
}).ToPropertyEx(this, x => x.MaxAddress);
this.WhenAnyValue(x => x.Label.Label, x => x.Label.Comment, x => x.Label.Length, x => x.Label.MemoryType, x => x.Label.Address, (label, comment, length, memoryType, address) => {
CodeLabel? sameLabel = LabelManager.GetLabel(label);
int maxAddress = DebugApi.GetMemorySize(memoryType) - 1;
for(UInt32 i = 0; i < length; i++) {
CodeLabel? sameAddress = LabelManager.GetLabel(address + i, memoryType);
if(sameAddress != null) {
if(originalLabel == null) {
//A label already exists and we're not editing an existing label, so we can't add it
return false;
} else {
if(sameAddress.Label != originalLabel.Label && !sameAddress.Label.StartsWith(originalLabel.Label + "+")) {
//A label already exists, we're trying to edit an existing label, but the existing label
//and the label we're editing aren't the same label. Can't override an existing label with a different one.
return false;
}
}
}
}
return
length >= 1 && length <= 65536 &&
address + (length - 1) <= maxAddress &&
(sameLabel == null || sameLabel == originalLabel)
&& (label.Length > 0 || comment.Length > 0)
&& !comment.Contains('\x1')
&& (label.Length == 0 || LabelManager.LabelRegex.IsMatch(label));
}).ToPropertyEx(this, x => x.OkEnabled);
}
}
}

View file

@ -0,0 +1,34 @@
using Dock.Model.ReactiveUI.Controls;
using Mesen.Debugger.Labels;
using Mesen.Interop;
using Mesen.ViewModels;
using ReactiveUI.Fody.Helpers;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace Mesen.Debugger.ViewModels
{
public class LabelListViewModel : Tool
{
private CpuType _cpuType;
[Reactive] public List<CodeLabel> Labels { get; private set; } = new List<CodeLabel>();
//For designer
public LabelListViewModel() : this(CpuType.Cpu) { }
public LabelListViewModel(CpuType cpuType)
{
_cpuType = cpuType;
Id = "Labels";
Title = "Labels";
UpdateLabelList();
}
public void UpdateLabelList()
{
Labels = LabelManager.GetLabels(_cpuType);
}
}
}

View file

@ -13,40 +13,6 @@
<vm:BreakpointListViewModel />
</Design.DataContext>
<UserControl.Styles>
<Style Selector="DataGridColumnHeader">
<Setter Property="MinHeight" Value="18" />
</Style>
<Style Selector="DataGridColumnHeader Grid">
<Setter Property="Margin" Value="-8 0 0 0" />
</Style>
<Style Selector="DataGridColumnHeader Grid ContentPresenter">
<Setter Property="Margin" Value="0 0 -30 0" />
</Style>
<Style Selector="DataGridColumnHeader Path">
<Setter Property="Width" Value="0" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridRow">
<Setter Property="Height" Value="18" />
</Style>
<Style Selector="DataGridCell">
<Setter Property="FontSize" Value="10.5" />
<Setter Property="Height" Value="14" />
<Setter Property="Padding" Value="0" />
</Style>
<Style Selector="DataGridCell /template/ ContentPresenter > TextBlock">
<Setter Property="Margin" Value="0" />
</Style>
<Style Selector="DataGridCell CheckBox">
<Setter Property="MinWidth" Value="0" />
<Setter Property="Padding" Value="0" />
</Style>
<Style Selector="DataGridCell Grid#FocusVisual">
<Setter Property="Opacity" Value="0" />
</Style>
</UserControl.Styles>
<Border BorderThickness="1" BorderBrush="LightGray">
<DataGrid
Items="{Binding Breakpoints}"

View file

@ -0,0 +1,40 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:Mesen.Debugger.ViewModels"
xmlns:dbg="using:Mesen.Debugger"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="110"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
x:DataType="vm:CallStackViewModel"
x:Class="Mesen.Debugger.Views.CallStackView"
>
<Design.DataContext>
<vm:CallStackViewModel />
</Design.DataContext>
<UserControl.Resources>
<dbg:HexConverter x:Key="Hex" />
</UserControl.Resources>
<Border BorderThickness="1" BorderBrush="LightGray">
<DataGrid
Name="DataGrid"
Items="{CompiledBinding StackFrames}"
CanUserSortColumns="True"
CanUserResizeColumns="True"
CanUserReorderColumns="True"
SelectionMode="Extended"
FontFamily="Microsoft Sans Serif"
GridLinesVisibility="All"
IsReadOnly="True"
>
<DataGrid.Columns>
<DataGridTextColumn Header="Function (Entry Address)" Binding="{Binding SubName}" Width="1*" CanUserResize="True" />
<DataGridTextColumn Header="PC Address" Binding="{Binding Address, Converter={StaticResource Hex}, ConverterParameter='X4'}}" Width="1*" CanUserResize="True" />
</DataGrid.Columns>
</DataGrid>
</Border>
</UserControl>

View file

@ -0,0 +1,27 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Mesen.ViewModels;
using Mesen.Debugger;
using Mesen.Debugger.ViewModels;
using Mesen.Debugger.Labels;
using Mesen.Debugger.Windows;
using Mesen.Utilities;
using Avalonia.Input;
namespace Mesen.Debugger.Views
{
public class CallStackView : UserControl
{
public CallStackView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

View file

@ -0,0 +1,66 @@
<UserControl
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:Mesen.Debugger.ViewModels"
xmlns:dbg="using:Mesen.Debugger"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="110"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
x:DataType="vm:LabelListViewModel"
x:Class="Mesen.Debugger.Views.LabelListView"
>
<Design.DataContext>
<vm:LabelListViewModel />
</Design.DataContext>
<UserControl.Resources>
<dbg:HexConverter x:Key="Hex" />
</UserControl.Resources>
<Border BorderThickness="1" BorderBrush="LightGray">
<DataGrid
Name="DataGrid"
Items="{CompiledBinding Labels}"
CanUserSortColumns="True"
CanUserResizeColumns="True"
CanUserReorderColumns="True"
SelectionMode="Extended"
FontFamily="Microsoft Sans Serif"
GridLinesVisibility="All"
DoubleTapped="OnGridDoubleClick"
Tapped="OnGridClick"
CellPointerPressed="OnCellPointerPressed"
IsReadOnly="True"
>
<DataGrid.Columns>
<DataGridTextColumn Header="Label" Binding="{Binding Label}" Width="1*" CanUserResize="True" />
<DataGridTextColumn Header="CPU Addr" Width="1*" CanUserResize="True" />
<DataGridTextColumn Header="ROM Addr" Binding="{Binding Address, Converter={StaticResource Hex}, ConverterParameter='X4'}}" Width="1*" CanUserResize="True" />
<DataGridTextColumn Header="Comment" Binding="{Binding Comment}" Width="1*" CanUserResize="True" />
</DataGrid.Columns>
<DataGrid.ContextMenu>
<ContextMenu PlacementMode="Pointer">
<MenuItem Header="Add" Click="mnuAddLabel_Click">
<MenuItem.Icon>
<Image Source="/Assets/Add.png" />
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Edit" Click="mnuEditLabel_Click">
<MenuItem.Icon>
<Image Source="/Assets/EditLabel.png" />
</MenuItem.Icon>
</MenuItem>
<Separator />
<MenuItem Header="Delete" Click="mnuDeleteLabel_Click">
<MenuItem.Icon>
<Image Source="/Assets/Close.png" />
</MenuItem.Icon>
</MenuItem>
</ContextMenu>
</DataGrid.ContextMenu>
</DataGrid>
</Border>
</UserControl>

View file

@ -0,0 +1,94 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Mesen.ViewModels;
using Mesen.Debugger;
using Mesen.Debugger.ViewModels;
using Mesen.Debugger.Labels;
using Mesen.Debugger.Windows;
using Mesen.Utilities;
using Avalonia.Input;
namespace Mesen.Debugger.Views
{
public class LabelListView : UserControl
{
public LabelListView()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void OnCellPointerPressed(object sender, DataGridCellPointerPressedEventArgs e)
{
DataGrid grid = this.FindControl<DataGrid>("DataGrid");
grid.SelectedIndex = e.Row.GetIndex();
}
private void OnGridClick(object sender, RoutedEventArgs e)
{
}
private async void mnuAddLabel_Click(object sender, RoutedEventArgs e)
{
CodeLabel newLabel = new CodeLabel();
LabelEditWindow wnd = new LabelEditWindow() {
DataContext = new LabelEditViewModel(newLabel)
};
bool result = await wnd.ShowCenteredDialog<bool>(this);
if(result) {
LabelManager.SetLabel(newLabel, true);
((LabelListViewModel)DataContext!).UpdateLabelList();
}
}
private void mnuEditLabel_Click(object sender, RoutedEventArgs e)
{
DataGrid grid = this.FindControl<DataGrid>("DataGrid");
CodeLabel ? label = grid.SelectedItem as CodeLabel;
if(label != null && grid != null) {
EditLabel(label);
}
}
private void mnuDeleteLabel_Click(object sender, RoutedEventArgs e)
{
DataGrid grid = this.FindControl<DataGrid>("DataGrid");
CodeLabel? label = grid.SelectedItem as CodeLabel;
if(label != null && grid != null) {
LabelManager.DeleteLabel(label, true);
((LabelListViewModel)DataContext!).UpdateLabelList();
}
}
private void OnGridDoubleClick(object sender, RoutedEventArgs e)
{
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);
}
}
}
}

View file

@ -27,6 +27,7 @@
<Style Selector="TextBox.h1">
<Setter Property="FontFamily" Value="Consolas" />
<Setter Property="FontSize" Value="12" />
<Setter Property="MinWidth" Value="0" />
</Style>
</UserControl.Styles>
@ -39,6 +40,8 @@
<TextBlock>Y:</TextBlock>
<TextBox Classes="h1" Text="{CompiledBinding RegY, Converter={StaticResource Hex}}" />
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<TextBlock>K:</TextBlock>
<TextBox Classes="h1" Text="{CompiledBinding RegK, Converter={StaticResource Hex}}" />
<TextBlock>PC:</TextBlock>
<TextBox Classes="h1" Text="{CompiledBinding RegPC, Converter={StaticResource Hex}}" />
</StackPanel>
@ -46,20 +49,20 @@
</DockPanel>
<DockPanel>
<TextBlock>D:</TextBlock>
<TextBox Classes="h1" Text="{Binding RegD}" />
<TextBox Classes="h1" Text="{Binding RegD, Converter={StaticResource Hex}}" />
<TextBlock>DB:</TextBlock>
<TextBox Classes="h1" Text="{Binding RegDBR}" />
<TextBox Classes="h1" Text="{Binding RegDBR, Converter={StaticResource Hex}}" />
<StackPanel DockPanel.Dock="Right" Orientation="Horizontal">
<TextBlock>S:</TextBlock>
<TextBox Classes="h1" Text="{Binding RegSP}" />
<TextBox Classes="h1" Text="{Binding RegSP, Converter={StaticResource Hex}}" />
</StackPanel>
<TextBlock />
</DockPanel>
<DockPanel Margin="0 2 0 0">
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal" VerticalAlignment="Top">
<TextBlock>P:</TextBlock>
<TextBox Classes="h1" Text="{Binding RegPS}" />
<TextBox Classes="h1" Text="{Binding RegPS, Converter={StaticResource Hex}}" />
</StackPanel>
<TextBox

View file

@ -9,6 +9,7 @@ using Mesen.Debugger.Disassembly;
using Mesen.Interop;
using Avalonia.Interactivity;
using System.ComponentModel;
using Mesen.Debugger.Labels;
namespace Mesen.Debugger.Windows
{
@ -66,6 +67,7 @@ namespace Mesen.Debugger.Windows
if(e.NotificationType == ConsoleNotificationType.CodeBreak) {
_model.UpdateCpuState();
_model.UpdateDisassembly();
_model.CallStack.UpdateCallStack();
}
}

View file

@ -0,0 +1,80 @@
<Window
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:m="clr-namespace:Mesen"
xmlns:vm="using:Mesen.Debugger.ViewModels"
xmlns:sys="using:System"
xmlns:v="using:Mesen.Views"
xmlns:dbg="using:Mesen.Debugger"
xmlns:c="using:Mesen.Controls"
xmlns:i="using:Mesen.Interop"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:dvm="using:Mesen.Debugger.ViewModels"
xmlns:dc="using:Mesen.Debugger.Controls"
mc:Ignorable="d" d:DesignWidth="300" d:DesignHeight="230"
x:Class="Mesen.Debugger.Windows.LabelEditWindow"
x:DataType="vm:LabelEditViewModel"
Icon="/Assets/EditLabel.png"
Width="300" Height="230"
Title="Edit label..."
>
<Design.DataContext>
<vm:LabelEditViewModel />
</Design.DataContext>
<Window.Resources>
<dbg:HexConverter x:Key="Hex" />
</Window.Resources>
<DockPanel>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right">
<Button Width="70" HorizontalContentAlignment="Center" Click="Ok_OnClick" Content="OK" IsEnabled="{CompiledBinding OkEnabled}" />
<Button Width="70" HorizontalContentAlignment="Center" IsCancel="True" Click="Cancel_OnClick" Content="Cancel" />
</StackPanel>
<Grid
DockPanel.Dock="Right"
ColumnDefinitions="Auto,*"
RowDefinitions="Auto,Auto,Auto,Auto,Auto"
Margin="5 0 5 0"
>
<TextBlock Grid.Column="0" Grid.Row="0" Text="Memory type:" />
<c:EnumComboBox
Grid.Column="1" Grid.Row="0"
EnumType="{x:Type i:SnesMemoryType}"
SelectedItem="{CompiledBinding Label.MemoryType}"
/>
<TextBlock Grid.Column="0" Grid.Row="1" Text="Address:" />
<StackPanel Grid.Column="1" Grid.Row="1" Orientation="Horizontal">
<TextBlock Margin="0 0 3 0">$</TextBlock>
<TextBox Text="{CompiledBinding Label.Address, Converter={StaticResource Hex}, ConverterParameter='X'}" Width="50" />
<TextBlock Margin="3 0 0 0" Text="{CompiledBinding MaxAddress}" Foreground="Gray" />
</StackPanel>
<TextBlock Grid.Column="0" Grid.Row="2" Text="Label:" />
<TextBox
Grid.Column="1" Grid.Row="2"
Text="{CompiledBinding Label.Label}"
/>
<TextBlock Grid.Column="0" Grid.Row="3" Text="Comment:" />
<TextBox
Grid.Column="1" Grid.Row="3"
AcceptsReturn="True"
Height="100"
VerticalContentAlignment="Top"
Text="{CompiledBinding Label.Comment}"
/>
<TextBlock Grid.Column="0" Grid.Row="4" Text="Length:" />
<NumericUpDown
Grid.Column="1" Grid.Row="4"
Value="{CompiledBinding Label.Length}"
Minimum="1"
Maximum="65536"
/>
</Grid>
</DockPanel>
</Window>

View file

@ -0,0 +1,33 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
namespace Mesen.Debugger.Windows
{
public class LabelEditWindow : Window
{
public LabelEditWindow()
{
InitializeComponent();
#if DEBUG
this.AttachDevTools();
#endif
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private void Ok_OnClick(object sender, RoutedEventArgs e)
{
Close(true);
}
private void Cancel_OnClick(object sender, RoutedEventArgs e)
{
Close(false);
}
}
}

View file

@ -721,7 +721,7 @@ namespace Mesen.Interop
Zero = 0x80
}
public struct GbCpuState
public struct GbCpuState : BaseState
{
public UInt16 PC;
public UInt16 SP;

View file

@ -179,6 +179,12 @@
<Compile Update="Controls\OptionSection.axaml.cs">
<DependentUpon>OptionSection.axaml</DependentUpon>
</Compile>
<Compile Update="Debugger\Views\CallStackView.axaml.cs">
<DependentUpon>CallStackView.axaml</DependentUpon>
</Compile>
<Compile Update="Debugger\Views\LabelListView.axaml.cs">
<DependentUpon>LabelListView.axaml</DependentUpon>
</Compile>
<Compile Update="Debugger\Views\DebuggerDock\StatusToolView.axaml.cs">
<DependentUpon>StatusToolView.axaml</DependentUpon>
</Compile>
@ -191,6 +197,9 @@
<Compile Update="Debugger\Views\SnesPpuView.axaml.cs">
<DependentUpon>SnesPpuView.axaml</DependentUpon>
</Compile>
<Compile Update="Debugger\Windows\LabelEditWindow.axaml.cs">
<DependentUpon>LabelEditWindow.axaml</DependentUpon>
</Compile>
<Compile Update="Debugger\Windows\TileViewerWindow.axaml.cs">
<DependentUpon>TileViewerWindow.axaml</DependentUpon>
</Compile>

View file

@ -344,6 +344,55 @@
<Setter Property="CornerRadius" Value="0" />
</Style>
<Style Selector="ContextMenu">
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="0"
MaxWidth="{TemplateBinding MaxWidth}"
MinHeight="{TemplateBinding MinHeight}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
CornerRadius="{DynamicResource OverlayCornerRadius}">
<Panel>
<Rectangle
HorizontalAlignment="Left"
Width="24">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="24,0">
<GradientStop Color="#fcfcfc" Offset="0.0" />
<GradientStop Color="#f1f1f1" Offset="1.0" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<ScrollViewer Classes="menuscroller">
<ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}"
ItemTemplate="{TemplateBinding ItemTemplate}"
Margin="{DynamicResource MenuFlyoutScrollerMargin}"
KeyboardNavigation.TabNavigation="Continue"
Grid.IsSharedSizeScope="True" />
</ScrollViewer>
</Panel>
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="ContextMenu > MenuItem">
<Setter Property="Height" Value="20" />
<Setter Property="Padding" Value="2 0" />
</Style>
<Style Selector="ContextMenu > MenuItem:selected /template/ Border">
<Setter Property="Background" Value="{DynamicResource MesenMenuBackgroundHighlight}" />
<Setter Property="BorderBrush" Value="{DynamicResource MesenMenuBorderHighlight}" />
<Setter Property="BorderThickness" Value="1" />
</Style>
<Style Selector="Button">
<Setter Property="Height" Value="21" />
<Setter Property="Margin" Value="1" />
@ -491,6 +540,44 @@
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
<Style Selector="ScrollBar">
<Setter Property="AllowAutoHide" Value="False" />
</Style>
<!-- Data grid -->
<Style Selector="DataGridColumnHeader">
<Setter Property="MinHeight" Value="18" />
</Style>
<Style Selector="DataGridColumnHeader Grid">
<Setter Property="Margin" Value="-8 0 0 0" />
</Style>
<Style Selector="DataGridColumnHeader Grid ContentPresenter">
<Setter Property="Margin" Value="0 0 -30 0" />
</Style>
<Style Selector="DataGridColumnHeader Path">
<Setter Property="Width" Value="0" />
<Setter Property="MinWidth" Value="0" />
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridRow">
<Setter Property="Height" Value="18" />
</Style>
<Style Selector="DataGridCell">
<Setter Property="FontSize" Value="10.5" />
<Setter Property="Height" Value="14" />
<Setter Property="Padding" Value="0" />
</Style>
<Style Selector="DataGridCell /template/ ContentPresenter > TextBlock">
<Setter Property="Margin" Value="0" />
</Style>
<Style Selector="DataGridCell CheckBox">
<Setter Property="MinWidth" Value="0" />
<Setter Property="Padding" Value="0" />
</Style>
<Style Selector="DataGridCell Grid#FocusVisual">
<Setter Property="Opacity" Value="0" />
</Style>
<!-- Disable overlapped scrollbar in scrollviewer -->
<Style Selector="ScrollViewer">
<Setter Property="Background"

View file

@ -10,24 +10,36 @@ namespace Mesen.Utilities
{
static class WindowExtensions
{
private static void CenterWindow(Window child, Window parent)
private static void CenterWindow(Window child, Control parent)
{
child.WindowStartupLocation = WindowStartupLocation.Manual;
Size wndCenter = (parent.ClientSize / 2);
PixelPoint screenCenter = new PixelPoint(parent.Position.X + (int)wndCenter.Width, parent.Position.Y + (int)wndCenter.Height);
Size wndCenter = new Size(parent.Bounds.Width / 2, parent.Bounds.Height / 2);
PixelPoint controlPosition =parent.PointToScreen(new Point(0, 0));
PixelPoint screenCenter = new PixelPoint(controlPosition.X + (int)wndCenter.Width, controlPosition.Y + (int)wndCenter.Height);
child.Position = new PixelPoint(screenCenter.X - (int)child.Width / 2, screenCenter.Y - (int)child.Height / 2);
}
public static void ShowCentered(this Window child, Window parent)
public static void ShowCentered(this Window child, Control parent)
{
CenterWindow(child, parent);
child.Show();
}
public static Task ShowCenteredDialog(this Window child, Window parent)
public static Task ShowCenteredDialog(this Window child, Control parent)
{
return ShowCenteredDialog<object>(child, parent);
}
public static Task<TResult> ShowCenteredDialog<TResult>(this Window child, Control parent)
{
CenterWindow(child, parent);
return child.ShowDialog(parent);
IControl parentWnd = parent;
while(!(parentWnd is Window) && parentWnd != null) {
parentWnd = parentWnd.Parent;
}
return child.ShowDialog<TResult>(parentWnd as Window);
}
}
}