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; 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 Into // Step Out // Step // Advance 1 scanline? // Settable registers, also implement in lua // Breakpoints - Double click toggle, Delete to remove // Save breakpoints to file? private Atari2600 _core = Global.Emulator as Atari2600; private readonly List _instructions = new List(); private readonly AtariBreakpointList Breakpoints = new AtariBreakpointList(); private int _defaultWidth; private int _defaultHeight; //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 }; public Atari2600Debugger() { InitializeComponent(); TraceView.QueryItemText += TraceView_QueryItemText; TraceView.VirtualMode = true; BreakpointView.QueryItemText += BreakPointView_QueryItemText; BreakpointView.VirtualMode = true; TopMost = Global.Config.Atari2600DebuggerSettings.TopMost; Closing += (o, e) => Shutdown(); Breakpoints.Callback = BreakpointCallback; } private void Atari2600Debugger_Load(object sender, EventArgs e) { _defaultWidth = Size.Width; _defaultHeight = Size.Height; // 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(); Global.CoreComm.Tracer.Enabled = true; if (Global.Config.Atari2600DebuggerSettings.UseWindowPosition) { Location = Global.Config.Atari2600DebuggerSettings.WindowPosition; } if (Global.Config.Atari2600DebuggerSettings.UseWindowSize) { Size = Global.Config.Atari2600DebuggerSettings.WindowSize; } } private void Shutdown() { //TODO: add a Mainform.ResumeControl() call Global.CoreComm.Tracer.TakeContents(); Global.CoreComm.Tracer.Enabled = false; } public void Restart() { // TODO } public bool AskSave() { return true; } public bool UpdateBefore { get { return false; } } public void UpdateValues() { 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; 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(); } private void UpdateTraceLog() { var instructions = Global.CoreComm.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; } 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; } 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() { GlobalWin.MainForm.PauseEmulator(); UpdateValues(); } #region Menu 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); } 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(b.Address, b.BreakType); } BreakpointView.ItemCount = Breakpoints.Count; } private void RefreshFloatingWindowControl() { Owner = Global.Config.RamSearchSettings.FloatingWindow ? null : GlobalWin.MainForm; } #endregion // TODO: these can be generic to any debugger #region Breakpoint Classes public class AtariBreakpointList : List { public Action Callback { get; set; } public void Add(uint address, BreakpointType type) { Add(new AtariBreakpoint(Callback, address, type)); } } public class AtariBreakpoint { private bool _active; public AtariBreakpoint(Action callBack, uint address, BreakpointType type, bool enabled = true) { Callback = callBack; Address = address; Active = enabled; if (enabled) { AddCallback(); } } public Action Callback { get; set; } public uint Address { get; set; } public BreakpointType Type { get; set; } 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() { switch (Type) { case BreakpointType.Read: Global.CoreComm.MemoryCallbackSystem.AddRead(Callback, Address); break; case BreakpointType.Write: Global.CoreComm.MemoryCallbackSystem.AddWrite(Callback, Address); break; case BreakpointType.Execute: Global.CoreComm.MemoryCallbackSystem.AddExecute(Callback, Address); break; } } private void RemoveCallback() { Global.CoreComm.MemoryCallbackSystem.Remove(Callback); } } #endregion } }