use crate::ifo::NavCommand;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Register {
Gprm(u8),
Sprm(u8),
Invalid(u8),
}
impl Register {
pub fn decode(byte: u8) -> Self {
match byte {
0x00..=0x0F => Self::Gprm(byte),
0x80..=0x97 => Self::Sprm(byte - 0x80),
_ => Self::Invalid(byte),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SetOp {
None,
Mov,
Swp,
Add,
Sub,
Mul,
Div,
Mod,
Rnd,
And,
Or,
Xor,
Invalid(u8),
}
impl SetOp {
pub fn decode(code: u8) -> Self {
match code & 0x0F {
0 => Self::None,
1 => Self::Mov,
2 => Self::Swp,
3 => Self::Add,
4 => Self::Sub,
5 => Self::Mul,
6 => Self::Div,
7 => Self::Mod,
8 => Self::Rnd,
9 => Self::And,
0xA => Self::Or,
0xB => Self::Xor,
other => Self::Invalid(other),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CmpOp {
None,
Bc,
Eq,
Ne,
Ge,
Gt,
Le,
Lt,
}
impl CmpOp {
pub fn decode(code: u8) -> Self {
match code & 0x07 {
0 => Self::None,
1 => Self::Bc,
2 => Self::Eq,
3 => Self::Ne,
4 => Self::Ge,
5 => Self::Gt,
6 => Self::Le,
_ => Self::Lt,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Operand {
Register(Register),
Immediate(u16),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinkSubset {
Nop,
LinkTopCell,
LinkNextCell,
LinkPrevCell,
LinkTopPG,
LinkNextPG,
LinkPrevPG,
LinkTopPGC,
LinkNextPGC,
LinkPrevPGC,
LinkGoupPGC,
LinkTailPGC,
Rsm,
Invalid(u8),
}
impl LinkSubset {
pub fn decode(code: u8) -> Self {
match code & 0x1F {
0x00 => Self::Nop,
0x01 => Self::LinkTopCell,
0x02 => Self::LinkNextCell,
0x03 => Self::LinkPrevCell,
0x05 => Self::LinkTopPG,
0x06 => Self::LinkNextPG,
0x07 => Self::LinkPrevPG,
0x09 => Self::LinkTopPGC,
0x0A => Self::LinkNextPGC,
0x0B => Self::LinkPrevPGC,
0x0C => Self::LinkGoupPGC,
0x0D => Self::LinkTailPGC,
0x10 => Self::Rsm,
other => Self::Invalid(other),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JumpSSTarget {
FirstPlay,
VmgmMenu { menu: u8 },
VtsmMenu { vts: u8, ttn: u8, menu: u8 },
VmgmPgcn { pgcn: u16 },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CallSSTarget {
FirstPlay { rsm_cell: u8 },
VmgmMenu { menu: u8, rsm_cell: u8 },
VtsmMenu { menu: u8, rsm_cell: u8 },
VmgmPgcn { pgcn: u16, rsm_cell: u8 },
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NavInstruction {
Nop,
Goto { line: u8 },
Break,
SetTmpPml { level: u8, line: u8 },
LinkSub { subset: LinkSubset, hl_bn: u8 },
LinkPgcn { pgcn: u16 },
LinkPttn { pttn: u16, hl_bn: u8 },
LinkPgn { pgn: u8, hl_bn: u8 },
LinkCn { cn: u8, hl_bn: u8 },
Exit,
JumpTT { ttn: u8 },
JumpVtsTt { ttn: u8 },
JumpVtsPtt { ttn: u8, pttn: u16 },
JumpSs(JumpSSTarget),
CallSs(CallSSTarget),
SetStn {
direct: bool,
af: bool,
audio_src: u8,
sf: bool,
subpic_src: u8,
nf: bool,
angle_src: u8,
},
SetNvtmr { src: Operand, pgcn: u16 },
SetGprmMd {
src: Operand,
dst: Register,
counter: bool,
},
SetAmxMd { src: Operand },
SetHlBtnn { src: Operand },
Set {
op: SetOp,
dst: Register,
src: Operand,
},
SetCLnk {
set_op: SetOp,
cmp_op: CmpOp,
scr: Register,
set_src: Operand,
cmp_rhs: Operand,
hl_bn: u8,
link: LinkSubset,
},
CSetCLnk {
set_op: SetOp,
cmp_op: CmpOp,
sr1: Register,
set_src: Operand,
cmp_lhs: Register,
cmp_rhs: Operand,
hl_bn: u8,
link: LinkSubset,
},
CmpSetLnk {
set_op: SetOp,
cmp_op: CmpOp,
sr1: Register,
set_src: Operand,
cmp_rhs: Operand,
hl_bn: u8,
link: LinkSubset,
},
Unknown,
Invalid,
}
impl NavCommand {
pub fn decode(&self) -> NavInstruction {
let b = &self.bytes;
let cmd_type = b[0] >> 5;
let set_direct = (b[0] & 0x10) != 0;
let set_nibble = b[0] & 0x0F;
let cmd_nibble = b[1] & 0x0F;
let cmp_op = CmpOp::decode((b[1] >> 4) & 0x07);
let cmp_direct = (b[1] & 0x80) != 0;
match cmd_type {
0 => decode_type0(b, cmd_nibble),
1 => {
if (b[0] & 0x10) == 0 {
decode_type1_link(b, cmd_nibble)
} else {
decode_type1_jumpcall(b, cmd_nibble)
}
}
2 => decode_type2_setsystem(b, set_direct, set_nibble),
3 => decode_type3_set(b, set_direct, set_nibble),
4 => decode_type4(b, set_direct, cmp_direct, set_nibble, cmp_op),
5 => decode_type5(b, set_direct, cmp_direct, set_nibble, cmp_op),
6 => decode_type6(b, set_direct, cmp_direct, set_nibble, cmp_op),
_ => NavInstruction::Unknown,
}
}
}
fn decode_type0(b: &[u8; 8], cmd_nibble: u8) -> NavInstruction {
match cmd_nibble {
0 => NavInstruction::Nop,
1 => NavInstruction::Goto { line: b[7] },
2 => NavInstruction::Break,
3 => NavInstruction::SetTmpPml {
level: b[6] & 0x0F,
line: b[7],
},
_ => NavInstruction::Invalid,
}
}
fn decode_type1_link(b: &[u8; 8], cmd_nibble: u8) -> NavInstruction {
let hl_bn = b[6] & 0x3F;
match cmd_nibble {
0 => NavInstruction::Nop,
1 => NavInstruction::LinkSub {
subset: LinkSubset::decode(b[7]),
hl_bn,
},
4 => NavInstruction::LinkPgcn {
pgcn: u16::from_be_bytes([b[6], b[7]]),
},
5 => NavInstruction::LinkPttn {
pttn: u16::from_be_bytes([b[6] & 0x03, b[7]]),
hl_bn,
},
6 => NavInstruction::LinkPgn { pgn: b[7], hl_bn },
7 => NavInstruction::LinkCn { cn: b[7], hl_bn },
_ => NavInstruction::Invalid,
}
}
fn decode_type1_jumpcall(b: &[u8; 8], cmd_nibble: u8) -> NavInstruction {
match cmd_nibble {
0 => NavInstruction::Nop,
1 => NavInstruction::Exit,
2 => NavInstruction::JumpTT { ttn: b[5] },
3 => NavInstruction::JumpVtsTt { ttn: b[5] },
5 => NavInstruction::JumpVtsPtt {
ttn: b[5],
pttn: u16::from_be_bytes([b[2] & 0x03, b[3]]),
},
6 => NavInstruction::JumpSs(decode_jumpss_target(b)),
8 => NavInstruction::CallSs(decode_callss_target(b)),
_ => NavInstruction::Invalid,
}
}
fn decode_jumpss_target(b: &[u8; 8]) -> JumpSSTarget {
let selector = (b[5] >> 4) & 0x03;
match selector {
0 => JumpSSTarget::FirstPlay,
1 => JumpSSTarget::VmgmMenu { menu: b[5] & 0x0F },
2 => JumpSSTarget::VtsmMenu {
ttn: b[3],
vts: b[4],
menu: b[5] & 0x0F,
},
_ => JumpSSTarget::VmgmPgcn {
pgcn: u16::from_be_bytes([b[2], b[3]]),
},
}
}
fn decode_callss_target(b: &[u8; 8]) -> CallSSTarget {
let selector = (b[5] >> 4) & 0x03;
let rsm_cell = b[4];
match selector {
0 => CallSSTarget::FirstPlay { rsm_cell },
1 => CallSSTarget::VmgmMenu {
menu: b[5] & 0x0F,
rsm_cell,
},
2 => CallSSTarget::VtsmMenu {
menu: b[5] & 0x0F,
rsm_cell,
},
_ => CallSSTarget::VmgmPgcn {
pgcn: u16::from_be_bytes([b[2], b[3]]),
rsm_cell,
},
}
}
fn decode_type2_setsystem(b: &[u8; 8], direct: bool, sub: u8) -> NavInstruction {
match sub {
1 => {
let af = (b[3] & 0x80) != 0;
let sf = (b[4] & 0x80) != 0;
let nf = (b[5] & 0x80) != 0;
let mask = if direct { 0x7F } else { 0x0F };
NavInstruction::SetStn {
direct,
af,
audio_src: b[3] & mask,
sf,
subpic_src: b[4] & mask,
nf,
angle_src: b[5] & mask,
}
}
2 => NavInstruction::SetNvtmr {
src: if direct {
Operand::Immediate(u16::from_be_bytes([b[2], b[3]]))
} else {
Operand::Register(Register::decode(b[3]))
},
pgcn: u16::from_be_bytes([b[4], b[5]]),
},
3 => NavInstruction::SetGprmMd {
src: if direct {
Operand::Immediate(u16::from_be_bytes([b[2], b[3]]))
} else {
Operand::Register(Register::decode(b[3]))
},
dst: Register::Gprm(b[5] & 0x0F),
counter: (b[4] & 0x80) != 0,
},
4 => NavInstruction::SetAmxMd {
src: if direct {
Operand::Immediate(u16::from_be_bytes([b[4], b[5]]))
} else {
Operand::Register(Register::Gprm(b[5] & 0x0F))
},
},
6 => NavInstruction::SetHlBtnn {
src: if direct {
Operand::Immediate(u16::from_be_bytes([b[4], b[5]]))
} else {
Operand::Register(Register::Gprm(b[5] & 0x0F))
},
},
_ => NavInstruction::Invalid,
}
}
fn decode_type3_set(b: &[u8; 8], direct: bool, sub: u8) -> NavInstruction {
if !(1..=0x0B).contains(&sub) {
return NavInstruction::Invalid;
}
let dst = Register::Gprm(b[3] & 0x0F);
let src = if direct {
Operand::Immediate(u16::from_be_bytes([b[4], b[5]]))
} else {
Operand::Register(Register::decode(b[5]))
};
NavInstruction::Set {
op: SetOp::decode(sub),
dst,
src,
}
}
fn decode_type4(
b: &[u8; 8],
set_direct: bool,
cmp_direct: bool,
set_nibble: u8,
cmp_op: CmpOp,
) -> NavInstruction {
let scr = Register::Gprm(b[1] & 0x0F);
let set_src = if set_direct {
Operand::Immediate(u16::from_be_bytes([b[2], b[3]]))
} else {
Operand::Register(Register::decode(b[3]))
};
let cmp_rhs = if cmp_direct {
Operand::Immediate(u16::from_be_bytes([b[4], b[5]]))
} else {
Operand::Register(Register::decode(b[5]))
};
NavInstruction::SetCLnk {
set_op: SetOp::decode(set_nibble),
cmp_op,
scr,
set_src,
cmp_rhs,
hl_bn: (b[6] >> 2) & 0x3F,
link: LinkSubset::decode(b[7]),
}
}
fn decode_type5(
b: &[u8; 8],
set_direct: bool,
cmp_direct: bool,
set_nibble: u8,
cmp_op: CmpOp,
) -> NavInstruction {
if set_direct && cmp_direct {
return NavInstruction::Invalid;
}
let sr1 = Register::Gprm(b[1] & 0x0F);
let set_src = if set_direct {
Operand::Immediate(u16::from_be_bytes([b[2], b[3]]))
} else {
Operand::Register(Register::decode(b[3]))
};
let cmp_lhs = Register::decode(b[4]);
let cmp_rhs = if cmp_direct {
Operand::Immediate(u16::from_be_bytes([b[4], b[5]]))
} else {
Operand::Register(Register::decode(b[5]))
};
NavInstruction::CSetCLnk {
set_op: SetOp::decode(set_nibble),
cmp_op,
sr1,
set_src,
cmp_lhs,
cmp_rhs,
hl_bn: (b[6] >> 2) & 0x3F,
link: LinkSubset::decode(b[7]),
}
}
fn decode_type6(
b: &[u8; 8],
set_direct: bool,
cmp_direct: bool,
set_nibble: u8,
cmp_op: CmpOp,
) -> NavInstruction {
if set_direct && cmp_direct {
return NavInstruction::Invalid;
}
let sr1 = Register::Gprm(b[1] & 0x0F);
let set_src = if set_direct {
Operand::Immediate(u16::from_be_bytes([b[2], b[3]]))
} else {
Operand::Register(Register::decode(b[3]))
};
let cmp_rhs = if cmp_direct {
Operand::Immediate(u16::from_be_bytes([b[4], b[5]]))
} else {
Operand::Register(Register::decode(b[5]))
};
NavInstruction::CmpSetLnk {
set_op: SetOp::decode(set_nibble),
cmp_op,
sr1,
set_src,
cmp_rhs,
hl_bn: (b[6] >> 2) & 0x3F,
link: LinkSubset::decode(b[7]),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ifo::NavCommand;
fn cmd(bytes: [u8; 8]) -> NavCommand {
NavCommand { bytes }
}
#[test]
fn register_decode_gprm_range() {
for i in 0u8..=15 {
assert_eq!(Register::decode(i), Register::Gprm(i));
}
}
#[test]
fn register_decode_sprm_range() {
for i in 0u8..=23 {
assert_eq!(Register::decode(0x80 + i), Register::Sprm(i));
}
}
#[test]
fn register_decode_invalid_holes() {
assert_eq!(Register::decode(0x10), Register::Invalid(0x10));
assert_eq!(Register::decode(0x7F), Register::Invalid(0x7F));
assert_eq!(Register::decode(0x98), Register::Invalid(0x98));
assert_eq!(Register::decode(0xFF), Register::Invalid(0xFF));
}
#[test]
fn set_op_decodes_all_named_codes() {
let table: &[(u8, SetOp)] = &[
(0, SetOp::None),
(1, SetOp::Mov),
(2, SetOp::Swp),
(3, SetOp::Add),
(4, SetOp::Sub),
(5, SetOp::Mul),
(6, SetOp::Div),
(7, SetOp::Mod),
(8, SetOp::Rnd),
(9, SetOp::And),
(0xA, SetOp::Or),
(0xB, SetOp::Xor),
];
for (code, expect) in table {
assert_eq!(SetOp::decode(*code), *expect);
}
assert_eq!(SetOp::decode(0xC), SetOp::Invalid(0xC));
assert_eq!(SetOp::decode(0xF), SetOp::Invalid(0xF));
}
#[test]
fn cmp_op_decodes_all_codes() {
let table: &[(u8, CmpOp)] = &[
(0, CmpOp::None),
(1, CmpOp::Bc),
(2, CmpOp::Eq),
(3, CmpOp::Ne),
(4, CmpOp::Ge),
(5, CmpOp::Gt),
(6, CmpOp::Le),
(7, CmpOp::Lt),
];
for (code, expect) in table {
assert_eq!(CmpOp::decode(*code), *expect);
}
}
#[test]
fn link_subset_decodes_named_codes() {
let table: &[(u8, LinkSubset)] = &[
(0x00, LinkSubset::Nop),
(0x01, LinkSubset::LinkTopCell),
(0x02, LinkSubset::LinkNextCell),
(0x03, LinkSubset::LinkPrevCell),
(0x05, LinkSubset::LinkTopPG),
(0x06, LinkSubset::LinkNextPG),
(0x07, LinkSubset::LinkPrevPG),
(0x09, LinkSubset::LinkTopPGC),
(0x0A, LinkSubset::LinkNextPGC),
(0x0B, LinkSubset::LinkPrevPGC),
(0x0C, LinkSubset::LinkGoupPGC),
(0x0D, LinkSubset::LinkTailPGC),
(0x10, LinkSubset::Rsm),
];
for (code, expect) in table {
assert_eq!(LinkSubset::decode(*code), *expect);
}
assert_eq!(LinkSubset::decode(0x04), LinkSubset::Invalid(0x04));
assert_eq!(LinkSubset::decode(0x08), LinkSubset::Invalid(0x08));
assert_eq!(LinkSubset::decode(0x0E), LinkSubset::Invalid(0x0E));
assert_eq!(LinkSubset::decode(0x1F), LinkSubset::Invalid(0x1F));
}
#[test]
fn decode_type0_nop() {
assert_eq!(
cmd([0x00, 0x00, 0, 0, 0, 0, 0, 0]).decode(),
NavInstruction::Nop
);
}
#[test]
fn decode_type0_goto() {
let i = cmd([0x00, 0x01, 0, 0, 0, 0, 0, 0x2A]).decode();
assert_eq!(i, NavInstruction::Goto { line: 0x2A });
}
#[test]
fn decode_type0_break() {
assert_eq!(
cmd([0x00, 0x02, 0, 0, 0, 0, 0, 0]).decode(),
NavInstruction::Break
);
}
#[test]
fn decode_type0_settmppml() {
let i = cmd([0x00, 0x03, 0, 0, 0, 0, 0x05, 0x99]).decode();
assert_eq!(
i,
NavInstruction::SetTmpPml {
level: 5,
line: 0x99
}
);
}
#[test]
fn decode_type0_invalid_cmd_nibble() {
assert_eq!(
cmd([0x00, 0x04, 0, 0, 0, 0, 0, 0]).decode(),
NavInstruction::Invalid
);
}
#[test]
fn decode_link_subset_with_button() {
let i = cmd([0x20, 0x01, 0, 0, 0, 0, 0x05, 0x10]).decode();
assert_eq!(
i,
NavInstruction::LinkSub {
subset: LinkSubset::Rsm,
hl_bn: 5,
}
);
}
#[test]
fn decode_link_pgcn() {
let i = cmd([0x20, 0x04, 0, 0, 0, 0, 0x12, 0x34]).decode();
assert_eq!(i, NavInstruction::LinkPgcn { pgcn: 0x1234 });
}
#[test]
fn decode_link_pttn() {
let i = cmd([0x20, 0x05, 0, 0, 0, 0, (0x09 & 0x3F) | 0x40 | 0x01, 0x03]).decode();
match i {
NavInstruction::LinkPttn { pttn, hl_bn } => {
assert_eq!(pttn, 0x103);
assert_eq!(hl_bn, 0x09);
}
_ => panic!("expected LinkPttn"),
}
}
#[test]
fn decode_link_pgn_and_cn() {
let i = cmd([0x20, 0x06, 0, 0, 0, 0, 0x05, 0x11]).decode();
assert_eq!(
i,
NavInstruction::LinkPgn {
pgn: 0x11,
hl_bn: 5
}
);
let i = cmd([0x20, 0x07, 0, 0, 0, 0, 0x06, 0x42]).decode();
assert_eq!(i, NavInstruction::LinkCn { cn: 0x42, hl_bn: 6 });
}
#[test]
fn decode_link_invalid_nibble() {
assert_eq!(
cmd([0x20, 0x02, 0, 0, 0, 0, 0, 0]).decode(),
NavInstruction::Invalid
);
}
#[test]
fn decode_exit() {
assert_eq!(
cmd([0x30, 0x01, 0, 0, 0, 0, 0, 0]).decode(),
NavInstruction::Exit
);
}
#[test]
fn decode_jump_tt_and_vts_tt() {
let i = cmd([0x30, 0x02, 0, 0, 0, 0x07, 0, 0]).decode();
assert_eq!(i, NavInstruction::JumpTT { ttn: 7 });
let i = cmd([0x30, 0x03, 0, 0, 0, 0x09, 0, 0]).decode();
assert_eq!(i, NavInstruction::JumpVtsTt { ttn: 9 });
}
#[test]
fn decode_jump_vts_ptt() {
let i = cmd([0x30, 0x05, 0x02, 0x05, 0, 0x04, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::JumpVtsPtt {
ttn: 4,
pttn: 0x205
}
);
}
#[test]
fn decode_jump_ss_first_play() {
let i = cmd([0x30, 0x06, 0, 0, 0, 0x00, 0, 0]).decode();
assert_eq!(i, NavInstruction::JumpSs(JumpSSTarget::FirstPlay));
}
#[test]
fn decode_jump_ss_vmgm_menu() {
let i = cmd([0x30, 0x06, 0, 0, 0, 0x1A, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::JumpSs(JumpSSTarget::VmgmMenu { menu: 0x0A })
);
}
#[test]
fn decode_jump_ss_vtsm() {
let i = cmd([0x30, 0x06, 0, 0x07, 0x02, 0x23, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::JumpSs(JumpSSTarget::VtsmMenu {
vts: 2,
ttn: 7,
menu: 3,
})
);
}
#[test]
fn decode_jump_ss_vmgm_pgcn() {
let i = cmd([0x30, 0x06, 0xAB, 0xCD, 0, 0x30, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::JumpSs(JumpSSTarget::VmgmPgcn { pgcn: 0xABCD })
);
}
#[test]
fn decode_call_ss_first_play() {
let i = cmd([0x30, 0x08, 0, 0, 0x42, 0x00, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::CallSs(CallSSTarget::FirstPlay { rsm_cell: 0x42 })
);
}
#[test]
fn decode_call_ss_vmgm_pgcn() {
let i = cmd([0x30, 0x08, 0x11, 0x22, 0x07, 0x30, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::CallSs(CallSSTarget::VmgmPgcn {
pgcn: 0x1122,
rsm_cell: 0x07,
})
);
}
#[test]
fn decode_set_stn_register_form() {
let i = cmd([0x41, 0x00, 0, 0x84, 0x85, 0x00, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::SetStn {
direct: false,
af: true,
audio_src: 4,
sf: true,
subpic_src: 5,
nf: false,
angle_src: 0,
}
);
}
#[test]
fn decode_set_stn_immediate_form() {
let i = cmd([0x51, 0x00, 0, 0x80 | 0x12, 0x80 | 0x34, 0x80 | 0x56, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::SetStn {
direct: true,
af: true,
audio_src: 0x12,
sf: true,
subpic_src: 0x34,
nf: true,
angle_src: 0x56,
}
);
}
#[test]
fn decode_set_nvtmr_register_form() {
let i = cmd([0x42, 0, 0, 0x03, 0x12, 0x34, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::SetNvtmr {
src: Operand::Register(Register::Gprm(3)),
pgcn: 0x1234,
}
);
}
#[test]
fn decode_set_nvtmr_immediate_form() {
let i = cmd([0x52, 0, 0xAA, 0xBB, 0x12, 0x34, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::SetNvtmr {
src: Operand::Immediate(0xAABB),
pgcn: 0x1234,
}
);
}
#[test]
fn decode_set_gprmmd_with_counter() {
let i = cmd([0x43, 0, 0, 0x06, 0x80, 0x09, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::SetGprmMd {
src: Operand::Register(Register::Gprm(6)),
dst: Register::Gprm(9),
counter: true,
}
);
}
#[test]
fn decode_set_amxmd_immediate() {
let i = cmd([0x54, 0, 0, 0, 0x01, 0x23, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::SetAmxMd {
src: Operand::Immediate(0x0123),
}
);
}
#[test]
fn decode_set_hlbtnn_register() {
let i = cmd([0x46, 0, 0, 0, 0, 0x07, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::SetHlBtnn {
src: Operand::Register(Register::Gprm(7)),
}
);
}
#[test]
fn decode_setsystem_invalid_subcode() {
assert_eq!(
cmd([0x45, 0, 0, 0, 0, 0, 0, 0]).decode(),
NavInstruction::Invalid
);
}
#[test]
fn decode_set_add_register_form() {
let i = cmd([0x63, 0, 0, 0x04, 0, 0x09, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::Set {
op: SetOp::Add,
dst: Register::Gprm(4),
src: Operand::Register(Register::Gprm(9)),
}
);
}
#[test]
fn decode_set_mov_immediate_form() {
let i = cmd([0x71, 0, 0, 0x00, 0xFF, 0xEE, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::Set {
op: SetOp::Mov,
dst: Register::Gprm(0),
src: Operand::Immediate(0xFFEE),
}
);
}
#[test]
fn decode_set_invalid_subcode() {
assert_eq!(
cmd([0x60, 0, 0, 0, 0, 0, 0, 0]).decode(),
NavInstruction::Invalid
);
assert_eq!(
cmd([0x6C, 0, 0, 0, 0, 0, 0, 0]).decode(),
NavInstruction::Invalid
);
assert_eq!(
cmd([0x6F, 0, 0, 0, 0, 0, 0, 0]).decode(),
NavInstruction::Invalid
);
}
#[test]
fn decode_set_src_routes_to_sprm() {
let i = cmd([0x61, 0, 0, 0x05, 0, 0x82, 0, 0]).decode();
assert_eq!(
i,
NavInstruction::Set {
op: SetOp::Mov,
dst: Register::Gprm(5),
src: Operand::Register(Register::Sprm(2)),
}
);
}
#[test]
fn decode_type4_setclnk_full_operand_register_form() {
let i = cmd([0x83, 0x17, 0x00, 0x09, 0x00, 0x84, 0x3C, 0x06]).decode();
assert_eq!(
i,
NavInstruction::SetCLnk {
set_op: SetOp::Add,
cmp_op: CmpOp::Bc,
scr: Register::Gprm(7),
set_src: Operand::Register(Register::Gprm(9)),
cmp_rhs: Operand::Register(Register::Sprm(4)),
hl_bn: 0x0F,
link: LinkSubset::LinkNextPG,
}
);
}
#[test]
fn decode_type4_setclnk_immediate_forms() {
let i = cmd([0x93, 0x95, 0x12, 0x34, 0x56, 0x78, 0x04, 0x10]).decode();
assert_eq!(
i,
NavInstruction::SetCLnk {
set_op: SetOp::Add,
cmp_op: CmpOp::Bc,
scr: Register::Gprm(5),
set_src: Operand::Immediate(0x1234),
cmp_rhs: Operand::Immediate(0x5678),
hl_bn: 1,
link: LinkSubset::Rsm,
}
);
}
#[test]
fn decode_type5_csetclnk_register_form() {
let i = cmd([0xA2, 0x23, 0x00, 0x05, 0x07, 0x08, 0x10, 0x09]).decode();
assert_eq!(
i,
NavInstruction::CSetCLnk {
set_op: SetOp::Swp,
cmp_op: CmpOp::Eq,
sr1: Register::Gprm(3),
set_src: Operand::Register(Register::Gprm(5)),
cmp_lhs: Register::Gprm(7),
cmp_rhs: Operand::Register(Register::Gprm(8)),
hl_bn: 4,
link: LinkSubset::LinkTopPGC,
}
);
}
#[test]
fn decode_type5_csetclnk_set_immediate_form() {
let i = cmd([0xB1, 0x22, 0xAB, 0xCD, 0x06, 0x07, 0x00, 0x05]).decode();
assert_eq!(
i,
NavInstruction::CSetCLnk {
set_op: SetOp::Mov,
cmp_op: CmpOp::Eq,
sr1: Register::Gprm(2),
set_src: Operand::Immediate(0xABCD),
cmp_lhs: Register::Gprm(6),
cmp_rhs: Operand::Register(Register::Gprm(7)),
hl_bn: 0,
link: LinkSubset::LinkTopPG,
}
);
}
#[test]
fn decode_type5_set_dir_and_cmp_dir_both_set_is_invalid() {
let i = cmd([0xB1, 0xA2, 0, 0, 0, 0, 0, 0]).decode();
assert_eq!(i, NavInstruction::Invalid);
}
#[test]
fn decode_type6_cmpsetlnk_register_form() {
let i = cmd([0xC4, 0x71, 0x00, 0x06, 0x00, 0x82, 0x08, 0x0A]).decode();
assert_eq!(
i,
NavInstruction::CmpSetLnk {
set_op: SetOp::Sub,
cmp_op: CmpOp::Lt,
sr1: Register::Gprm(1),
set_src: Operand::Register(Register::Gprm(6)),
cmp_rhs: Operand::Register(Register::Sprm(2)),
hl_bn: 2,
link: LinkSubset::LinkNextPGC,
}
);
}
#[test]
fn decode_type6_set_dir_and_cmp_dir_both_set_is_invalid() {
let i = cmd([0xD1, 0xA2, 0, 0, 0, 0, 0, 0]).decode();
assert_eq!(i, NavInstruction::Invalid);
}
#[test]
fn decode_type7_unknown() {
assert_eq!(
cmd([0xE0, 0, 0, 0, 0, 0, 0, 0]).decode(),
NavInstruction::Unknown
);
assert_eq!(
cmd([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]).decode(),
NavInstruction::Unknown
);
}
#[test]
fn decoded_from_navcommand_default() {
let nc = NavCommand::default();
assert_eq!(nc.decode(), NavInstruction::Nop);
}
}