use core::fmt;
use core::fmt::Formatter;
#[cfg(feature = "arbitrary")]
extern crate std;
#[derive(Debug, Copy, Clone, PartialEq, Eq, core::hash::Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum AddressMode {
Accumulator,
Immediate(u8),
Absolute(u16),
ZeroPage(u8),
Relative(i8),
AbsoluteIndirect(u16),
AbsoluteX(u16),
AbsoluteY(u16),
ZeroPageX(u8),
ZeroPageY(u8),
ZeroPageIndirectX(u8),
ZeroPageYIndirect(u8),
}
impl fmt::Display for AddressMode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
AddressMode::Accumulator => Ok(()),
AddressMode::Immediate(n) => write!(f, "#${n:02X}"),
AddressMode::Absolute(n) => write!(f, "${n:04X}"),
AddressMode::ZeroPage(n) => write!(f, "${n:02X}"),
AddressMode::Relative(n) => write!(f, "${n:02X}"),
AddressMode::AbsoluteIndirect(n) => write!(f, "(${n:04X})"),
AddressMode::AbsoluteX(n) => write!(f, "${n:04X},X"),
AddressMode::AbsoluteY(n) => write!(f, "${n:04X},Y"),
AddressMode::ZeroPageX(n) => write!(f, "${n:02X},X"),
AddressMode::ZeroPageY(n) => write!(f, "${n:02X},Y"),
AddressMode::ZeroPageIndirectX(n) => write!(f, "(${n:02X},X)"),
AddressMode::ZeroPageYIndirect(n) => write!(f, "(${n:02X}),Y"),
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, core::hash::Hash)]
#[allow(non_camel_case_types)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub enum Instruction {
LDA,
LDX,
LDY,
STA,
STX,
STY,
ADC,
SBC,
INC,
INX,
INY,
DEC,
DEX,
DEY,
ASL,
LSR,
ROL,
ROR,
AND,
ORA,
EOR,
BIT,
CMP,
CPX,
CPY,
BCC,
BCS,
BNE,
BEQ,
BPL,
BMI,
BVC,
BVS,
TAX,
TAY,
TXA,
TYA,
TSX,
TXS,
PHA,
PLA,
PHP,
PLP,
JMP,
JSR,
RTS,
RTI,
CLC,
SEC,
CLD,
SED,
CLI,
SEI,
CLV,
BRK,
NOP,
}
impl Instruction {
pub const XOR: Instruction = Self::EOR;
}
impl fmt::Display for Instruction {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{self:?}") }
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, core::hash::Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
pub struct Opcode {
pub instruction: Instruction,
pub address_mode: Option<AddressMode>,
}
impl fmt::Display for Opcode {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.instruction)?;
if let Some(mode) = self.address_mode {
write!(f, " {mode}")?;
}
Ok(())
}
}
impl Opcode {
#[must_use]
#[inline]
pub fn new(instruction: Instruction, address_mode: Option<AddressMode>) -> Self {
Self {
instruction,
address_mode,
}
}
}
macro_rules! opcodes {
($(Opcode::new($inst: ident, $($tt: tt)+) => $repr: literal),* $(,)?) => {
impl Opcode {
pub fn load(data: &[u8]) -> Result<Option<Opcode>, usize> {
if data.is_empty() { return Err(1) }
match data[0] {
$($repr => opcodes!(__handle data $inst $($tt)+)),*,
_ => Ok(None)
}
}
pub fn dump(&self, buf: &mut [u8]) -> Result<bool, usize> {
if buf.is_empty() { return Err(1) }
$(opcodes!{__dump self $inst buf $repr $($tt)+})*
Ok(false)
}
}
};
(__handle $data: ident $inst: ident None) => {
Ok(Some(Opcode::new(Instruction::$inst, None)))
};
(__handle $data: ident $inst: ident Some(Accumulator)) => {
Ok(Some(Opcode::new(Instruction::$inst, Some(AddressMode::Accumulator))))
};
(__handle $data: ident $inst: ident Some(Immediate)) => {
opcodes!(__handle_u8 $data $inst Immediate)
};
(__handle $data: ident $inst: ident Some(Absolute)) => {
opcodes!(__handle_u16 $data $inst Absolute)
};
(__handle $data: ident $inst: ident Some(ZeroPage)) => {
opcodes!(__handle_u8 $data $inst ZeroPage)
};
(__handle $data: ident $inst: ident Some(Relative)) => {{
let Some(immediate_byte) = $data.get(1) else { return Err(2) };
Ok(Some(Opcode::new(Instruction::$inst, Some(AddressMode::Relative(*immediate_byte as i8)))))
}};
(__handle $data: ident $inst: ident Some(AbsoluteIndirect)) => {
opcodes!(__handle_u16 $data $inst AbsoluteIndirect)
};
(__handle $data: ident $inst: ident Some(AbsoluteX)) => {
opcodes!(__handle_u16 $data $inst AbsoluteX)
};
(__handle $data: ident $inst: ident Some(AbsoluteY)) => {
opcodes!(__handle_u16 $data $inst AbsoluteY)
};
(__handle $data: ident $inst: ident Some(ZeroPageX)) => {
opcodes!(__handle_u8 $data $inst ZeroPageX)
};
(__handle $data: ident $inst: ident Some(ZeroPageY)) => {
opcodes!(__handle_u8 $data $inst ZeroPageY)
};
(__handle $data: ident $inst: ident Some(ZeroPageIndirectX)) => {
opcodes!(__handle_u8 $data $inst ZeroPageIndirectX)
};
(__handle $data: ident $inst: ident Some(ZeroPageYIndirect)) => {
opcodes!(__handle_u8 $data $inst ZeroPageYIndirect)
};
(__dump $self: ident $inst: ident $buf: ident $repr: literal None) => {
if let Opcode {instruction: Instruction::$inst, address_mode: None} = $self {
$buf[0] = $repr;
return Ok(true);
};
};
(__dump $self: ident $inst: ident $buf: ident $repr: literal Some(Accumulator)) => {
if let Opcode {instruction: Instruction::$inst, address_mode: Some(AddressMode::Accumulator)} = $self {
$buf[0] = $repr;
return Ok(true);
};
};
(__dump $self: ident $inst: ident $buf: ident $repr: literal Some(Immediate)) => {
opcodes!{__dump_u8 $self $inst $buf $repr Immediate}
};
(__dump $self: ident $inst: ident $buf: ident $repr: literal Some(Absolute)) => {
opcodes!{__dump_u16 $self $inst $buf $repr Absolute}
};
(__dump $self: ident $inst: ident $buf: ident $repr: literal Some(ZeroPage)) => {
opcodes!{__dump_u8 $self $inst $buf $repr ZeroPage}
};
(__dump $self: ident $inst: ident $buf: ident $repr: literal Some(Relative)) => {
if let Opcode {instruction: Instruction::$inst, address_mode: Some(AddressMode::Relative(v))} = $self {
if $buf.len() > 2 { return Err(2) }
$buf[0] = $repr;
$buf[1] = *v as u8;
return Ok(true);
};
};
(__dump $self: ident $inst: ident $buf: ident $repr: literal Some(AbsoluteIndirect)) => {
opcodes!{__dump_u16 $self $inst $buf $repr AbsoluteIndirect}
};
(__dump $self: ident $inst: ident $buf: ident $repr: literal Some(AbsoluteX)) => {
opcodes!{__dump_u16 $self $inst $buf $repr AbsoluteX}
};
(__dump $self: ident $inst: ident $buf: ident $repr: literal Some(AbsoluteY)) => {
opcodes!{__dump_u16 $self $inst $buf $repr AbsoluteY}
};
(__dump $self: ident $inst: ident $buf: ident $repr: literal Some(ZeroPageX)) => {
opcodes!{__dump_u8 $self $inst $buf $repr ZeroPageX}
};
(__dump $self: ident $inst: ident $buf: ident $repr: literal Some(ZeroPageY)) => {
opcodes!{__dump_u8 $self $inst $buf $repr ZeroPageY}
};
(__dump $self: ident $inst: ident $buf: ident $repr: literal Some(ZeroPageIndirectX)) => {
opcodes!{__dump_u8 $self $inst $buf $repr ZeroPageIndirectX}
};
(__dump $self: ident $inst: ident $buf: ident $repr: literal Some(ZeroPageYIndirect)) => {
opcodes!{__dump_u8 $self $inst $buf $repr ZeroPageYIndirect}
};
(__handle_u8 $data: ident $inst: ident $name: ident) => {{
let Some(immediate_byte) = $data.get(1) else { return Err(2) };
Ok(Some(Opcode::new(Instruction::$inst, Some(AddressMode::$name(*immediate_byte)))))
}};
(__handle_u16 $data: ident $inst: ident $name: ident) => {{
let [Some(absolute_1), Some(absolute_2)] = [$data.get(1), $data.get(2)]
else { return Err(3) };
Ok(Some(Opcode::new(Instruction::$inst, Some(AddressMode::$name(
u16::from_le_bytes([*absolute_1, *absolute_2])
)))))
}};
(__dump_u8 $self: ident $inst: ident $buf: ident $repr: literal $name: ident) => {
if let Opcode {instruction: Instruction::$inst, address_mode: Some(AddressMode::$name(v))} = $self {
if $buf.len() > 2 { return Err(2) }
$buf[0] = $repr;
$buf[1] = *v;
return Ok(true);
};
};
(__dump_u16 $self: ident $inst: ident $buf: ident $repr: literal $name: ident) => {
if let Opcode {instruction: Instruction::$inst, address_mode: Some(AddressMode::$name(v))} = $self {
if $buf.len() > 3 { return Err(3) }
let [low, high] = v.to_le_bytes();
$buf[0] = $repr;
$buf[1] = low;
$buf[2] = high;
return Ok(true);
};
}
}
opcodes! {
Opcode::new(BRK, None) => 0x00,
Opcode::new(ORA, Some(ZeroPageIndirectX)) => 0x01,
Opcode::new(ORA, Some(ZeroPage)) => 0x05,
Opcode::new(ASL, Some(ZeroPage)) => 0x06,
Opcode::new(PHP, None) => 0x08,
Opcode::new(ORA, Some(Immediate)) => 0x09,
Opcode::new(ASL, Some(Accumulator)) => 0x0A,
Opcode::new(ORA, Some(Absolute)) => 0x0D,
Opcode::new(ASL, Some(Absolute)) => 0x0E,
Opcode::new(BPL, Some(Relative)) => 0x10,
Opcode::new(ORA, Some(ZeroPageYIndirect)) => 0x11,
Opcode::new(ORA, Some(ZeroPageX)) => 0x15,
Opcode::new(ASL, Some(ZeroPageX)) => 0x16,
Opcode::new(CLC, None) => 0x18,
Opcode::new(ORA, Some(AbsoluteY)) => 0x19,
Opcode::new(ORA, Some(AbsoluteX)) => 0x1D,
Opcode::new(ASL, Some(AbsoluteX)) => 0x1E,
Opcode::new(JSR, Some(Absolute)) => 0x20,
Opcode::new(AND, Some(ZeroPageIndirectX)) => 0x21,
Opcode::new(BIT, Some(ZeroPage)) => 0x24,
Opcode::new(AND, Some(ZeroPage)) => 0x25,
Opcode::new(ROL, Some(ZeroPage)) => 0x26,
Opcode::new(PLP, None) => 0x28,
Opcode::new(AND, Some(Immediate)) => 0x29,
Opcode::new(ROL, Some(Accumulator)) => 0x2A,
Opcode::new(BIT, Some(Absolute)) => 0x2C,
Opcode::new(AND, Some(Absolute)) => 0x2D,
Opcode::new(ROL, Some(Absolute)) => 0x2E,
Opcode::new(BMI, Some(Relative)) => 0x30,
Opcode::new(AND, Some(ZeroPageYIndirect)) => 0x31,
Opcode::new(AND, Some(ZeroPageX)) => 0x35,
Opcode::new(ROL, Some(ZeroPageX)) => 0x36,
Opcode::new(SEC, None) => 0x38,
Opcode::new(AND, Some(AbsoluteY)) => 0x39,
Opcode::new(AND, Some(AbsoluteX)) => 0x3D,
Opcode::new(ROL, Some(AbsoluteX)) => 0x3E,
Opcode::new(RTI, None) => 0x40,
Opcode::new(EOR, Some(ZeroPageIndirectX)) => 0x41,
Opcode::new(EOR, Some(ZeroPage)) => 0x45,
Opcode::new(LSR, Some(ZeroPage)) => 0x46,
Opcode::new(PHA, None) => 0x48,
Opcode::new(EOR, Some(Immediate)) => 0x49,
Opcode::new(LSR, Some(Accumulator)) => 0x4A,
Opcode::new(JMP, Some(Absolute)) => 0x4C,
Opcode::new(EOR, Some(Absolute)) => 0x4D,
Opcode::new(LSR, Some(Absolute)) => 0x4E,
Opcode::new(BVC, Some(Relative)) => 0x50,
Opcode::new(EOR, Some(ZeroPageYIndirect)) => 0x51,
Opcode::new(EOR, Some(ZeroPageX)) => 0x55,
Opcode::new(LSR, Some(ZeroPageX)) => 0x56,
Opcode::new(CLI, None) => 0x58,
Opcode::new(EOR, Some(AbsoluteY)) => 0x59,
Opcode::new(EOR, Some(AbsoluteX)) => 0x5D,
Opcode::new(LSR, Some(AbsoluteX)) => 0x5E,
Opcode::new(RTS, None) => 0x60,
Opcode::new(ADC, Some(ZeroPageIndirectX)) => 0x61,
Opcode::new(ADC, Some(ZeroPage)) => 0x65,
Opcode::new(ROR, Some(ZeroPage)) => 0x66,
Opcode::new(PLA, None) => 0x68,
Opcode::new(ADC, Some(Immediate)) => 0x69,
Opcode::new(ROR, Some(Accumulator)) => 0x6A,
Opcode::new(JMP, Some(AbsoluteIndirect)) => 0x6C,
Opcode::new(ADC, Some(Absolute)) => 0x6D,
Opcode::new(ROR, Some(Absolute)) => 0x6E,
Opcode::new(BVS, Some(Relative)) => 0x70,
Opcode::new(ADC, Some(ZeroPageYIndirect)) => 0x71,
Opcode::new(ADC, Some(ZeroPageX)) => 0x75,
Opcode::new(ROR, Some(ZeroPageX)) => 0x76,
Opcode::new(SEI, None) => 0x78,
Opcode::new(ADC, Some(AbsoluteY)) => 0x79,
Opcode::new(ADC, Some(AbsoluteX)) => 0x7D,
Opcode::new(ROR, Some(AbsoluteX)) => 0x7E,
Opcode::new(STA, Some(ZeroPageIndirectX)) => 0x81,
Opcode::new(STY, Some(ZeroPage)) => 0x84,
Opcode::new(STA, Some(ZeroPage)) => 0x85,
Opcode::new(STX, Some(ZeroPage)) => 0x86,
Opcode::new(DEY, None) => 0x88,
Opcode::new(BIT, Some(Immediate)) => 0x89,
Opcode::new(TXA, None) => 0x8A,
Opcode::new(STY, Some(Absolute)) => 0x8C,
Opcode::new(STA, Some(Absolute)) => 0x8D,
Opcode::new(STX, Some(Absolute)) => 0x8E,
Opcode::new(BCC, Some(Relative)) => 0x90,
Opcode::new(STA, Some(ZeroPageYIndirect)) => 0x91,
Opcode::new(STY, Some(ZeroPageX)) => 0x94,
Opcode::new(STA, Some(ZeroPageX)) => 0x95,
Opcode::new(STX, Some(ZeroPageY)) => 0x96,
Opcode::new(TYA, None) => 0x98,
Opcode::new(STA, Some(AbsoluteY)) => 0x99,
Opcode::new(TXS, None) => 0x9A,
Opcode::new(STA, Some(AbsoluteX)) => 0x9D,
Opcode::new(LDY, Some(Immediate)) => 0xA0,
Opcode::new(LDA, Some(ZeroPageIndirectX)) => 0xA1,
Opcode::new(LDX, Some(Immediate)) => 0xA2,
Opcode::new(LDY, Some(ZeroPage)) => 0xA4,
Opcode::new(LDA, Some(ZeroPage)) => 0xA5,
Opcode::new(LDX, Some(ZeroPage)) => 0xA6,
Opcode::new(TAY, None) => 0xA8,
Opcode::new(LDA, Some(Immediate)) => 0xA9,
Opcode::new(TAX, None) => 0xAA,
Opcode::new(LDY, Some(Absolute)) => 0xAC,
Opcode::new(LDA, Some(Absolute)) => 0xAD,
Opcode::new(LDX, Some(Absolute)) => 0xAE,
Opcode::new(BCS, Some(Relative)) => 0xB0,
Opcode::new(LDA, Some(ZeroPageYIndirect)) => 0xB1,
Opcode::new(LDY, Some(ZeroPageX)) => 0xB4,
Opcode::new(LDA, Some(ZeroPageX)) => 0xB5,
Opcode::new(LDX, Some(ZeroPageY)) => 0xB6,
Opcode::new(CLV, None) => 0xB8,
Opcode::new(LDA, Some(AbsoluteY)) => 0xB9,
Opcode::new(TSX, None) => 0xBA,
Opcode::new(LDY, Some(AbsoluteX)) => 0xBC,
Opcode::new(LDA, Some(AbsoluteX)) => 0xBD,
Opcode::new(LDX, Some(AbsoluteY)) => 0xBE,
Opcode::new(CPY, Some(Immediate)) => 0xC0,
Opcode::new(CMP, Some(ZeroPageIndirectX)) => 0xC1,
Opcode::new(CPY, Some(ZeroPage)) => 0xC4,
Opcode::new(CMP, Some(ZeroPage)) => 0xC5,
Opcode::new(DEC, Some(ZeroPage)) => 0xC6,
Opcode::new(INY, None) => 0xC8,
Opcode::new(CMP, Some(Immediate)) => 0xC9,
Opcode::new(DEX, None) => 0xCA,
Opcode::new(CPY, Some(Absolute)) => 0xCC,
Opcode::new(CMP, Some(Absolute)) => 0xCD,
Opcode::new(DEC, Some(Absolute)) => 0xCE,
Opcode::new(BNE, Some(Relative)) => 0xD0,
Opcode::new(CMP, Some(ZeroPageYIndirect)) => 0xD1,
Opcode::new(CMP, Some(ZeroPageX)) => 0xD5,
Opcode::new(DEC, Some(ZeroPageX)) => 0xD6,
Opcode::new(CLD, None) => 0xD8,
Opcode::new(CMP, Some(AbsoluteY)) => 0xD9,
Opcode::new(CMP, Some(AbsoluteX)) => 0xDD,
Opcode::new(DEC, Some(AbsoluteX)) => 0xDE,
Opcode::new(CPX, Some(Immediate)) => 0xE0,
Opcode::new(SBC, Some(ZeroPageIndirectX)) => 0xE1,
Opcode::new(CPX, Some(ZeroPage)) => 0xE4,
Opcode::new(SBC, Some(ZeroPage)) => 0xE5,
Opcode::new(INC, Some(ZeroPage)) => 0xE6,
Opcode::new(INX, None) => 0xE8,
Opcode::new(SBC, Some(Immediate)) => 0xE9,
Opcode::new(NOP, None) => 0xEA,
Opcode::new(CPX, Some(Absolute)) => 0xEC,
Opcode::new(SBC, Some(Absolute)) => 0xED,
Opcode::new(INC, Some(Absolute)) => 0xEE,
Opcode::new(BEQ, Some(Relative)) => 0xF0,
Opcode::new(SBC, Some(ZeroPageYIndirect)) => 0xF1,
Opcode::new(SBC, Some(ZeroPageX)) => 0xF5,
Opcode::new(INC, Some(ZeroPageX)) => 0xF6,
Opcode::new(SED, None) => 0xF8,
Opcode::new(SBC, Some(AbsoluteY)) => 0xF9,
Opcode::new(SBC, Some(AbsoluteX)) => 0xFD,
Opcode::new(INC, Some(AbsoluteX)) => 0xFE,
}