summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore2
-rw-r--r--CHANGELOG2
-rw-r--r--Cargo.toml24
-rw-r--r--README.md22
-rw-r--r--src/lib.rs804
-rw-r--r--test/test.rs137
6 files changed, 991 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4fffb2f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+/target
+/Cargo.lock
diff --git a/CHANGELOG b/CHANGELOG
new file mode 100644
index 0000000..d63ba77
--- /dev/null
+++ b/CHANGELOG
@@ -0,0 +1,2 @@
+## 0.1.0
+* wrote a decoder
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..5b12b52
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+
+name = "yaxpeax-sm83"
+version = "0.1.0"
+authors = [ "iximeow <me@iximeow.net>" ]
+license = "0BSD"
+repository = "http://git.iximeow.net/yaxpeax-sm83/"
+description = "sm83 decoder for the yaxpeax project"
+keywords = ["diassembler", "gameboy", "gbc", "sm83", "lr35902"]
+
+[dependencies]
+yaxpeax-arch = { version = "0.0.4", default-features = false, features = [] }
+"serde" = { version = "1.0", optional = true }
+"serde_derive" = { version = "1.0", optional = true }
+"num_enum" = { version = "0.2", default-features = false }
+
+[[test]]
+name = "test"
+path = "test/test.rs"
+
+[features]
+default = []
+
+use-serde = []
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a696fd2
--- /dev/null
+++ b/README.md
@@ -0,0 +1,22 @@
+## yaxpeax-sm83
+
+decoder for the Sharp SM83 cpu core, which was famously used in the Nintendo
+Game Boy and Game Boy Color.
+
+some documentation refers to the processor in those devices as the `Sharp
+LR35902` - this is partially correct: the SoC powering the Game Boy and Game
+Boy Color is branded `LR35902`, but the cpu contained therein appears to be
+very much like an `SM83` core. gekkio has done significantly more Game Boy
+reverse engineering than i plan to do in my life, and has a more compelling
+argument with citations in [this nesdev
+post](https://forums.nesdev.com/viewtopic.php?f=20&t=18335)
+
+this decoder is heavily derived from the opcode tables at
+[pastraiser](https://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html) and in [gekkio's technical reference](https://gekkio.fi/files/gb-docs/gbctr.pdf)
+
+## stability
+the sm83 microcomputer, being over two decades old, is not changing much. the initial release of `yaxpeax-sm83` will likely be 0.1. a 1.0 release has a short but important worklist:
+
+### 1.0 checklist
+- [ ] compare the opcode table from pastraiser with gekkio's documentation. if there are disagreements, figure out what is correct and add appropriate tests
+- [ ] confirm acceptable disassembly of real sm83 programs
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..6c0f79c
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,804 @@
+#[cfg(feature="use-serde")]
+#[macro_use] extern crate serde_derive;
+#[cfg(feature="use-serde")]
+extern crate serde;
+
+extern crate yaxpeax_arch;
+
+extern crate core;
+
+use core::fmt;
+
+use yaxpeax_arch::AddressDiff;
+use yaxpeax_arch::Arch;
+use yaxpeax_arch::Decoder;
+use yaxpeax_arch::LengthedInstruction;
+
+#[cfg(feature="use-serde")]
+#[derive(Debug, Serialize, Deserialize)]
+pub struct SM83;
+
+#[cfg(not(feature="use-serde"))]
+#[derive(Debug)]
+pub struct SM83;
+
+impl Arch for SM83 {
+ type Address = u16;
+ type Instruction = Instruction;
+ type DecodeError = DecodeError;
+ type Decoder = InstDecoder;
+ type Operand = Operand;
+}
+
+#[derive(Debug, Copy, Clone)]
+pub struct Instruction {
+ opcode: Opcode,
+ operands: [Operand; 2],
+ length: u8,
+}
+
+impl Instruction {
+ pub fn opcode(&self) -> Opcode {
+ self.opcode
+ }
+
+ pub fn operands(&self) -> &[Operand; 2] {
+ &self.operands
+ }
+
+ pub fn length(&self) -> u8 {
+ self.length
+ }
+}
+
+impl Default for Instruction {
+ fn default() -> Instruction {
+ Instruction {
+ opcode: Opcode::NOP,
+ operands: [Operand::Nothing, Operand::Nothing],
+ length: 1,
+ }
+ }
+}
+
+impl fmt::Display for Instruction {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(f, "{}", self.opcode())?;
+ let ops = self.operands();
+ if ops[0] != Operand::Nothing {
+ write!(f, " {}", ops[0])?;
+ }
+ if ops[1] != Operand::Nothing {
+ write!(f, ", {}", ops[1])?;
+ }
+ Ok(())
+ }
+}
+
+impl LengthedInstruction for Instruction {
+ type Unit = AddressDiff<<SM83 as Arch>::Address>;
+ fn min_size() -> Self::Unit {
+ AddressDiff::from_const(1)
+ }
+ fn len(&self) -> Self::Unit {
+ AddressDiff::from_const(self.length as u16)
+ }
+}
+
+#[derive(Debug, PartialEq)]
+pub enum DecodeError {
+ ExhaustedInput,
+ InvalidOpcode,
+ Incomplete,
+}
+
+impl yaxpeax_arch::DecodeError for DecodeError {
+ fn data_exhausted(&self) -> bool { self == &DecodeError::ExhaustedInput }
+ fn bad_opcode(&self) -> bool { self == &DecodeError::InvalidOpcode }
+ fn bad_operand(&self) -> bool { false }
+}
+
+impl fmt::Display for DecodeError {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ DecodeError::ExhaustedInput => { write!(f, "exhausted input") }
+ DecodeError::InvalidOpcode => { write!(f, "invalid opcode") }
+ DecodeError::Incomplete => { write!(f, "incomplete") }
+ }
+ }
+}
+
+impl yaxpeax_arch::Instruction for Instruction {
+ // only decode well-formed instructions (for now???)
+ fn well_defined(&self) -> bool { true }
+}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum Operand {
+ A,
+ B,
+ C,
+ D,
+ E,
+ H,
+ L,
+ AF,
+ BC,
+ DE,
+ HL,
+ SP,
+ DerefHL,
+ DerefBC,
+ DerefDE,
+ DerefDecHL,
+ DerefIncHL,
+ DerefHighC,
+ DerefHighD8(u8),
+ SPWithOffset(i8),
+ Bit(u8),
+ D8(u8),
+ I8(i8),
+ R8(i8),
+ A16(u16),
+ D16(u16),
+
+ CondC,
+ CondNC,
+ CondZ,
+ CondNZ,
+
+ Nothing,
+}
+
+impl fmt::Display for Operand {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Operand::A => write!(f, "a"),
+ Operand::B => write!(f, "b"),
+ Operand::C => write!(f, "c"),
+ Operand::D => write!(f, "d"),
+ Operand::E => write!(f, "e"),
+ Operand::H => write!(f, "h"),
+ Operand::L => write!(f, "l"),
+ Operand::AF => write!(f, "af"),
+ Operand::BC => write!(f, "bc"),
+ Operand::DE => write!(f, "de"),
+ Operand::HL => write!(f, "hl"),
+ Operand::SP => write!(f, "sp"),
+ Operand::DerefHL => write!(f, "[hl]"),
+ Operand::DerefBC => write!(f, "[bc]"),
+ Operand::DerefDE => write!(f, "[de]"),
+ Operand::DerefDecHL => write!(f, "[hl-]"),
+ Operand::DerefIncHL => write!(f, "[hl+]"),
+ Operand::DerefHighC => write!(f, "[0xff00 + c]"),
+ Operand::DerefHighD8(imm) => write!(f, "[$ff00 + ${:02x}]", imm),
+ Operand::SPWithOffset(imm) => {
+ if *imm == -128 {
+ write!(f, "[sp - $80]")
+ } else if *imm >= 0 {
+ write!(f, "[sp + ${:02x}]", imm)
+ } else {
+ write!(f, "[sp - ${:02x}]", -imm)
+ }
+ }
+ Operand::Bit(imm) => write!(f, "{}", imm),
+ Operand::D8(imm) => write!(f, "${:02x}", imm),
+ Operand::D16(imm) => write!(f, "${:04x}", imm),
+ Operand::I8(imm) => {
+ if *imm == -128 {
+ write!(f, "-0x80")
+ } else if *imm >= 0 {
+ write!(f, "${:02x}", imm)
+ } else {
+ write!(f, "-${:02x}", -imm)
+ }
+ }
+ Operand::R8(imm) => {
+ if *imm == -128 {
+ write!(f, "$-0x80")
+ } else if *imm >= 0 {
+ write!(f, "$+${:02x}", imm)
+ } else {
+ write!(f, "$-${:02x}", -imm)
+ }
+ }
+ Operand::A16(addr) => write!(f, "[${:4x}]", addr),
+
+ Operand::CondC => write!(f, "C"),
+ Operand::CondNC => write!(f, "nC"),
+ Operand::CondZ => write!(f, "Z"),
+ Operand::CondNZ => write!(f, "nZ"),
+
+ Operand::Nothing => write!(f, "nothing (BUG: should not be shown)"),
+ }
+ }
+}
+
+#[derive(Debug, Copy, Clone)]
+enum OperandSpec {
+ A,
+ B,
+ C,
+ D,
+ E,
+ H,
+ L,
+ AF,
+ BC,
+ DE,
+ HL,
+ SP,
+ DerefHL,
+ DerefBC,
+ DerefDE,
+ DerefDecHL,
+ DerefIncHL,
+ DerefHighC,
+ DerefHighD8,
+ SPWithOffset,
+ D8,
+ R8,
+ I8,
+ A16,
+ D16,
+
+ CondC,
+ CondNC,
+ CondZ,
+ CondNZ,
+
+ Bit(u8),
+ Imm(u8),
+
+ Nothing,
+}
+
+#[derive(Debug, Copy, Clone)]
+pub enum Opcode {
+ NOP,
+
+ LD,
+
+ DEC,
+ INC,
+
+ ADC,
+ ADD,
+ SBC,
+ SUB,
+
+ AND,
+ XOR,
+ OR,
+ CP,
+
+ POP,
+ PUSH,
+
+ JP,
+ JR,
+ CALL,
+ RET,
+ RETI,
+ HALT,
+ RST,
+ STOP,
+
+ RLCA,
+ RRCA,
+ RLA,
+ RRA,
+
+ DAA,
+ CPL,
+ SCF,
+ CCF,
+
+ DI,
+ EI,
+
+ LDH,
+
+ RLC,
+ RRC,
+ RL,
+ RR,
+ SLA,
+ SRA,
+ SWAP,
+ SRL,
+ BIT,
+ RES,
+ SET,
+}
+
+impl fmt::Display for Opcode {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ match self {
+ Opcode::NOP => write!(f, "nop"),
+
+ Opcode::LD => write!(f, "ld"),
+
+ Opcode::DEC => write!(f, "dec"),
+ Opcode::INC => write!(f, "inc"),
+
+ Opcode::ADC => write!(f, "adc"),
+ Opcode::ADD => write!(f, "add"),
+ Opcode::SBC => write!(f, "sbc"),
+ Opcode::SUB => write!(f, "sub"),
+
+ Opcode::AND => write!(f, "and"),
+ Opcode::XOR => write!(f, "xor"),
+ Opcode::OR => write!(f, "or"),
+ Opcode::CP => write!(f, "cp"),
+
+ Opcode::POP => write!(f, "pop"),
+ Opcode::PUSH => write!(f, "push"),
+
+ Opcode::JP => write!(f, "jp"),
+ Opcode::JR => write!(f, "jr"),
+ Opcode::CALL => write!(f, "call"),
+ Opcode::RET => write!(f, "ret"),
+ Opcode::RETI => write!(f, "reti"),
+ Opcode::HALT => write!(f, "halt"),
+ Opcode::RST => write!(f, "rst"),
+ Opcode::STOP => write!(f, "stop"),
+
+ Opcode::RLCA => write!(f, "rlca"),
+ Opcode::RRCA => write!(f, "rrca"),
+ Opcode::RLA => write!(f, "rla"),
+ Opcode::RRA => write!(f, "rra"),
+
+ Opcode::DAA => write!(f, "daa"),
+ Opcode::CPL => write!(f, "cpl"),
+ Opcode::SCF => write!(f, "scf"),
+ Opcode::CCF => write!(f, "ccf"),
+
+ Opcode::DI => write!(f, "di"),
+ Opcode::EI => write!(f, "ei"),
+
+ Opcode::LDH => write!(f, "ldh"),
+
+ Opcode::RLC => write!(f, "rlc"),
+ Opcode::RRC => write!(f, "rrc"),
+ Opcode::RL => write!(f, "rl"),
+ Opcode::RR => write!(f, "rr"),
+ Opcode::SLA => write!(f, "sla"),
+ Opcode::SRA => write!(f, "sra"),
+ Opcode::SWAP => write!(f, "swap"),
+ Opcode::SRL => write!(f, "srl"),
+ Opcode::BIT => write!(f, "bit"),
+ Opcode::RES => write!(f, "res"),
+ Opcode::SET => write!(f, "set"),
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct InstDecoder { }
+
+impl Default for InstDecoder {
+ fn default() -> Self {
+ InstDecoder { }
+ }
+}
+
+// main operand map is used for ld, arithmetic, and $CB-prefixed instructions
+const OPMAP: [OperandSpec; 8] = [
+ OperandSpec::B,
+ OperandSpec::C,
+ OperandSpec::D,
+ OperandSpec::E,
+ OperandSpec::H,
+ OperandSpec::L,
+ OperandSpec::DerefHL,
+ OperandSpec::A,
+];
+
+const UPPER_INSTRUCTIONS: [(Option<Opcode>, [OperandSpec; 2]); 64] = [
+ // 0xc0
+ (Some(Opcode::RET), [OperandSpec::CondNZ, OperandSpec::Nothing]),
+ (Some(Opcode::POP), [OperandSpec::BC, OperandSpec::Nothing]),
+ (Some(Opcode::JP), [OperandSpec::CondNZ, OperandSpec::D16]),
+ (Some(Opcode::JP), [OperandSpec::D16, OperandSpec::Nothing]),
+ (Some(Opcode::CALL), [OperandSpec::CondNZ, OperandSpec::D16]),
+ (Some(Opcode::PUSH), [OperandSpec::BC, OperandSpec::Nothing]),
+ (Some(Opcode::ADD), [OperandSpec::D8, OperandSpec::Nothing]),
+ (Some(Opcode::RST), [OperandSpec::Imm(0x00), OperandSpec::Nothing]),
+ // 0xc8
+ (Some(Opcode::RET), [OperandSpec::CondZ, OperandSpec::Nothing]),
+ (Some(Opcode::RET), [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (Some(Opcode::JP), [OperandSpec::CondNC, OperandSpec::D16]),
+ (None, [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (Some(Opcode::CALL), [OperandSpec::CondZ, OperandSpec::D16]),
+ (Some(Opcode::CALL), [OperandSpec::D16, OperandSpec::Nothing]),
+ (Some(Opcode::ADC), [OperandSpec::D8, OperandSpec::Nothing]),
+ (Some(Opcode::RST), [OperandSpec::Imm(0x08), OperandSpec::Nothing]),
+ // 0xd0
+ (Some(Opcode::RET), [OperandSpec::CondNC, OperandSpec::Nothing]),
+ (Some(Opcode::POP), [OperandSpec::DE, OperandSpec::Nothing]),
+ (Some(Opcode::JP), [OperandSpec::CondNC, OperandSpec::D16]),
+ (None, [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (Some(Opcode::CALL), [OperandSpec::CondNC, OperandSpec::D16]),
+ (Some(Opcode::PUSH), [OperandSpec::DE, OperandSpec::Nothing]),
+ (Some(Opcode::SUB), [OperandSpec::D8, OperandSpec::Nothing]),
+ (Some(Opcode::RST), [OperandSpec::Imm(0x10), OperandSpec::Nothing]),
+ // 0xd8
+ (Some(Opcode::RET), [OperandSpec::CondC, OperandSpec::Nothing]),
+ (Some(Opcode::RETI), [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (Some(Opcode::JP), [OperandSpec::CondC, OperandSpec::D16]),
+ (None, [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (Some(Opcode::CALL), [OperandSpec::CondC, OperandSpec::D16]),
+ (None, [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (Some(Opcode::SBC), [OperandSpec::D8, OperandSpec::Nothing]),
+ (Some(Opcode::RST), [OperandSpec::Imm(0x18), OperandSpec::Nothing]),
+ // 0xe0
+ (Some(Opcode::LDH), [OperandSpec::DerefHighD8, OperandSpec::A]),
+ (Some(Opcode::POP), [OperandSpec::HL, OperandSpec::Nothing]),
+ (Some(Opcode::LDH), [OperandSpec::DerefHighC, OperandSpec::A]),
+ (None, [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (None, [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (Some(Opcode::PUSH), [OperandSpec::HL, OperandSpec::Nothing]),
+ (Some(Opcode::AND), [OperandSpec::D8, OperandSpec::Nothing]),
+ (Some(Opcode::RST), [OperandSpec::Imm(0x20), OperandSpec::Nothing]),
+ // 0xe8
+ (Some(Opcode::ADD), [OperandSpec::SP, OperandSpec::I8]),
+ (Some(Opcode::JP), [OperandSpec::HL, OperandSpec::Nothing]),
+ (Some(Opcode::LD), [OperandSpec::A16, OperandSpec::A]),
+ (None, [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (None, [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (None, [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (Some(Opcode::XOR), [OperandSpec::D8, OperandSpec::Nothing]),
+ (Some(Opcode::RST), [OperandSpec::Imm(0x28), OperandSpec::Nothing]),
+ // 0xf0
+ (Some(Opcode::LDH), [OperandSpec::A, OperandSpec::DerefHighD8]),
+ (Some(Opcode::POP), [OperandSpec::AF, OperandSpec::Nothing]),
+ (Some(Opcode::LDH), [OperandSpec::A, OperandSpec::DerefHighC]),
+ (Some(Opcode::DI), [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (None, [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (Some(Opcode::PUSH), [OperandSpec::AF, OperandSpec::Nothing]),
+ (Some(Opcode::OR), [OperandSpec::D8, OperandSpec::Nothing]),
+ (Some(Opcode::RST), [OperandSpec::Imm(0x30), OperandSpec::Nothing]),
+ // 0xf8
+ (Some(Opcode::LD), [OperandSpec::HL, OperandSpec::SPWithOffset]),
+ (Some(Opcode::LD), [OperandSpec::SP, OperandSpec::HL]),
+ (Some(Opcode::LD), [OperandSpec::A, OperandSpec::A16]),
+ (Some(Opcode::EI), [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (None, [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (None, [OperandSpec::Nothing, OperandSpec::Nothing]),
+ (Some(Opcode::CP), [OperandSpec::D8, OperandSpec::Nothing]),
+ (Some(Opcode::RST), [OperandSpec::Imm(0x38), OperandSpec::Nothing]),
+];
+
+impl Decoder<Instruction> for InstDecoder {
+ type Error = DecodeError;
+
+ fn decode_into<T: IntoIterator<Item=u8>>(&self, inst: &mut Instruction, bytes: T) -> Result<(), Self::Error> {
+ let mut iter = bytes.into_iter();
+
+ let opc: u8 = iter.next().ok_or(DecodeError::ExhaustedInput)?;
+ inst.length = 1;
+
+ let high = ((opc >> 3) & 0b111) as usize;
+ let low = (opc & 0b111) as usize;
+
+ // early part of the table is more varied, more special cases
+ if opc < 0x40 {
+ let (opcode, operands) = match low {
+ 0 => {
+ // special case on high
+ match high {
+ 0 => { (Opcode::NOP, [OperandSpec::Nothing, OperandSpec::Nothing]) },
+ 1 => { (Opcode::LD, [OperandSpec::A16, OperandSpec::SP]) },
+ 2 => { (Opcode::STOP, [OperandSpec::Nothing, OperandSpec::Nothing]) },
+ 3 => { (Opcode::JR, [OperandSpec::R8, OperandSpec::Nothing]) },
+ 4 => { (Opcode::JR, [OperandSpec::CondNZ, OperandSpec::R8]) },
+ 5 => { (Opcode::JR, [OperandSpec::CondZ, OperandSpec::R8]) },
+ 6 => { (Opcode::JR, [OperandSpec::CondNC, OperandSpec::R8]) },
+ 7 => { (Opcode::JR, [OperandSpec::CondC, OperandSpec::R8]) },
+ _ => { unreachable!("impossible bit pattern"); }
+ }
+ }
+ 1 => {
+ // also special case, but fewer
+ if high & 1 == 0 {
+ let opcode = Opcode::LD;
+ let ops = [[
+ OperandSpec::BC,
+ OperandSpec::DE,
+ OperandSpec::HL,
+ OperandSpec::SP,
+ ][high as usize >> 1], OperandSpec::D16];
+ (opcode, ops)
+ } else {
+ let opcode = Opcode::ADD;
+ let ops = [OperandSpec::HL, [
+ OperandSpec::BC,
+ OperandSpec::DE,
+ OperandSpec::HL,
+ OperandSpec::SP,
+ ][high as usize >> 1]];
+ (opcode, ops)
+ }
+ }
+ 2 => {
+ let op = Opcode::LD;
+ if high & 1 == 0 {
+ let op0 = [
+ OperandSpec::DerefBC,
+ OperandSpec::DerefDE,
+ OperandSpec::DerefIncHL,
+ OperandSpec::DerefDecHL,
+ ][high as usize >> 1];
+ (op, [op0, OperandSpec::A])
+ } else {
+ let op1 = [
+ OperandSpec::DerefBC,
+ OperandSpec::DerefDE,
+ OperandSpec::DerefIncHL,
+ OperandSpec::DerefDecHL,
+ ][high as usize >> 1];
+ (op, [OperandSpec::A, op1])
+ }
+ }
+ 3 => {
+ if high & 1 == 0 {
+ let op0 = [
+ OperandSpec::BC,
+ OperandSpec::DE,
+ OperandSpec::HL,
+ OperandSpec::SP,
+ ][high as usize >> 1];
+ (Opcode::INC, [op0, OperandSpec::Nothing])
+ } else {
+ let op0 = [
+ OperandSpec::BC,
+ OperandSpec::DE,
+ OperandSpec::HL,
+ OperandSpec::SP,
+ ][high as usize >> 1];
+ (Opcode::DEC, [op0, OperandSpec::Nothing])
+ }
+ }
+ 4 => {
+ let op = Opcode::INC;
+ let op0 = [
+ OperandSpec::B,
+ OperandSpec::C,
+ OperandSpec::D,
+ OperandSpec::E,
+ OperandSpec::H,
+ OperandSpec::L,
+ OperandSpec::DerefHL,
+ OperandSpec::A,
+ ][high as usize];
+ (op, [op0, OperandSpec::Nothing])
+ }
+ 5 => {
+ let op = Opcode::DEC;
+ let op0 = [
+ OperandSpec::B,
+ OperandSpec::C,
+ OperandSpec::D,
+ OperandSpec::E,
+ OperandSpec::H,
+ OperandSpec::L,
+ OperandSpec::DerefHL,
+ OperandSpec::A,
+ ][high as usize];
+ (op, [op0, OperandSpec::Nothing])
+ }
+ 6 => {
+ let op = Opcode::LD;
+ let op0 = [
+ OperandSpec::B,
+ OperandSpec::C,
+ OperandSpec::D,
+ OperandSpec::E,
+ OperandSpec::H,
+ OperandSpec::L,
+ OperandSpec::DerefHL,
+ OperandSpec::A,
+ ][high as usize];
+ (op, [op0, OperandSpec::D8])
+ }
+ 7 => {
+ // special cases here too
+ let op = [
+ Opcode::RLCA,
+ Opcode::RRCA,
+ Opcode::RLA,
+ Opcode::RRA,
+ Opcode::DAA,
+ Opcode::CPL,
+ Opcode::SCF,
+ Opcode::CCF,
+ ][high as usize];
+ (op, [OperandSpec::Nothing, OperandSpec::Nothing])
+ }
+ _ => {
+ unreachable!("impossible bit pattern");
+ }
+ };
+ inst.opcode = opcode;
+ interpret_operands(iter, inst, operands)?;
+ return Ok(());
+ } else if opc < 0x80 {
+ if opc == 0x76 {
+ inst.opcode = Opcode::HALT;
+ inst.operands = [Operand::Nothing, Operand::Nothing];
+ return Ok(());
+ } else {
+ inst.opcode = Opcode::LD;
+ interpret_operands(iter, inst, [OPMAP[high], OPMAP[low]])?;
+ return Ok(());
+ }
+ } else if opc < 0xc0 {
+ const OPCODES: [Opcode; 8] = [
+ Opcode::ADD,
+ Opcode::ADC,
+ Opcode::SUB,
+ Opcode::SBC,
+ Opcode::AND,
+ Opcode::XOR,
+ Opcode::OR,
+ Opcode::CP,
+ ];
+ inst.opcode = OPCODES[high];
+ let operands = [OPMAP[low], OperandSpec::Nothing];
+ interpret_operands(iter, inst, operands)?;
+ return Ok(());
+ } else {
+ if opc == 0xcb {
+ // sm83 special CB-prefixed instructions
+ let opc: u8 = iter.next().ok_or(DecodeError::ExhaustedInput)?;
+ inst.length += 1;
+ if opc < 0x40 {
+ let high = (opc >> 3) & 0b111;
+ let low = opc & 0b111;
+ const LOW_OP: [OperandSpec; 8] = [
+ OperandSpec::B,
+ OperandSpec::C,
+ OperandSpec::D,
+ OperandSpec::E,
+ OperandSpec::H,
+ OperandSpec::L,
+ OperandSpec::DerefHL,
+ OperandSpec::A,
+ ];
+ let operands = [LOW_OP[low as usize], OperandSpec::Nothing];
+ const OPCODES: [Opcode; 8] = [
+ Opcode::RLC,
+ Opcode::RRC,
+ Opcode::RL,
+ Opcode::RR,
+ Opcode::SLA,
+ Opcode::SRA,
+ Opcode::SWAP,
+ Opcode::SRL,
+ ];
+ inst.opcode = OPCODES[high as usize];
+ interpret_operands(iter, inst, operands)?;
+ return Ok(());
+ } else {
+ let bit = (opc >> 3) & 0b111;
+ let low = opc & 0b111;
+ const LOW_OP: [OperandSpec; 8] = [
+ OperandSpec::B,
+ OperandSpec::C,
+ OperandSpec::D,
+ OperandSpec::E,
+ OperandSpec::H,
+ OperandSpec::L,
+ OperandSpec::DerefHL,
+ OperandSpec::A,
+ ];
+ let operands = [OperandSpec::Bit(bit), LOW_OP[low as usize]];
+ const OPCODES: [Opcode; 3] = [
+ Opcode::BIT,
+ Opcode::RES,
+ Opcode::SET,
+ ];
+ inst.opcode = OPCODES[(opc >> 6) as usize - 1];
+ interpret_operands(iter, inst, operands)?;
+ return Ok(());
+ }
+ } else {
+ // there is no special thing we can do here. do a table lookup.
+ let (maybe_opcode, operands) = UPPER_INSTRUCTIONS[opc as usize - 0xc0];
+ if let Some(opcode) = maybe_opcode {
+ inst.opcode = opcode;
+ interpret_operands(iter, inst, operands)?;
+ return Ok(());
+ } else {
+ return Err(DecodeError::InvalidOpcode);
+ }
+ }
+ }
+ }
+}
+
+fn interpret_operands<I: Iterator<Item=u8>>(mut iter: I, inst: &mut Instruction, operands: [OperandSpec; 2]) -> Result<(), DecodeError> {
+ inst.operands[0] = interpret_operand(&mut iter, inst, operands[0])?;
+ inst.operands[1] = interpret_operand(&mut iter, inst, operands[1])?;
+ Ok(())
+}
+
+fn interpret_operand<I: Iterator<Item=u8>>(iter: &mut I, inst: &mut Instruction, operand: OperandSpec) -> Result<Operand, DecodeError> {
+ let operand = match operand {
+ OperandSpec::A => Operand::A,
+ OperandSpec::B => Operand::B,
+ OperandSpec::C => Operand::C,
+ OperandSpec::D => Operand::D,
+ OperandSpec::E => Operand::E,
+ OperandSpec::H => Operand::H,
+ OperandSpec::L => Operand::L,
+ OperandSpec::AF => Operand::AF,
+ OperandSpec::BC => Operand::BC,
+ OperandSpec::DE => Operand::DE,
+ OperandSpec::HL => Operand::HL,
+ OperandSpec::SP => Operand::SP,
+ OperandSpec::DerefHL => Operand::DerefHL,
+ OperandSpec::DerefBC => Operand::DerefBC,
+ OperandSpec::DerefDE => Operand::DerefDE,
+ OperandSpec::DerefDecHL => Operand::DerefDecHL,
+ OperandSpec::DerefIncHL => Operand::DerefIncHL,
+ OperandSpec::D8 => {
+ let imm = iter.next().ok_or(DecodeError::ExhaustedInput)?;
+ inst.length += 1;
+ Operand::D8(imm)
+ }
+ OperandSpec::DerefHighC => {
+ Operand::DerefHighC
+ }
+ OperandSpec::DerefHighD8 => {
+ let imm = iter.next().ok_or(DecodeError::ExhaustedInput)?;
+ inst.length += 1;
+ Operand::DerefHighD8(imm)
+ }
+ OperandSpec::R8 => {
+ let imm = iter.next().ok_or(DecodeError::ExhaustedInput)?;
+ inst.length += 1;
+ Operand::R8(imm as i8)
+ }
+ OperandSpec::I8 => {
+ let imm = iter.next().ok_or(DecodeError::ExhaustedInput)?;
+ inst.length += 1;
+ Operand::I8(imm as i8)
+ }
+ OperandSpec::A16 => {
+ let imm =
+ (iter.next().ok_or(DecodeError::ExhaustedInput)? as u16) |
+ ((iter.next().ok_or(DecodeError::ExhaustedInput)? as u16) << 8);
+ inst.length += 2;
+ Operand::A16(imm as u16)
+ }
+ OperandSpec::D16 => {
+ let imm =
+ (iter.next().ok_or(DecodeError::ExhaustedInput)? as u16) |
+ ((iter.next().ok_or(DecodeError::ExhaustedInput)? as u16) << 8);
+ inst.length += 2;
+ Operand::D16(imm as u16)
+ }
+ OperandSpec::SPWithOffset => {
+ let imm = iter.next().ok_or(DecodeError::ExhaustedInput)?;
+ inst.length += 1;
+ Operand::SPWithOffset(imm as i8)
+ }
+
+ OperandSpec::Imm(u) => {
+ Operand::D8(u)
+ }
+ OperandSpec::Bit(u) => {
+ Operand::Bit(u)
+ }
+
+ OperandSpec::CondC => Operand::CondC,
+ OperandSpec::CondNC => Operand::CondNC,
+ OperandSpec::CondZ => Operand::CondZ,
+ OperandSpec::CondNZ => Operand::CondNZ,
+
+ OperandSpec::Nothing => Operand::Nothing,
+ };
+ Ok(operand)
+}
diff --git a/test/test.rs b/test/test.rs
new file mode 100644
index 0000000..856db45
--- /dev/null
+++ b/test/test.rs
@@ -0,0 +1,137 @@
+use std::fmt::Write;
+
+extern crate yaxpeax_arch;
+extern crate yaxpeax_sm83;
+
+use yaxpeax_arch::{AddressBase, Decoder, LengthedInstruction};
+use yaxpeax_sm83::InstDecoder;
+
+fn test_invalid(data: &[u8]) {
+ test_invalid_under(&InstDecoder::default(), data);
+}
+
+fn test_invalid_under(decoder: &InstDecoder, data: &[u8]) {
+ if let Ok(inst) = decoder.decode(data.into_iter().cloned()) {
+ panic!("decoded {:?} from {:02x?}", inst.opcode(), data);
+ } else {
+ // this is fine
+ }
+}
+
+fn test_display(data: &[u8], expected: &'static str) {
+ test_display_under(&InstDecoder::default(), data, expected);
+}
+
+fn test_display_under(decoder: &InstDecoder, data: &[u8], expected: &'static str) {
+ let mut hex = String::new();
+ for b in data {
+ write!(hex, "{:02x}", b).unwrap();
+ }
+ match decoder.decode(data.into_iter().map(|x| *x)) {
+ Ok(instr) => {
+ let text = format!("{}", instr);
+ assert!(
+ text == expected,
+ "display error for {}:\n decoded: {:?}\n displayed: {}\n expected: {}\n",
+ hex,
+ instr,
+ text,
+ expected
+ );
+ // while we're at it, test that the instruction is as long, and no longer, than its
+ // input
+ assert_eq!((0u16.wrapping_offset(instr.len()).to_linear()) as usize, data.len(), "instruction length is incorrect, wanted instruction {}", expected);
+ },
+ Err(e) => {
+ assert!(false, "decode error ({}) for {}:\n expected: {}\n", e, hex, expected);
+ }
+ }
+}
+
+#[test]
+fn test_invalid_ops() {
+ test_invalid(&[0xd3]);
+ test_invalid(&[0xdb]);
+ test_invalid(&[0xdd]);
+ test_invalid(&[0xe3]);
+ test_invalid(&[0xe4]);
+ test_invalid(&[0xeb]);
+ test_invalid(&[0xec]);
+ test_invalid(&[0xed]);
+ test_invalid(&[0xf4]);
+ test_invalid(&[0xfc]);
+ test_invalid(&[0xfd]);
+}
+
+#[test]
+fn test_ld_decode() {
+ test_display(&[0x6f], "ld l, a");
+ test_display(&[0x6e], "ld l, [hl]");
+ test_display(&[0x32], "ld [hl-], a");
+ test_display(&[0x3a], "ld a, [hl-]");
+ test_display(&[0x73], "ld [hl], e");
+ test_display(&[0x74], "ld [hl], h");
+ test_display(&[0x67], "ld h, a");
+ test_display(&[0x79], "ld a, c");
+ test_display(&[0x52], "ld d, d");
+ test_display(&[0x64], "ld h, h");
+ test_display(&[0x0e, 0x75], "ld c, $75");
+ test_display(&[0x3e, 0x75], "ld a, $75");
+ test_display(&[0x01, 0x99, 0x88], "ld bc, $8899");
+ test_display(&[0x11, 0x99, 0x88], "ld de, $8899");
+ test_display(&[0x21, 0x99, 0x88], "ld hl, $8899");
+ test_display(&[0x31, 0x99, 0x88], "ld sp, $8899");
+ test_display(&[0xea, 0x34, 0x12], "ld [$1234], a");
+}
+
+#[test]
+fn test_push_pop() {
+ test_display(&[0xf1], "pop af");
+ test_display(&[0xe1], "pop hl");
+ test_display(&[0xe5], "push hl");
+}
+
+#[test]
+fn test_control_flow() {
+ test_display(&[0xc3, 0x10, 0x20], "jp $2010"); // should this be relative?
+ test_display(&[0xda, 0x10, 0x20], "jp C, $2010"); // should this be relative?
+ test_display(&[0x20, 0x33], "jr nZ, $+$33");
+ test_display(&[0xd9], "reti");
+ test_display(&[0xc9], "ret");
+ test_display(&[0x00], "nop");
+ test_display(&[0xcd, 0x44, 0x66], "call $6644"); // not relative, right
+ test_display(&[0xfe, 0x10], "cp $10");
+ test_display(&[0xb8], "cp b");
+}
+
+#[test]
+fn test_arithmetic() {
+ test_display(&[0xce, 0x40], "adc $40");
+ test_display(&[0x19], "add hl, de");
+ test_display(&[0x81], "add c");
+ test_display(&[0x83], "add e");
+ test_display(&[0x88], "adc b");
+ test_display(&[0x89], "adc c");
+ test_display(&[0x99], "sbc c");
+ test_display(&[0x9d], "sbc l");
+ test_display(&[0x91], "sub c");
+ test_display(&[0x0c], "inc c");
+ test_display(&[0x0d], "dec c");
+ test_display(&[0x2d], "dec l");
+}
+
+#[test]
+fn test_bitwise() {
+ test_display(&[0xb5], "or l");
+ test_display(&[0xf6, 0x10], "or $10");
+ test_display(&[0xcb, 0x71], "bit 6, c");
+ test_display(&[0xaf], "xor a");
+ test_display(&[0x2b], "dec hl");
+ test_display(&[0x34], "inc [hl]");
+}
+
+#[test]
+fn test_misc() {
+ test_display(&[0x37], "scf");
+ test_display(&[0x10], "stop");
+}