// // Copyright (c) Adrian Conlon. All rights reserved. // namespace EightBit.GameBoy { public enum IoRegister { Abbreviated, // FF00 + dd Absolute, // FFdd Register, // C Unused, // Unused! } public class Disassembler { private bool prefixCB = false; public Disassembler(Bus bus) => this.Bus = bus; public Bus Bus { get; } public static string AsFlag(byte value, byte flag, string represents) => (value & flag) != 0 ? represents : "-"; public static string AsFlag(byte value, StatusBits flag, string represents) => AsFlag(value, (byte)flag, represents); public static string AsFlag(byte value, Bits flag, string represents) => AsFlag(value, (byte)flag, represents); public static string AsFlags(byte value) => $"{AsFlag(value, StatusBits.ZF, "Z")}" + $"{AsFlag(value, StatusBits.NF, "N")}" + $"{AsFlag(value, StatusBits.HC, "H")}" + $"{AsFlag(value, StatusBits.CF, "C")}" + $"{AsFlag(value, Bits.Bit3, "+")}" + $"{AsFlag(value, Bits.Bit2, "+")}" + $"{AsFlag(value, Bits.Bit1, "+")}" + $"{AsFlag(value, Bits.Bit0, "+")}"; public static string State(LR35902 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; 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}"; } public string Disassemble(LR35902 cpu) { this.prefixCB = false; return this.Disassemble(cpu, cpu.PC.Word); } private static string RP(int rp) { switch (rp) { case 0: return "BC"; case 1: return "DE"; case 2: return "HL"; case 3: return "SP"; } throw new System.ArgumentOutOfRangeException(nameof(rp)); } private static string RP2(int rp) { switch (rp) { case 0: return "BC"; case 1: return "DE"; case 2: return "HL"; case 3: return "AF"; } throw new System.ArgumentOutOfRangeException(nameof(rp)); } private static string R(int r) { switch (r) { case 0: return "B"; case 1: return "C"; case 2: return "D"; case 3: return "E"; case 4: return "H"; case 5: return "L"; case 6: return "(HL)"; case 7: return "A"; } throw new System.ArgumentOutOfRangeException(nameof(r)); } private static string IO(byte value) { switch (value) { // Port/Mode Registers case IoRegisters.P1: return "P1"; case IoRegisters.SB: return "SB"; case IoRegisters.SC: return "SC"; case IoRegisters.DIV: return "DIV"; case IoRegisters.TIMA: return "TIMA"; case IoRegisters.TMA: return "TMA"; case IoRegisters.TAC: return "TAC"; // Interrupt Flags case IoRegisters.IF: return "IF"; case IoRegisters.IE: return "IE"; // LCD Display Registers case IoRegisters.LCDC: return "LCDC"; case IoRegisters.STAT: return "STAT"; case IoRegisters.SCY: return "SCY"; case IoRegisters.SCX: return "SCX"; case IoRegisters.LY: return "LY"; case IoRegisters.LYC: return "LYC"; case IoRegisters.DMA: return "DMA"; case IoRegisters.BGP: return "BGP"; case IoRegisters.OBP0: return "OBP0"; case IoRegisters.OBP1: return "OBP1"; case IoRegisters.WY: return "WY"; case IoRegisters.WX: return "WX"; // Sound Registers case IoRegisters.NR10: return "NR10"; case IoRegisters.NR11: return "NR11"; case IoRegisters.NR12: return "NR12"; case IoRegisters.NR13: return "NR13"; case IoRegisters.NR14: return "NR14"; case IoRegisters.NR21: return "NR21"; case IoRegisters.NR22: return "NR22"; case IoRegisters.NR23: return "NR23"; case IoRegisters.NR24: return "NR24"; case IoRegisters.NR30: return "NR30"; case IoRegisters.NR31: return "NR31"; case IoRegisters.NR32: return "NR32"; case IoRegisters.NR33: return "NR33"; case IoRegisters.NR34: return "NR34"; case IoRegisters.NR41: return "NR41"; case IoRegisters.NR42: return "NR42"; case IoRegisters.NR43: return "NR43"; case IoRegisters.NR44: return "NR44"; case IoRegisters.NR50: return "NR50"; case IoRegisters.NR51: return "NR51"; case IoRegisters.NR52: return "NR52"; case IoRegisters.WAVE_PATTERN_RAM_START: return "WAVE_PATTERN_RAM_START"; case IoRegisters.WAVE_PATTERN_RAM_END: return "WAVE_PATTERN_RAM_END"; // Boot rom control case IoRegisters.BOOT_DISABLE: return "BOOT_DISABLE"; default: return $"{value:x2}"; } } private static string CC(int flag) { switch (flag) { case 0: return "NZ"; case 1: return "Z"; case 2: return "NC"; case 3: return "C"; case 4: return "PO"; case 5: return "PE"; case 6: return "P"; case 7: return "M"; } throw new System.ArgumentOutOfRangeException(nameof(flag)); } private static string ALU(int which) { switch (which) { case 0: // ADD A,n return "ADD"; case 1: // ADC return "ADC"; case 2: // SUB n return "SUB"; case 3: // SBC A,n return "SBC"; case 4: // AND n return "AND"; case 5: // XOR n return "XOR"; case 6: // OR n return "OR"; case 7: // CP n return "CP"; } throw new System.ArgumentOutOfRangeException(nameof(which)); } private static 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 {R(z)}"; break; case 1: specification = $"RRC {R(z)}"; break; case 2: specification = $"RL {R(z)}"; break; case 3: specification = $"RR {R(z)}"; break; case 4: specification = $"SLA {R(z)}"; break; case 5: specification = $"SRA {R(z)}"; break; case 6: specification = $"SWAP {R(z)}"; break; case 7: specification = $"SRL {R(z)}"; break; } break; case 1: // BIT y, r[z] specification = $"BIT {y},{R(z)}"; break; case 2: // RES y, r[z] specification = $"RES {y},{R(z)}"; break; case 3: // SET y, r[z] specification = $"SET {y},{R(z)}"; break; } return output; } private string Disassemble(LR35902 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 ioRegister = IoRegister.Unused; var output = $"{opCode:x2}"; var specification = string.Empty; if (this.prefixCB) { output += DisassembleCB(ref specification, x, y, z); } else { output += this.DisassembleOther(cpu, pc, ref specification, ref dumpCount, ref ioRegister, x, y, z, p, q); } for (var i = 0; i < dumpCount; ++i) { output += $"{this.Bus.Peek((ushort)(pc + i + 1)):x2}"; } output += '\t'; output += string.Format(specification, (int)immediate, (int)absolute, relative, (int)displacement, indexedImmediate); switch (ioRegister) { case IoRegister.Abbreviated: output += $"; register {IO(immediate)}"; break; case IoRegister.Absolute: output += "; register (Absolute)"; break; case IoRegister.Register: output += $"; register C:{IO(cpu.C)}"; break; case IoRegister.Unused: break; } return output; } private string DisassembleOther(LR35902 cpu, ushort pc, ref string specification, ref int dumpCount, ref IoRegister ioRegister, 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: // GB: LD (nn),SP specification = "LD ({1:X4}H),SP"; dumpCount += 2; break; case 2: // GB: STOP specification = "STOP"; 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 {RP(p)}," + "{1:X4}H"; dumpCount += 2; break; case 1: // ADD HL,rp specification = $"ADD HL,{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: // GB: LDI (HL),A specification = "LDI (HL),A"; break; case 3: // GB: LDD (HL),A specification = "LDD (HL),A"; 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: // GB: LDI A,(HL) specification = "LDI A,(HL)"; break; case 3: // GB: LDD A,(HL) specification = "LDD A,(HL)"; break; } break; } break; case 3: // 16-bit INC/DEC switch (q) { case 0: // INC rp specification = $"INC {RP(p)}"; break; case 1: // DEC rp specification = $"DEC {RP(p)}"; break; } break; case 4: // 8-bit INC specification = $"INC {R(y)}"; break; case 5: // 8-bit DEC specification = $"DEC {R(y)}"; break; case 6: // 8-bit load immediate specification = $"LD {R(y)}," + "{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 if (z == 6 && y == 6) { specification = "HALT"; // Exception (replaces LD (HL), (HL)) } else { specification = $"LD {R(y)},{R(z)}"; } break; case 2: // Operate on accumulator and register/memory location specification = $"{ALU(y)} A,{R(z)}"; break; case 3: switch (z) { case 0: // Conditional return switch (y) { case 0: case 1: case 2: case 3: specification = $"RET {CC(y)}"; break; case 4: specification = "LD (FF00H+{0:X2}H),A"; ioRegister = IoRegister.Abbreviated; dumpCount++; break; case 5: specification = "ADD SP,{4:X4}H"; dumpCount++; break; case 6: specification = "LD A,(FF00H+{0:X2}H)"; ioRegister = IoRegister.Abbreviated; dumpCount++; break; case 7: specification = "LD HL,SP+{4}"; dumpCount++; break; } break; case 1: // POP & various ops switch (q) { case 0: // POP rp2[p] specification = $"POP {RP2(p)}"; break; case 1: switch (p) { case 0: // RET specification = "RET"; break; case 1: // GB: RETI specification = "RETI"; 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 switch (y) { case 0: case 1: case 2: case 3: specification = $"JP {CC(y)}" + ",{1:X4}H"; dumpCount += 2; break; case 4: specification = "LD (FF00H+C),A"; ioRegister = IoRegister.Register; break; case 5: specification = "LD ({1:X4}H),A"; dumpCount += 2; break; case 6: specification = "LD A,(FF00H+C)"; ioRegister = IoRegister.Register; break; case 7: specification = "LD A,({1:X4}H)"; dumpCount += 2; break; } 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 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 {RP2(p)}"; break; case 1: switch (p) { case 0: // CALL nn specification = "CALL {1:X4}H"; dumpCount += 2; 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; } } }