BizHawk/BizHawk.Client.EmuHawk/tools/Atari2600/Atari2600Debugger.cs

515 lines
14 KiB
C#
Raw Normal View History

2014-04-19 19:01:13 +00:00
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using BizHawk.Client.Common;
using BizHawk.Emulation.Cores.Atari.Atari2600;
using BizHawk.Emulation.Common;
2014-04-19 19:01:13 +00:00
namespace BizHawk.Client.EmuHawk
{
public partial class Atari2600Debugger : Form, IToolForm
{
// TODO:
// Take control of mainform
// Consider how to handle trace logger (the two will compete with each other with the TakeContents() method)
// Step Over
// Step Out
// Breakpoints - Double click toggle
2014-05-26 18:23:58 +00:00
// Save breakpoints to file?
// Video Frame advance
// Add to toolbox
2014-04-19 19:01:13 +00:00
private Atari2600 _core = Global.Emulator as Atari2600;
private readonly List<string> _instructions = new List<string>();
2014-04-19 19:01:13 +00:00
2014-05-26 18:23:58 +00:00
private readonly AtariBreakpointList Breakpoints = new AtariBreakpointList();
private int _defaultWidth;
private int _defaultHeight;
private bool _programmaticUpdateOfRegisterBoxes = false; // Winforms have no way to programmitcally set the value of a widget without invoking the change event so hacks like this are necessary
//the opsize table is used to quickly grab the instruction sizes (in bytes)
private readonly byte[] opsize = new byte[]
{
/*0x00*/ 1,2,0,0,0,2,2,0,1,2,1,0,0,3,3,0,
/*0x10*/ 2,2,0,0,0,2,2,0,1,3,0,0,0,3,3,0,
/*0x20*/ 3,2,0,0,2,2,2,0,1,2,1,0,3,3,3,0,
/*0x30*/ 2,2,0,0,0,2,2,0,1,3,0,0,0,3,3,0,
/*0x40*/ 1,2,0,0,0,2,2,0,1,2,1,0,3,3,3,0,
/*0x50*/ 2,2,0,0,0,2,2,0,1,3,0,0,0,3,3,0,
/*0x60*/ 1,2,0,0,0,2,2,0,1,2,1,0,3,3,3,0,
/*0x70*/ 2,2,0,0,0,2,2,0,1,3,0,0,0,3,3,0,
/*0x80*/ 0,2,0,0,2,2,2,0,1,0,1,0,3,3,3,0,
/*0x90*/ 2,2,0,0,2,2,2,0,1,3,1,0,0,3,0,0,
/*0xA0*/ 2,2,2,0,2,2,2,0,1,2,1,0,3,3,3,0,
/*0xB0*/ 2,2,0,0,2,2,2,0,1,3,1,0,3,3,3,0,
/*0xC0*/ 2,2,0,0,2,2,2,0,1,2,1,0,3,3,3,0,
/*0xD0*/ 2,2,0,0,0,2,2,0,1,3,0,0,0,3,3,0,
/*0xE0*/ 2,2,0,0,2,2,2,0,1,2,1,0,3,3,3,0,
/*0xF0*/ 2,2,0,0,0,2,2,0,1,3,0,0,0,3,3,0
};
/*the optype table is a quick way to grab the addressing mode for any 6502 opcode
//
// 0 = Implied\Accumulator\Immediate\Branch\NULL
// 1 = (Indirect,X)
// 2 = Zero Page
// 3 = Absolute
// 4 = (Indirect),Y
// 5 = Zero Page,X
// 6 = Absolute,Y
// 7 = Absolute,X
// 8 = Zero Page,Y
*/
private readonly byte[] optype = new byte[]
{
/*0x00*/ 0,1,0,0,0,2,2,0,0,0,0,0,0,3,3,0,
/*0x10*/ 0,4,0,0,0,5,5,0,0,6,0,0,0,7,7,0,
/*0x20*/ 0,1,0,0,2,2,2,0,0,0,0,0,3,3,3,0,
/*0x30*/ 0,4,0,0,0,5,5,0,0,6,0,0,0,7,7,0,
/*0x40*/ 0,1,0,0,0,2,2,0,0,0,0,0,0,3,3,0,
/*0x50*/ 0,4,0,0,0,5,5,0,0,6,0,0,0,7,7,0,
/*0x60*/ 0,1,0,0,0,2,2,0,0,0,0,0,3,3,3,0,
/*0x70*/ 0,4,0,0,0,5,5,0,0,6,0,0,0,7,7,0,
/*0x80*/ 0,1,0,0,2,2,2,0,0,0,0,0,3,3,3,0,
/*0x90*/ 0,4,0,0,5,5,8,0,0,6,0,0,0,7,0,0,
/*0xA0*/ 0,1,0,0,2,2,2,0,0,0,0,0,3,3,3,0,
/*0xB0*/ 0,4,0,0,5,5,8,0,0,6,0,0,7,7,6,0,
/*0xC0*/ 0,1,0,0,2,2,2,0,0,0,0,0,3,3,3,0,
/*0xD0*/ 0,4,0,0,0,5,5,0,0,6,0,0,0,7,7,0,
/*0xE0*/ 0,1,0,0,2,2,2,0,0,0,0,0,3,3,3,0,
/*0xF0*/ 0,4,0,0,0,5,5,0,0,6,0,0,0,7,7,0
};
2014-04-19 19:01:13 +00:00
public Atari2600Debugger()
{
InitializeComponent();
TraceView.QueryItemText += TraceView_QueryItemText;
TraceView.VirtualMode = true;
2014-05-26 18:23:58 +00:00
BreakpointView.QueryItemText += BreakPointView_QueryItemText;
BreakpointView.VirtualMode = true;
TopMost = Global.Config.Atari2600DebuggerSettings.TopMost;
Closing += (o, e) => Shutdown();
2014-05-26 18:23:58 +00:00
Breakpoints.Callback = BreakpointCallback;
2014-04-19 19:01:13 +00:00
}
private void Atari2600Debugger_Load(object sender, EventArgs e)
{
_defaultWidth = Size.Width;
_defaultHeight = Size.Height;
2014-04-19 19:01:13 +00:00
// TODO: some kind of method like PauseAndRelinquishControl() which will set a flag preventing unpausing by the user, and then a ResumeControl() method that is done on close
//GlobalWin.MainForm.PauseEmulator();
(_core as IDebuggable).Tracer.Enabled = true;
if (Global.Config.Atari2600DebuggerSettings.UseWindowPosition)
{
Location = Global.Config.Atari2600DebuggerSettings.WindowPosition;
}
if (Global.Config.Atari2600DebuggerSettings.UseWindowSize)
{
Size = Global.Config.Atari2600DebuggerSettings.WindowSize;
}
UpdateBreakpointRemoveButton();
UpdateValues();
}
private IEnumerable<int> SelectedIndices
{
get { return BreakpointView.SelectedIndices.Cast<int>(); }
}
private IEnumerable<AtariBreakpoint> SelectedItems
{
get { return SelectedIndices.Select(index => Breakpoints[index]); }
}
private void UpdateBreakpointRemoveButton()
{
RemoveBreakpointButton.Enabled = BreakpointView.SelectedIndices.Count > 0;
}
private void Shutdown()
{
//TODO: add a Mainform.ResumeControl() call
(_core as IDebuggable).Tracer.TakeContents();
(_core as IDebuggable).Tracer.Enabled = false;
2014-04-19 19:01:13 +00:00
}
public void Restart()
{
// TODO
}
public bool AskSaveChanges()
2014-04-19 19:01:13 +00:00
{
return true;
2014-04-19 19:01:13 +00:00
}
public bool UpdateBefore
{
get { return false; }
2014-04-19 19:01:13 +00:00
}
public void UpdateValues()
{
_programmaticUpdateOfRegisterBoxes = true;
var flags = _core.GetCpuFlagsAndRegisters();
PCRegisterBox.Text = flags["PC"].ToString();
SPRegisterBox.Text = flags["S"].ToString();
SPRegisterHexBox.Text = string.Format("{0:X2}", flags["S"]);
SPRegisterBinaryBox.Text = ToBinStr(flags["S"]);
ARegisterBox.Text = flags["A"].ToString();
ARegisterHexBox.Text = string.Format("{0:X2}", flags["A"]);
ARegisterBinaryBox.Text = ToBinStr(flags["A"]);
XRegisterBox.Text = flags["X"].ToString();
XRegisterHexBox.Text = string.Format("{0:X2}", flags["X"]);
XRegisterBinaryBox.Text = ToBinStr(flags["X"]);
YRegisterBox.Text = flags["Y"].ToString();
YRegisterHexBox.Text = string.Format("{0:X2}", flags["Y"]);
YRegisterBinaryBox.Text = ToBinStr(flags["Y"]);
NFlagCheckbox.Checked = flags["Flag N"] == 1;
VFlagCheckbox.Checked = flags["Flag V"] == 1;
TFlagCheckbox.Checked = flags["Flag T"] == 1;
BFlagCheckbox.Checked = flags["Flag B"] == 1;
DFlagCheckbox.Checked = flags["Flag D"] == 1;
IFlagCheckbox.Checked = flags["Flag I"] == 1;
ZFlagCheckbox.Checked = flags["Flag Z"] == 1;
CFlagCheckbox.Checked = flags["Flag C"] == 1;
2014-04-19 19:01:13 +00:00
FrameLabel.Text = _core.Frame.ToString();
ScanlineLabel.Text = _core.CurrentScanLine.ToString();
TotalCyclesLabel.Text = _core.Cpu.TotalExecutedCycles.ToString();
DistinctAccesLabel.Text = _core.DistinctAccessCount.ToString();
LastAddressLabel.Text = _core.LastAddress.ToString();
VSyncChexkbox.Checked = _core.IsVsync;
VBlankCheckbox.Checked = _core.IsVBlank;
UpdateTraceLog();
_programmaticUpdateOfRegisterBoxes = false;
}
public void FastUpdate()
{
/* TODO */
}
private void UpdateTraceLog()
{
var instructions = (_core as IDebuggable).Tracer.TakeContents().Split('\n');
if (!string.IsNullOrWhiteSpace(instructions[0]))
{
_instructions.AddRange(instructions.Where(str => !string.IsNullOrEmpty(str)));
}
if (_instructions.Count >= Global.Config.TraceLoggerMaxLines)
{
_instructions.RemoveRange(0, _instructions.Count - Global.Config.TraceLoggerMaxLines);
}
TraceView.ItemCount = _instructions.Count;
2014-04-19 19:01:13 +00:00
}
private string ToBinStr(int val)
{
return Convert.ToString((uint)val, 2).PadLeft(8, '0');
}
private void TraceView_QueryItemText(int index, int column, out string text)
{
text = index < _instructions.Count ? _instructions[index] : string.Empty;
}
2014-05-26 18:23:58 +00:00
private void BreakPointView_QueryItemText(int index, int column, out string text)
{
text = string.Empty;
switch(column)
{
case 0:
text = string.Format("{0:X4}", Breakpoints[index].Address);
break;
case 1:
text = Breakpoints[index].Type.ToString();
break;
}
}
private void BreakPointView_QueryItemBkColor(int index, int column, ref Color color)
{
if (index >= BreakpointView.ItemCount)
{
return;
}
if (column == 0)
{
if (Breakpoints[index].Active)
{
color = Color.LightCyan;
}
else
{
color = BackColor;
}
}
}
private void BreakpointCallback()
2014-04-19 19:01:13 +00:00
{
GlobalWin.MainForm.PauseEmulator();
2014-04-19 19:01:13 +00:00
UpdateValues();
}
private void SPRegisterBox_ValueChanged(object sender, EventArgs e)
{
if (!_programmaticUpdateOfRegisterBoxes)
{
_core.SetCpuRegister("S", (int)SPRegisterBox.Value);
}
}
private void ARegisterBox_ValueChanged(object sender, EventArgs e)
{
if (!_programmaticUpdateOfRegisterBoxes)
{
_core.SetCpuRegister("A", (int)SPRegisterBox.Value);
}
}
private void XRegisterBox_ValueChanged(object sender, EventArgs e)
{
if (!_programmaticUpdateOfRegisterBoxes)
{
_core.SetCpuRegister("X", (int)SPRegisterBox.Value);
}
}
private void YRegisterBox_ValueChanged(object sender, EventArgs e)
{
if (!_programmaticUpdateOfRegisterBoxes)
{
_core.SetCpuRegister("Y", (int)SPRegisterBox.Value);
}
}
#region Menu
2014-04-19 19:01:13 +00:00
private void ExitMenuItem_Click(object sender, EventArgs e)
{
Close();
}
private void OptionsSubMenu_DropDownOpened(object sender, EventArgs e)
{
AutoloadMenuItem.Checked = Global.Config.Atari2600DebuggerAutoload;
SaveWindowPositionMenuItem.Checked = Global.Config.Atari2600DebuggerSettings.SaveWindowPosition;
TopmostMenuItem.Checked = Global.Config.Atari2600DebuggerSettings.TopMost;
FloatingWindowMenuItem.Checked = Global.Config.Atari2600DebuggerSettings.FloatingWindow;
}
private void AutoloadMenuItem_Click(object sender, EventArgs e)
{
Global.Config.Atari2600DebuggerAutoload ^= true;
}
private void SaveWindowPositionMenuItem_Click(object sender, EventArgs e)
{
Global.Config.Atari2600DebuggerSettings.SaveWindowPosition ^= true;
}
private void TopmostMenuItem_Click(object sender, EventArgs e)
{
TopMost = Global.Config.Atari2600DebuggerSettings.TopMost ^= true;
}
private void FloatingWindowMenuItem_Click(object sender, EventArgs e)
{
Global.Config.Atari2600DebuggerSettings.FloatingWindow ^= true;
RefreshFloatingWindowControl();
}
private void RestoreDefaultsMenuItem_Click(object sender, EventArgs e)
{
Size = new Size(_defaultWidth, _defaultHeight);
Global.Config.Atari2600DebuggerSettings = new ToolDialogSettings();
TopMost = Global.Config.Atari2600DebuggerSettings.TopMost;
RefreshFloatingWindowControl();
}
#endregion
#region Dialog Events
protected override void OnShown(EventArgs e)
{
RefreshFloatingWindowControl();
base.OnShown(e);
}
2014-05-26 18:23:58 +00:00
private void StepBtn_Click(object sender, EventArgs e)
{
var size = opsize[_core.Cpu.PeekMemory(_core.Cpu.PC)];
for (int i = 0; i < size; i++)
{
_core.CycleAdvance();
}
UpdateValues();
}
private void ScanlineAdvanceBtn_Click(object sender, EventArgs e)
{
_core.ScanlineAdvance();
UpdateValues();
}
private void FrameAdvButton_Click(object sender, EventArgs e)
{
_core.FrameAdvance(true, true);
UpdateValues();
}
private void AddBreakpointButton_Click(object sender, EventArgs e)
{
var b = new AddBreakpointDialog();
if (b.ShowDialog() == DialogResult.OK)
{
Breakpoints.Add(_core, b.Address, b.BreakType);
}
BreakpointView.ItemCount = Breakpoints.Count;
UpdateBreakpointRemoveButton();
}
private void RemoveBreakpointButton_Click(object sender, EventArgs e)
{
if (BreakpointView.SelectedIndices.Count > 0)
{
var items = SelectedItems.ToList();
if (items.Any())
{
foreach (var item in items)
{
Breakpoints.Remove(item);
}
BreakpointView.ItemCount = Breakpoints.Count;
UpdateBreakpointRemoveButton();
}
}
}
private void BreakpointView_SelectedIndexChanged(object sender, EventArgs e)
{
UpdateBreakpointRemoveButton();
}
private void BreakpointView_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Delete && !e.Control && !e.Alt && !e.Shift)
{
RemoveBreakpointButton_Click(sender, e);
}
}
private void RefreshFloatingWindowControl()
{
Owner = Global.Config.RamSearchSettings.FloatingWindow ? null : GlobalWin.MainForm;
}
#endregion
2014-05-26 18:23:58 +00:00
// TODO: these can be generic to any debugger
#region Breakpoint Classes
public class AtariBreakpointList : List<AtariBreakpoint>
{
public Action Callback { get; set; }
public void Add(Atari2600 core, uint address, MemoryCallbackType type)
2014-05-26 18:23:58 +00:00
{
Add(new AtariBreakpoint(core, Callback, address, type));
2014-05-26 18:23:58 +00:00
}
}
public class AtariBreakpoint
{
private bool _active;
private readonly Atari2600 _core;
2014-05-26 18:23:58 +00:00
public AtariBreakpoint(Atari2600 core, Action callBack, uint address, MemoryCallbackType type, bool enabled = true)
2014-05-26 18:23:58 +00:00
{
_core = core;
2014-05-26 18:23:58 +00:00
Callback = callBack;
Address = address;
Active = enabled;
if (enabled)
{
AddCallback();
}
}
public Action Callback { get; set; }
public uint Address { get; set; }
public MemoryCallbackType Type { get; set; }
2014-05-26 18:23:58 +00:00
public bool Active
{
get
{
return _active;
}
set
{
if (!value)
{
RemoveCallback();
}
if (!_active && value) // If inactive and changing to active
{
AddCallback();
}
_active = value;
}
}
private void AddCallback()
{
_core.MemoryCallbacks.Add(new MemoryCallback(Type, "", Callback, Address));
2014-05-26 18:23:58 +00:00
}
private void RemoveCallback()
{
_core.MemoryCallbacks.Remove(Callback);
2014-05-26 18:23:58 +00:00
}
}
#endregion
2014-04-19 19:01:13 +00:00
}
}