// // Copyright (c) Adrian Conlon. All rights reserved. // namespace EightBit { using System; public class Disassembler(Bus bus) { private bool prefixCB = false; private bool prefixDD = false; private bool prefixED = false; private bool prefixFD = false; public Bus Bus { get; } = bus; public static string AsFlag(byte value, StatusBits flag, string represents) => (value & (byte)flag) != 0 ? represents : "-"; public static string AsFlags(byte value) => $"{AsFlag(value, StatusBits.SF, "S")}" + $"{AsFlag(value, StatusBits.ZF, "Z")}" + $"{AsFlag(value, StatusBits.YF, "Y")}" + $"{AsFlag(value, StatusBits.HC, "H")}" + $"{AsFlag(value, StatusBits.XF, "X")}" + $"{AsFlag(value, StatusBits.PF, "P")}" + $"{AsFlag(value, StatusBits.NF, "N")}" + $"{AsFlag(value, StatusBits.CF, "C")}"; public static string State(Z80 cpu) { ArgumentNullException.ThrowIfNull(cpu); var pc = cpu.PC; var sp = cpu.SP; var a = cpu.A; var f = cpu.F; var b = cpu.B; var c = cpu.C; var d = cpu.D; var e = cpu.E; var h = cpu.H; var l = cpu.L; var i = cpu.IV; var r = cpu.REFRESH; var im = cpu.IM; return $"PC={pc.Word:x4} SP={sp.Word:x4} " + $"A={a:x2} F={AsFlags(f)} " + $"B={b:x2} C={c:x2} " + $"D={d:x2} E={e:x2} " + $"H={h:x2} L={l:x2} " + $"IX={cpu.IX.Word:x4} " + $"IY={cpu.IY.Word:x4} " + $"I={i:x2} R={(byte)r:x2} " + $"IM={im} " + $"IFF1={(cpu.IFF1 ? 1 : 0)} " + $"{(cpu.RESET.Lowered() ? "R" : "-")}" + $"{(cpu.INT.Lowered() ? "I" : "-")}" + $"{(cpu.HALT.Lowered() ? "H" : "-")}" + $"{(cpu.NMI.Lowered() ? "N" : "-")}"; } public string Disassemble(Z80 cpu) { ArgumentNullException.ThrowIfNull(cpu); this.prefixCB = this.prefixDD = this.prefixED = this.prefixFD = false; return this.Disassemble(cpu, cpu.PC.Word); } private static string CC(int flag) => flag switch { 0 => "NZ", 1 => "Z", 2 => "NC", 3 => "C", 4 => "PO", 5 => "PE", 6 => "P", 7 => "M", _ => throw new System.ArgumentOutOfRangeException(nameof(flag)), }; private static string ALU(int which) => which switch { 0 => "ADD", 1 => "ADC", 2 => "SUB", 3 => "SBC", 4 => "AND", 5 => "XOR", 6 => "OR", 7 => "CP", _ => throw new System.ArgumentOutOfRangeException(nameof(which)), }; private string Disassemble(Z80 cpu, ushort pc) { var opCode = this.Bus.Peek(pc); var decoded = cpu.GetDecodedOpCode(opCode); var x = decoded.X; var y = decoded.Y; var z = decoded.Z; var p = decoded.P; var q = decoded.Q; var immediate = this.Bus.Peek((ushort)(pc + 1)); var absolute = cpu.PeekWord((ushort)(pc + 1)).Word; var displacement = (sbyte)immediate; var relative = pc + displacement + 2; var indexedImmediate = this.Bus.Peek((ushort)(pc + 1)); var dumpCount = 0; var output = $"{opCode:x2}"; var specification = string.Empty; if (this.prefixCB) { output += this.DisassembleCB(ref specification, x, y, z); } else if (this.prefixED) { output += this.DisassembleED(ref specification, ref dumpCount, x, y, z, p, q); } else { output += this.DisassembleOther(cpu, pc, ref specification, ref dumpCount, x, y, z, p, q); } for (var i = 0; i < dumpCount; ++i) { output += $"{this.Bus.Peek((ushort)(pc + i + 1)):x2}"; } var outputFormatSpecification = !this.prefixDD; if (this.prefixDD) { if (opCode != 0xdd) { outputFormatSpecification = true; } } if (outputFormatSpecification) { output += '\t'; output += string.Format(specification, (int)immediate, (int)absolute, relative, (int)displacement, indexedImmediate); } return output; } private string DisassembleCB(ref string specification, int x, int y, int z) { var output = string.Empty; switch (x) { case 0: // rot[y] r[z] switch (y) { case 0: specification = $"RLC {this.R(z)}"; break; case 1: specification = $"RRC {this.R(z)}"; break; case 2: specification = $"RL {this.R(z)}"; break; case 3: specification = $"RR {this.R(z)}"; break; case 4: specification = $"SLA {this.R(z)}"; break; case 5: specification = $"SRA {this.R(z)}"; break; case 6: specification = $"SWAP {this.R(z)}"; break; case 7: specification = $"SRL {this.R(z)}"; break; } break; case 1: // BIT y, r[z] specification = $"BIT {y},{this.R(z)}"; break; case 2: // RES y, r[z] specification = $"RES {y},{this.R(z)}"; break; case 3: // SET y, r[z] specification = $"SET {y},{this.R(z)}"; break; } return output; } private string DisassembleED(ref string specification, ref int dumpCount, int x, int y, int z, int p, int q) { var output = string.Empty; switch (x) { case 0: case 3: specification = "NONI NOP"; break; case 1: switch (z) { case 2: switch (q) { case 0: // SBC HL,rp specification = $"SBC HL,{this.RP(p)}"; break; case 1: // ADC HL,rp specification = $"ADC HL,{this.RP(p)}"; break; } break; case 3: switch (q) { case 0: // LD (nn),rp specification = "LD ({1:X4}H)," + this.RP(p); break; case 1: // LD rp,(nn) specification = "LD " + this.RP(p) + ",(%2$04XH)"; break; } dumpCount += 2; break; case 4: // Negate accumulator specification = "NEG"; break; case 5: // Return from interrupt specification = y switch { 1 => "RETI", _ => "RETN", }; break; case 6: // Set interrupt mode switch (y) { case 0: case 1: case 4: case 5: specification = "IM 0"; break; case 2: case 6: specification = "IM 1"; break; case 3: case 7: specification = "IM 2"; break; } break; case 7: switch (y) { case 0: specification = "LD I,A"; break; case 1: specification = "LD R,A"; break; case 2: specification = "LD A,I"; break; case 3: specification = "LD A,R"; break; case 4: specification = "RRD"; break; case 5: specification = "RLD"; break; case 6: case 7: specification = "NOP"; break; } break; } break; case 2: switch (z) { case 0: // LD switch (y) { case 4: // LDI specification = "LDI"; break; case 5: // LDD specification = "LDD"; break; case 6: // LDIR specification = "LDIR"; break; case 7: // LDDR specification = "LDDR"; break; } break; case 1: // CP switch (y) { case 4: // CPI specification = "CPI"; break; case 5: // CPD specification = "CPD"; break; case 6: // CPIR specification = "CPIR"; break; case 7: // CPDR specification = "CPDR"; break; } break; case 2: // IN switch (y) { case 4: // INI specification = "INI"; break; case 5: // IND specification = "IND"; break; case 6: // INIR specification = "INIR"; break; case 7: // INDR specification = "INDR"; break; } break; case 3: // OUT switch (y) { case 4: // OUTI specification = "OUTI"; break; case 5: // OUTD specification = "OUTD"; break; case 6: // OTIR specification = "OTIR"; break; case 7: // OTDR specification = "OTDR"; break; } break; } break; } return output; } private string DisassembleOther(Z80 cpu, ushort pc, ref string specification, ref int dumpCount, int x, int y, int z, int p, int q) { var output = string.Empty; switch (x) { case 0: switch (z) { case 0: // Relative jumps and assorted ops switch (y) { case 0: // NOP specification = "NOP"; break; case 1: // EX AF AF' specification = "EX AF AF'"; break; case 2: // DJNZ d specification = "DJNZ {2:X4}H"; dumpCount += 2; break; case 3: // JR d specification = "JR {2:X4}H"; dumpCount++; break; default: // JR cc,d specification = "JR " + CC(y - 4) + ",{2:X4}H"; dumpCount++; break; } break; case 1: // 16-bit load immediate/add switch (q) { case 0: // LD rp,nn specification = "LD " + this.RP(p) + ",{1:X4}H"; dumpCount += 2; break; case 1: // ADD HL,rp specification = $"ADD HL,{this.RP(p)}"; break; } break; case 2: // Indirect loading switch (q) { case 0: switch (p) { case 0: // LD (BC),A specification = "LD (BC),A"; break; case 1: // LD (DE),A specification = "LD (DE),A"; break; case 2: // LD (nn),HL specification = "LD ({1:X4}H),HL"; dumpCount += 2; break; case 3: // LD (nn),A specification = "LD ({1:X4}H),A"; dumpCount += 2; break; } break; case 1: switch (p) { case 0: // LD A,(BC) specification = "LD A,(BC)"; break; case 1: // LD A,(DE) specification = "LD A,(DE)"; break; case 2: // LD HL,(nn) specification = "LD HL,({1:X4}H)"; dumpCount += 2; break; case 3: // LD A,(nn) specification = "LD A,({1:X4}H)"; dumpCount += 2; break; } break; } break; case 3: // 16-bit INC/DEC switch (q) { case 0: // INC rp specification = $"INC {this.RP(p)}"; break; case 1: // DEC rp specification = $"DEC {this.RP(p)}"; break; } break; case 4: // 8-bit INC specification = $"INC {this.R(y)}"; break; case 5: // 8-bit DEC specification = $"DEC {this.R(y)}"; break; case 6: // 8-bit load immediate specification = $"LD {this.R(y)}"; if (y == 6 && (this.prefixDD || this.prefixFD)) { specification += ",{4:X2}H"; dumpCount++; } else { specification += ",{0:X2}H"; } dumpCount++; break; case 7: // Assorted operations on accumulator/flags switch (y) { case 0: specification = "RLCA"; break; case 1: specification = "RRCA"; break; case 2: specification = "RLA"; break; case 3: specification = "RRA"; break; case 4: specification = "DAA"; break; case 5: specification = "CPL"; break; case 6: specification = "SCF"; break; case 7: specification = "CCF"; break; } break; } break; case 1: // 8-bit loading specification = z == 6 && y == 6 ? "HALT" : $"LD {this.R(y)},{this.R(z)}"; break; case 2: // Operate on accumulator and register/memory location specification = $"{ALU(y)} A,{this.R(z)}"; break; case 3: switch (z) { case 0: // Conditional return specification = $"RET {CC(y)}"; break; case 1: // POP & various ops switch (q) { case 0: // POP rp2[p] specification = $"POP {this.RP2(p)}"; break; case 1: switch (p) { case 0: // RET specification = "RET"; break; case 1: // EXX specification = "EXX"; break; case 2: // JP (HL) specification = "JP (HL)"; break; case 3: // LD SP,HL specification = "LD SP,HL"; break; } break; } break; case 2: // Conditional jump specification = $"JP {CC(y)}" + ",{1:X4}H"; dumpCount += 2; break; case 3: // Assorted operations switch (y) { case 0: // JP nn specification = "JP {1:X4}H"; dumpCount += 2; break; case 1: // CB prefix this.prefixCB = true; output += this.Disassemble(cpu, ++pc); break; case 2: // OUT (n),A specification = "OUT ({0:X2}H),A"; dumpCount++; break; case 3: // IN A,(n) specification = "IN A,({0:X2}H)"; dumpCount++; break; case 4: // EX (SP),HL specification = "EX (SP),HL"; break; case 5: // EX DE,HL specification = "EX DE,HL"; break; case 6: // DI specification = "DI"; break; case 7: // EI specification = "EI"; break; } break; case 4: // Conditional call: CALL cc[y], nn specification = $"CALL {CC(y)}" + ",{1:X4}H"; dumpCount += 2; break; case 5: // PUSH & various ops switch (q) { case 0: // PUSH rp2[p] specification = $"PUSH {this.RP2(p)}"; break; case 1: switch (p) { case 0: // CALL nn specification = "CALL {1:X4}H"; dumpCount += 2; break; case 1: // DD prefix this.prefixDD = true; output += this.Disassemble(cpu, ++pc); break; case 2: // ED prefix this.prefixED = true; output += this.Disassemble(cpu, ++pc); break; case 3: // FD prefix this.prefixFD = true; output += this.Disassemble(cpu, ++pc); break; } break; } break; case 6: // Operate on accumulator and immediate operand: alu[y] n specification = ALU(y) + " A,{0:X2}H"; dumpCount++; break; case 7: // Restart: RST y * 8 specification = $"RST {y * 8:X2}"; break; } break; } return output; } private string RP(int rp) { switch (rp) { case 0: return "BC"; case 1: return "DE"; case 2: if (this.prefixDD) { return "IX"; } if (this.prefixFD) { return "IY"; } return "HL"; case 3: return "SP"; } throw new System.ArgumentOutOfRangeException(nameof(rp)); } private string RP2(int rp) { switch (rp) { case 0: return "BC"; case 1: return "DE"; case 2: if (this.prefixDD) { return "IX"; } if (this.prefixFD) { return "IY"; } return "HL"; case 3: return "AF"; } throw new System.ArgumentOutOfRangeException(nameof(rp)); } private string R(int r) { switch (r) { case 0: return "B"; case 1: return "C"; case 2: return "D"; case 3: return "E"; case 4: if (this.prefixDD) { return "IXH"; } if (this.prefixFD) { return "IYH"; } return "H"; case 5: if (this.prefixDD) { return "IXL"; } if (this.prefixFD) { return "IYL"; } return "L"; case 6: if (this.prefixDD || this.prefixFD) { if (this.prefixDD) { return "IX+{4}"; } if (this.prefixFD) { return "IY+{4}"; } } else { return "(HL)"; } break; case 7: return "A"; } throw new System.ArgumentOutOfRangeException(nameof(r)); } } }