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 btnReset_Click(object sender, EventArgs e) { // 1. Safely stop the emulator if it is currently in a Run loop if (_isRunning) { _isRunning = false; btnRun.Text = "Run"; } // 2. Power cycle the CPU _cpu.Reset(); // Note: A true hard reset also wipes the RAM. // If you add a RAM clear method to your MemoryBus later, call it here! _memoryBus.ClearRam(); //To Do // 3. Clear the UI tracking lists lstDisassembly.Items.Clear(); lstStack.Items.Clear(); // 4. Force a full UI refresh to show the clean slate 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}"; lblIX.Text = $"IX: {_cpu.IX.Word:X4}"; lblIY.Text = $"IY: {_cpu.IY.Word:X4}"; lblIff1.Text = $"IFF1: {_cpu.IFF1}"; lblIff2.Text = $"IFF2: {_cpu.IFF2}"; lblIE.Text = $"Interrupt Mode: {_cpu.InterruptMode}"; // 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 0x01: ushort bcVal = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); mnemonic = $"LD BC, 0x{bcVal:X4}"; instructionLength = 3; break; // --- 16-Bit Increments --- case 0x03: mnemonic = "INC BC"; break; case 0x13: mnemonic = "INC DE"; break; case 0x33: mnemonic = "INC SP"; break; // --- 16-Bit Decrements --- case 0x0B: mnemonic = "DEC BC"; break; case 0x3B: mnemonic = "DEC SP"; break; // --- 8-Bit Increments --- case 0x04: mnemonic = "INC B"; break; case 0x0C: mnemonic = "INC C"; break; case 0x14: mnemonic = "INC D"; break; case 0x1C: mnemonic = "INC E"; break; case 0x24: mnemonic = "INC H"; break; case 0x2C: mnemonic = "INC L"; break; case 0x34: mnemonic = "INC (HL)"; break; case 0x3C: mnemonic = "INC A"; break; // --- 8-Bit Decrements --- case 0x05: mnemonic = "DEC B"; break; case 0x0D: mnemonic = "DEC C"; break; case 0x15: mnemonic = "DEC D"; break; case 0x1D: mnemonic = "DEC E"; break; case 0x25: mnemonic = "DEC H"; break; case 0x2D: mnemonic = "DEC L"; break; case 0x35: mnemonic = "DEC (HL)"; break; case 0x3D: mnemonic = "DEC A"; break; case 0x06: byte bImm = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"LD B, 0x{bImm:X2}"; instructionLength = 2; break; // --- ADD HL, rr --- case 0x09: mnemonic = "ADD HL, BC"; break; case 0x19: mnemonic = "ADD HL, DE"; break; case 0x29: mnemonic = "ADD HL, HL"; break; case 0x39: mnemonic = "ADD HL, SP"; break; case 0x0E: byte cImm = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"LD C, 0x{cImm:X2}"; instructionLength = 2; break; case 0x0F: mnemonic = "RRCA"; break; case 0x10: sbyte djnzOffset = (sbyte)_memoryBus.Read((ushort)(currentPc + 1)); ushort djnzDest = (ushort)(currentPc + 2 + djnzOffset); mnemonic = $"DJNZ 0x{djnzDest:X4}"; instructionLength = 2; 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 0x16: byte dImm = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"LD D, 0x{dImm:X2}"; instructionLength = 2; break; case 0x18: sbyte dUnconditional = (sbyte)_memoryBus.Read((ushort)(currentPc + 1)); // Calculate the target address based on the PC *after* this 2-byte instruction ushort targetAddressUnconditional = (ushort)(currentPc + 2 + dUnconditional); mnemonic = $"JR 0x{targetAddressUnconditional:X4}"; instructionLength = 2; break; case 0x1A: mnemonic = "LD A, (DE)"; break; case 0x1B: mnemonic = "DEC 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 0x21: { ushort hlImm = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); mnemonic = $"LD HL, 0x{hlImm:X4}"; instructionLength = 3; break; } case 0x22: ushort hlAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); mnemonic = $"LD (0x{hlAddr:X4}), HL"; instructionLength = 3; break; case 0x23: mnemonic = "INC HL"; break; case 0x26: byte hImm = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"LD H, 0x{hImm:X2}"; instructionLength = 2; 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 0x2A: { ushort addr2A = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); mnemonic = $"LD HL, (0x{addr2A:X4})"; instructionLength = 3; 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 0x32: { ushort addr32 = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); mnemonic = $"LD (0x{addr32:X4}), A"; instructionLength = 3; break; } case 0x36: byte memValue = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"LD (HL), 0x{memValue:X2}"; instructionLength = 2; break; case 0x37: mnemonic = "SCF"; break; case 0x38: sbyte dC = (sbyte)_memoryBus.Read((ushort)(currentPc + 1)); ushort targetC = (ushort)(currentPc + 2 + dC); mnemonic = $"JR C, 0x{targetC:X4}"; instructionLength = 2; break; case 0x3A: ushort addr3A = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); mnemonic = $"LD A, (0x{addr3A:X4})"; instructionLength = 3; break; case 0x3E: mnemonic = $"LD A, 0x{_memoryBus.Read((ushort)(currentPc + 1)):X2}"; instructionLength = 2; break; case 0x3F: mnemonic = "CCF"; break; // --- LD B, r --- case 0x40: mnemonic = "LD B, B"; break; case 0x41: mnemonic = "LD B, C"; break; case 0x42: mnemonic = "LD B, D"; break; case 0x43: mnemonic = "LD B, E"; break; case 0x44: mnemonic = "LD B, H"; break; case 0x45: mnemonic = "LD B, L"; break; case 0x46: mnemonic = "LD B, (HL)"; break; case 0x47: mnemonic = "LD B, A"; break; // --- LD C, r --- case 0x48: mnemonic = "LD C, B"; break; case 0x49: mnemonic = "LD C, C"; break; case 0x4A: mnemonic = "LD C, D"; break; case 0x4B: mnemonic = "LD C, E"; break; case 0x4C: mnemonic = "LD C, H"; break; case 0x4D: mnemonic = "LD C, L"; break; case 0x4E: mnemonic = "LD C, (HL)"; break; case 0x4F: mnemonic = "LD C, A"; break; // --- LD D, r --- case 0x50: mnemonic = "LD D, B"; break; case 0x51: mnemonic = "LD D, C"; break; case 0x52: mnemonic = "LD D, D"; break; case 0x53: mnemonic = "LD D, E"; break; case 0x54: mnemonic = "LD D, H"; break; case 0x55: mnemonic = "LD D, L"; break; case 0x56: mnemonic = "LD D, (HL)"; break; case 0x57: mnemonic = "LD D, A"; break; // --- LD E, r --- case 0x58: mnemonic = "LD E, B"; break; case 0x59: mnemonic = "LD E, C"; break; case 0x5A: mnemonic = "LD E, D"; break; case 0x5B: mnemonic = "LD E, E"; break; case 0x5C: mnemonic = "LD E, H"; break; case 0x5D: mnemonic = "LD E, L"; break; case 0x5E: mnemonic = "LD E, (HL)"; break; case 0x5F: mnemonic = "LD E, A"; break; // --- LD H, r --- case 0x60: mnemonic = "LD H, B"; break; case 0x61: mnemonic = "LD H, C"; break; case 0x62: mnemonic = "LD H, D"; break; case 0x63: mnemonic = "LD H, E"; break; case 0x64: mnemonic = "LD H, H"; break; case 0x65: mnemonic = "LD H, L"; break; case 0x66: mnemonic = "LD H, (HL)"; break; case 0x67: mnemonic = "LD H, A"; break; // --- LD L, r --- case 0x68: mnemonic = "LD L, B"; break; case 0x69: mnemonic = "LD L, C"; break; case 0x6A: mnemonic = "LD L, D"; break; case 0x6B: mnemonic = "LD L, E"; break; case 0x6C: mnemonic = "LD L, H"; break; case 0x6D: mnemonic = "LD L, L"; break; case 0x6E: mnemonic = "LD L, (HL)"; break; case 0x6F: mnemonic = "LD L, A"; break; // --- LD (HL), r --- (Note: 0x76 is HALT, so it is skipped) case 0x70: mnemonic = "LD (HL), B"; break; case 0x71: mnemonic = "LD (HL), C"; break; case 0x72: mnemonic = "LD (HL), D"; break; case 0x73: mnemonic = "LD (HL), E"; break; case 0x74: mnemonic = "LD (HL), H"; break; case 0x75: mnemonic = "LD (HL), L"; break; case 0x77: mnemonic = "LD (HL), A"; break; // --- LD A, r --- case 0x78: mnemonic = "LD A, B"; break; case 0x79: mnemonic = "LD A, C"; break; case 0x7A: mnemonic = "LD A, D"; break; case 0x7B: mnemonic = "LD A, E"; break; case 0x7C: mnemonic = "LD A, H"; break; case 0x7D: mnemonic = "LD A, L"; break; case 0x7E: mnemonic = "LD A, (HL)"; break; case 0x7F: mnemonic = "LD A, A"; break; // --- ADD A, r --- case 0x80: mnemonic = "ADD A, B"; break; case 0x81: mnemonic = "ADD A, C"; break; case 0x82: mnemonic = "ADD A, D"; break; case 0x83: mnemonic = "ADD A, E"; break; case 0x84: mnemonic = "ADD A, H"; break; case 0x85: mnemonic = "ADD A, L"; break; case 0x86: mnemonic = "ADD A, (HL)"; break; case 0x87: mnemonic = "ADD A, A"; break; // --- SUB r --- case 0x90: mnemonic = "SUB B"; break; case 0x91: mnemonic = "SUB C"; break; case 0x92: mnemonic = "SUB D"; break; case 0x93: mnemonic = "SUB E"; break; case 0x94: mnemonic = "SUB H"; break; case 0x95: mnemonic = "SUB L"; break; case 0x96: mnemonic = "SUB (HL)"; break; case 0x97: mnemonic = "SUB A"; break; // --- AND r --- case 0xA0: mnemonic = "AND B"; break; case 0xA1: mnemonic = "AND C"; break; case 0xA2: mnemonic = "AND D"; break; case 0xA3: mnemonic = "AND E"; break; case 0xA4: mnemonic = "AND H"; break; case 0xA5: mnemonic = "AND L"; break; case 0xA6: mnemonic = "AND (HL)"; break; case 0xA7: mnemonic = "AND A"; break; // --- XOR r --- case 0xA8: mnemonic = "XOR B"; break; case 0xA9: mnemonic = "XOR C"; break; case 0xAA: mnemonic = "XOR D"; break; case 0xAB: mnemonic = "XOR E"; break; case 0xAC: mnemonic = "XOR H"; break; case 0xAD: mnemonic = "XOR L"; break; case 0xAE: mnemonic = "XOR (HL)"; break; case 0xAF: mnemonic = "XOR A"; break; // --- OR r --- case 0xB0: mnemonic = "OR B"; break; case 0xB1: mnemonic = "OR C"; break; case 0xB2: mnemonic = "OR D"; break; case 0xB3: mnemonic = "OR E"; break; case 0xB4: mnemonic = "OR H"; break; case 0xB5: mnemonic = "OR L"; break; case 0xB6: mnemonic = "OR (HL)"; break; case 0xB7: mnemonic = "OR A"; break; // --- CP r --- case 0xB8: mnemonic = "CP B"; break; case 0xB9: mnemonic = "CP C"; break; case 0xBA: mnemonic = "CP D"; break; case 0xBB: mnemonic = "CP E"; break; case 0xBC: mnemonic = "CP H"; break; case 0xBD: mnemonic = "CP L"; break; case 0xBE: mnemonic = "CP (HL)"; break; case 0xBF: mnemonic = "CP A"; break; case 0xC1: mnemonic = "POP BC"; 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 0xc5: mnemonic = "PUSH BC"; break; case 0xC6: { byte addImm = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"ADD A, 0x{addImm:X2}"; instructionLength = 2; break; } // --- RST Instructions --- case 0xC7: mnemonic = "RST 00h"; break; case 0xCF: mnemonic = "RST 08h"; break; case 0xD7: mnemonic = "RST 10h"; break; case 0xDF: mnemonic = "RST 18h"; break; case 0xE7: mnemonic = "RST 20h"; break; case 0xEF: mnemonic = "RST 28h"; break; case 0xF7: mnemonic = "RST 30h"; break; case 0xFF: mnemonic = "RST 38h"; break; case 0xC8: mnemonic = "RET Z"; break; case 0xC9: mnemonic = "RET"; break; case 0xCB: byte cbOp = _memoryBus.Read((ushort)(currentPc + 1)); if (cbOp == 0x7E) { mnemonic = "BIT 7, (HL)"; } else if (cbOp == 0xAE) { mnemonic = "RES 5, (HL)"; } else if (cbOp == 0xC6) { mnemonic = "SET 0, (HL)"; } else { mnemonic = $"CB UNKNOWN (0x{cbOp:X2})"; } instructionLength = 2; break; case 0xCD: ushort callDest = (ushort)(_memoryBus.Read((ushort)(currentPc + 1)) | (_memoryBus.Read((ushort)(currentPc + 2)) << 8)); mnemonic = $"CALL 0x{callDest:X4}"; instructionLength = 3; break; case 0xD0: mnemonic = "RET NC"; break; case 0xD1: mnemonic = "POP DE"; break; case 0xD3: byte outPort = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"OUT (0x{outPort:X2}), A"; instructionLength = 2; break; case 0xD9: mnemonic = "EXX"; break; break; case 0xd5: mnemonic = "PUSH DE"; break; case 0xD6: byte subImm = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"SUB 0x{subImm:X2}"; instructionLength = 2; break; case 0xD8: mnemonic = "RET C"; break; case 0xDE: byte sbcValue = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"SBC A, 0x{sbcValue:X2}"; instructionLength = 2; break; case 0xE1: mnemonic = "POP HL"; break; case 0xE3: mnemonic = "EX (SP), HL"; break; case 0xE5: mnemonic = "PUSH HL"; break; case 0xE6: byte andImm = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"AND 0x{andImm:X2}"; instructionLength = 2; break; case 0xE9: mnemonic = "JP (HL)"; break; case 0xEB: mnemonic = "EX DE, HL"; break; case 0xED: byte extendedOp = _memoryBus.Read((ushort)(currentPc + 1)); switch (extendedOp) { case 0x43: ushort bcAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8)); mnemonic = $"LD (0x{bcAddr:X4}), BC"; instructionLength = 4; break; case 0x47: mnemonic = "LD I, A"; instructionLength = 2; // 0xED + 0x47 break; case 0x4B: ushort addr4B = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8)); mnemonic = $"LD BC, (0x{addr4B:X4})"; instructionLength = 4; break; case 0x52: mnemonic = "SBC HL, DE"; instructionLength = 2; // ED 52 break; case 0x53: ushort deAddr = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8)); mnemonic = $"LD (0x{deAddr:X4}), DE"; instructionLength = 4; break; case 0x56: mnemonic = "IM 1"; instructionLength = 2; break; case 0xB0: mnemonic = "LDIR"; instructionLength = 2; break; case 0xB8: mnemonic = "LDDR"; 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 0xF1: mnemonic = "POP AF"; break; case 0xF3: mnemonic = "DI"; break; case 0xf5: mnemonic = "PUSH AF"; break; case 0xF6: byte orImm = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"OR 0x{orImm:X2}"; instructionLength = 2; break; case 0xF9: mnemonic = "LD SP, HL"; break; case 0xFB: mnemonic = "EI"; break; case 0xFD: { byte fdOpcode = _memoryBus.Read((ushort)(currentPc + 1)); if (fdOpcode == 0x21) // LD IY, nn { ushort iyVal = (ushort)(_memoryBus.Read((ushort)(currentPc + 2)) | (_memoryBus.Read((ushort)(currentPc + 3)) << 8)); mnemonic = $"LD IY, 0x{iyVal:X4}"; instructionLength = 4; } else if (fdOpcode == 0x35) // DEC (IY+d) { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); string sign = d >= 0 ? "+" : ""; // Gives us a clean + or - mnemonic = $"DEC (IY{sign}{d})"; instructionLength = 3; } else if (fdOpcode == 0x36) // LD (IY+d), n { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); byte n = _memoryBus.Read((ushort)(currentPc + 3)); string sign = d >= 0 ? "+" : ""; mnemonic = $"LD (IY{sign}{d}), 0x{n:X2}"; instructionLength = 4; } else if (fdOpcode == 0x46) // LD B, (IY+d) { sbyte dB = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); string signB = dB >= 0 ? "+" : ""; mnemonic = $"LD B, (IY{signB}{dB})"; instructionLength = 3; } else if (fdOpcode == 0x6E) { sbyte offsetL = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); string signL = offsetL >= 0 ? "+" : ""; mnemonic = $"LD L, (IY{signL}{offsetL})"; instructionLength = 3; } else if (fdOpcode == 0x86) // ADD A, (IY+d) { sbyte dAdd = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); string signAdd = dAdd >= 0 ? "+" : ""; mnemonic = $"ADD A, (IY{signAdd}{dAdd})"; instructionLength = 3; } else if (fdOpcode == 0xCB) // FD CB prefix { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); byte cbOpcode = _memoryBus.Read((ushort)(currentPc + 3)); string sign = d >= 0 ? "+" : ""; if (cbOpcode == 0x46) { mnemonic = $"BIT 0, (IY{sign}{d})"; } else if (cbOpcode == 0x4E) { mnemonic = $"BIT 1, (IY{sign}{d})"; } else if (cbOpcode == 0x86) { mnemonic = $"RES 0, (IY{sign}{d})"; } else if (cbOpcode == 0xA6) { mnemonic = $"RES 4, (IY{sign}{d})"; } else if (cbOpcode == 0xCE) { mnemonic = $"SET 1, (IY{sign}{d})"; } else { mnemonic = $"FD CB {d:X2} {cbOpcode:X2}"; // Fallback } instructionLength = 4; } else if (fdOpcode == 0x71) // LD (IY+d), C { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); string sign = d >= 0 ? "+" : ""; mnemonic = $"LD (IY{sign}{d}), C"; instructionLength = 3; } else if (fdOpcode == 0x75) // LD (IY+d), L { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); string sign = d >= 0 ? "+" : ""; mnemonic = $"LD (IY{sign}{d}), L"; instructionLength = 3; } else if (fdOpcode == 0xCB) // FD CB prefix { sbyte d = (sbyte)_memoryBus.Read((ushort)(currentPc + 2)); byte cbOpcode = _memoryBus.Read((ushort)(currentPc + 3)); string sign = d >= 0 ? "+" : ""; if (cbOpcode == 0x8E) { mnemonic = $"RES 1, (IY{sign}{d})"; } else if (cbOpcode == 0xAE) { mnemonic = $"RES 5, (IY{sign}{d})"; } else if (cbOpcode == 0xC6) { mnemonic = $"SET 0, (IY{sign}{d})"; } else if (cbOpcode == 0xCE) { mnemonic = $"SET 1, (IY{sign}{d})"; } else if (cbOpcode == 0xE6) { mnemonic = $"SET 4, (IY{sign}{d})"; } else { mnemonic = $"FD CB {d:X2} {cbOpcode:X2}"; // Fallback } instructionLength = 4; } else { mnemonic = $"FD PREFIX UNKNOWN (0x{fdOpcode:X2})"; instructionLength = 2; // Fallback so we don't freeze the UI } break; } case 0xFE: byte cpImm = _memoryBus.Read((ushort)(currentPc + 1)); mnemonic = $"CP 0x{cpImm:X2}"; instructionLength = 2; 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; } } } }