676 lines
28 KiB
C#
676 lines
28 KiB
C#
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;
|
|
case 0x04:
|
|
mnemonic = "INC B";
|
|
break;
|
|
case 0x0E:
|
|
byte cImm = _memoryBus.Read((ushort)(currentPc + 1));
|
|
mnemonic = $"LD C, 0x{cImm:X2}";
|
|
instructionLength = 2;
|
|
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 0x19:
|
|
mnemonic = "ADD HL, 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 0x35:
|
|
mnemonic = "DEC (HL)";
|
|
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 0x3E:
|
|
mnemonic = $"LD A, 0x{_memoryBus.Read((ushort)(currentPc + 1)):X2}";
|
|
instructionLength = 2;
|
|
break;
|
|
case 0x3F:
|
|
mnemonic = "CCF";
|
|
break;
|
|
case 0x47:
|
|
mnemonic = "LD B, A";
|
|
break;
|
|
case 0x4E:
|
|
mnemonic = "LD C, (HL)";
|
|
break;
|
|
case 0x56:
|
|
mnemonic = "LD D, (HL)";
|
|
break;
|
|
case 0x5E:
|
|
mnemonic = "LD E, (HL)";
|
|
break;
|
|
case 0x5F:
|
|
mnemonic = "LD E, A";
|
|
break;
|
|
case 0x62:
|
|
mnemonic = "LD H, D";
|
|
break;
|
|
case 0x67:
|
|
mnemonic = "LD H, A";
|
|
break;
|
|
case 0x6B:
|
|
mnemonic = "LD L, E";
|
|
break;
|
|
case 0x6F:
|
|
mnemonic = "LD L, A";
|
|
break;
|
|
case 0x72:
|
|
mnemonic = "LD (HL), D";
|
|
break;
|
|
case 0x73:
|
|
mnemonic = "LD (HL), E";
|
|
break;
|
|
case 0x77:
|
|
mnemonic = "LD (HL), A";
|
|
break;
|
|
case 0x7A:
|
|
mnemonic = "LD A, D";
|
|
break;
|
|
case 0x7E:
|
|
mnemonic = "LD A, (HL)";
|
|
break;
|
|
case 0x87:
|
|
mnemonic = "ADD A, A";
|
|
break;
|
|
case 0x91:
|
|
mnemonic = "SUB C";
|
|
break;
|
|
case 0xA7:
|
|
mnemonic = "AND A";
|
|
break;
|
|
case 0xAE:
|
|
mnemonic = "XOR (HL)";
|
|
break;
|
|
case 0xAF:
|
|
mnemonic = "XOR A";
|
|
break;
|
|
case 0xB3:
|
|
mnemonic = "OR E";
|
|
break;
|
|
case 0xB9:
|
|
mnemonic = "CP C";
|
|
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 0xC6:
|
|
{
|
|
byte addImm = _memoryBus.Read((ushort)(currentPc + 1));
|
|
mnemonic = $"ADD A, 0x{addImm:X2}";
|
|
instructionLength = 2;
|
|
break;
|
|
}
|
|
case 0xC8:
|
|
mnemonic = "RET Z";
|
|
break;
|
|
case 0xC9:
|
|
mnemonic = "RET";
|
|
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 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 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 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 0xF3:
|
|
mnemonic = "DI";
|
|
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 == 0x6E)
|
|
{
|
|
sbyte offsetL = (sbyte)_memoryBus.Read((ushort)(currentPc + 2));
|
|
string signL = offsetL >= 0 ? "+" : "";
|
|
|
|
mnemonic = $"LD L, (IY{signL}{offsetL})";
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
} |