diff --git a/dcb-tools/src/decompiler/main.rs b/dcb-tools/src/decompiler/main.rs index f9284db..b3a9951 100644 --- a/dcb-tools/src/decompiler/main.rs +++ b/dcb-tools/src/decompiler/main.rs @@ -82,9 +82,9 @@ use dcb::{ data::DataTable, func::FuncTable, instruction::{ - Directive, + BasicInstruction, Directive, PseudoInstruction::{self, Nop}, - Raw, SimpleInstruction, + Raw, }, Func, Instruction, Pos, }, @@ -107,6 +107,8 @@ fn main() -> Result<(), anyhow::Error> { log::debug!("Deserializing executable"); let exe = dcb::game::Exe::deserialize(&mut game_file).context("Unable to parse game executable")?; + log::info!("Header:\n{}\n", exe.header); + // Get all instructions log::debug!("Retrieving all instructions"); let instructions: Vec<(Pos, Instruction)> = Instruction::new_iter( @@ -182,8 +184,9 @@ fn main() -> Result<(), anyhow::Error> { } // Space out data if it had a name - if let Some(data) = data_pos.get(cur_pos) { + if let Some(data) = data_pos.get(cur_pos - 1) { if data.end_pos() == cur_pos && !data.name.is_empty() { + println!("####################"); println!(); } } @@ -207,6 +210,8 @@ fn main() -> Result<(), anyhow::Error> { } if let Some(data) = data_pos.get(cur_pos) { if data.pos == cur_pos { + println!(); + println!("####################"); println!("{}:", data.name); for description in data.desc.lines() { println!("# {}", description); @@ -215,19 +220,22 @@ fn main() -> Result<(), anyhow::Error> { } // Print the instruction and it's location. - print!("{cur_pos:#010x}:\t"); + match cur_func { + Some(cur_func) => print!("{:#05x}:\t", cur_pos - cur_func.start_pos), + None => print!("{cur_pos:#010x}:\t"), + } match instruction { - Instruction::Simple( - SimpleInstruction::J { target } | - SimpleInstruction::Jal { target } | - SimpleInstruction::Beq { target, .. } | - SimpleInstruction::Bne { target, .. } | - SimpleInstruction::Bltz { target, .. } | - SimpleInstruction::Bgez { target, .. } | - SimpleInstruction::Bgtz { target, .. } | - SimpleInstruction::Blez { target, .. } | - SimpleInstruction::Bltzal { target, .. } | - SimpleInstruction::Bgezal { target, .. }, + Instruction::Basic( + BasicInstruction::J { target } | + BasicInstruction::Jal { target } | + BasicInstruction::Beq { target, .. } | + BasicInstruction::Bne { target, .. } | + BasicInstruction::Bltz { target, .. } | + BasicInstruction::Bgez { target, .. } | + BasicInstruction::Bgtz { target, .. } | + BasicInstruction::Blez { target, .. } | + BasicInstruction::Bltzal { target, .. } | + BasicInstruction::Bgezal { target, .. }, ) | Instruction::Pseudo( PseudoInstruction::B { target } | PseudoInstruction::Beqz { target, .. } | PseudoInstruction::Bnez { target, .. }, @@ -256,7 +264,8 @@ fn main() -> Result<(), anyhow::Error> { PseudoInstruction::SwlImm { offset: target, .. } | PseudoInstruction::SwImm { offset: target, .. } | PseudoInstruction::SwrImm { offset: target, .. }, - ) => match functions + ) | + Instruction::Directive(Directive::Dw(target)) => match functions .get(Pos(*target)) .map(|func| (func.start_pos, &func.name)) .or_else(|| data_pos.get(Pos(*target)).map(|data| (data.pos, &data.name))) @@ -277,22 +286,6 @@ fn main() -> Result<(), anyhow::Error> { _ => print!("{instruction}"), } - // Comment any `dw` instructions that are function, data or string pointers - if let Instruction::Directive(Directive::Dw(target)) = instruction { - if let Some(func) = functions.get(Pos(*target)) { - print!(" # {}", func.name); - } else if let Some(data) = data_pos.get(Pos(*target)) { - if data.pos == Pos(*target) { - print!(" # {}", data.name); - } else { - let offset = Pos(*target) - data.pos; - if offset > 0 { - print!(" # {} + {offset:#x}", data.name); - } - } - } - } - // Append any comments in this line if let Some(cur_func) = cur_func { if let Some(comment) = cur_func.comments.get(&cur_pos) { diff --git a/dcb/Cargo.toml b/dcb/Cargo.toml index 6efe2f6..3dde55b 100644 --- a/dcb/Cargo.toml +++ b/dcb/Cargo.toml @@ -20,6 +20,7 @@ int-conv = "0.1" bitmatch = "0.1" either = "1.6" smallvec = "1.4" +num_enum = "0.5" # Serde serde = { version = "1.0", features = ["derive"] } diff --git a/dcb/src/ascii_str_arr.rs b/dcb/src/ascii_str_arr.rs index b564981..71a90b5 100644 --- a/dcb/src/ascii_str_arr.rs +++ b/dcb/src/ascii_str_arr.rs @@ -306,13 +306,13 @@ impl Default for AsciiStrArr { } impl fmt::Debug for AsciiStrArr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { AsciiStr::fmt(self.as_ascii(), f) } } impl fmt::Display for AsciiStrArr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { AsciiStr::fmt(self.as_ascii(), f) } } diff --git a/dcb/src/game.rs b/dcb/src/game.rs index f780a6b..660327a 100644 --- a/dcb/src/game.rs +++ b/dcb/src/game.rs @@ -11,9 +11,6 @@ //! All these strings must only contain ascii characters, thus on read and on write, if any //! strings contain non-ascii characters, an error will occur -// Lints -//#![allow(clippy::missing_docs_in_private_items)] // A lot of our private items are simple digimon types, so they don't need documentation - // Modules pub mod card; pub mod deck; diff --git a/dcb/src/game/exe/func/table.rs b/dcb/src/game/exe/func/table.rs index 62f8d4a..131ae7a 100644 --- a/dcb/src/game/exe/func/table.rs +++ b/dcb/src/game/exe/func/table.rs @@ -20,7 +20,7 @@ pub use iter::WithInstructionsIter; use super::Func; use crate::{ game::exe::{ - instruction::{Directive, PseudoInstruction, Register, SimpleInstruction}, + instruction::{BasicInstruction, Directive, PseudoInstruction, Register}, Instruction, Pos, }, util::discarding_sorted_merge_iter::DiscardingSortedMergeIter, @@ -84,15 +84,15 @@ impl FuncTable { #[allow(clippy::too_many_lines)] // TODO: Refactor? #[allow(clippy::enum_glob_use)] // It's only for this function pub fn from_instructions<'a>(instructions: &(impl Iterator + Clone)) -> Self { - use Instruction::{Pseudo, Simple}; + use BasicInstruction::*; + use Instruction::{Basic, Pseudo}; use PseudoInstruction::*; - use SimpleInstruction::*; // Get all returns let returns: BTreeSet = instructions .clone() .filter_map(|(pos, instruction)| match instruction { - Simple(Jr { rs: Register::Ra }) => Some(pos), + Basic(Jr { rs: Register::Ra }) => Some(pos), _ => None, }) .collect(); @@ -101,7 +101,7 @@ impl FuncTable { let tailcalls: BTreeSet = instructions .clone() .filter_map(|(pos, instruction)| match instruction { - Simple(J { .. } | Jr { .. }) => Some(pos), + Basic(J { .. } | Jr { .. }) => Some(pos), _ => None, }) .collect(); @@ -110,7 +110,7 @@ impl FuncTable { let labels: BTreeSet = instructions .clone() .filter_map(|(_, instruction)| match instruction { - Simple( + Basic( J { target } | Beq { target, .. } | Bne { target, .. } | @@ -131,7 +131,7 @@ impl FuncTable { let function_entries: BTreeSet = instructions .clone() .filter_map(|(_, instruction)| match instruction { - Simple(Jal { target }) => Some(*target), + Basic(Jal { target }) => Some(*target), Instruction::Directive(Directive::Dw(target)) => Some(Pos(*target)), _ => None, }) @@ -179,7 +179,7 @@ impl FuncTable { // TODO: Generalize this in `Instruction` as a method that // returns all registers used maybe. match instruction { - Simple(Sb { rt, rs, .. } | Lb { rt, rs, .. } | Lbu { rt, rs, .. }) => { + Basic(Sb { rt, rs, .. } | Lb { rt, rs, .. } | Lbu { rt, rs, .. }) => { if let Some(idx) = rt.arg_idx() { if arguments[idx].is_none() { arguments[idx] = Some("u8"); @@ -199,7 +199,7 @@ impl FuncTable { } }, - Simple(Sh { rt, rs, .. } | Lh { rt, rs, .. } | Lhu { rt, rs, .. }) => { + Basic(Sh { rt, rs, .. } | Lh { rt, rs, .. } | Lhu { rt, rs, .. }) => { if let Some(idx) = rt.arg_idx() { if arguments[idx].is_none() { arguments[idx] = Some("u16"); @@ -219,7 +219,7 @@ impl FuncTable { } }, - Simple( + Basic( Swl { rt, rs, .. } | Sw { rt, rs, .. } | Swr { rt, rs, .. } | Lwl { rt, rs, .. } | Lw { rt, rs, .. } | Lwr { rt, rs, .. }, ) => { if let Some(idx) = rt.arg_idx() { @@ -244,7 +244,7 @@ impl FuncTable { } }, - Simple( + Basic( Addi { rt, rs, .. } | Addiu { rt, rs, .. } | Slti { rt, rs, .. } | @@ -274,7 +274,7 @@ impl FuncTable { } }, - Simple( + Basic( Add { rd, rs, rt } | Addu { rd, rs, rt } | Sub { rd, rs, rt } | @@ -306,7 +306,7 @@ impl FuncTable { } }, - Simple( + Basic( Sll { rd, rt, .. } | Srl { rd, rt, .. } | Sra { rd, rt, .. } | @@ -327,7 +327,7 @@ impl FuncTable { } }, - Simple(Jalr { rd, rs }) => { + Basic(Jalr { rd, rs }) => { if let Some(idx) = rd.arg_idx() { if arguments[idx].is_none() { arguments[idx] = Some("u32"); @@ -340,7 +340,7 @@ impl FuncTable { } }, - Simple(Lui { rt, .. }) => { + Basic(Lui { rt, .. }) => { if let Some(idx) = rt.arg_idx() { if arguments[idx].is_none() { arguments[idx] = Some("u32"); @@ -348,7 +348,7 @@ impl FuncTable { } }, - Simple(Mfhi { rd } | Mflo { rd }) => { + Basic(Mfhi { rd } | Mflo { rd }) => { if let Some(idx) = rd.arg_idx() { if arguments[idx].is_none() { arguments[idx] = Some("u32"); @@ -356,7 +356,7 @@ impl FuncTable { } }, - Simple( + Basic( Bltz { rs, .. } | Bgez { rs, .. } | Bgtz { rs, .. } | diff --git a/dcb/src/game/exe/header.rs b/dcb/src/game/exe/header.rs index 5b68844..3b22725 100644 --- a/dcb/src/game/exe/header.rs +++ b/dcb/src/game/exe/header.rs @@ -4,6 +4,8 @@ pub mod error; // Exports +use std::fmt; + pub use error::{FromBytesError, ToBytesError}; // Import @@ -49,7 +51,34 @@ pub struct Header { pub initial_sp_offset: u32, /// Executable region marker - pub marker: AsciiStrArr<0x7b4>, + pub marker: AsciiStrArr<0x7b3>, +} + +impl fmt::Display for Header { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { + ref initial_pc, + ref initial_gp, + ref dest, + ref size, + ref memfill_start, + ref memfill_size, + ref initial_sp_base, + ref initial_sp_offset, + ref marker, + .. + } = self; + + write!( + f, + "PC: {initial_pc:#x} +GP: {initial_gp:#x} +Destination: {dest:#x} / size: {size:#x} +Memfill: {memfill_start:#X} / size: {memfill_size:#x} +SP: {initial_sp_base:#x} / offset: {initial_sp_offset:#x} +Marker: {marker:?}" + ) + } } impl Header { @@ -76,8 +105,8 @@ impl Bytes for Header { memfill_size : [0x4], initial_sp_base : [0x4], initial_sp_offset: [0x4], - _zero2 : [0x13], - marker : [0x7b5], + _zero2 : [0x14], + marker : [0x7b4], ); // If the magic is wrong, return Err diff --git a/dcb/src/game/exe/instruction.rs b/dcb/src/game/exe/instruction.rs index 959e688..0562c23 100644 --- a/dcb/src/game/exe/instruction.rs +++ b/dcb/src/game/exe/instruction.rs @@ -1,18 +1,18 @@ //! Psx cpu instructions // Modules +pub mod basic; pub mod directive; pub mod pseudo; pub mod raw; pub mod reg; -pub mod simple; // Exports +pub use basic::BasicInstruction; pub use directive::Directive; pub use pseudo::PseudoInstruction; pub use raw::{FromRawIter, Raw}; pub use reg::Register; -pub use simple::SimpleInstruction; // Imports use crate::game::exe::Pos; @@ -21,8 +21,8 @@ use crate::game::exe::Pos; #[derive(PartialEq, Eq, Clone, Debug)] #[derive(derive_more::Display)] pub enum Instruction { - /// A simple instruction - Simple(SimpleInstruction), + /// A basic instruction + Basic(BasicInstruction), /// A pseudo instruction Pseudo(PseudoInstruction), @@ -111,9 +111,9 @@ impl + Clone> Iterator for Iter { return self.try_next_from(Instruction::Directive); } - // Else try to decode it as a pseudo, simple or directive, in that order. + // Else try to decode it as a pseudo, basic or directive, in that order. self.try_next_from(Instruction::Pseudo) - .or_else(|| self.try_next_from(Instruction::Simple)) + .or_else(|| self.try_next_from(Instruction::Basic)) .or_else(|| self.try_next_from(Instruction::Directive)) } } diff --git a/dcb/src/game/exe/instruction/simple.rs b/dcb/src/game/exe/instruction/basic.rs similarity index 54% rename from dcb/src/game/exe/instruction/simple.rs rename to dcb/src/game/exe/instruction/basic.rs index 02bd30b..0d5862f 100644 --- a/dcb/src/game/exe/instruction/simple.rs +++ b/dcb/src/game/exe/instruction/basic.rs @@ -1,19 +1,130 @@ -//! Raw instructions +//! Basic instructions +//! +//! This modules defines all the basic instructions from the psx. +//! They are all 1 word (`u32`) long. -// Lints -// #[allow(clippy::similar_names)] +// Modules +pub mod alu_imm; +pub mod cond; +pub mod jmp; +pub mod load; +pub mod special; +pub mod store; + +// Exports +pub use alu_imm::{AluImmInst, AluImmRaw}; +pub use cond::{CondInst, CondRaw}; +pub use jmp::{JmpInst, JmpRaw}; +pub use load::{LoadInst, LoadRaw}; +pub use special::{SpecialInst, SpecialRaw}; +pub use store::{StoreInst, StoreRaw}; // Imports use super::{FromRawIter, Raw, Register}; use crate::{game::exe::Pos, util::SignedHex}; use bitmatch::bitmatch; -use int_conv::{SignExtended, Signed, Truncate, Truncated}; +use int_conv::{SignExtended, Signed, Truncate, Truncated, ZeroExtended}; + +/// All basic instructions +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(derive_more::Display)] +pub enum BasicInst { + /// Store + Store(StoreInst), + + /// Load + Load(LoadInst), + + /// Special + Special(SpecialInst), + + /// Condition + Cond(CondInst), + + /// Jump + Jmp(JmpInst), + + /// Alu immediate + AluImm(AluImmInst), + + /// Load upper immediate + #[display(fmt = "lui {dest}, {value:#x}")] + Lui { + /// Destination + dest: Register, + + /// Value + value: u16, + }, +} + +impl BasicInst { + // TODO: MAybe extract the strings if the bitmatch macro allows for it. + + /// Decodes this instruction + #[must_use] + #[bitmatch::bitmatch] + #[allow(clippy::many_single_char_names)] // `bitmatch` can only output single character names. + pub fn decode(raw: u32) -> Option { + Some( + #[bitmatch] + #[allow(unused_variables)] // TODO: Remove once `CopInst` is implemented. + match raw { + "000000_sssss_ttttt_ddddd_iiiii_ffffff" => Self::Special(SpecialInst::decode(SpecialRaw { s, t, d, i, f })?), + "00001p_iiiii_iiiii_iiiii_iiiii_iiiiii" => Self::Jmp(JmpInst::decode(JmpRaw { p, i })), + "000ppp_sssss_ttttt_iiiii_iiiii_iiiiii" => Self::Cond(CondInst::decode(CondRaw { p, s, t, i })?), + "001111_?????_ttttt_iiiii_iiiii_iiiiii" => Self::Lui { + dest: Register::new(t)?, + value: i.truncated::(), + }, + "001ppp_sssss_ttttt_iiiii_iiiii_iiiiii" => Self::AluImm(AluImmInst::decode(AluImmRaw { p, s, t, i })?), + "100ppp_sssss_ttttt_iiiii_iiiii_iiiiii" => Self::Store(StoreInst::decode(StoreRaw { p, s, t, i })?), + "101ppp_sssss_ttttt_iiiii_iiiii_iiiiii" => Self::Load(LoadInst::decode(LoadRaw { p, s, t, i })?), + + /* + "0100nn_1iiii_iiiii_iiiii_iiiii_iiiiii" => Self::Cop(), + "0100nn_00000_ttttt_ddddd_?????_000000" => Self::Cop(), + "0100nn_00010_ttttt_ddddd_?????_000000" => Self::Cop(), + "0100nn_00100_ttttt_ddddd_?????_000000" => Self::Cop(), + "0100nn_00110_ttttt_ddddd_?????_000000" => Self::Cop(), + "0100nn_01000_00000_iiiii_iiiii_iiiiii" => Self::Cop(), + "0100nn_01000_00001_iiiii_iiiii_iiiiii" => Self::Cop(), + "1100nn_sssss_ttttt_iiiii_iiiii_iiiiii" => Self::Cop(), + "1110nn_sssss_ttttt_iiiii_iiiii_iiiiii" => Self::Cop(), + */ + _ => return None, + }, + ) + } + + /// Encodes this instruction + #[must_use] + #[bitmatch] + pub fn encode(self) -> u32 { + #[rustfmt::skip] + match self { + Self::Special(inst) => { let SpecialRaw { s, t, d, i, f } = inst.encode(); bitpack!("000000_sssss_ttttt_ddddd_iiiii_ffffff") }, + Self::Jmp (inst) => { let JmpRaw { p, i } = inst.encode(); bitpack!("00001p_iiiii_iiiii_iiiii_iiiii_iiiiii") }, + Self::Cond (inst) => { let CondRaw { p, s, t, i } = inst.encode(); bitpack!("000ppp_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Self::AluImm (inst) => { let AluImmRaw { p, s, t, i } = inst.encode(); bitpack!("001ppp_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Self::Store (inst) => { let StoreRaw { p, s, t, i } = inst.encode(); bitpack!("100ppp_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Self::Load (inst) => { let LoadRaw { p, s, t, i } = inst.encode(); bitpack!("101ppp_sssss_ttttt_iiiii_iiiii_iiiiii") }, + + Self::Lui { dest, value } => { + let t = dest.idx(); + let i = value.zero_extended::(); + bitpack!("001111_?????_ttttt_iiiii_iiiii_iiiiii") + }, + } + } +} + /// All simple instructions #[derive(PartialEq, Eq, Clone, Copy, Debug)] #[derive(derive_more::Display)] #[allow(clippy::missing_docs_in_private_items)] // They're mostly register and immediate names. -pub enum SimpleInstruction { +pub enum BasicInstruction { /// Store byte #[display(fmt = "sb {rt}, {:#x}({rs})", "SignedHex(offset)")] Sb { rt: Register, rs: Register, offset: i16 }, @@ -287,22 +398,27 @@ pub enum SimpleInstruction { Break { imm: u32 }, } -impl SimpleInstruction { +impl BasicInstruction { /// Decodes an instruction from it's raw representation #[bitmatch] #[allow(clippy::cognitive_complexity)] // It's just a big match, not much we can do about it. - fn decode_repr(Raw { repr, pos }: Raw) -> Option { + fn decode_repr(raws: &[u32], pos: Pos) -> Option<(Self, &[u32])> { #[allow(clippy::enum_glob_use)] // It's local to this function and REALLY reduces on the noise - use SimpleInstruction::*; + use BasicInstruction::*; /// Alias for `Register::new` fn reg(idx: u32) -> Option { Register::new(idx.truncated()) } + let (raw, raws) = match raws { + [raw, raws @ ..] => (raw, raws), + _ => return None, + }; + #[rustfmt::skip] let instruction = #[bitmatch] - match repr { + match raw { "000000_?????_ttttt_ddddd_iiiii_000000" => Sll { rd: reg(d)?, rt: reg(t)?, imm: i.truncated()}, "000000_?????_ttttt_ddddd_iiiii_000010" => Srl { rd: reg(d)?, rt: reg(t)?, imm: i.truncated()}, "000000_?????_ttttt_ddddd_iiiii_000011" => Sra { rd: reg(d)?, rt: reg(t)?, imm: i.truncated()}, @@ -349,8 +465,8 @@ impl SimpleInstruction { "000100_sssss_ttttt_iiiii_iiiii_iiiiii" => Beq { rs: reg(s)?, rt: reg(t)?, target: pos + (i.truncated::().as_signed().sign_extended::() + 1) * 4 }, "000101_sssss_ttttt_iiiii_iiiii_iiiiii" => Bne { rs: reg(s)?, rt: reg(t)?, target: pos + (i.truncated::().as_signed().sign_extended::() + 1) * 4 }, - "000110_sssss_?????_iiiii_iiiii_iiiiii" => Blez { rs: reg(s)? , target: pos + (i.truncated::().as_signed().sign_extended::() + 1) * 4 }, - "000111_sssss_?????_iiiii_iiiii_iiiiii" => Bgtz { rs: reg(s)? , target: pos + (i.truncated::().as_signed().sign_extended::() + 1) * 4 }, + "000110_sssss_?????_iiiii_iiiii_iiiiii" => Blez { rs: reg(s)? , target: pos + (i.truncated::().as_signed().sign_extended::() + 1) * 4 }, + "000111_sssss_?????_iiiii_iiiii_iiiiii" => Bgtz { rs: reg(s)? , target: pos + (i.truncated::().as_signed().sign_extended::() + 1) * 4 }, "001000_sssss_ttttt_iiiii_iiiii_iiiiii" => Addi { rt: reg(t)?, rs: reg(s)?, imm: i.truncated::().as_signed() }, "001001_sssss_ttttt_iiiii_iiiii_iiiiii" => Addiu { rt: reg(t)?, rs: reg(s)?, imm: i.truncated::().as_signed() }, @@ -359,7 +475,7 @@ impl SimpleInstruction { "001100_sssss_ttttt_iiiii_iiiii_iiiiii" => Andi { rt: reg(t)?, rs: reg(s)?, imm: i.truncated() }, "001101_sssss_ttttt_iiiii_iiiii_iiiiii" => Ori { rt: reg(t)?, rs: reg(s)?, imm: i.truncated() }, "001110_sssss_ttttt_iiiii_iiiii_iiiiii" => Xori { rt: reg(t)?, rs: reg(s)?, imm: i.truncated() }, - "001111_?????_ttttt_iiiii_iiiii_iiiiii" => Lui { rt: reg(t)? , imm: i.truncated() }, + "001111_?????_ttttt_iiiii_iiiii_iiiiii" => Lui { rt: reg(t)? , imm: i.truncated() }, "0100nn_1iiii_iiiii_iiiii_iiiii_iiiiii" => CopN { n: n.truncate(), imm: i}, @@ -390,16 +506,114 @@ impl SimpleInstruction { _ => return None, }; - Some(instruction) + Some((instruction, raws)) + } + + /// Encodes an instruction. + #[bitmatch] + #[must_use] + pub fn encode_repr(self, pos: Pos) -> Raw { + #[allow(clippy::enum_glob_use)] // It's local to this function and REALLY reduces on the noise + use BasicInstruction::*; + + #[rustfmt::skip] + let repr = match self { + Sll { rd, rt, imm: i } => { let (d, t) = (rd.idx(), rt.idx()); bitpack!("000000_00000_ttttt_ddddd_iiiii_000000") }, + + Srl { rd, rt, imm: i } => { let (d, t) = (rd.idx(), rt.idx()); bitpack!("000000_00000_ttttt_ddddd_iiiii_000010") }, + Sra { rd, rt, imm: i } => { let (d, t) = (rd.idx(), rt.idx()); bitpack!("000000_00000_ttttt_ddddd_iiiii_000011") }, + + Sllv { rd, rt, rs } => { let (d, t, s) = (rd.idx(), rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_ddddd_00000_000100") }, + Srlv { rd, rt, rs } => { let (d, t, s) = (rd.idx(), rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_ddddd_00000_000110") }, + Srav { rd, rt, rs } => { let (d, t, s) = (rd.idx(), rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_ddddd_00000_000111") }, + + Jr { rs } => { let s = rs.idx() ; bitpack!("000000_sssss_00000_00000_00000_001000") }, + Jalr { rd, rs } => { let (d, s) = (rd.idx(), rs.idx()); bitpack!("000000_sssss_00000_ddddd_00000_001001") }, + + Syscall { imm: i } => { bitpack!("000000_iiiii_iiiii_iiiii_iiiii_001100") }, + Break { imm: i } => { bitpack!("000000_iiiii_iiiii_iiiii_iiiii_001101") }, + + Mfhi { rd } => { let d = rd.idx(); bitpack!("000000_00000_00000_ddddd_00000_010000") }, + Mthi { rs } => { let s = rs.idx(); bitpack!("000000_sssss_00000_00000_00000_010001") }, + Mflo { rd } => { let d = rd.idx(); bitpack!("000000_00000_00000_ddddd_00000_010010") }, + Mtlo { rs } => { let s = rs.idx(); bitpack!("000000_sssss_00000_00000_00000_010011") }, + + Mult { rs, rt } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_00000_00000_011000") }, + Multu { rs, rt } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_00000_00000_011001") }, + Div { rs, rt } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_00000_00000_011010") }, + Divu { rs, rt } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_00000_00000_011011") }, + + Add { rd, rs, rt } => { let (d, t, s) = (rd.idx(), rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_ddddd_00000_100000") }, + Addu { rd, rs, rt } => { let (d, t, s) = (rd.idx(), rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_ddddd_00000_100001") }, + Sub { rd, rs, rt } => { let (d, t, s) = (rd.idx(), rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_ddddd_00000_100010") }, + Subu { rd, rs, rt } => { let (d, t, s) = (rd.idx(), rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_ddddd_00000_100011") }, + And { rd, rs, rt } => { let (d, t, s) = (rd.idx(), rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_ddddd_00000_100100") }, + Or { rd, rs, rt } => { let (d, t, s) = (rd.idx(), rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_ddddd_00000_100101") }, + Xor { rd, rs, rt } => { let (d, t, s) = (rd.idx(), rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_ddddd_00000_100110") }, + Nor { rd, rs, rt } => { let (d, t, s) = (rd.idx(), rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_ddddd_00000_100111") }, + + Slt { rd, rs, rt } => { let (d, t, s) = (rd.idx(), rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_ddddd_00000_101010") }, + Sltu { rd, rs, rt } => { let (d, t, s) = (rd.idx(), rt.idx(), rs.idx()); bitpack!("000000_sssss_ttttt_ddddd_00000_101011") }, + + Bltz { rs, target } => { let s = rs.idx(); let i = (target - pos) / 4 - 1; bitpack!("000001_sssss_00000_iiiii_iiiii_iiiiii") }, + Bgez { rs, target } => { let s = rs.idx(); let i = (target - pos) / 4 - 1; bitpack!("000001_sssss_00000_iiiii_iiiii_iiiiii") }, + Bltzal { rs, target } => { let s = rs.idx(); let i = (target - pos) / 4 - 1; bitpack!("000001_sssss_00000_iiiii_iiiii_iiiiii") }, + Bgezal { rs, target } => { let s = rs.idx(); let i = (target - pos) / 4 - 1; bitpack!("000001_sssss_00000_iiiii_iiiii_iiiiii") }, + + J { target } => { let i = (target - (pos & 0x0fff_ffff)) / 4; bitpack!("000010_iiiii_iiiii_iiiii_iiiii_iiiiii") }, + Jal { target } => { let i = (target - (pos & 0x0fff_ffff)) / 4; bitpack!("000011_iiiii_iiiii_iiiii_iiiii_iiiiii") }, + + Beq { rs, rt, target } => { let (s, t) = (rs.idx(), rt.idx()); let i = (target - pos) / 4 - 1; bitpack!("000100_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Bne { rs, rt, target } => { let (s, t) = (rs.idx(), rt.idx()); let i = (target - pos) / 4 - 1; bitpack!("000101_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Blez { rs , target } => { let s = rs.idx() ; let i = (target - pos) / 4 - 1; bitpack!("000110_sssss_00000_iiiii_iiiii_iiiiii") }, + Bgtz { rs , target } => { let s = rs.idx() ; let i = (target - pos) / 4 - 1; bitpack!("000111_sssss_00000_iiiii_iiiii_iiiiii") }, + + Addi { rt, rs, imm: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("001000_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Addiu { rt, rs, imm: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("001001_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Slti { rt, rs, imm: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("001010_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Sltiu { rt, rs, imm: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("001011_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Andi { rt, rs, imm: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("001100_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Ori { rt, rs, imm: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("001101_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Xori { rt, rs, imm: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("001110_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Lui { rt, imm: i } => { let t = rt.idx(); bitpack!("001111_00000_ttttt_iiiii_iiiii_iiiiii") }, + + CopN { n, imm: i } => { bitpack!("0100nn_1iiii_iiiii_iiiii_iiiii_iiiiii") }, + + MfcN { n, rt, rd } => { let (t, d) = (rt.idx(), rd.idx()); bitpack!("0100nn_00000_ttttt_ddddd_00000_000000") }, + CfcN { n, rt, rd } => { let (t, d) = (rt.idx(), rd.idx()); bitpack!("0100nn_00010_ttttt_ddddd_00000_000000") }, + MtcN { n, rt, rd } => { let (t, d) = (rt.idx(), rd.idx()); bitpack!("0100nn_00100_ttttt_ddddd_00000_000000") }, + CtcN { n, rt, rd } => { let (t, d) = (rt.idx(), rd.idx()); bitpack!("0100nn_00110_ttttt_ddddd_00000_000000") }, + BcNf { n, target: i } => { bitpack!("0100nn_01000_00000_iiiii_iiiii_iiiiii") }, + BcNt { n, target: i } => { bitpack!("0100nn_01000_00001_iiiii_iiiii_iiiiii") }, + + Lb { rt, rs, offset: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("100000_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Lh { rt, rs, offset: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("100001_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Lwl { rt, rs, offset: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("100010_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Lw { rt, rs, offset: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("100011_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Lbu { rt, rs, offset: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("100100_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Lhu { rt, rs, offset: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("100101_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Lwr { rt, rs, offset: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("100110_sssss_ttttt_iiiii_iiiii_iiiiii") }, + + Sb { rt, rs, offset: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("101000_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Sh { rt, rs, offset: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("101001_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Swl { rt, rs, offset: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("101010_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Sw { rt, rs, offset: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("101011_sssss_ttttt_iiiii_iiiii_iiiiii") }, + Swr { rt, rs, offset: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("101110_sssss_ttttt_iiiii_iiiii_iiiiii") }, + + LwcN { n, rs, rt, imm: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("1100nn_sssss_ttttt_iiiii_iiiii_iiiiii") }, + SwcN { n, rs, rt, imm: i } => { let (t, s) = (rt.idx(), rs.idx()); bitpack!("1110nn_sssss_ttttt_iiiii_iiiii_iiiiii") }, + }; + + Raw { repr, pos } } } -impl FromRawIter for SimpleInstruction { +impl FromRawIter for BasicInstruction { type Decoded = Option<(Pos, Self)>; fn decode + Clone>(iter: &mut I) -> Self::Decoded { let raw = iter.next()?; - let instruction = Self::decode_repr(raw)?; + let (instruction, _) = Self::decode_repr(std::slice::from_ref(&raw.repr), raw.pos)?; Some((raw.pos, instruction)) } } diff --git a/dcb/src/game/exe/instruction/basic/alu_imm.rs b/dcb/src/game/exe/instruction/basic/alu_imm.rs new file mode 100644 index 0000000..950a457 --- /dev/null +++ b/dcb/src/game/exe/instruction/basic/alu_imm.rs @@ -0,0 +1,108 @@ +//! Alu immediate instructions + +// Imports +use crate::{game::exe::instruction::Register, util::SignedHex}; +use int_conv::{Signed, Truncated, ZeroExtended}; +use std::{convert::TryFrom, fmt}; + +/// Alu register opcode (lower 3 bits) +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum AluImmOp { + /// Add + Add = 0x0, + + /// Add unsigned + AddUnsigned = 0x1, + + /// Set less than + SetLessThan = 0x2, + + /// Set less than unsigned + SetLessThanUnsigned = 0x3, + + /// And + And = 0x4, + + /// Or + Or = 0x5, + + /// Xor + Xor = 0x6, +} + +/// Raw representation +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct AluImmRaw { + /// Opcode (lower 3 bits) + pub p: u32, + + /// Rs + pub s: u32, + + /// Rt + pub t: u32, + + /// Immediate + pub i: u32, +} + +/// Alu register instructions +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct AluImmInst { + /// Destination register, `rd` + pub dest: Register, + + /// Lhs argument, `rs` + pub lhs: Register, + + /// Rhs immediate argument + pub rhs: u32, + + /// Opcode + pub op: AluImmOp, +} + +impl AluImmInst { + /// Decodes this instruction + #[must_use] + pub fn decode(raw: AluImmRaw) -> Option { + let op = AluImmOp::try_from(raw.p.truncated::()).ok()?; + + Some(Self { + dest: Register::new(raw.t)?, + lhs: Register::new(raw.s)?, + rhs: raw.i, + op, + }) + } + + /// Encodes this instruction + #[must_use] + pub fn encode(self) -> AluImmRaw { + let p = u8::from(self.op).zero_extended::(); + let s = self.lhs.idx(); + let t = self.dest.idx(); + let i = self.rhs; + + AluImmRaw { p, s, t, i } + } +} + +impl fmt::Display for AluImmInst { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { op, dest, lhs, rhs } = self; + + #[rustfmt::skip] + match op { + AluImmOp::Add => write!(f, "addi {dest}, {lhs}, {:#x}", SignedHex(rhs.as_signed())), + AluImmOp::AddUnsigned => write!(f, "addiu {dest}, {lhs}, {:#x}", SignedHex(rhs.as_signed())), + AluImmOp::SetLessThan => write!(f, "slti {dest}, {lhs}, {:#x}", SignedHex(rhs.as_signed())), + AluImmOp::SetLessThanUnsigned => write!(f, "sltiu {dest}, {lhs}, {rhs:#x}"), + AluImmOp::And => write!(f, "andi {dest}, {lhs}, {rhs:#x}"), + AluImmOp::Or => write!(f, "ori {dest}, {lhs}, {rhs:#x}"), + AluImmOp::Xor => write!(f, "xori {dest}, {lhs}, {rhs:#x}"), + } + } +} diff --git a/dcb/src/game/exe/instruction/basic/cond.rs b/dcb/src/game/exe/instruction/basic/cond.rs new file mode 100644 index 0000000..e6c1dfd --- /dev/null +++ b/dcb/src/game/exe/instruction/basic/cond.rs @@ -0,0 +1,131 @@ +//! Condition branches + +// Imports +use crate::{game::exe::instruction::Register, util::SignedHex}; +use int_conv::{Signed, Truncated, ZeroExtended}; +use std::fmt; + +/// Raw representation +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct CondRaw { + /// Opcode (lower 3 bits) + pub p: u32, + + /// Rs + pub s: u32, + + /// Rt + pub t: u32, + + /// Immediate + pub i: u32, +} + +/// Condition kind +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum CondKind { + /// Equal + Equal(Register), + + /// Not equal + NotEqual(Register), + + /// Less than or zero + LessOrEqualZero, + + /// Greater than zero + GreaterThanZero, + + /// Less than zero + LessThanZero, + + /// Greater than or zero + GreaterOrEqualZero, + + /// Less than zero and link + LessThanZeroLink, + + /// Greater than or zero and link + GreaterOrEqualZeroLink, +} + +/// Condition instructions +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct CondInst { + /// Argument, `rs` + pub arg: Register, + + /// Offset + pub offset: i16, + + /// Kind + pub kind: CondKind, +} + +impl CondInst { + /// Decodes this instruction + #[must_use] + pub fn decode(raw: CondRaw) -> Option { + let kind = match raw.p { + 0x1 => match raw.t { + 0b00000 => CondKind::LessThanZero, + 0b00001 => CondKind::GreaterOrEqualZero, + 0b10000 => CondKind::LessThanZeroLink, + 0b10001 => CondKind::GreaterOrEqualZeroLink, + _ => return None, + }, + 0x4 => CondKind::Equal(Register::new(raw.t)?), + 0x5 => CondKind::NotEqual(Register::new(raw.t)?), + 0x6 => CondKind::LessOrEqualZero, + 0x7 => CondKind::GreaterThanZero, + _ => return None, + }; + + Some(Self { + arg: Register::new(raw.s)?, + offset: raw.i.truncated::().as_signed(), + kind, + }) + } + + /// Encodes this instruction + #[must_use] + pub fn encode(self) -> CondRaw { + #[rustfmt::skip] + let (p, t) = match self.kind { + CondKind::Equal(reg) => (0x4, reg.idx()), + CondKind::NotEqual(reg) => (0x5, reg.idx()), + CondKind::LessOrEqualZero => (0x6, 0), + CondKind::GreaterThanZero => (0x7, 0), + CondKind::LessThanZero => (0x1, 0b00000), + CondKind::GreaterOrEqualZero => (0x1, 0b00001), + CondKind::LessThanZeroLink => (0x1, 0b10000), + CondKind::GreaterOrEqualZeroLink => (0x1, 0b10001), + }; + + let s = self.arg.idx(); + let i = self.offset.as_unsigned().zero_extended(); + + CondRaw { p, s, t, i } + } +} + +// TODO: Fmt given `pc` / label + +impl fmt::Display for CondInst { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { arg, offset, kind } = self; + + #[rustfmt::skip] + match kind { + CondKind::Equal(reg) => write!(f, "beq {arg}, {reg}, {:#x}", SignedHex(offset)), + CondKind::NotEqual(reg) => write!(f, "bne {arg}, {reg}, {:#x}", SignedHex(offset)), + CondKind::LessOrEqualZero => write!(f, "blez {arg}, {:#x}" , SignedHex(offset)), + CondKind::GreaterThanZero => write!(f, "bgtz {arg}, {:#x}" , SignedHex(offset)), + CondKind::LessThanZero => write!(f, "bltz {arg}, {:#x}" , SignedHex(offset)), + CondKind::GreaterOrEqualZero => write!(f, "bgez {arg}, {:#x}" , SignedHex(offset)), + CondKind::LessThanZeroLink => write!(f, "bltzal {arg}, {:#x}" , SignedHex(offset)), + CondKind::GreaterOrEqualZeroLink => write!(f, "bgezal {arg}, {:#x}" , SignedHex(offset)), + } + } +} diff --git a/dcb/src/game/exe/instruction/basic/jmp.rs b/dcb/src/game/exe/instruction/basic/jmp.rs new file mode 100644 index 0000000..d6bc041 --- /dev/null +++ b/dcb/src/game/exe/instruction/basic/jmp.rs @@ -0,0 +1,74 @@ +//! Alu register instructions + +// Imports +use std::fmt; + +/// Alu register func (bottom bit) +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum JmpKind { + /// Jump + Jump, + + /// Jump and link + Link, +} + +/// Raw representation +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct JmpRaw { + /// Opcode (bottom bit) + pub p: u32, + + /// Immediate + pub i: u32, +} + +/// Alu register instructions +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct JmpInst { + /// Target + pub target: u32, + + /// Kind + pub kind: JmpKind, +} + +impl JmpInst { + /// Decodes this instruction + #[must_use] + pub fn decode(raw: JmpRaw) -> Self { + let kind = match raw.p { + 0 => JmpKind::Jump, + 1 => JmpKind::Link, + _ => unreachable!("Received invalid bit in opcode."), + }; + + Self { target: raw.i, kind } + } + + /// Encodes this instruction + #[must_use] + pub const fn encode(self) -> JmpRaw { + let p = match self.kind { + JmpKind::Jump => 0, + JmpKind::Link => 1, + }; + + JmpRaw { p, i: self.target } + } +} + +// TODO: Format with `pc` / `label`. + +impl fmt::Display for JmpInst { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { target, kind } = self; + + let mnemonic = match kind { + JmpKind::Jump => "j", + JmpKind::Link => "jal", + }; + + write!(f, "{mnemonic} {target}") + } +} diff --git a/dcb/src/game/exe/instruction/basic/load.rs b/dcb/src/game/exe/instruction/basic/load.rs new file mode 100644 index 0000000..8235c36 --- /dev/null +++ b/dcb/src/game/exe/instruction/basic/load.rs @@ -0,0 +1,109 @@ +//! Load instructions + +// Imports +use crate::game::exe::instruction::Register; +use int_conv::{Signed, Truncated, ZeroExtended}; +use std::{convert::TryFrom, fmt}; + +/// Load instruction opcode (lower 3 bits) +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum LoadOpcode { + /// Byte, `i8` + Byte = 0x0, + + /// Half-word, `i16` + HalfWord = 0x1, + + /// Word left-bits, `u32` + WordLeft = 0x2, + + /// Word, `u32` + Word = 0x3, + + /// Byte unsigned, `u8` + ByteUnsigned = 0x4, + + /// Half-word unsigned, `u16` + HalfWordUnsigned = 0x5, + + /// Word right-bits, `u32` + WordRight = 0x6, +} + +/// Raw representation +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct LoadRaw { + /// Opcode (lower 3 bits) + pub p: u32, + + /// Rs + pub s: u32, + + /// Rt + pub t: u32, + + /// Immediate + pub i: u32, +} + +/// Load instructions +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct LoadInst { + /// Source register, `rt` + pub source: Register, + + /// Destination register, `rs` + pub dest: Register, + + /// Destination offset. + pub offset: i16, + + /// Opcode + pub op: LoadOpcode, +} + +impl LoadInst { + /// Decodes this instruction + #[must_use] + pub fn decode(raw: LoadRaw) -> Option { + let op = LoadOpcode::try_from(raw.p.truncated::()).ok()?; + + Some(Self { + source: Register::new(raw.t)?, + dest: Register::new(raw.s)?, + offset: raw.i.truncated::().as_signed(), + op, + }) + } + + /// Encodes this instruction + #[must_use] + pub fn encode(self) -> LoadRaw { + let t = self.source.idx(); + let s = self.dest.idx(); + let i = self.offset.as_unsigned().zero_extended::(); + let p = u8::from(self.op).zero_extended::(); + + LoadRaw { p, s, t, i } + } +} + +impl fmt::Display for LoadInst { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { source, dest, offset, op } = self; + + let mnemonic = match op { + LoadOpcode::Byte => "lb", + LoadOpcode::HalfWord => "lh", + LoadOpcode::WordLeft => "lwl", + LoadOpcode::Word => "lw", + LoadOpcode::ByteUnsigned => "lbu", + LoadOpcode::HalfWordUnsigned => "lhu", + LoadOpcode::WordRight => "lwr", + }; + + write!(f, "{mnemonic} {dest}, {offset}({source})") + } +} diff --git a/dcb/src/game/exe/instruction/basic/special.rs b/dcb/src/game/exe/instruction/basic/special.rs new file mode 100644 index 0000000..6e398e3 --- /dev/null +++ b/dcb/src/game/exe/instruction/basic/special.rs @@ -0,0 +1,143 @@ +//! ALU instructions + +// Modules +pub mod alu_reg; +pub mod jmp; +pub mod mult; +pub mod shift; +pub mod sys; + +// Exports +pub use alu_reg::{AluRegInst, AluRegRaw}; +pub use jmp::{JmpInst, JmpRaw}; +pub use mult::{MultInst, MultRaw}; +pub use shift::{ShiftInst, ShiftRaw}; +pub use sys::{SysInst, SysRaw}; + +/// Raw representation +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct SpecialRaw { + /// Rs + pub s: u32, + + /// Rt + pub t: u32, + + /// Rd + pub d: u32, + + /// Immediate + pub i: u32, + + /// Func + pub f: u32, +} + +impl From for ShiftRaw { + fn from(SpecialRaw { t, d, s, i, f }: SpecialRaw) -> Self { + Self { t, d, s, i, f } + } +} + +impl From for SpecialRaw { + fn from(ShiftRaw { t, d, s, i, f }: ShiftRaw) -> Self { + Self { t, d, s, i, f } + } +} + +impl From for JmpRaw { + fn from(SpecialRaw { d, s, f, .. }: SpecialRaw) -> Self { + Self { d, s, f } + } +} + +impl From for SpecialRaw { + fn from(JmpRaw { d, s, f }: JmpRaw) -> Self { + Self { t: 0, d, s, i: 0, f } + } +} + +impl From for SysRaw { + fn from(SpecialRaw { t, d, s, i, f }: SpecialRaw) -> Self { + Self { t, d, s, i, f } + } +} + +impl From for SpecialRaw { + fn from(SysRaw { t, d, s, i, f }: SysRaw) -> Self { + Self { t, d, s, i, f } + } +} + +impl From for MultRaw { + fn from(SpecialRaw { t, d, s, f, .. }: SpecialRaw) -> Self { + Self { t, d, s, f } + } +} + +impl From for SpecialRaw { + fn from(MultRaw { t, d, s, f }: MultRaw) -> Self { + Self { t, d, s, i: 0, f } + } +} + +impl From for AluRegRaw { + fn from(SpecialRaw { t, d, s, f, .. }: SpecialRaw) -> Self { + Self { t, d, s, f } + } +} + +impl From for SpecialRaw { + fn from(AluRegRaw { t, d, s, f }: AluRegRaw) -> Self { + Self { t, d, s, i: 0, f } + } +} + +/// Special instructions for `opcode: 0` +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(derive_more::Display)] +pub enum SpecialInst { + /// Shift instruction + Shift(ShiftInst), + + /// Jump + Jmp(JmpInst), + + /// Sys + Sys(SysInst), + + /// Mult + Mult(MultInst), + + /// Alu + Alu(AluRegInst), +} + +impl SpecialInst { + /// Decodes this instruction + #[must_use] + pub fn decode(raw: SpecialRaw) -> Option { + Some(match raw.f { + 0x00..0x08 => Self::Shift(ShiftInst::decode(raw.into())?), + 0x08..0x0c => Self::Jmp(JmpInst::decode(raw.into())?), + 0x0c..0x10 => Self::Sys(SysInst::decode(raw.into())?), + 0x10..0x20 => Self::Mult(MultInst::decode(raw.into())?), + 0x20..0x30 => Self::Alu(AluRegInst::decode(raw.into())?), + 0x30..0x40 => return None, + + _ => unreachable!("Func was larger than 6 bits."), + }) + } + + /// Encodes this instruction + #[must_use] + pub fn encode(self) -> SpecialRaw { + match self { + Self::Shift(inst) => inst.encode().into(), + Self::Jmp(inst) => inst.encode().into(), + Self::Sys(inst) => inst.encode().into(), + Self::Mult(inst) => inst.encode().into(), + Self::Alu(inst) => inst.encode().into(), + } + } +} diff --git a/dcb/src/game/exe/instruction/basic/special/alu_reg.rs b/dcb/src/game/exe/instruction/basic/special/alu_reg.rs new file mode 100644 index 0000000..a6710ac --- /dev/null +++ b/dcb/src/game/exe/instruction/basic/special/alu_reg.rs @@ -0,0 +1,121 @@ +//! Alu register instructions + +// Imports +use crate::game::exe::instruction::Register; +use int_conv::{Truncated, ZeroExtended}; +use std::{convert::TryFrom, fmt}; + +/// Alu register func +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum AluRegFunc { + /// Add + Add = 0x20, + + /// Add unsigned + AddUnsigned = 0x21, + + /// Sub + Sub = 0x22, + + /// Sub unsigned + SubUnsigned = 0x23, + + /// And + And = 0x24, + + /// Or + Or = 0x25, + + /// Xor + Xor = 0x26, + + /// Nor + Nor = 0x27, + + /// Set less than + SetLessThan = 0x2a, + + /// Set less than unsigned + SetLessThanUnsigned = 0x2b, +} + +/// Raw representation +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct AluRegRaw { + /// Rs + pub s: u32, + + /// Rt + pub t: u32, + + /// Rd + pub d: u32, + + /// Func + pub f: u32, +} + +/// Alu register instructions +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct AluRegInst { + /// Destination register, `rd` + pub dest: Register, + + /// Lhs argument, `rs` + pub lhs: Register, + + /// Rhs argument, `rt` + pub rhs: Register, + + /// Function + pub func: AluRegFunc, +} + +impl AluRegInst { + /// Decodes this instruction + #[must_use] + pub fn decode(raw: AluRegRaw) -> Option { + let func = AluRegFunc::try_from(raw.f.truncated::()).ok()?; + + Some(Self { + dest: Register::new(raw.d)?, + lhs: Register::new(raw.s)?, + rhs: Register::new(raw.t)?, + func, + }) + } + + /// Encodes this instruction + #[must_use] + pub fn encode(self) -> AluRegRaw { + let d = self.dest.idx(); + let s = self.lhs.idx(); + let t = self.rhs.idx(); + let f = u8::from(self.func).zero_extended::(); + + AluRegRaw { f, t, d, s } + } +} + +impl fmt::Display for AluRegInst { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { dest, lhs, rhs, func } = self; + + let mnemonic = match func { + AluRegFunc::Add => "add", + AluRegFunc::AddUnsigned => "addu", + AluRegFunc::Sub => "sub", + AluRegFunc::SubUnsigned => "subu", + AluRegFunc::And => "and", + AluRegFunc::Or => "or", + AluRegFunc::Xor => "xor", + AluRegFunc::Nor => "nor", + AluRegFunc::SetLessThan => "slt", + AluRegFunc::SetLessThanUnsigned => "sltu", + }; + + write!(f, "{mnemonic} {dest}, {lhs}, {rhs}") + } +} diff --git a/dcb/src/game/exe/instruction/basic/special/jmp.rs b/dcb/src/game/exe/instruction/basic/special/jmp.rs new file mode 100644 index 0000000..4d84231 --- /dev/null +++ b/dcb/src/game/exe/instruction/basic/special/jmp.rs @@ -0,0 +1,78 @@ +//! Jumps + +// Imports +use crate::game::exe::instruction::Register; +use std::fmt; + +/// Jump kind +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum JmpKind { + /// Simple + Simple, + + /// With link + Link(Register), +} + +/// Raw representation +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct JmpRaw { + /// Rs + pub s: u32, + + /// Rd + pub d: u32, + + /// Func + pub f: u32, +} + +/// Jump instructions +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct JmpInst { + /// Target register, `rs` + pub target: Register, + + /// Jump kind, `rs`if `jalr`. + pub kind: JmpKind, +} + +impl JmpInst { + /// Decodes this instruction + #[must_use] + pub fn decode(raw: JmpRaw) -> Option { + let kind = match raw.f { + 0x8 => JmpKind::Simple, + 0x9 => JmpKind::Link(Register::new(raw.d)?), + _ => return None, + }; + + Some(Self { + target: Register::new(raw.s)?, + kind, + }) + } + + /// Encodes this instruction + #[must_use] + pub fn encode(self) -> JmpRaw { + let s = self.target.idx(); + let (d, f) = match self.kind { + JmpKind::Simple => (0, 0x8), + JmpKind::Link(d) => (d.idx(), 0x9), + }; + + JmpRaw { s, d, f } + } +} + +impl fmt::Display for JmpInst { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { target, kind } = self; + + match kind { + JmpKind::Simple => write!(f, "jr {target}"), + JmpKind::Link(link) => write!(f, "jalr {link}, {target}"), + } + } +} diff --git a/dcb/src/game/exe/instruction/basic/special/mult.rs b/dcb/src/game/exe/instruction/basic/special/mult.rs new file mode 100644 index 0000000..40f2449 --- /dev/null +++ b/dcb/src/game/exe/instruction/basic/special/mult.rs @@ -0,0 +1,168 @@ +//! Multiplications + +// Imports +use crate::game::exe::instruction::Register; +use std::fmt; + +/// Operation func +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum MultKind { + /// Multiplication + Mult, + + /// Division + Div, +} + +/// Operation mode +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum MultMode { + /// Signed + Signed, + + /// Unsigned + Unsigned, +} + +/// Multiplication register +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum MultReg { + /// Lo + Lo, + + /// Hi + Hi, +} + +/// Raw representation +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct MultRaw { + /// Rs + pub s: u32, + + /// Rt + pub t: u32, + + /// Rd + pub d: u32, + + /// Func + pub f: u32, +} + +/// Multiplication instructions +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub enum MultInst { + /// Multiplication + Mult { + /// Kind + kind: MultKind, + + /// Mode + mode: MultMode, + + /// Lhs argument + lhs: Register, + + /// Rhs argument + rhs: Register, + }, + + /// Move from + MoveFrom { + /// Destination + dest: Register, + + /// Source + source: MultReg, + }, + + /// Move to + MoveTo { + /// Source + source: Register, + + /// Destination + dest: MultReg, + }, +} + +impl MultInst { + /// Decodes this instruction + #[must_use] + pub fn decode(raw: MultRaw) -> Option { + #[rustfmt::skip] + Some(match raw.f { + 0x10 => Self::MoveFrom { dest: Register::new(raw.d)?, source: MultReg::Hi }, + 0x12 => Self::MoveFrom { dest: Register::new(raw.d)?, source: MultReg::Lo }, + + 0x11 => Self::MoveTo { source: Register::new(raw.s)?, dest: MultReg::Hi }, + 0x13 => Self::MoveTo { source: Register::new(raw.s)?, dest: MultReg::Lo }, + + 0x18 => Self::Mult { kind: MultKind::Mult, mode: MultMode:: Signed, lhs: Register::new(raw.s)?, rhs: Register::new(raw.t)? }, + 0x19 => Self::Mult { kind: MultKind::Mult, mode: MultMode::Unsigned, lhs: Register::new(raw.s)?, rhs: Register::new(raw.t)? }, + 0x1a => Self::Mult { kind: MultKind::Div , mode: MultMode:: Signed, lhs: Register::new(raw.s)?, rhs: Register::new(raw.t)? }, + 0x1b => Self::Mult { kind: MultKind::Div , mode: MultMode::Unsigned, lhs: Register::new(raw.s)?, rhs: Register::new(raw.t)? }, + + _ => return None, + }) + } + + /// Encodes this instruction + #[must_use] + pub fn encode(self) -> MultRaw { + match self { + Self::Mult { kind, mode, lhs, rhs } => MultRaw { + s: lhs.idx(), + t: rhs.idx(), + d: 0, + f: match (kind, mode) { + (MultKind::Mult, MultMode::Signed) => 0x18, + (MultKind::Mult, MultMode::Unsigned) => 0x19, + (MultKind::Div, MultMode::Signed) => 0x1a, + (MultKind::Div, MultMode::Unsigned) => 0x1b, + }, + }, + Self::MoveFrom { dest, source } => MultRaw { + s: 0, + t: 0, + d: dest.idx(), + f: match source { + MultReg::Hi => 0x10, + MultReg::Lo => 0x12, + }, + }, + Self::MoveTo { source, dest } => MultRaw { + s: source.idx(), + t: 0, + d: 0, + f: match dest { + MultReg::Hi => 0x11, + MultReg::Lo => 0x13, + }, + }, + } + } +} + +impl fmt::Display for MultInst { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + #[rustfmt::skip] + Self::Mult { kind, mode, lhs, rhs } => match (kind, mode) { + (MultKind::Mult, MultMode:: Signed) => write!(f, "mult {lhs}, {rhs}"), + (MultKind::Mult, MultMode::Unsigned) => write!(f, "multu {lhs}, {rhs}"), + (MultKind::Div , MultMode:: Signed) => write!(f, "div {lhs}, {rhs}"), + (MultKind::Div , MultMode::Unsigned) => write!(f, "diu {lhs}, {rhs}"), + }, + Self::MoveFrom { dest, source } => match source { + MultReg::Hi => write!(f, "mfhi {dest}"), + MultReg::Lo => write!(f, "mflo {dest}"), + }, + Self::MoveTo { source, dest } => match dest { + MultReg::Hi => write!(f, "mthi {source}"), + MultReg::Lo => write!(f, "mtlo {source}"), + }, + } + } +} diff --git a/dcb/src/game/exe/instruction/basic/special/shift.rs b/dcb/src/game/exe/instruction/basic/special/shift.rs new file mode 100644 index 0000000..e8d9217 --- /dev/null +++ b/dcb/src/game/exe/instruction/basic/special/shift.rs @@ -0,0 +1,84 @@ +//! Shift instructions + +// Modules +pub mod imm; +pub mod reg; + +// Exports +pub use imm::{ShiftImmInst, ShiftImmRaw}; +pub use reg::{ShiftRegInst, ShiftRegRaw}; + +/// Raw representation +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ShiftRaw { + /// Rs + pub s: u32, + + /// Rt + pub t: u32, + + /// Rd + pub d: u32, + + /// Immediate + pub i: u32, + + /// Func + pub f: u32, +} + +impl From for ShiftImmRaw { + fn from(ShiftRaw { t, d, i, f, .. }: ShiftRaw) -> Self { + Self { t, d, i, f } + } +} + +impl From for ShiftRaw { + fn from(ShiftImmRaw { t, d, i, f }: ShiftImmRaw) -> Self { + Self { t, d, s: 0, i, f } + } +} + +impl From for ShiftRegRaw { + fn from(ShiftRaw { t, d, s, f, .. }: ShiftRaw) -> Self { + Self { t, d, s, f } + } +} + +impl From for ShiftRaw { + fn from(ShiftRegRaw { t, d, s, f }: ShiftRegRaw) -> Self { + Self { t, d, s, i: 0, f } + } +} + +/// Shift instructions +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(derive_more::Display)] +pub enum ShiftInst { + /// Register + Reg(ShiftRegInst), + + /// Immediate + Imm(ShiftImmInst), +} + +impl ShiftInst { + /// Decodes this instruction + #[must_use] + pub fn decode(raw: ShiftRaw) -> Option { + Some(match raw.f { + 0x0..0x4 => Self::Imm(ShiftImmInst::decode(raw.into())?), + 0x4..0x8 => Self::Reg(ShiftRegInst::decode(raw.into())?), + _ => return None, + }) + } + + /// Encodes this instruction + #[must_use] + pub fn encode(self) -> ShiftRaw { + match self { + Self::Reg(inst) => inst.encode().into(), + Self::Imm(inst) => inst.encode().into(), + } + } +} diff --git a/dcb/src/game/exe/instruction/basic/special/shift/imm.rs b/dcb/src/game/exe/instruction/basic/special/shift/imm.rs new file mode 100644 index 0000000..65539fb --- /dev/null +++ b/dcb/src/game/exe/instruction/basic/special/shift/imm.rs @@ -0,0 +1,93 @@ +//! Immediate shifts + +// Imports +use crate::game::exe::instruction::Register; +use int_conv::{Signed, Truncated, ZeroExtended}; +use std::{convert::TryFrom, fmt}; + +/// Shift immediate instruction func +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum ShiftImmFunc { + /// Left logical + LeftLogical = 0x0, + + /// Right logical + RightLogical = 0x2, + + /// Right arithmetic + RightArithmetic = 0x3, +} + +/// Raw representation +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ShiftImmRaw { + /// Rt + pub t: u32, + + /// Rd + pub d: u32, + + /// Immediate + pub i: u32, + + /// Func + pub f: u32, +} + +/// Shift immediate instructions +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ShiftImmInst { + /// Source register, `rd` + pub source: Register, + + /// Destination register, `rt` + pub dest: Register, + + /// Immediate argument + pub arg: i16, + + /// Function + pub func: ShiftImmFunc, +} + +impl ShiftImmInst { + /// Decodes this instruction + #[must_use] + pub fn decode(raw: ShiftImmRaw) -> Option { + let func = ShiftImmFunc::try_from(raw.f.truncated::()).ok()?; + + Some(Self { + source: Register::new(raw.t)?, + dest: Register::new(raw.d)?, + arg: raw.i.truncated::().as_signed(), + func, + }) + } + + /// Encodes this instruction + #[must_use] + pub fn encode(self) -> ShiftImmRaw { + let t = self.source.idx(); + let d = self.dest.idx(); + let i = self.arg.as_unsigned().zero_extended::(); + let f = u8::from(self.func).zero_extended::(); + + ShiftImmRaw { f, t, d, i } + } +} + +impl fmt::Display for ShiftImmInst { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { source, dest, arg, func } = self; + + let mnemonic = match func { + ShiftImmFunc::LeftLogical => "sll", + ShiftImmFunc::RightLogical => "srl", + ShiftImmFunc::RightArithmetic => "sra", + }; + + write!(f, "{mnemonic} {dest}, {source}, {arg}") + } +} diff --git a/dcb/src/game/exe/instruction/basic/special/shift/reg.rs b/dcb/src/game/exe/instruction/basic/special/shift/reg.rs new file mode 100644 index 0000000..d6a08a0 --- /dev/null +++ b/dcb/src/game/exe/instruction/basic/special/shift/reg.rs @@ -0,0 +1,93 @@ +//! Register shifts + +// Imports +use crate::game::exe::instruction::Register; +use int_conv::{Truncated, ZeroExtended}; +use std::{convert::TryFrom, fmt}; + +/// Shift register instruction func +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum ShiftRegFunc { + /// Left logical + LeftLogical = 0x4, + + /// Right logical + RightLogical = 0x6, + + /// Right arithmetic + RightArithmetic = 0x7, +} + +/// Raw representation +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ShiftRegRaw { + /// Rs + pub s: u32, + + /// Rt + pub t: u32, + + /// Rd + pub d: u32, + + /// Func + pub f: u32, +} + +/// Shift register instructions +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct ShiftRegInst { + /// Destination register, `rd` + pub dest: Register, + + /// Lhs argument, `rt` + pub lhs: Register, + + /// Rhs argument, `rs` + pub rhs: Register, + + /// Function + pub func: ShiftRegFunc, +} + +impl ShiftRegInst { + /// Decodes this instruction + #[must_use] + pub fn decode(raw: ShiftRegRaw) -> Option { + let func = ShiftRegFunc::try_from(raw.f.truncated::()).ok()?; + + Some(Self { + dest: Register::new(raw.d)?, + lhs: Register::new(raw.t)?, + rhs: Register::new(raw.s)?, + func, + }) + } + + /// Encodes this instruction + #[must_use] + pub fn encode(self) -> ShiftRegRaw { + let d = self.dest.idx(); + let t = self.lhs.idx(); + let s = self.rhs.idx(); + let f = u8::from(self.func).zero_extended::(); + + ShiftRegRaw { f, t, d, s } + } +} + +impl fmt::Display for ShiftRegInst { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { dest, lhs, rhs, func } = self; + + let mnemonic = match func { + ShiftRegFunc::LeftLogical => "sllv", + ShiftRegFunc::RightLogical => "srlv", + ShiftRegFunc::RightArithmetic => "srav", + }; + + write!(f, "{mnemonic} {dest}, {lhs}, {rhs}") + } +} diff --git a/dcb/src/game/exe/instruction/basic/special/sys.rs b/dcb/src/game/exe/instruction/basic/special/sys.rs new file mode 100644 index 0000000..5b78acb --- /dev/null +++ b/dcb/src/game/exe/instruction/basic/special/sys.rs @@ -0,0 +1,89 @@ +//! System calls + +// Imports +use int_conv::{Truncated, ZeroExtended}; +use std::{convert::TryFrom, fmt}; + +/// Sys instruction func +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum SysFunc { + /// Sys + Sys = 0xc, + + /// Break + Break = 0xd, +} + +/// Raw representation +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct SysRaw { + /// Rs + pub s: u32, + + /// Rt + pub t: u32, + + /// Rd + pub d: u32, + + /// Immediate + pub i: u32, + + /// Func + pub f: u32, +} + +/// Syscall instructions +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct SysInst { + /// Comment + pub comment: u32, + + /// Function + pub func: SysFunc, +} + +impl SysInst { + /// Decodes this instruction + #[must_use] + pub fn decode(SysRaw { t, d, s, i, f }: SysRaw) -> Option { + let s = s.truncated::(); + let t = t.truncated::(); + let d = d.truncated::(); + let i = i.truncated::(); + let comment = u32::from_be_bytes([s, t, d, i]); + + let func = SysFunc::try_from(f.truncated::()).ok()?; + + Some(Self { comment, func }) + } + + /// Encodes this instruction + #[must_use] + #[allow(clippy::many_single_char_names)] // `Raw` has single character names + pub fn encode(self) -> SysRaw { + let [s, t, d, i] = self.comment.to_be_bytes(); + let s = s.zero_extended::(); + let t = t.zero_extended::(); + let d = d.zero_extended::(); + let i = i.zero_extended::(); + let f = u8::from(self.func).zero_extended::(); + + SysRaw { s, t, d, i, f } + } +} + +impl fmt::Display for SysInst { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { func, comment } = self; + + let mnemonic = match func { + SysFunc::Sys => "sys", + SysFunc::Break => "break", + }; + + write!(f, "{mnemonic} {comment}") + } +} diff --git a/dcb/src/game/exe/instruction/basic/store.rs b/dcb/src/game/exe/instruction/basic/store.rs new file mode 100644 index 0000000..ed63255 --- /dev/null +++ b/dcb/src/game/exe/instruction/basic/store.rs @@ -0,0 +1,106 @@ +//! Store instructions + +// Imports +use crate::game::exe::instruction::Register; +use int_conv::{Signed, Truncated, ZeroExtended}; +use std::{convert::TryFrom, fmt}; + +/// Store instruction opcode (lower 3 bits) +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)] +#[repr(u8)] +pub enum StoreOpcode { + /// Byte, `u8` + Byte = 0x0, + + /// Half-word, `u16` + HalfWord = 0x1, + + /// Word left-bits, `u32` + WordLeft = 0x2, + + /// Word, `u32` + Word = 0x3, + + /// Word right-bits, `u32` + WordRight = 0x6, +} + +/// Raw representation +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct StoreRaw { + /// Opcode (lower 3 bits) + pub p: u32, + + /// Rs + pub s: u32, + + /// Rt + pub t: u32, + + /// Immediate + pub i: u32, +} + +/// Store instructions +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +pub struct StoreInst { + /// Source register, `rt` + pub source: Register, + + /// Destination register, `rs` + pub dest: Register, + + /// Destination offset. + pub offset: i16, + + /// Opcode + pub op: StoreOpcode, +} + +impl StoreInst { + /// Decodes this instruction + #[must_use] + pub fn decode(raw: StoreRaw) -> Option { + let kind = StoreOpcode::try_from(raw.p.truncated::()).ok()?; + + Some(Self { + source: Register::new(raw.t)?, + dest: Register::new(raw.s)?, + offset: raw.i.truncated::().as_signed(), + op: kind, + }) + } + + /// Encodes this instruction + #[must_use] + pub fn encode(self) -> StoreRaw { + let t = self.source.idx(); + let s = self.dest.idx(); + let i = self.offset.as_unsigned().zero_extended::(); + let p = u8::from(self.op).zero_extended::(); + + StoreRaw { p, s, t, i } + } +} + +impl fmt::Display for StoreInst { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + let Self { + source, + dest, + offset, + op: kind, + } = self; + + let mnemonic = match kind { + StoreOpcode::Byte => "sb", + StoreOpcode::HalfWord => "sh", + StoreOpcode::Word => "sw", + StoreOpcode::WordRight => "swr", + StoreOpcode::WordLeft => "swl", + }; + + write!(f, "{mnemonic} {dest}, {offset}({source})") + } +} diff --git a/dcb/src/game/exe/instruction/pseudo.rs b/dcb/src/game/exe/instruction/pseudo.rs index af974fd..7528fee 100644 --- a/dcb/src/game/exe/instruction/pseudo.rs +++ b/dcb/src/game/exe/instruction/pseudo.rs @@ -1,7 +1,7 @@ //! Pseudo instructions // Imports -use super::{FromRawIter, Raw, Register, SimpleInstruction}; +use super::{BasicInstruction, FromRawIter, Raw, Register}; use crate::{game::exe::Pos, util::SignedHex}; use int_conv::{Join, SignExtended, Signed, ZeroExtended}; @@ -248,17 +248,17 @@ impl FromRawIter for PseudoInstruction { #[allow(clippy::similar_names)] // With register names, this happens too much #[allow(clippy::too_many_lines, clippy::clippy::cognitive_complexity)] // We can't separate this into several functions, it's just 1 big match - #[allow(clippy::enum_glob_use)] // This reduces the amount of typing for simple instructions and registers + #[allow(clippy::enum_glob_use)] // This reduces the amount of typing for basic instructions and registers fn decode + Clone>(iter: &mut I) -> Self::Decoded { + use BasicInstruction::*; use Register::*; - use SimpleInstruction::*; // Get the first instruction - let (pos, instruction) = SimpleInstruction::decode(iter)?; + let (pos, instruction) = BasicInstruction::decode(iter)?; let pseudo = match instruction { Lui { imm: imm_hi, rt: prev_rt } => { let iter_before = iter.clone(); - match SimpleInstruction::decode(iter)?.1 { + match BasicInstruction::decode(iter)?.1 { Addiu { imm: imm_lo, rt, rs } if rt == prev_rt && rs == prev_rt => Self::La { rx: prev_rt, target: self::hi_plus_lo(imm_lo, imm_hi), diff --git a/dcb/src/game/exe/instruction/raw.rs b/dcb/src/game/exe/instruction/raw.rs index 70de899..e7c9943 100644 --- a/dcb/src/game/exe/instruction/raw.rs +++ b/dcb/src/game/exe/instruction/raw.rs @@ -19,7 +19,7 @@ pub struct Raw { /// returned iterator, that is, consume the least possible /// input in order to produce an atomic part of themselves. pub trait FromRawIter: Sized { - /// Returned iterator from [`decode`] + /// Returned iterator from [`decode`]. type Decoded: IntoIterator; /// Attempts to decode an instruction from an iterator of raw instructions diff --git a/dcb/src/game/exe/instruction/reg.rs b/dcb/src/game/exe/instruction/reg.rs index fbf6788..f557458 100644 --- a/dcb/src/game/exe/instruction/reg.rs +++ b/dcb/src/game/exe/instruction/reg.rs @@ -31,7 +31,7 @@ macro_rules! generate_register { /// Creates a new register index from a `u8`. #[must_use] - pub const fn new(idx: u8) -> Option { + pub const fn new(idx: u32) -> Option { match idx { $( $value => Some( Self::$variant ), @@ -40,6 +40,16 @@ macro_rules! generate_register { _ => None, } } + + /// Returns the index of this register + #[must_use] + pub const fn idx(self) -> u32 { + match self { + $( + Self::$variant => $value, + )* + } + } } impl From for usize { diff --git a/dcb/src/io/address/data.rs b/dcb/src/io/address/data.rs index 4937de5..9631a6d 100644 --- a/dcb/src/io/address/data.rs +++ b/dcb/src/io/address/data.rs @@ -128,7 +128,7 @@ impl std::ops::Sub for Data { // Display impl std::fmt::Display for Data { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:#x}", u64::from(*self)) } } diff --git a/dcb/src/io/address/real.rs b/dcb/src/io/address/real.rs index cdab9b6..d703bcd 100644 --- a/dcb/src/io/address/real.rs +++ b/dcb/src/io/address/real.rs @@ -165,7 +165,7 @@ impl std::ops::Sub for Real { // Display impl std::fmt::Display for Real { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{:#x}", self.as_u64()) } } diff --git a/dcb/src/util/null_ascii_string.rs b/dcb/src/util/null_ascii_string.rs index 8f5f511..30f4f71 100644 --- a/dcb/src/util/null_ascii_string.rs +++ b/dcb/src/util/null_ascii_string.rs @@ -58,5 +58,5 @@ impl_null_ascii_string!( 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, - 1972 + 1971 ); diff --git a/dcb/src/util/signed_hex.rs b/dcb/src/util/signed_hex.rs index 9b7631a..d29bb37 100644 --- a/dcb/src/util/signed_hex.rs +++ b/dcb/src/util/signed_hex.rs @@ -17,7 +17,7 @@ impl<'a, T> LowerHex for SignedHex<&'a T> where SignedHex: LowerHex, { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { as LowerHex>::fmt(SignedHex::::ref_cast(self.0), f) } }