using System; using System.Text; using System.Windows.Forms; using Core.Cpu; using Core.Memory; namespace Desktop { public partial class DebuggerForm : Form { private readonly Z80 _cpu; private readonly MemoryBus _memoryBus; private bool _isRunning = false; private ushort? _breakpoint = null; public DebuggerForm(Z80 cpu, MemoryBus memoryBus) { InitializeComponent(); _cpu = cpu; _memoryBus = memoryBus; // Set default memory view address txtMemoryStart.Text = "0000"; UpdateDisplay(); UpdateStackView(); UpdateDisassemblyView(); } private void btnStep_Click(object sender, EventArgs e) { try { _cpu.Step(); UpdateDisplay(); } catch (Exception ex) { MessageBox.Show(ex.Message, "CPU Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { UpdateDisplay(); } } private void btnRefreshMemory_Click(object sender, EventArgs e) { UpdateDisplay(); } private async void btnRun_Click(object sender, EventArgs e) { // If it is already running, this button acts as a STOP button if (_isRunning) { _isRunning = false; btnRun.Text = "Run"; return; } // --- NEW: Parse the Breakpoint --- if (!string.IsNullOrWhiteSpace(txtBreakpoint.Text)) { if (ushort.TryParse(txtBreakpoint.Text, System.Globalization.NumberStyles.HexNumber, null, out ushort parsedBp)) { _breakpoint = parsedBp; } else { MessageBox.Show("Invalid hex address in breakpoint!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); return; } } else { _breakpoint = null; // No text means no breakpoint } // --------------------------------- // Start the run state _isRunning = true; btnRun.Text = "Stop"; // Fire up a background thread await Task.Run(() => { try { while (_isRunning) { // --- NEW: Breakpoint Check --- // We check BEFORE stepping so it stops exactly on the instruction if (_breakpoint.HasValue && _cpu.PC == _breakpoint.Value) { _isRunning = false; break; // Cleanly exit the while loop } // ----------------------------- _cpu.Step(); } } catch (Exception ex) { _isRunning = false; this.Invoke((MethodInvoker)delegate { MessageBox.Show(ex.Message, "CPU Break", MessageBoxButtons.OK, MessageBoxIcon.Information); }); } }); // Whether it stopped because of a breakpoint, a crash, or the user clicking "Stop", // we MUST update the UI when the background thread finishes! btnRun.Text = "Run"; UpdateDisplay(); } private void btnExit_Click(object sender, EventArgs e) { Environment.Exit(0); } // This is the master function that pulls state from the CPU private void UpdateDisplay() { // 1. Update Registers (Formatting as 4-character Hex strings) lblAF.Text = $"AF: {_cpu.AF.Word:X4}"; lblBC.Text = $"BC: {_cpu.BC.Word:X4}"; lblDE.Text = $"DE: {_cpu.DE.Word:X4}"; lblHL.Text = $"HL: {_cpu.HL.Word:X4}"; lblPC.Text = $"PC: {_cpu.PC:X4}"; lblSP.Text = $"SP: {_cpu.SP:X4}"; // 2. Update Flags & T-States lblFlags.Text = $"Flags: {_cpu.GetFlagsString()}"; lblTStates.Text = $"T-States: {_cpu.TotalTStates}"; // 3. Update Memory Viewer UpdateMemoryView(); UpdateStackView(); UpdateDisassemblyView(); } private void UpdateMemoryView() { // Try to parse the hex string the user typed in if (!ushort.TryParse(txtMemoryStart.Text, System.Globalization.NumberStyles.HexNumber, null, out ushort startAddress)) { txtMemoryView.Text = "Invalid Hex Address!"; return; } StringBuilder sb = new StringBuilder(); // Read 100 bytes (or roughly 6 lines of 16 bytes) for (int line = 0; line < 7; line++) { ushort currentAddr = (ushort)(startAddress + (line * 16)); // Print the address header for this line (e.g., "0000: ") sb.Append($"{currentAddr:X4}: "); // Print 16 bytes across for (int i = 0; i < 16; i++) { // Careful not to overflow the 64k address space! if (currentAddr + i <= 0xFFFF) { byte b = _memoryBus.Read((ushort)(currentAddr + i)); sb.Append($"{b:X2} "); } } sb.AppendLine(); } txtMemoryView.Text = sb.ToString(); } private void UpdateStackView() { lstStack.Items.Clear(); int itemsToShow = 5; ushort currentSp = _cpu.SP; for (int i = 0; i < itemsToShow; i++) { // Prevent reading past 0xFFFF if (currentSp >= 0xFFFE) { lstStack.Items.Add($"{currentSp:X4}: [End of Mem]"); break; } // Read the 16-bit value (Little-Endian: Low byte first, then High byte) byte low = _memoryBus.Read(currentSp); byte high = _memoryBus.Read((ushort)(currentSp + 1)); ushort value = (ushort)((high << 8) | low); lstStack.Items.Add($"{currentSp:X4}: {value:X4}"); // Move to the next 16-bit word on the stack currentSp += 2; } } private void UpdateDisassemblyView() { lstDisassembly.Items.Clear(); ushort currentPc = _cpu.PC; int instructionsToShow = 8; for (int i = 0; i < instructionsToShow; i++) { byte opcode = _memoryBus.Read(currentPc); string mnemonic; int instructionLength = 1; // Default to 1 switch (opcode) { case 0x00: mnemonic = "NOP"; break; case 0x11: // LD DE, nn byte deLow = _memoryBus.Read((ushort)(currentPc + 1)); byte deHigh = _memoryBus.Read((ushort)(currentPc + 2)); mnemonic = $"LD DE, 0x{deHigh:X2}{deLow:X2}"; instructionLength = 3; break; case 0x19: mnemonic = "ADD HL, DE"; break; case 0x20: sbyte jrOffset = (sbyte)_memoryBus.Read((ushort)(currentPc + 1)); ushort destination = (ushort)(currentPc + 2 + jrOffset); mnemonic = $"JR NZ, 0x{destination:X4}"; instructionLength = 2; break; case 0x23: mnemonic = "INC HL"; break; case 0x28: sbyte jrZOffset = (sbyte)_memoryBus.Read((ushort)(currentPc + 1)); ushort jrZDest = (ushort)(currentPc + 2 + jrZOffset); mnemonic = $"JR Z, 0x{jrZDest:X4}"; instructionLength = 2; break; case 0x2B: mnemonic = "DEC HL"; break; case 0x30: sbyte jrNcOffset = (sbyte)_memoryBus.Read((ushort)(currentPc + 1)); ushort dest = (ushort)(currentPc + 2 + jrNcOffset); mnemonic = $"JR NC, 0x{dest:X4}"; instructionLength = 2; break; case 0x35: mnemonic = "DEC (HL)"; break; case 0x36: byte memValue = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"LD (HL), 0x{memValue:X2}"; instructionLength = 2; break; case 0x3E: mnemonic = $"LD A, 0x{_memoryBus.Read((ushort)(currentPc + 1)):X2}"; instructionLength = 2; break; case 0x47: mnemonic = "LD B, A"; break; case 0x62: mnemonic = "LD H, D"; break; case 0x6B: mnemonic = "LD L, E"; break; case 0xA7: mnemonic = "AND A"; break; case 0xAF: mnemonic = "XOR A"; break; case 0xBC: mnemonic = "CP H"; break; case 0xC3: // JP nn byte jpLow = _memoryBus.Read((ushort)(currentPc + 1)); byte jpHigh = _memoryBus.Read((ushort)(currentPc + 2)); mnemonic = $"JP 0x{jpHigh:X2}{jpLow:X2}"; instructionLength = 3; break; case 0xD3: byte outPort = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"OUT (0x{outPort:X2}), A"; instructionLength = 2; break; case 0xD9: mnemonic = "EXX"; break; case 0xDE: byte sbcValue = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"SBC A, 0x{sbcValue:X2}"; instructionLength = 2; break; case 0xED: byte extendedOp = _memoryBus.Read((ushort)(currentPc + 1)); switch (extendedOp) { // Example: ED 47 is LD I, A case 0x47: mnemonic = "LD I, A"; instructionLength = 2; // 0xED + 0x47 break; case 0x52: mnemonic = "SBC HL, DE"; instructionLength = 2; // ED 52 break; // Example: ED B0 is LDIR (a massive block copy instruction) case 0xB0: mnemonic = "LDIR"; instructionLength = 2; break; default: mnemonic = $"EXT UNKNOWN (ED {extendedOp:X2})"; instructionLength = 2; // Most ED instructions are 2 bytes, but some have operands! break; } break; case 0xF3: mnemonic = "DI"; break; default: mnemonic = $"UNKNOWN (0x{opcode:X2})"; break; } lstDisassembly.Items.Add($"{currentPc:X4}: {mnemonic}"); // Advance the fake PC just for drawing the next line in the UI currentPc += (ushort)instructionLength; } } } }