1#![allow(clippy::cast_lossless)]
14#![allow(clippy::cast_possible_truncation)]
15#![allow(clippy::cast_possible_wrap)]
16#![allow(clippy::cast_sign_loss)]
17#![allow(clippy::enum_glob_use)]
18#![allow(clippy::items_after_statements)]
19#![allow(clippy::too_many_lines)]
20
21use alloc::format;
22use alloc::{string::String, vec::Vec};
23
24#[derive(Debug, Clone)]
26pub struct DisasmLine {
27 pub addr: u16,
29 pub bytes: Vec<u8>,
31 pub mnemonic: &'static str,
33 pub operand: String,
35}
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
39enum AddrMode {
40 Implied,
42 Accumulator,
44 Immediate,
46 ZeroPage,
48 ZeroPageX,
50 ZeroPageY,
52 Absolute,
54 AbsoluteX,
56 AbsoluteY,
58 Indirect,
60 IndirectX,
62 IndirectY,
64 Relative,
66}
67
68const fn op_len(mode: AddrMode) -> u16 {
69 match mode {
70 AddrMode::Implied | AddrMode::Accumulator => 1,
71 AddrMode::Immediate
72 | AddrMode::ZeroPage
73 | AddrMode::ZeroPageX
74 | AddrMode::ZeroPageY
75 | AddrMode::IndirectX
76 | AddrMode::IndirectY
77 | AddrMode::Relative => 2,
78 AddrMode::Absolute | AddrMode::AbsoluteX | AddrMode::AbsoluteY | AddrMode::Indirect => 3,
79 }
80}
81
82static OPCODE_TABLE: [(&str, AddrMode); 256] = build_opcode_table();
87
88const fn build_opcode_table() -> [(&'static str, AddrMode); 256] {
89 let mut t = [("???", AddrMode::Implied); 256];
90 use AddrMode::*;
92 macro_rules! set {
93 ($op:expr_2021, $m:expr_2021, $mode:expr_2021) => {
94 t[$op as usize] = ($m, $mode);
95 };
96 }
97 set!(0xA9, "LDA", Immediate);
99 set!(0xA5, "LDA", ZeroPage);
100 set!(0xB5, "LDA", ZeroPageX);
101 set!(0xAD, "LDA", Absolute);
102 set!(0xBD, "LDA", AbsoluteX);
103 set!(0xB9, "LDA", AbsoluteY);
104 set!(0xA1, "LDA", IndirectX);
105 set!(0xB1, "LDA", IndirectY);
106 set!(0xA2, "LDX", Immediate);
107 set!(0xA6, "LDX", ZeroPage);
108 set!(0xB6, "LDX", ZeroPageY);
109 set!(0xAE, "LDX", Absolute);
110 set!(0xBE, "LDX", AbsoluteY);
111 set!(0xA0, "LDY", Immediate);
112 set!(0xA4, "LDY", ZeroPage);
113 set!(0xB4, "LDY", ZeroPageX);
114 set!(0xAC, "LDY", Absolute);
115 set!(0xBC, "LDY", AbsoluteX);
116 set!(0x85, "STA", ZeroPage);
117 set!(0x95, "STA", ZeroPageX);
118 set!(0x8D, "STA", Absolute);
119 set!(0x9D, "STA", AbsoluteX);
120 set!(0x99, "STA", AbsoluteY);
121 set!(0x81, "STA", IndirectX);
122 set!(0x91, "STA", IndirectY);
123 set!(0x86, "STX", ZeroPage);
124 set!(0x96, "STX", ZeroPageY);
125 set!(0x8E, "STX", Absolute);
126 set!(0x84, "STY", ZeroPage);
127 set!(0x94, "STY", ZeroPageX);
128 set!(0x8C, "STY", Absolute);
129 set!(0xAA, "TAX", Implied);
130 set!(0xA8, "TAY", Implied);
131 set!(0xBA, "TSX", Implied);
132 set!(0x8A, "TXA", Implied);
133 set!(0x9A, "TXS", Implied);
134 set!(0x98, "TYA", Implied);
135 set!(0x48, "PHA", Implied);
137 set!(0x08, "PHP", Implied);
138 set!(0x68, "PLA", Implied);
139 set!(0x28, "PLP", Implied);
140 set!(0x29, "AND", Immediate);
142 set!(0x25, "AND", ZeroPage);
143 set!(0x35, "AND", ZeroPageX);
144 set!(0x2D, "AND", Absolute);
145 set!(0x3D, "AND", AbsoluteX);
146 set!(0x39, "AND", AbsoluteY);
147 set!(0x21, "AND", IndirectX);
148 set!(0x31, "AND", IndirectY);
149 set!(0x49, "EOR", Immediate);
150 set!(0x45, "EOR", ZeroPage);
151 set!(0x55, "EOR", ZeroPageX);
152 set!(0x4D, "EOR", Absolute);
153 set!(0x5D, "EOR", AbsoluteX);
154 set!(0x59, "EOR", AbsoluteY);
155 set!(0x41, "EOR", IndirectX);
156 set!(0x51, "EOR", IndirectY);
157 set!(0x09, "ORA", Immediate);
158 set!(0x05, "ORA", ZeroPage);
159 set!(0x15, "ORA", ZeroPageX);
160 set!(0x0D, "ORA", Absolute);
161 set!(0x1D, "ORA", AbsoluteX);
162 set!(0x19, "ORA", AbsoluteY);
163 set!(0x01, "ORA", IndirectX);
164 set!(0x11, "ORA", IndirectY);
165 set!(0x24, "BIT", ZeroPage);
166 set!(0x2C, "BIT", Absolute);
167 set!(0x69, "ADC", Immediate);
169 set!(0x65, "ADC", ZeroPage);
170 set!(0x75, "ADC", ZeroPageX);
171 set!(0x6D, "ADC", Absolute);
172 set!(0x7D, "ADC", AbsoluteX);
173 set!(0x79, "ADC", AbsoluteY);
174 set!(0x61, "ADC", IndirectX);
175 set!(0x71, "ADC", IndirectY);
176 set!(0xE9, "SBC", Immediate);
177 set!(0xE5, "SBC", ZeroPage);
178 set!(0xF5, "SBC", ZeroPageX);
179 set!(0xED, "SBC", Absolute);
180 set!(0xFD, "SBC", AbsoluteX);
181 set!(0xF9, "SBC", AbsoluteY);
182 set!(0xE1, "SBC", IndirectX);
183 set!(0xF1, "SBC", IndirectY);
184 set!(0xC9, "CMP", Immediate);
185 set!(0xC5, "CMP", ZeroPage);
186 set!(0xD5, "CMP", ZeroPageX);
187 set!(0xCD, "CMP", Absolute);
188 set!(0xDD, "CMP", AbsoluteX);
189 set!(0xD9, "CMP", AbsoluteY);
190 set!(0xC1, "CMP", IndirectX);
191 set!(0xD1, "CMP", IndirectY);
192 set!(0xE0, "CPX", Immediate);
193 set!(0xE4, "CPX", ZeroPage);
194 set!(0xEC, "CPX", Absolute);
195 set!(0xC0, "CPY", Immediate);
196 set!(0xC4, "CPY", ZeroPage);
197 set!(0xCC, "CPY", Absolute);
198 set!(0xE6, "INC", ZeroPage);
200 set!(0xF6, "INC", ZeroPageX);
201 set!(0xEE, "INC", Absolute);
202 set!(0xFE, "INC", AbsoluteX);
203 set!(0xE8, "INX", Implied);
204 set!(0xC8, "INY", Implied);
205 set!(0xC6, "DEC", ZeroPage);
206 set!(0xD6, "DEC", ZeroPageX);
207 set!(0xCE, "DEC", Absolute);
208 set!(0xDE, "DEC", AbsoluteX);
209 set!(0xCA, "DEX", Implied);
210 set!(0x88, "DEY", Implied);
211 set!(0x0A, "ASL", Accumulator);
213 set!(0x06, "ASL", ZeroPage);
214 set!(0x16, "ASL", ZeroPageX);
215 set!(0x0E, "ASL", Absolute);
216 set!(0x1E, "ASL", AbsoluteX);
217 set!(0x4A, "LSR", Accumulator);
218 set!(0x46, "LSR", ZeroPage);
219 set!(0x56, "LSR", ZeroPageX);
220 set!(0x4E, "LSR", Absolute);
221 set!(0x5E, "LSR", AbsoluteX);
222 set!(0x2A, "ROL", Accumulator);
223 set!(0x26, "ROL", ZeroPage);
224 set!(0x36, "ROL", ZeroPageX);
225 set!(0x2E, "ROL", Absolute);
226 set!(0x3E, "ROL", AbsoluteX);
227 set!(0x6A, "ROR", Accumulator);
228 set!(0x66, "ROR", ZeroPage);
229 set!(0x76, "ROR", ZeroPageX);
230 set!(0x6E, "ROR", Absolute);
231 set!(0x7E, "ROR", AbsoluteX);
232 set!(0x4C, "JMP", Absolute);
234 set!(0x6C, "JMP", Indirect);
235 set!(0x20, "JSR", Absolute);
236 set!(0x60, "RTS", Implied);
237 set!(0x40, "RTI", Implied);
238 set!(0x00, "BRK", Implied);
239 set!(0x10, "BPL", Relative);
241 set!(0x30, "BMI", Relative);
242 set!(0x50, "BVC", Relative);
243 set!(0x70, "BVS", Relative);
244 set!(0x90, "BCC", Relative);
245 set!(0xB0, "BCS", Relative);
246 set!(0xD0, "BNE", Relative);
247 set!(0xF0, "BEQ", Relative);
248 set!(0x18, "CLC", Implied);
250 set!(0x38, "SEC", Implied);
251 set!(0x58, "CLI", Implied);
252 set!(0x78, "SEI", Implied);
253 set!(0xB8, "CLV", Implied);
254 set!(0xD8, "CLD", Implied);
255 set!(0xF8, "SED", Implied);
256 set!(0xEA, "NOP", Implied);
257 t
258}
259
260pub fn disassemble_at<F: Fn(u16) -> u8>(peek: F, pc: u16, count: usize) -> Vec<DisasmLine> {
266 let mut out = Vec::with_capacity(count);
267 let mut cur = pc;
268 for _ in 0..count {
269 let op = peek(cur);
270 let (mnemonic, mode) = OPCODE_TABLE[op as usize];
271 let len = op_len(mode);
272 let mut bytes = Vec::with_capacity(len as usize);
273 for i in 0..len {
274 bytes.push(peek(cur.wrapping_add(i)));
275 }
276 let operand = if mnemonic == "???" {
277 format!(".byte ${op:02X}")
278 } else {
279 format_operand(mode, cur, &bytes)
280 };
281 out.push(DisasmLine {
282 addr: cur,
283 bytes,
284 mnemonic,
285 operand,
286 });
287 cur = cur.wrapping_add(len);
288 }
289 out
290}
291
292fn format_operand(mode: AddrMode, pc: u16, bytes: &[u8]) -> String {
293 let b1 = bytes.get(1).copied().unwrap_or(0);
294 let b2 = bytes.get(2).copied().unwrap_or(0);
295 let abs16 = u16::from(b1) | (u16::from(b2) << 8);
296 match mode {
297 AddrMode::Implied => String::new(),
298 AddrMode::Accumulator => "A".into(),
299 AddrMode::Immediate => format!("#${b1:02X}"),
300 AddrMode::ZeroPage => format!("${b1:02X}"),
301 AddrMode::ZeroPageX => format!("${b1:02X},X"),
302 AddrMode::ZeroPageY => format!("${b1:02X},Y"),
303 AddrMode::Absolute => format!("${abs16:04X}"),
304 AddrMode::AbsoluteX => format!("${abs16:04X},X"),
305 AddrMode::AbsoluteY => format!("${abs16:04X},Y"),
306 AddrMode::Indirect => format!("(${abs16:04X})"),
307 AddrMode::IndirectX => format!("(${b1:02X},X)"),
308 AddrMode::IndirectY => format!("(${b1:02X}),Y"),
309 AddrMode::Relative => {
310 let delta = b1 as i8;
311 let target = pc.wrapping_add(2).wrapping_add(delta as u16);
312 format!("${target:04X}")
313 }
314 }
315}
316
317#[cfg(test)]
318mod tests {
319 use super::*;
320
321 fn peek_from(bytes: &[u8], base: u16) -> impl Fn(u16) -> u8 + '_ {
322 move |addr: u16| {
323 let off = addr.wrapping_sub(base) as usize;
324 bytes.get(off).copied().unwrap_or(0)
325 }
326 }
327
328 #[test]
329 fn disasm_lda_imm() {
330 let prog = [0xA9, 0x42];
332 let lines = disassemble_at(peek_from(&prog, 0xC000), 0xC000, 1);
333 assert_eq!(lines.len(), 1);
334 assert_eq!(lines[0].addr, 0xC000);
335 assert_eq!(lines[0].mnemonic, "LDA");
336 assert_eq!(lines[0].operand, "#$42");
337 }
338
339 #[test]
340 fn disasm_jmp_indirect() {
341 let prog = [0x6C, 0x34, 0x12];
343 let lines = disassemble_at(peek_from(&prog, 0xC000), 0xC000, 1);
344 assert_eq!(lines[0].mnemonic, "JMP");
345 assert_eq!(lines[0].operand, "($1234)");
346 }
347
348 #[test]
349 fn disasm_walks_forward_past_unknown() {
350 let prog = [0x02, 0xEA];
352 let lines = disassemble_at(peek_from(&prog, 0xC000), 0xC000, 2);
353 assert_eq!(lines.len(), 2);
354 assert_eq!(lines[1].mnemonic, "NOP");
355 }
356
357 #[test]
358 fn disasm_branch_target_is_pc_plus_2_plus_delta() {
359 let prog = [0x10, 0xFE];
361 let lines = disassemble_at(peek_from(&prog, 0xC000), 0xC000, 1);
362 assert_eq!(lines[0].mnemonic, "BPL");
363 assert_eq!(lines[0].operand, "$C000");
364 }
365}