jevsnes.git / third-party / rust / jgenesis / cpu / z80-emu / src / disassemble.rs
1use crate::Z80;
2use std::borrow::Cow;
3use std::cmp::Ordering;
4use std::mem;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7enum IndexRegister {
8    Ix,
9    Iy,
10}
11
12impl IndexRegister {
13    fn label(self) -> &'static str {
14        match self {
15            Self::Ix => "ix",
16            Self::Iy => "iy",
17        }
18    }
19
20    fn indirect_with_offset(self, offset: i8) -> String {
21        match offset.cmp(&0) {
22            Ordering::Equal => match self {
23                Self::Ix => "(ix)".into(),
24                Self::Iy => "(iy)".into(),
25            },
26            Ordering::Greater => match self {
27                Self::Ix => format!("(ix+{offset})"),
28                Self::Iy => format!("(iy+{offset})"),
29            },
30            Ordering::Less => match self {
31                Self::Ix => format!("(ix{offset})"),
32                Self::Iy => format!("(iy{offset})"),
33            },
34        }
35    }
36
37    fn high_label(self) -> &'static str {
38        match self {
39            Self::Ix => "ixh",
40            Self::Iy => "iyh",
41        }
42    }
43
44    fn low_label(self) -> &'static str {
45        match self {
46            Self::Ix => "ixl",
47            Self::Iy => "iyl",
48        }
49    }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53struct IndexWithOffset(IndexRegister, i8);
54
55impl IndexWithOffset {
56    fn indirect_with_offset(self) -> String {
57        self.0.indirect_with_offset(self.1)
58    }
59}
60
61struct ByteReader<F> {
62    pc: u16,
63    reader: F,
64    opcodes: Vec<u8>,
65}
66
67impl<F: FnMut() -> u8> ByteReader<F> {
68    fn read_byte(&mut self) -> u8 {
69        let opcode = (self.reader)();
70
71        self.pc = self.pc.wrapping_add(1);
72        self.opcodes.push(opcode);
73
74        opcode
75    }
76
77    fn read_word(&mut self) -> u16 {
78        let lsb = self.read_byte();
79        let msb = self.read_byte();
80        u16::from_le_bytes([lsb, msb])
81    }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq, Eq)]
85pub enum IndirectRegister {
86    Bc,
87    De,
88    Hl,
89    Sp,
90    Ix { displacement: i8 },
91    Iy { displacement: i8 },
92}
93
94impl IndirectRegister {
95    fn from_index(index: IndexRegister, displacement: i8) -> Self {
96        match index {
97            IndexRegister::Ix => Self::Ix { displacement },
98            IndexRegister::Iy => Self::Iy { displacement },
99        }
100    }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub enum MemoryAccess {
105    Absolute(u16),
106    Indirect(IndirectRegister),
107}
108
109impl MemoryAccess {
110    #[must_use]
111    pub fn resolve_address(self, cpu: &Z80) -> u16 {
112        match self {
113            Self::Absolute(address) => address,
114            Self::Indirect(register) => {
115                let registers = cpu.registers();
116                match register {
117                    IndirectRegister::Bc => u16::from_be_bytes([registers.b, registers.c]),
118                    IndirectRegister::De => u16::from_be_bytes([registers.d, registers.e]),
119                    IndirectRegister::Hl => u16::from_be_bytes([registers.h, registers.l]),
120                    IndirectRegister::Sp => registers.sp,
121                    IndirectRegister::Ix { displacement } => {
122                        registers.ix.wrapping_add_signed(displacement.into())
123                    }
124                    IndirectRegister::Iy { displacement } => {
125                        registers.iy.wrapping_add_signed(displacement.into())
126                    }
127                }
128            }
129        }
130    }
131}
132
133#[derive(Debug, Clone)]
134pub struct DisassembledInstruction {
135    pub opcodes: Vec<u8>,
136    pub text: String,
137    pub memory_access: Option<MemoryAccess>,
138}
139
140impl DisassembledInstruction {
141    #[must_use]
142    pub fn new() -> Self {
143        Self { opcodes: Vec::new(), text: String::new(), memory_access: None }
144    }
145
146    fn clear(&mut self) {
147        self.opcodes.clear();
148        self.text.clear();
149        self.memory_access = None;
150    }
151
152    #[must_use]
153    fn parse_memory_access(&self, index: Option<IndexRegister>) -> Option<MemoryAccess> {
154        let i = (0..self.opcodes.len())
155            .find(|&i| self.opcodes[i] != 0xDD && self.opcodes[i] != 0xFD)?;
156
157        let opcode = self.opcodes[i];
158        match opcode {
159            0x02 | 0x0A => {
160                // ld (bc), a
161                // ld a, (bc)
162                Some(MemoryAccess::Indirect(IndirectRegister::Bc))
163            }
164            0x12 | 0x1A => {
165                // ld (de), a
166                // ld a, (de)
167                Some(MemoryAccess::Indirect(IndirectRegister::De))
168            }
169            0x22 | 0x2A | 0x32 | 0x3A => {
170                // ld (nn), hl
171                // ld hl, (nn)
172                // ld (nn), a
173                // ld a, (nn)
174                let lsb = self.opcodes.get(i + 1).copied()?;
175                let msb = self.opcodes.get(i + 2).copied()?;
176                Some(MemoryAccess::Absolute(u16::from_le_bytes([lsb, msb])))
177            }
178            0x34..=0x36 | 0x86 | 0x8E | 0x96 | 0x9E | 0xA6 | 0xAE | 0xB6 | 0xBE => {
179                // inc (hl)
180                // dec (hl)
181                // ld (hl), nn
182                // add a, (hl)
183                // adc a, (hl)
184                // sub (hl)
185                // sbc a, (hl)
186                // and (hl)
187                // xor (hl)
188                // or (hl)
189                // cp (hl)
190                let register = index.map_or(IndirectRegister::Hl, |index| {
191                    let displacement = self.opcodes.get(i + 1).copied().unwrap_or(0) as i8;
192                    IndirectRegister::from_index(index, displacement)
193                });
194                Some(MemoryAccess::Indirect(register))
195            }
196            0x40..=0x75 | 0x77..=0x7F => {
197                // ld r, (hl)
198                // ld (hl), r
199                if opcode & 7 != 6 && (opcode >> 3) & 7 != 6 {
200                    return None;
201                }
202                let register = index.map_or(IndirectRegister::Hl, |index| {
203                    let displacement = self.opcodes.get(i + 1).copied().unwrap_or(0) as i8;
204                    IndirectRegister::from_index(index, displacement)
205                });
206                Some(MemoryAccess::Indirect(register))
207            }
208            0xE3 => {
209                // ex (sp), hl
210                Some(MemoryAccess::Indirect(IndirectRegister::Sp))
211            }
212            0xCB => self.parse_memory_access_cb(i + 1, index),
213            0xED => self.parse_memory_access_ed(i + 1),
214            _ => None,
215        }
216    }
217
218    fn parse_memory_access_cb(
219        &self,
220        i: usize,
221        index: Option<IndexRegister>,
222    ) -> Option<MemoryAccess> {
223        if let Some(index) = index {
224            // All index-prefixed CB-prefixed instructions perform the same memory access
225            let displacement = self.opcodes.get(i).copied()? as i8;
226            return Some(MemoryAccess::Indirect(IndirectRegister::from_index(index, displacement)));
227        }
228
229        let opcode = self.opcodes.get(i).copied()?;
230        if opcode & 7 == 6 {
231            return Some(MemoryAccess::Indirect(IndirectRegister::Hl));
232        }
233
234        None
235    }
236
237    fn parse_memory_access_ed(&self, i: usize) -> Option<MemoryAccess> {
238        let opcode = self.opcodes.get(i).copied()?;
239
240        match opcode {
241            0x43 | 0x4B | 0x53 | 0x5B | 0x63 | 0x6B | 0x73 | 0x7B => {
242                // ld (nn), rr
243                // ld rr, (nn)
244                let lsb = self.opcodes.get(i + 1).copied()?;
245                let msb = self.opcodes.get(i + 2).copied()?;
246                Some(MemoryAccess::Absolute(u16::from_le_bytes([lsb, msb])))
247            }
248            0xA0 | 0xA1 | 0xA8 | 0xA9 | 0xB0 | 0xB1 | 0xB8 | 0xB9 => {
249                // ldi[r], cpi[r], ldd[r], cpd[r]
250                Some(MemoryAccess::Indirect(IndirectRegister::Hl))
251            }
252            _ => None,
253        }
254    }
255}
256
257impl Default for DisassembledInstruction {
258    fn default() -> Self {
259        Self::new()
260    }
261}
262
263pub fn disassemble_into(out: &mut DisassembledInstruction, pc: u16, reader: impl FnMut() -> u8) {
264    out.clear();
265    let mut reader = ByteReader { pc, reader, opcodes: mem::take(&mut out.opcodes) };
266
267    let mut index: Option<IndexRegister> = None;
268
269    let opcode = loop {
270        match reader.read_byte() {
271            0xDD => index = Some(IndexRegister::Ix),
272            0xFD => index = Some(IndexRegister::Iy),
273            opcode => break opcode,
274        }
275    };
276
277    out.text = match opcode {
278        0x00 => "nop".into(),
279        0x01 | 0x11 | 0x21 | 0x31 => ld_dd_immediate(opcode, index, &mut reader),
280        0x02 => "ld (bc), a".into(),
281        0x03 | 0x13 | 0x23 | 0x33 => inc_ss(opcode, index),
282        0x04 | 0x0C | 0x14 | 0x1C | 0x24 | 0x2C | 0x34 | 0x3C => inc_r(opcode, index, &mut reader),
283        0x05 | 0x0D | 0x15 | 0x1D | 0x25 | 0x2D | 0x35 | 0x3D => dec_r(opcode, index, &mut reader),
284        0x06 | 0x0E | 0x16 | 0x1E | 0x26 | 0x2E | 0x36 | 0x3E => {
285            ld_r_immediate(opcode, index, &mut reader)
286        }
287        0x07 => "rlca".into(),
288        0x08 => "ex af, af'".into(),
289        0x09 | 0x19 | 0x29 | 0x39 => add_hl_ss(opcode, index),
290        0x0A => "ld a, (bc)".into(),
291        0x0B | 0x1B | 0x2B | 0x3B => dec_ss(opcode, index),
292        0x0F => "rrca".into(),
293        0x10 => djnz(&mut reader),
294        0x12 => "ld (de), a".into(),
295        0x17 => "rla".into(),
296        0x18 => jr(&mut reader),
297        0x1A => "ld a, (de)".into(),
298        0x1F => "rra".into(),
299        0x20 | 0x28 | 0x30 | 0x38 => jr_cc(opcode, &mut reader),
300        0x22 => ld_direct_hl(index, &mut reader),
301        0x27 => "daa".into(),
302        0x2A => ld_hl_direct(index, &mut reader),
303        0x2F => "cpl".into(),
304        0x32 => ld_direct_a(&mut reader),
305        0x37 => "scf".into(),
306        0x3A => ld_a_direct(&mut reader),
307        0x3F => "ccf".into(),
308        0x40..=0x75 | 0x77..=0x7F => ld_r_r(opcode, index, &mut reader),
309        0x76 => "halt".into(),
310        0x80..=0x87 => add_a_r(opcode, index, &mut reader),
311        0x88..=0x8F => adc_a_r(opcode, index, &mut reader),
312        0x90..=0x97 => sub_a_r(opcode, index, &mut reader),
313        0x98..=0x9F => sbc_a_r(opcode, index, &mut reader),
314        0xA0..=0xA7 => and_a_r(opcode, index, &mut reader),
315        0xA8..=0xAF => xor_a_r(opcode, index, &mut reader),
316        0xB0..=0xB7 => or_a_r(opcode, index, &mut reader),
317        0xB8..=0xBF => cp_a_r(opcode, index, &mut reader),
318        0xC0 | 0xC8 | 0xD0 | 0xD8 | 0xE0 | 0xE8 | 0xF0 | 0xF8 => ret_cc(opcode),
319        0xC1 | 0xD1 | 0xE1 | 0xF1 => pop_qq(opcode, index),
320        0xC2 | 0xCA | 0xD2 | 0xDA | 0xE2 | 0xEA | 0xF2 | 0xFA => jp_cc_nn(opcode, &mut reader),
321        0xC3 => jp_nn(&mut reader),
322        0xC4 | 0xCC | 0xD4 | 0xDC | 0xE4 | 0xEC | 0xF4 | 0xFC => call_cc_nn(opcode, &mut reader),
323        0xC5 | 0xD5 | 0xE5 | 0xF5 => push_qq(opcode, index),
324        0xC6 => add_a_immediate(&mut reader),
325        0xC7 | 0xCF | 0xD7 | 0xDF | 0xE7 | 0xEF | 0xF7 | 0xFF => rst(opcode),
326        0xC9 => "ret".into(),
327        0xCD => call_nn(&mut reader),
328        0xCE => adc_a_immediate(&mut reader),
329        0xD3 => out_n_a(&mut reader),
330        0xD6 => sub_a_immediate(&mut reader),
331        0xD9 => "exx".into(),
332        0xDB => in_a_n(&mut reader),
333        0xDE => sbc_a_immediate(&mut reader),
334        0xE3 => ex_stack_hl(index),
335        0xE6 => and_a_immediate(&mut reader),
336        0xE9 => jp_hl(index),
337        0xEB => "ex de, hl".into(),
338        0xEE => xor_a_immediate(&mut reader),
339        0xF3 => "di".into(),
340        0xF6 => or_a_immediate(&mut reader),
341        0xF9 => ld_sp_hl(index),
342        0xFB => "ei".into(),
343        0xFE => cp_a_immediate(&mut reader),
344        0xCB => cb_prefix(index, &mut reader),
345        0xED => ed_prefix(&mut reader),
346        0xDD | 0xFD => unreachable!("would still be in above index register loop"),
347    };
348
349    out.opcodes = mem::take(&mut reader.opcodes);
350    out.memory_access = out.parse_memory_access(index);
351}
352
353fn cb_prefix(index: Option<IndexRegister>, reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
354    // For DD+CB and FD+CB instructions, the index offset comes before the last opcode byte
355    let index = index.map(|index| {
356        let offset = reader.read_byte() as i8;
357        IndexWithOffset(index, offset)
358    });
359
360    let opcode = reader.read_byte();
361
362    match opcode {
363        0x00..=0x07 => shift_rotate_op("rlc", opcode, index),
364        0x08..=0x0F => shift_rotate_op("rrc", opcode, index),
365        0x10..=0x17 => shift_rotate_op("rl", opcode, index),
366        0x18..=0x1F => shift_rotate_op("rr", opcode, index),
367        0x20..=0x27 => shift_rotate_op("sla", opcode, index),
368        0x28..=0x2F => shift_rotate_op("sra", opcode, index),
369        0x30..=0x37 => shift_rotate_op("sll", opcode, index),
370        0x38..=0x3F => shift_rotate_op("srl", opcode, index),
371        0x40..=0x7F => bit_op("bit", opcode, index),
372        0x80..=0xBF => bit_op("res", opcode, index),
373        0xC0..=0xFF => bit_op("set", opcode, index),
374    }
375}
376
377fn ed_prefix(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
378    let opcode = reader.read_byte();
379
380    match opcode {
381        0x40 | 0x48 | 0x50 | 0x58 | 0x60 | 0x68 | 0x70 | 0x78 => in_r_c(opcode),
382        0x41 | 0x49 | 0x51 | 0x59 | 0x61 | 0x69 | 0x71 | 0x79 => out_c_r(opcode),
383        0x42 | 0x52 | 0x62 | 0x72 => sbc_hl_ss(opcode),
384        0x43 | 0x53 | 0x63 | 0x73 => ld_direct_dd(opcode, reader),
385        0x44 => "neg".into(),
386        0x45 => "retn".into(),
387        0x46 => "im 0".into(),
388        0x47 => "ld i, a".into(),
389        0x4A | 0x5A | 0x6A | 0x7A => adc_hl_ss(opcode),
390        0x4B | 0x5B | 0x6B | 0x7B => ld_dd_direct(opcode, reader),
391        0x4F => "ld r, a".into(),
392        0x56 => "im 1".into(),
393        0x57 => "ld a, i".into(),
394        0x5E => "im 2".into(),
395        0x5F => "ld a, r".into(),
396        0x67 => "rrd".into(),
397        0x6F => "rld".into(),
398        0xA0 => "ldi".into(),
399        0xA1 => "cpi".into(),
400        0xA2 => "ini".into(),
401        0xA3 => "outi".into(),
402        0xA8 => "ldd".into(),
403        0xA9 => "cpd".into(),
404        0xAA => "ind".into(),
405        0xAB => "outd".into(),
406        0xB0 => "ldir".into(),
407        0xB1 => "cpir".into(),
408        0xB2 => "inir".into(),
409        0xB3 => "otir".into(),
410        0xB8 => "lddr".into(),
411        0xB9 => "cpdr".into(),
412        0xBA => "indr".into(),
413        0xBB => "otdr".into(),
414        _ => "illegal".into(),
415    }
416}
417
418fn r_register(
419    opcode: u8,
420    index: Option<IndexRegister>,
421    reader: &mut ByteReader<impl FnMut() -> u8>,
422) -> Cow<'static, str> {
423    match opcode & 7 {
424        0 => "b".into(),
425        1 => "c".into(),
426        2 => "d".into(),
427        3 => "e".into(),
428        4 => index.map_or("h", IndexRegister::high_label).into(),
429        5 => index.map_or("l", IndexRegister::low_label).into(),
430        6 => index
431            .map_or("(hl)".into(), |index| {
432                let offset = reader.read_byte() as i8;
433                index.indirect_with_offset(offset)
434            })
435            .into(),
436        7 => "a".into(),
437        _ => unreachable!("value & 7 is always <= 7"),
438    }
439}
440
441fn dd_register(opcode: u8, index: Option<IndexRegister>) -> &'static str {
442    match (opcode >> 4) & 3 {
443        0 => "bc",
444        1 => "de",
445        2 => index.map_or("hl", IndexRegister::label),
446        3 => "sp",
447        _ => unreachable!("value & 3 is always <= 3"),
448    }
449}
450
451fn qq_register(opcode: u8, index: Option<IndexRegister>) -> &'static str {
452    match (opcode >> 4) & 3 {
453        0 => "bc",
454        1 => "de",
455        2 => index.map_or("hl", IndexRegister::label),
456        3 => "af",
457        _ => unreachable!("value & 3 is always <= 3"),
458    }
459}
460
461fn ld_dd_immediate(
462    opcode: u8,
463    index: Option<IndexRegister>,
464    reader: &mut ByteReader<impl FnMut() -> u8>,
465) -> String {
466    let register = dd_register(opcode, index);
467    let immediate = reader.read_word();
468    format!("ld {register}, #0x{immediate:04X}")
469}
470
471fn inc_ss(opcode: u8, index: Option<IndexRegister>) -> String {
472    let register = dd_register(opcode, index);
473    format!("inc {register}")
474}
475
476fn dec_ss(opcode: u8, index: Option<IndexRegister>) -> String {
477    let register = dd_register(opcode, index);
478    format!("dec {register}")
479}
480
481fn inc_r(
482    opcode: u8,
483    index: Option<IndexRegister>,
484    reader: &mut ByteReader<impl FnMut() -> u8>,
485) -> String {
486    let register = r_register(opcode >> 3, index, reader);
487    format!("inc {register}")
488}
489
490fn dec_r(
491    opcode: u8,
492    index: Option<IndexRegister>,
493    reader: &mut ByteReader<impl FnMut() -> u8>,
494) -> String {
495    let register = r_register(opcode >> 3, index, reader);
496    format!("dec {register}")
497}
498
499fn ld_r_immediate(
500    opcode: u8,
501    index: Option<IndexRegister>,
502    reader: &mut ByteReader<impl FnMut() -> u8>,
503) -> String {
504    let register = r_register(opcode >> 3, index, reader);
505    let immediate = reader.read_byte();
506    format!("ld {register}, #0x{immediate:02X}")
507}
508
509fn add_hl_ss(opcode: u8, index: Option<IndexRegister>) -> String {
510    let dest = index.map_or("hl", IndexRegister::label);
511    let source = dd_register(opcode, index);
512    format!("add {dest}, {source}")
513}
514
515fn djnz(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
516    let offset = reader.read_byte() as i8;
517    let address = reader.pc.wrapping_add_signed(offset.into());
518    format!("djnz ${address:04X}")
519}
520
521fn jr(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
522    let offset = reader.read_byte() as i8;
523    let address = reader.pc.wrapping_add_signed(offset.into());
524    format!("jr ${address:04X}")
525}
526
527fn jr_cc(opcode: u8, reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
528    let offset = reader.read_byte() as i8;
529    let address = reader.pc.wrapping_add_signed(offset.into());
530    let condition = match (opcode >> 3) & 3 {
531        0 => "nz",
532        1 => "z",
533        2 => "nc",
534        3 => "c",
535        _ => unreachable!("value & 3 is always <= 3"),
536    };
537    format!("jr {condition}, ${address:04X}")
538}
539
540fn ld_direct_hl(
541    index: Option<IndexRegister>,
542    reader: &mut ByteReader<impl FnMut() -> u8>,
543) -> String {
544    let address = reader.read_word();
545    let register = index.map_or("hl", IndexRegister::label);
546    format!("ld (${address:04X}), {register}")
547}
548
549fn ld_hl_direct(
550    index: Option<IndexRegister>,
551    reader: &mut ByteReader<impl FnMut() -> u8>,
552) -> String {
553    let address = reader.read_word();
554    let register = index.map_or("hl", IndexRegister::label);
555    format!("ld {register}, (${address:04X})")
556}
557
558fn ld_direct_a(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
559    let address = reader.read_word();
560    format!("ld (${address:04X}), a")
561}
562
563fn ld_a_direct(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
564    let address = reader.read_word();
565    format!("ld a, (${address:04X})")
566}
567
568fn ld_r_r(
569    opcode: u8,
570    index: Option<IndexRegister>,
571    reader: &mut ByteReader<impl FnMut() -> u8>,
572) -> String {
573    let dest_index = if (opcode & 7) != 6 { index } else { None };
574    let dest = r_register(opcode >> 3, dest_index, reader);
575
576    let source_index = if ((opcode >> 3) & 7) != 6 { index } else { None };
577    let source = r_register(opcode, source_index, reader);
578
579    format!("ld {dest}, {source}")
580}
581
582fn add_a_r(
583    opcode: u8,
584    index: Option<IndexRegister>,
585    reader: &mut ByteReader<impl FnMut() -> u8>,
586) -> String {
587    let source = r_register(opcode, index, reader);
588    format!("add a, {source}")
589}
590
591fn adc_a_r(
592    opcode: u8,
593    index: Option<IndexRegister>,
594    reader: &mut ByteReader<impl FnMut() -> u8>,
595) -> String {
596    let source = r_register(opcode, index, reader);
597    format!("adc a, {source}")
598}
599
600fn sub_a_r(
601    opcode: u8,
602    index: Option<IndexRegister>,
603    reader: &mut ByteReader<impl FnMut() -> u8>,
604) -> String {
605    let source = r_register(opcode, index, reader);
606    format!("sub {source}")
607}
608
609fn sbc_a_r(
610    opcode: u8,
611    index: Option<IndexRegister>,
612    reader: &mut ByteReader<impl FnMut() -> u8>,
613) -> String {
614    let source = r_register(opcode, index, reader);
615    format!("sbc a, {source}")
616}
617
618fn and_a_r(
619    opcode: u8,
620    index: Option<IndexRegister>,
621    reader: &mut ByteReader<impl FnMut() -> u8>,
622) -> String {
623    let source = r_register(opcode, index, reader);
624    format!("and {source}")
625}
626
627fn xor_a_r(
628    opcode: u8,
629    index: Option<IndexRegister>,
630    reader: &mut ByteReader<impl FnMut() -> u8>,
631) -> String {
632    let source = r_register(opcode, index, reader);
633    format!("xor {source}")
634}
635
636fn or_a_r(
637    opcode: u8,
638    index: Option<IndexRegister>,
639    reader: &mut ByteReader<impl FnMut() -> u8>,
640) -> String {
641    let source = r_register(opcode, index, reader);
642    format!("or {source}")
643}
644
645fn cp_a_r(
646    opcode: u8,
647    index: Option<IndexRegister>,
648    reader: &mut ByteReader<impl FnMut() -> u8>,
649) -> String {
650    let source = r_register(opcode, index, reader);
651    format!("cp {source}")
652}
653
654fn jump_condition(opcode: u8) -> &'static str {
655    match (opcode >> 3) & 7 {
656        0 => "nz",
657        1 => "z",
658        2 => "nc",
659        3 => "c",
660        4 => "po",
661        5 => "pe",
662        6 => "p",
663        7 => "m",
664        _ => unreachable!("value & 7 is <= 7"),
665    }
666}
667
668fn ret_cc(opcode: u8) -> String {
669    let condition = jump_condition(opcode);
670    format!("ret {condition}")
671}
672
673fn pop_qq(opcode: u8, index: Option<IndexRegister>) -> String {
674    let register = qq_register(opcode, index);
675    format!("pop {register}")
676}
677
678fn push_qq(opcode: u8, index: Option<IndexRegister>) -> String {
679    let register = qq_register(opcode, index);
680    format!("push {register}")
681}
682
683fn jp_nn(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
684    let address = reader.read_word();
685    format!("jp ${address:04X}")
686}
687
688fn call_nn(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
689    let address = reader.read_word();
690    format!("call ${address:04X}")
691}
692
693fn jp_cc_nn(opcode: u8, reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
694    let condition = jump_condition(opcode);
695    let address = reader.read_word();
696    format!("jp {condition}, ${address:04X}")
697}
698
699fn call_cc_nn(opcode: u8, reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
700    let condition = jump_condition(opcode);
701    let address = reader.read_word();
702    format!("call {condition}, ${address:04X}")
703}
704
705fn add_a_immediate(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
706    let operand = reader.read_byte();
707    format!("add a, #0x{operand:02X}")
708}
709
710fn adc_a_immediate(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
711    let operand = reader.read_byte();
712    format!("adc a, #0x{operand:02X}")
713}
714
715fn sub_a_immediate(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
716    let operand = reader.read_byte();
717    format!("sub #0x{operand:02X}")
718}
719
720fn sbc_a_immediate(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
721    let operand = reader.read_byte();
722    format!("sbc a, #0x{operand:02X}")
723}
724
725fn and_a_immediate(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
726    let operand = reader.read_byte();
727    format!("and #0x{operand:02X}")
728}
729
730fn xor_a_immediate(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
731    let operand = reader.read_byte();
732    format!("xor #0x{operand:02X}")
733}
734
735fn or_a_immediate(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
736    let operand = reader.read_byte();
737    format!("or #0x{operand:02X}")
738}
739
740fn cp_a_immediate(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
741    let operand = reader.read_byte();
742    format!("cp #0x{operand:02X}")
743}
744
745fn rst(opcode: u8) -> String {
746    let address = opcode & 0x38;
747    format!("rst ${address:02X}")
748}
749
750fn out_n_a(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
751    let address = reader.read_byte();
752    format!("out (${address:02X}), a")
753}
754
755fn in_a_n(reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
756    let address = reader.read_byte();
757    format!("in a, (${address:02X})")
758}
759
760fn ex_stack_hl(index: Option<IndexRegister>) -> String {
761    let source = index.map_or("hl", IndexRegister::label);
762    format!("ex (sp), {source}")
763}
764
765fn jp_hl(index: Option<IndexRegister>) -> String {
766    let register = index.map_or("hl", IndexRegister::label);
767    format!("jp ({register})")
768}
769
770fn ld_sp_hl(index: Option<IndexRegister>) -> String {
771    let register = index.map_or("hl", IndexRegister::label);
772    format!("ld sp, {register}")
773}
774
775fn bit_register(opcode: u8) -> &'static str {
776    match opcode & 7 {
777        0 => "b",
778        1 => "c",
779        2 => "d",
780        3 => "e",
781        4 => "h",
782        5 => "l",
783        6 => "(hl)",
784        7 => "a",
785        _ => unreachable!("value & 7 is always <= 7"),
786    }
787}
788
789fn shift_rotate_op(name: &str, opcode: u8, index: Option<IndexWithOffset>) -> String {
790    let register =
791        index.map_or_else(|| bit_register(opcode).into(), IndexWithOffset::indirect_with_offset);
792    format!("{name} {register}")
793}
794
795fn bit_op(name: &str, opcode: u8, index: Option<IndexWithOffset>) -> String {
796    let bit = (opcode >> 3) & 7;
797    let register =
798        index.map_or_else(|| bit_register(opcode).into(), IndexWithOffset::indirect_with_offset);
799    format!("{name} {bit}, {register}")
800}
801
802fn r_register_no_hl(opcode: u8) -> Option<&'static str> {
803    match (opcode >> 3) & 7 {
804        0 => Some("b"),
805        1 => Some("c"),
806        2 => Some("d"),
807        3 => Some("e"),
808        4 => Some("h"),
809        5 => Some("l"),
810        6 => None,
811        7 => Some("a"),
812        _ => unreachable!("value & 7 is always <= 7"),
813    }
814}
815
816fn in_r_c(opcode: u8) -> String {
817    let Some(register) = r_register_no_hl(opcode) else { return "illegal".into() };
818    format!("in {register}, (c)")
819}
820
821fn out_c_r(opcode: u8) -> String {
822    let Some(register) = r_register_no_hl(opcode) else { return "illegal".into() };
823    format!("out (c), {register}")
824}
825
826fn sbc_hl_ss(opcode: u8) -> String {
827    let register = dd_register(opcode, None);
828    format!("sbc hl, {register}")
829}
830
831fn adc_hl_ss(opcode: u8) -> String {
832    let register = dd_register(opcode, None);
833    format!("adc hl, {register}")
834}
835
836fn ld_direct_dd(opcode: u8, reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
837    let address = reader.read_word();
838    let register = dd_register(opcode, None);
839    format!("ld (${address:04X}), {register}")
840}
841
842fn ld_dd_direct(opcode: u8, reader: &mut ByteReader<impl FnMut() -> u8>) -> String {
843    let address = reader.read_word();
844    let register = dd_register(opcode, None);
845    format!("ld {register}, (${address:04X})")
846}
847
848#[cfg(test)]
849mod tests {
850    use super::*;
851
852    fn iter_reader(iterable: impl IntoIterator<Item = u8>) -> impl FnMut() -> u8 {
853        let mut iter = iterable.into_iter();
854        move || iter.next().expect("ran out of opcodes")
855    }
856
857    #[test]
858    fn ld_r_r_index() {
859        let mut out = DisassembledInstruction::new();
860
861        disassemble_into(&mut out, 0, iter_reader([0x66]));
862        assert_eq!(out.text.as_str(), "ld h, (hl)");
863
864        disassemble_into(&mut out, 0, iter_reader([0xDD, 0x66, 0x05]));
865        assert_eq!(out.text.as_str(), "ld h, (ix+5)");
866
867        disassemble_into(&mut out, 0, iter_reader([0x75]));
868        assert_eq!(out.text.as_str(), "ld (hl), l");
869
870        disassemble_into(&mut out, 0, iter_reader([0xFD, 0x75, 0xFD]));
871        assert_eq!(out.text.as_str(), "ld (iy-3), l");
872
873        disassemble_into(&mut out, 0, iter_reader([0x76]));
874        assert_eq!(out.text.as_str(), "halt");
875
876        disassemble_into(&mut out, 0, iter_reader([0xDD, 0x76]));
877        assert_eq!(out.text.as_str(), "halt");
878    }
879
880    #[test]
881    fn other_register_operands() {
882        let mut out = DisassembledInstruction::new();
883
884        disassemble_into(&mut out, 0, iter_reader([0x0C]));
885        assert_eq!(out.text.as_str(), "inc c");
886
887        disassemble_into(&mut out, 0, iter_reader([0xA2]));
888        assert_eq!(out.text.as_str(), "and d");
889
890        disassemble_into(&mut out, 0, iter_reader([0x2D]));
891        assert_eq!(out.text.as_str(), "dec l");
892
893        disassemble_into(&mut out, 0, iter_reader([0xFD, 0x36, 0x00, 0x34]));
894        assert_eq!(out.text.as_str(), "ld (iy), #0x34");
895    }
896}