1use alloc::format;
13use alloc::string::String;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
17enum Mode {
18 Implied,
19 Accumulator,
20 ImmediateM,
22 ImmediateX,
24 Immediate8,
26 Direct,
27 DirectX,
28 DirectY,
29 DirectIndirect,
30 DirectIndirectX,
31 DirectIndirectY,
32 DirectIndirectLong,
33 DirectIndirectLongY,
34 Absolute,
35 AbsoluteX,
36 AbsoluteY,
37 AbsoluteIndirect,
38 AbsoluteIndirectX,
39 AbsoluteIndirectLong,
40 AbsoluteLong,
41 AbsoluteLongX,
42 StackRelative,
43 StackRelativeIndirectY,
44 Relative,
45 RelativeLong,
46 BlockMove,
47}
48
49#[rustfmt::skip]
53const OPCODES: [(&str, Mode); 256] = {
54 use Mode::{
55 Absolute as A, AbsoluteIndirect as AI, AbsoluteIndirectLong as AIL,
56 AbsoluteIndirectX as AIX, AbsoluteLong as AL, AbsoluteLongX as ALX, AbsoluteX as AX,
57 AbsoluteY as AY, Accumulator as Acc, BlockMove as BM, Direct as D, DirectIndirect as DI,
58 DirectIndirectLong as DIL, DirectIndirectLongY as DILY, DirectIndirectX as DIX,
59 DirectIndirectY as DIY, DirectX as DX, DirectY as DY, Immediate8 as I8, ImmediateM as IM,
60 ImmediateX as IX, Implied as Imp, Relative as Rel, RelativeLong as RelL,
61 StackRelative as SR, StackRelativeIndirectY as SRIY,
62 };
63 [
64 ("BRK", I8), ("ORA", DIX), ("COP", I8), ("ORA", SR), ("TSB", D), ("ORA", D), ("ASL", D), ("ORA", DIL),
65 ("PHP", Imp), ("ORA", IM), ("ASL", Acc), ("PHD", Imp), ("TSB", A), ("ORA", A), ("ASL", A), ("ORA", AL),
66 ("BPL", Rel), ("ORA", DIY), ("ORA", DI), ("ORA", SRIY), ("TRB", D), ("ORA", DX), ("ASL", DX), ("ORA", DILY),
67 ("CLC", Imp), ("ORA", AY), ("INC", Acc), ("TCS", Imp), ("TRB", A), ("ORA", AX), ("ASL", AX), ("ORA", ALX),
68 ("JSR", A), ("AND", DIX), ("JSL", AL), ("AND", SR), ("BIT", D), ("AND", D), ("ROL", D), ("AND", DIL),
69 ("PLP", Imp), ("AND", IM), ("ROL", Acc), ("PLD", Imp), ("BIT", A), ("AND", A), ("ROL", A), ("AND", AL),
70 ("BMI", Rel), ("AND", DIY), ("AND", DI), ("AND", SRIY), ("BIT", DX), ("AND", DX), ("ROL", DX), ("AND", DILY),
71 ("SEC", Imp), ("AND", AY), ("DEC", Acc), ("TSC", Imp), ("BIT", AX), ("AND", AX), ("ROL", AX), ("AND", ALX),
72 ("RTI", Imp), ("EOR", DIX), ("WDM", I8), ("EOR", SR), ("MVP", BM), ("EOR", D), ("LSR", D), ("EOR", DIL),
73 ("PHA", Imp), ("EOR", IM), ("LSR", Acc), ("PHK", Imp), ("JMP", A), ("EOR", A), ("LSR", A), ("EOR", AL),
74 ("BVC", Rel), ("EOR", DIY), ("EOR", DI), ("EOR", SRIY), ("MVN", BM), ("EOR", DX), ("LSR", DX), ("EOR", DILY),
75 ("CLI", Imp), ("EOR", AY), ("PHY", Imp), ("TCD", Imp), ("JMP", AL), ("EOR", AX), ("LSR", AX), ("EOR", ALX),
76 ("RTS", Imp), ("ADC", DIX), ("PER", RelL), ("ADC", SR), ("STZ", D), ("ADC", D), ("ROR", D), ("ADC", DIL),
77 ("PLA", Imp), ("ADC", IM), ("ROR", Acc), ("RTL", Imp), ("JMP", AI), ("ADC", A), ("ROR", A), ("ADC", AL),
78 ("BVS", Rel), ("ADC", DIY), ("ADC", DI), ("ADC", SRIY), ("STZ", DX), ("ADC", DX), ("ROR", DX), ("ADC", DILY),
79 ("SEI", Imp), ("ADC", AY), ("PLY", Imp), ("TDC", Imp), ("JMP", AIX), ("ADC", AX), ("ROR", AX), ("ADC", ALX),
80 ("BRA", Rel), ("STA", DIX), ("BRL", RelL), ("STA", SR), ("STY", D), ("STA", D), ("STX", D), ("STA", DIL),
81 ("DEY", Imp), ("BIT", IM), ("TXA", Imp), ("PHB", Imp), ("STY", A), ("STA", A), ("STX", A), ("STA", AL),
82 ("BCC", Rel), ("STA", DIY), ("STA", DI), ("STA", SRIY), ("STY", DX), ("STA", DX), ("STX", DY), ("STA", DILY),
83 ("TYA", Imp), ("STA", AY), ("TXS", Imp), ("TXY", Imp), ("STZ", A), ("STA", AX), ("STZ", AX), ("STA", ALX),
84 ("LDY", IX), ("LDA", DIX), ("LDX", IX), ("LDA", SR), ("LDY", D), ("LDA", D), ("LDX", D), ("LDA", DIL),
85 ("TAY", Imp), ("LDA", IM), ("TAX", Imp), ("PLB", Imp), ("LDY", A), ("LDA", A), ("LDX", A), ("LDA", AL),
86 ("BCS", Rel), ("LDA", DIY), ("LDA", DI), ("LDA", SRIY), ("LDY", DX), ("LDA", DX), ("LDX", DY), ("LDA", DILY),
87 ("CLV", Imp), ("LDA", AY), ("TSX", Imp), ("TYX", Imp), ("LDY", AX), ("LDA", AX), ("LDX", AY), ("LDA", ALX),
88 ("CPY", IX), ("CMP", DIX), ("REP", I8), ("CMP", SR), ("CPY", D), ("CMP", D), ("DEC", D), ("CMP", DIL),
89 ("INY", Imp), ("CMP", IM), ("DEX", Imp), ("WAI", Imp), ("CPY", A), ("CMP", A), ("DEC", A), ("CMP", AL),
90 ("BNE", Rel), ("CMP", DIY), ("CMP", DI), ("CMP", SRIY), ("PEI", D), ("CMP", DX), ("DEC", DX), ("CMP", DILY),
91 ("CLD", Imp), ("CMP", AY), ("PHX", Imp), ("STP", Imp), ("JMP", AIL), ("CMP", AX), ("DEC", AX), ("CMP", ALX),
92 ("CPX", IX), ("SBC", DIX), ("SEP", I8), ("SBC", SR), ("CPX", D), ("SBC", D), ("INC", D), ("SBC", DIL),
93 ("INX", Imp), ("SBC", IM), ("NOP", Imp), ("XBA", Imp), ("CPX", A), ("SBC", A), ("INC", A), ("SBC", AL),
94 ("BEQ", Rel), ("SBC", DIY), ("SBC", DI), ("SBC", SRIY), ("PEA", A), ("SBC", DX), ("INC", DX), ("SBC", DILY),
95 ("SED", Imp), ("SBC", AY), ("PLX", Imp), ("XCE", Imp), ("JSR", AIX), ("SBC", AX), ("INC", AX), ("SBC", ALX),
96 ]
97};
98
99const fn operand_len(mode: Mode, m8: bool, x8: bool) -> usize {
101 match mode {
102 Mode::Implied | Mode::Accumulator => 0,
103 Mode::ImmediateM => {
104 if m8 {
105 1
106 } else {
107 2
108 }
109 }
110 Mode::ImmediateX => {
111 if x8 {
112 1
113 } else {
114 2
115 }
116 }
117 Mode::Immediate8
118 | Mode::Direct
119 | Mode::DirectX
120 | Mode::DirectY
121 | Mode::DirectIndirect
122 | Mode::DirectIndirectX
123 | Mode::DirectIndirectY
124 | Mode::DirectIndirectLong
125 | Mode::DirectIndirectLongY
126 | Mode::StackRelative
127 | Mode::StackRelativeIndirectY
128 | Mode::Relative => 1,
129 Mode::Absolute
130 | Mode::AbsoluteX
131 | Mode::AbsoluteY
132 | Mode::AbsoluteIndirect
133 | Mode::AbsoluteIndirectX
134 | Mode::RelativeLong
135 | Mode::BlockMove => 2,
136 Mode::AbsoluteLong | Mode::AbsoluteLongX | Mode::AbsoluteIndirectLong => 3,
137 }
138}
139
140#[must_use]
155pub fn disassemble_one(
156 mut peek: impl FnMut(u32) -> u8,
157 pbr: u8,
158 pc: u16,
159 m8: bool,
160 x8: bool,
161) -> (String, usize) {
162 let base = (u32::from(pbr) << 16) | u32::from(pc);
163 let opcode = peek(base);
164 let (mnemonic, mode) = OPCODES[opcode as usize];
165 let n = operand_len(mode, m8, x8);
166 let mut op = [0u8; 3];
167 for (i, byte) in op.iter_mut().enumerate().take(n) {
168 let i = u32::try_from(i).unwrap_or(u32::MAX);
169 *byte = peek(base.wrapping_add(1 + i) & 0x00FF_FFFF);
170 }
171 let text = match mode {
172 Mode::Implied => String::from(mnemonic),
173 Mode::Accumulator => format!("{mnemonic} A"),
174 Mode::ImmediateM | Mode::ImmediateX | Mode::Immediate8 => {
175 if n == 2 {
176 format!(
177 "{mnemonic} #${:04X}",
178 u16::from(op[0]) | (u16::from(op[1]) << 8)
179 )
180 } else {
181 format!("{mnemonic} #${:02X}", op[0])
182 }
183 }
184 Mode::Direct => format!("{mnemonic} ${:02X}", op[0]),
185 Mode::DirectX => format!("{mnemonic} ${:02X},X", op[0]),
186 Mode::DirectY => format!("{mnemonic} ${:02X},Y", op[0]),
187 Mode::DirectIndirect => format!("{mnemonic} (${:02X})", op[0]),
188 Mode::DirectIndirectX => format!("{mnemonic} (${:02X},X)", op[0]),
189 Mode::DirectIndirectY => format!("{mnemonic} (${:02X}),Y", op[0]),
190 Mode::DirectIndirectLong => format!("{mnemonic} [${:02X}]", op[0]),
191 Mode::DirectIndirectLongY => format!("{mnemonic} [${:02X}],Y", op[0]),
192 Mode::StackRelative => format!("{mnemonic} ${:02X},S", op[0]),
193 Mode::StackRelativeIndirectY => format!("{mnemonic} (${:02X},S),Y", op[0]),
194 Mode::Absolute => format!(
195 "{mnemonic} ${:04X}",
196 u16::from(op[0]) | (u16::from(op[1]) << 8)
197 ),
198 Mode::AbsoluteX => format!(
199 "{mnemonic} ${:04X},X",
200 u16::from(op[0]) | (u16::from(op[1]) << 8)
201 ),
202 Mode::AbsoluteY => format!(
203 "{mnemonic} ${:04X},Y",
204 u16::from(op[0]) | (u16::from(op[1]) << 8)
205 ),
206 Mode::AbsoluteIndirect => format!(
207 "{mnemonic} (${:04X})",
208 u16::from(op[0]) | (u16::from(op[1]) << 8)
209 ),
210 Mode::AbsoluteIndirectX => format!(
211 "{mnemonic} (${:04X},X)",
212 u16::from(op[0]) | (u16::from(op[1]) << 8)
213 ),
214 Mode::AbsoluteIndirectLong => format!(
215 "{mnemonic} [${:04X}]",
216 u16::from(op[0]) | (u16::from(op[1]) << 8)
217 ),
218 Mode::AbsoluteLong => format!("{mnemonic} ${:02X}{:02X}{:02X}", op[2], op[1], op[0]),
219 Mode::AbsoluteLongX => format!("{mnemonic} ${:02X}{:02X}{:02X},X", op[2], op[1], op[0]),
220 Mode::Relative => {
221 let rel = i16::from(op[0].cast_signed());
222 let target = pc.wrapping_add(2).wrapping_add(rel.cast_unsigned());
223 format!("{mnemonic} ${target:04X}")
224 }
225 Mode::RelativeLong => {
226 let rel = i16::from_le_bytes([op[0], op[1]]);
227 let target = pc.wrapping_add(3).wrapping_add(rel.cast_unsigned());
228 format!("{mnemonic} ${target:04X}")
229 }
230 Mode::BlockMove => format!("{mnemonic} ${:02X},${:02X}", op[0], op[1]),
231 };
232 (text, 1 + n)
233}
234
235#[cfg(test)]
236mod tests {
237 use super::disassemble_one;
238
239 fn rom(bytes: &'static [u8]) -> impl Fn(u32) -> u8 {
240 move |addr: u32| bytes.get(addr as usize).copied().unwrap_or(0)
241 }
242
243 fn rom_at(base: u32, bytes: &'static [u8]) -> impl Fn(u32) -> u8 {
247 move |addr: u32| {
248 addr.checked_sub(base)
249 .and_then(|i| bytes.get(i as usize))
250 .copied()
251 .unwrap_or(0)
252 }
253 }
254
255 #[test]
256 fn decodes_implied() {
257 let (text, len) = disassemble_one(rom(&[0xEA]), 0, 0, true, true);
258 assert_eq!(text, "NOP");
259 assert_eq!(len, 1);
260 }
261
262 #[test]
263 fn decodes_immediate_8bit_accumulator() {
264 let (text, len) = disassemble_one(rom(&[0xA9, 0x42, 0xFF]), 0, 0, true, true);
266 assert_eq!(text, "LDA #$42");
267 assert_eq!(len, 2);
268 }
269
270 #[test]
271 fn decodes_immediate_16bit_accumulator() {
272 let (text, len) = disassemble_one(rom(&[0xA9, 0x34, 0x12]), 0, 0, false, true);
274 assert_eq!(text, "LDA #$1234");
275 assert_eq!(len, 3);
276 }
277
278 #[test]
279 fn decodes_absolute() {
280 let (text, len) = disassemble_one(rom(&[0xAD, 0x00, 0x42]), 0, 0, true, true);
281 assert_eq!(text, "LDA $4200");
282 assert_eq!(len, 3);
283 }
284
285 #[test]
286 fn decodes_absolute_long() {
287 let (text, len) = disassemble_one(rom(&[0xAF, 0x00, 0x80, 0xC0]), 0, 0, true, true);
288 assert_eq!(text, "LDA $C08000");
289 assert_eq!(len, 4);
290 }
291
292 #[test]
293 fn decodes_direct_page_indexed() {
294 let (text, len) = disassemble_one(rom(&[0xB5, 0x10]), 0, 0, true, true);
295 assert_eq!(text, "LDA $10,X");
296 assert_eq!(len, 2);
297 }
298
299 #[test]
300 fn decodes_forward_branch() {
301 let (text, len) = disassemble_one(rom_at(0x1000, &[0xD0, 0x02]), 0, 0x1000, true, true);
303 assert_eq!(text, "BNE $1004");
304 assert_eq!(len, 2);
305 }
306
307 #[test]
308 fn decodes_backward_branch() {
309 let (text, len) = disassemble_one(rom_at(0x1000, &[0x80, 0xFE]), 0, 0x1000, true, true);
311 assert_eq!(text, "BRA $1000");
312 assert_eq!(len, 2);
313 }
314
315 #[test]
316 fn decodes_block_move() {
317 let (text, len) = disassemble_one(rom(&[0x54, 0x00, 0x7E]), 0, 0, true, true);
318 assert_eq!(text, "MVN $00,$7E");
319 assert_eq!(len, 3);
320 }
321
322 #[test]
323 fn decodes_relative_long() {
324 let (text, len) =
326 disassemble_one(rom_at(0x2000, &[0x82, 0x04, 0x00]), 0, 0x2000, true, true);
327 assert_eq!(text, "BRL $2007");
328 assert_eq!(len, 3);
329 }
330
331 #[test]
332 fn every_opcode_decodes_without_panicking() {
333 for opcode in 0u8..=255 {
334 let bytes: [u8; 4] = [opcode, 0, 0, 0];
335 let (text, len) = disassemble_one(
336 move |addr: u32| bytes.get(addr as usize).copied().unwrap_or(0),
337 0,
338 0,
339 true,
340 true,
341 );
342 assert!(!text.is_empty());
343 assert!((1..=4).contains(&len));
344 }
345 }
346}