Revamped most things.

Honstly not sure what's happened, but `dcb-exe` is mostly being redone from scratch.
This commit is contained in:
Filipe Rodrigues 2021-01-08 13:39:50 +00:00
parent c9644ae2fb
commit 48e29ec83c
56 changed files with 1250 additions and 1637 deletions

View File

@ -7,5 +7,5 @@ members = [
"dcb-exe",
"dcb-bytes",
"dcb-bytes-derive",
"dcb-tools"
"dcb-tools",
]

View File

@ -16,13 +16,13 @@ log = "0.4"
# Util
byteorder = "1.3"
ascii = { version = "1.0", features = ["serde"] }
arrayref = "0.3"
int-conv = "0.1"
ascii = { version = "1.0", features = ["serde"] }
arrayref = "0.3"
int-conv = "0.1"
bitmatch = "0.1"
either = "1.6"
smallvec = "1.4"
num_enum = "0.5"
either = "1.6.1"
# Serde
serde = { version = "1.0", features = ["derive"] }
@ -32,7 +32,6 @@ serde_yaml = "0.8"
derive_more = "0.99"
thiserror = "1.0"
ref-cast = "1.0"
dcb-bytes-derive = { path = "../dcb-bytes-derive" }
[dev-dependencies]

View File

@ -6,46 +6,46 @@
// Modules
pub mod data;
pub mod error;
//pub mod func;
pub mod header;
pub mod instruction;
pub mod inst;
pub mod pos;
//pub mod func;
// Exports
pub use data::{Data, DataTable, DataType};
pub use error::DeserializeError;
//pub use func::Func;
pub use header::Header;
pub use instruction::Instruction;
pub use pos::Pos;
//pub use func::Func;
// Imports
use self::inst::Inst;
use dcb_bytes::{ByteArray, Bytes};
use dcb_io::GameFile;
use std::{
convert::TryFrom,
io::{Read, Seek, Write},
};
use std::io::{Read, Seek, Write};
/// The game executable
#[derive(PartialEq, Eq, Clone, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
//#[derive(serde::Serialize, serde::Deserialize)]
#[allow(clippy::as_conversions)] // `SIZE` always fits
pub struct Exe {
/// The executable header
pub header: Header,
/// All bytes within the exe
pub bytes: Box<[u8; Self::SIZE as usize]>,
/// The data table.
pub data_table: DataTable,
//pub data: Vec<u8>,
}
impl Exe {
/// Code range
///
/// Everything outside of this range will be considered data.
pub const CODE_RANGE: std::ops::Range<Pos> = Pos(0x80013e4c)..Pos(0x8006dd3c);
/// Size of the executable
const SIZE: u32 = 0x68000;
/// Start address of the executable
const START_ADDRESS: dcb_io::Data = dcb_io::Data::from_u64(0x58b9000);
/// Start memory address of the executable
const START_MEM_ADDRESS: Pos = Pos(0x80010000);
}
impl Exe {
@ -60,13 +60,31 @@ impl Exe {
file.read_exact(&mut header_bytes).map_err(DeserializeError::ReadHeader)?;
let header = Header::from_bytes(&header_bytes).map_err(DeserializeError::ParseHeader)?;
//
//file.bytes();
// Make sure the header size is the one we expect
if header.size != Self::SIZE {
return Err(DeserializeError::WrongDataSize { header: Box::new(header) });
}
let mut data = vec![0u8; usize::try_from(header.size).expect("Header size didn't fit into a `usize`")];
file.read_exact(data.as_mut()).map_err(DeserializeError::ReadData)?;
// Read all of the bytes
#[allow(clippy::as_conversions)] // `SIZE` always fits
let mut bytes = Box::new([0u8; Self::SIZE as usize]);
file.read_exact(bytes.as_mut()).map_err(DeserializeError::ReadData)?;
todo!();
//Ok(Self { header, data })
// Parse all instructions
let insts = inst::ParseIter::new(&*bytes, Self::START_MEM_ADDRESS);
// Parse all data and code
let known_data_table = DataTable::get_known().map_err(DeserializeError::KnownDataTable)?;
let heuristics_data_table = DataTable::search_instructions(insts);
let data_table = known_data_table.merge_with(heuristics_data_table);
Ok(Self { header, bytes, data_table })
}
/// Returns a parsing iterator for all instructions
#[must_use]
pub const fn parse_iter(&self) -> inst::ParseIter {
inst::ParseIter::new(&*self.bytes, Self::START_MEM_ADDRESS)
}
}

View File

@ -21,15 +21,13 @@ use std::{borrow::Borrow, cmp::Ordering};
/// A data location.
///
/// Two data locations are considered equal if they
/// share the same position and kind.
///
/// Their relative
/// order is also firstly dependent on their position
/// and kind, although when 2 data locations share
/// the same position, the larger one will appear first
/// in the ordering. This is to implement 'specialized'
/// data locations, where one may have a certain data location
/// inside of another for a specific purpose.
/// share the same position.
///
/// Their relative order first depends on their position.
/// When their positions are equal, the larger one will
/// appear first in the order.
/// This is to implement `specialized` data locations, where
/// a large data location can have multiple data locations inside.
#[derive(Clone, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Data {

View File

@ -13,10 +13,14 @@ pub mod error;
// Exports
pub use error::GetKnownError;
use inst::directive::Directive;
// Imports
use super::Data;
use crate::exe::Pos;
use super::{Data, DataType};
use crate::exe::{
inst::{self, basic, Inst},
Pos,
};
use dcb_util::DiscardingSortedMergeIter;
use std::{collections::BTreeSet, fs::File, iter::FromIterator};
@ -43,7 +47,7 @@ impl DataTable {
/// discovered data locations, as the known functions are always kept, and the
/// duplicate discovered ones are discarded.
#[must_use]
pub fn merge(self, other: Self) -> Self {
pub fn merge_with(self, other: Self) -> Self {
// Note: We don't return the iterator, as we want the user to
// keep the guarantees supplied by this type.
DiscardingSortedMergeIter::new(self.0.into_iter(), other.0.into_iter()).collect()
@ -67,15 +71,17 @@ impl DataTable {
serde_yaml::from_reader(file).map_err(GetKnownError::Parse)
}
/*
/// Searches all instructions for references to
/// executable data using certain heuristics.
#[must_use]
pub fn search_instructions<'a>(instructions: impl Iterator<Item = (Pos, &'a Instruction)> + Clone) -> Self {
pub fn search_instructions(insts: impl Iterator<Item = (Pos, Inst)> + Clone) -> Self {
// Get all possible references to data
let data_references: BTreeSet<Pos> = instructions
let references: BTreeSet<Pos> = insts
.clone()
.filter_map(|(_, instruction)| match instruction {
.filter_map(|(pos, inst)| match inst {
Inst::Basic(basic::Inst::Load(basic::load::Inst { offset, .. }) | basic::Inst::Store(basic::store::Inst { offset, .. })) => {
Some(pos + offset)
},
/*
Instruction::Pseudo(
PseudoInst::La { target: offset, .. } |
@ -94,48 +100,47 @@ impl DataTable {
PseudoInst::SwrImm { offset, .. },
) |
*/
Instruction::Directive(Directive::Dw(offset)) => Some(Pos(*offset)),
Inst::Directive(Directive::Dw(address)) => Some(Pos(address)),
_ => None,
})
.collect();
// Then filter the instructions for data locations.
instructions
insts
// Filter all non-directives
.filter_map(|(pos, instruction)| match instruction {
Instruction::Directive(directive) if data_references.contains(&pos) => Some((pos, directive)),
Inst::Directive(directive) if references.contains(&pos) => Some((pos, directive)),
_ => None,
})
.zip(0..)
.map(|((pos, directive), idx)| {
match directive {
Directive::Ascii(ascii) => Data {
Directive::Ascii { len } => Data {
name: format!("string_{idx}"),
desc: String::new(),
pos,
kind: DataKind::AsciiStr { len: ascii.len().try_into().expect("String length didn't fit into a `u32`") },
ty: DataType::Array { ty: Box::new(DataType::AsciiChar), len },
},
Directive::Dw(_) => Data {
name: format!("data_w{idx}"),
desc: String::new(),
pos,
kind: DataKind::Word,
ty: DataType::Word,
},
Directive::Dh(_) => Data {
name: format!("data_h{idx}"),
desc: String::new(),
pos,
kind: DataKind::HalfWord,
ty: DataType::HalfWord,
},
Directive::Db(_) => Data {
name: format!("data_b{idx}"),
desc: String::new(),
pos,
kind: DataKind::Byte,
ty: DataType::Byte,
},
}
})
.collect()
}
*/
}

View File

@ -10,8 +10,8 @@ use serde::de::Visitor;
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
#[derive(derive_more::Display)]
pub enum DataType {
/// Ascii character
#[display(fmt = "AsciiChar")]
/// Ascii string
#[display(fmt = "str")]
AsciiChar,
/// Word
@ -88,24 +88,27 @@ impl std::str::FromStr for DataType {
type Err = FromStrError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
// Strip any whitespace.
let s = s.trim();
// If it starts with '[', read it as an array
if let Some(s) = s.strip_prefix('[') {
// Find the first ';' from the end to split.
let s = s.strip_suffix(']').ok_or(FromStrError::MissingArraySuffix)?;
// Note: We find the first ';' from the end to split.
let (ty, len) = s
.char_indices()
.rev()
.find_map(|(pos, c)| c.eq(&';').then(|| s.split_at(pos)))
.ok_or(FromStrError::MissingArraySep)?;
// Ignore the leading ';'
#[allow(clippy::indexing_slicing)] // This can't panic, as `pos < len.`
// Ignore the leading ';' on the second.
#[allow(clippy::indexing_slicing)] // This can't panic, as `pos < len`.
let len = &len[1..];
let ty = box Self::from_str(ty).map_err(|err| FromStrError::InvalidArrayTy(box err))?;
let len = self::parse_u32(len).map_err(|err| FromStrError::InvalidArrayLen { len: len.to_string(), err })?;
// Trim both strings
let ty = ty.trim();
let len = len.trim();
let ty = Self::from_str(ty).map_err(|err| FromStrError::InvalidArrayTy(Box::new(err)))?;
let ty = Box::new(ty);
let len = self::parse_u32(len).map_err(|err| FromStrError::InvalidArrayLen { len: len.to_owned(), err })?;
return Ok(Self::Array { ty, len });
}
@ -116,7 +119,7 @@ impl std::str::FromStr for DataType {
"u8" => Ok(Self::Byte),
"u16" => Ok(Self::HalfWord),
"u32" => Ok(Self::Word),
_ => Err(FromStrError::UnknownTy { ty: s.to_string() }),
_ => Err(FromStrError::UnknownTy { ty: s.to_owned() }),
}
}
}

View File

@ -1,7 +1,7 @@
//! Errors
// Imports
use super::header;
use super::{data, header, Exe, Header};
/// Error type for [`Table::deserialize`]
#[derive(Debug, thiserror::Error)]
@ -18,7 +18,18 @@ pub enum DeserializeError {
#[error("Unable to parse header")]
ParseHeader(#[source] header::FromBytesError),
/// Data had wrong size
#[error("Wrong data size, expected {}, found {}", Exe::SIZE, header.size)]
WrongDataSize {
/// The read header
header: Box<Header>,
},
/// Unable to read data
#[error("Unable to read data")]
ReadData(#[source] std::io::Error),
/// Unable to get known data
#[error("Unable to get known data table")]
KnownDataTable(#[source] data::table::GetKnownError),
}

View File

@ -4,14 +4,13 @@
pub mod error;
// Exports
use std::fmt;
pub use error::{FromBytesError, ToBytesError};
// Import
use byteorder::{ByteOrder, LittleEndian};
use dcb_bytes::Bytes;
use dcb_util::{array_split, null_ascii_string::NullAsciiString, AsciiStrArr};
use std::fmt;
/// The header of the executable.
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
@ -66,12 +65,12 @@ impl fmt::Display for Header {
..
} = self;
write!(f, "PC: {pc0:#x}")?;
write!(f, "GP: {gp0:#x}")?;
write!(f, "Destination: {dest:#x} / size: {size:#x}")?;
write!(f, "Memfill: {memfill_start:#X} / size: {memfill_size:#x}")?;
write!(f, "SP: {initial_sp_base:#x} / offset: {initial_sp_offset:#x}")?;
write!(f, "Marker: {marker:?}")
writeln!(f, "PC: {pc0:#x}")?;
writeln!(f, "GP: {gp0:#x}")?;
writeln!(f, "Destination: {dest:#x} / size: {size:#x}")?;
writeln!(f, "Memfill: {memfill_start:#X} / size: {memfill_size:#x}")?;
writeln!(f, "SP: {initial_sp_base:#x} / offset: {initial_sp_offset:#x}")?;
writeln!(f, "Marker: {marker:?}")
}
}

104
dcb-exe/src/exe/inst.rs Normal file
View File

@ -0,0 +1,104 @@
//! Psx cpu instructions
// Modules
pub mod basic;
pub mod directive;
pub mod pseudo;
pub mod raw;
pub mod reg;
// Exports
pub use directive::Directive;
pub use raw::Raw;
pub use reg::Register;
// Imports
use crate::Pos;
use dcb_util::NextFromBytes;
/// An assembler instruction
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Inst {
/// A basic instruction
Basic(basic::Inst),
/// A pseudo instruction
Pseudo(pseudo::PseudoInst),
/// A directive
Directive(Directive),
}
impl Inst {
/// End of the code itself in the executable.
pub const CODE_END: Pos = Pos(0x8006dd3c);
/// Code range
pub const CODE_RANGE: std::ops::Range<Pos> = Self::CODE_START..Self::CODE_END;
/// Start of the code itself in the executable.
pub const CODE_START: Pos = Pos(0x80013e4c);
}
/// Parsing iterator, reads instructions from a `[u8]` slice
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct ParseIter<'a> {
/// Remaining bytes
bytes: &'a [u8],
/// Starting position of bytes
cur_pos: Pos,
}
impl<'a> ParseIter<'a> {
/// Creates a new parsing iterator
#[must_use]
pub const fn new(bytes: &'a [u8], start_pos: Pos) -> Self {
Self { bytes, cur_pos: start_pos }
}
}
impl<'a> Iterator for ParseIter<'a> {
type Item = (Pos, Inst);
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)] // Byte lengths will always fit into a `u32`, as `self.bytes.len()` is always smaller than `u32`.
#[allow(clippy::indexing_slicing)] // Our lengths will always be smaller than the bytes array they are used to index.
fn next(&mut self) -> Option<Self::Item> {
// If we're outside of code range, decode a directive
if !Inst::CODE_RANGE.contains(&self.cur_pos) {
let (directive, len) = Directive::decode(self.cur_pos, self.bytes)?;
self.bytes = &self.bytes[len..];
let pos = self.cur_pos;
self.cur_pos += len as u32;
return Some((pos, Inst::Directive(directive)));
}
// Else decode an instruction, falling back to a directive if unable to
match self.bytes.next_u32().and_then(basic::Inst::decode) {
// If we got one, update our bytes and check if it's a pseudo instruction
Some(inst) => {
self.bytes = &self.bytes[4..];
let pos = self.cur_pos;
self.cur_pos += 4;
match pseudo::PseudoInst::decode(inst, self.bytes) {
Some((inst, len)) => {
self.bytes = &self.bytes[len..];
self.cur_pos += len as u32;
Some((pos, Inst::Pseudo(inst)))
},
None => Some((pos, Inst::Basic(inst))),
}
},
// If we don't have enough for a `u32` or we didn't manage to
// parse an instruction, try to parse a directive
None => match Directive::decode(self.cur_pos, self.bytes) {
Some((directive, len)) => {
self.bytes = &self.bytes[len..];
let pos = self.cur_pos;
self.cur_pos += len as u32;
Some((pos, Inst::Directive(directive)))
},
None => None,
},
}
}
}

View File

@ -0,0 +1,123 @@
//! Basic instructions
//!
//! This modules defines all the basic instructions from the psx.
//! They are all 1 word (`u32`) long.
// Modules
pub mod alu;
pub mod cond;
//pub mod iter;
pub mod jmp;
pub mod load;
pub mod lui;
pub mod mult;
pub mod store;
pub mod sys;
/// All basic instructions
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
pub enum Inst {
/// Store
Store(store::Inst),
/// Load
Load(load::Inst),
/// Condition
Cond(cond::Inst),
/// Jump
Jmp(jmp::Inst),
/// Alu
Alu(alu::Inst),
/// Load upper immediate
Lui(lui::Inst),
/// Syscall
Sys(sys::Inst),
}
impl Inst {
/// Decodes an instruction
#[must_use]
#[bitmatch::bitmatch]
#[allow(clippy::many_single_char_names)] // `bitmatch` can only output single character names.
pub fn decode(raw: u32) -> Option<Self> {
let inst = #[bitmatch]
match raw {
// Jump
"00001p_iiiii_iiiii_iiiii_iiiii_iiiiii" => Self::Jmp(jmp::Inst::decode(jmp::imm::Raw { p, i })?),
"000000_sssss_?????_ddddd_?????_00100f" => Self::Jmp(jmp::Inst::decode(jmp::reg::Raw { s, d, f })?),
"000ppp_sssss_ttttt_iiiii_iiiii_iiiiii" => Self::Cond(cond::Inst::decode(cond::Raw { p, s, t, i })?),
"001111_?????_ttttt_iiiii_iiiii_iiiiii" => Self::Lui(lui::Inst::decode(lui::Raw { t, i })?),
// Alu
"000000_sssss_ttttt_ddddd_?????_10ffff" => Self::Alu(alu::Inst::decode(alu::reg::Raw { s, t, d, f })?),
"001ppp_sssss_ttttt_iiiii_iiiii_iiiiii" => Self::Alu(alu::Inst::decode(alu::imm::Raw { p, s, t, i })?),
// Syscall
"000000_ccccc_ccccc_ccccc_ccccc_00110f" => Self::Sys(sys::Inst::decode(sys::Raw { c, f })?),
// Store / Load
"100ppp_sssss_ttttt_iiiii_iiiii_iiiiii" => Self::Store(store::Inst::decode(store::Raw { p, s, t, i })?),
"101ppp_sssss_ttttt_iiiii_iiiii_iiiiii" => Self::Load(load::Inst::decode(load::Raw { p, s, t, i })?),
// TODO: Remaining instructions, such as shift
/*
"0100nn_1iiii_iiiii_iiiii_iiiii_iiiiii" => CopN { n: n.truncate(), imm: i},
"0100nn_00000_ttttt_ddddd_?????_000000" => MfcN { n: n.truncate(), rt: reg(t)?, rd: reg(d)? },
"0100nn_00010_ttttt_ddddd_?????_000000" => CfcN { n: n.truncate(), rt: reg(t)?, rd: reg(d)? },
"0100nn_00100_ttttt_ddddd_?????_000000" => MtcN { n: n.truncate(), rt: reg(t)?, rd: reg(d)? },
"0100nn_00110_ttttt_ddddd_?????_000000" => CtcN { n: n.truncate(), rt: reg(t)?, rd: reg(d)? },
"0100nn_01000_00000_iiiii_iiiii_iiiiii" => BcNf { n: n.truncate(), target: i.truncate() },
"0100nn_01000_00001_iiiii_iiiii_iiiiii" => BcNt { n: n.truncate(), target: i.truncate() },
"1100nn_sssss_ttttt_iiiii_iiiii_iiiiii" => LwcN { n: n.truncate(), rs: reg(s)?, rt: reg(t)?, imm: i.truncate() },
"1110nn_sssss_ttttt_iiiii_iiiii_iiiiii" => SwcN { n: n.truncate(), rs: reg(s)?, rt: reg(t)?, imm: i.truncate() },
*/
_ => return None,
};
Some(inst)
}
/// Encodes this instruction
#[must_use]
#[bitmatch::bitmatch]
pub fn encode(self) -> u32 {
match self {
Self::Jmp(inst) => match inst.encode() {
jmp::Raw::Imm(jmp::imm::Raw { p, i }) => bitpack!("00001p_iiiii_iiiii_iiiii_iiiii_iiiiii"),
jmp::Raw::Reg(jmp::reg::Raw { s, d, f }) => bitpack!("000000_sssss_?????_ddddd_?????_00100f"),
},
Self::Cond(inst) => {
let cond::Raw { p, s, t, i } = inst.encode();
bitpack!("000ppp_sssss_ttttt_iiiii_iiiii_iiiiii")
},
Self::Lui(inst) => {
let lui::Raw { t, i } = inst.encode();
bitpack!("001111_?????_ttttt_iiiii_iiiii_iiiiii")
},
Self::Alu(inst) => match inst.encode() {
alu::Raw::Imm(alu::imm::Raw { p, s, t, i }) => bitpack!("001ppp_sssss_ttttt_iiiii_iiiii_iiiiii"),
alu::Raw::Reg(alu::reg::Raw { s, t, d, f }) => bitpack!("000000_sssss_ttttt_ddddd_?????_10ffff"),
},
Self::Sys(inst) => {
let sys::Raw { c, f } = inst.encode();
bitpack!("000000_ccccc_ccccc_ccccc_ccccc_00110f")
},
Self::Store(inst) => {
let store::Raw { p, s, t, i } = inst.encode();
bitpack!("100ppp_sssss_ttttt_iiiii_iiiii_iiiiii")
},
Self::Load(inst) => {
let load::Raw { p, s, t, i } = inst.encode();
bitpack!("101ppp_sssss_ttttt_iiiii_iiiii_iiiiii")
},
}
}
}

View File

@ -0,0 +1,47 @@
//! Alu instructions
// Modules
pub mod imm;
pub mod reg;
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::From)]
pub enum Raw {
/// Immediate
Imm(imm::Raw),
/// Register
Reg(reg::Raw),
}
/// Alu register instructions
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
pub enum Inst {
/// Immediate
Imm(imm::Inst),
/// Register
Reg(reg::Inst),
}
impl Inst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: impl Into<Raw>) -> Option<Self> {
match raw.into() {
Raw::Imm(raw) => Some(Self::Imm(imm::Inst::decode(raw)?)),
Raw::Reg(raw) => Some(Self::Reg(reg::Inst::decode(raw)?)),
}
}
/// Encodes this instruction
#[must_use]
pub fn encode(self) -> Raw {
match self {
Self::Imm(inst) => Raw::Imm(inst.encode()),
Self::Reg(inst) => Raw::Reg(inst.encode()),
}
}
}

View File

@ -1,14 +1,14 @@
//! Alu immediate instructions
// Imports
use crate::exe::instruction::Register;
use crate::exe::inst::Register;
use dcb_util::SignedHex;
use int_conv::{Signed, Truncated, ZeroExtended};
use std::fmt;
/// Alu immediate instruction kind
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum AluImmInstKind {
pub enum Kind {
/// Add signed with overflow trap
Add(i16),
@ -31,9 +31,10 @@ pub enum AluImmInstKind {
Xor(u16),
}
impl AluImmInstKind {
impl Kind {
/// Returns this kind's mnemonic
pub fn mnemonic(self) -> &'static str {
#[must_use]
pub const fn mnemonic(self) -> &'static str {
match self {
Self::Add(_) => "addi",
Self::AddUnsigned(_) => "addiu",
@ -46,17 +47,18 @@ impl AluImmInstKind {
}
/// Returns a displayable with the value of this kind
#[must_use]
pub fn value_fmt(self) -> impl fmt::Display {
struct FmtValue(Self);
/// Display wrapper
struct FmtValue(Kind);
impl fmt::Display for FmtValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use AluImmInstKind::*;
match self.0 {
// Signed
Add(rhs) | AddUnsigned(rhs) | SetLessThan(rhs) => write!(f, "{:#x}", SignedHex(rhs)),
Kind::Add(rhs) | Kind::AddUnsigned(rhs) | Kind::SetLessThan(rhs) => write!(f, "{}", SignedHex(rhs)),
// Unsigned
SetLessThanUnsigned(rhs) | And(rhs) | Or(rhs) | Xor(rhs) => write!(f, "{rhs:#x}"),
Kind::SetLessThanUnsigned(rhs) | Kind::And(rhs) | Kind::Or(rhs) | Kind::Xor(rhs) => write!(f, "{rhs}"),
}
}
}
@ -67,7 +69,7 @@ impl AluImmInstKind {
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct AluImmInstRaw {
pub struct Raw {
/// Opcode (lower 3 bits)
pub p: u32,
@ -81,11 +83,11 @@ pub struct AluImmInstRaw {
pub i: u32,
}
/// Alu register instructions
/// Alu immediate instructions
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
#[display(fmt = "{} {dst}, {lhs}, {}", "kind.mnemonic()", "kind.value_fmt()")]
pub struct AluImmInst {
pub struct Inst {
/// Destination register
pub dst: Register,
@ -93,22 +95,22 @@ pub struct AluImmInst {
pub lhs: Register,
/// Kind
pub kind: AluImmInstKind,
pub kind: Kind,
}
impl AluImmInst {
impl Inst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: AluImmInstRaw) -> Option<Self> {
pub fn decode(raw: Raw) -> Option<Self> {
#[rustfmt::skip]
let kind = match raw.p {
0x0 => AluImmInstKind::Add (raw.i.truncated::<u16>().as_signed()),
0x1 => AluImmInstKind::AddUnsigned (raw.i.truncated::<u16>().as_signed()),
0x2 => AluImmInstKind::SetLessThan (raw.i.truncated::<u16>().as_signed()),
0x3 => AluImmInstKind::SetLessThanUnsigned(raw.i.truncated::<u16>()),
0x4 => AluImmInstKind::And (raw.i.truncated::<u16>()),
0x5 => AluImmInstKind::Or (raw.i.truncated::<u16>()),
0x6 => AluImmInstKind::Xor (raw.i.truncated::<u16>()),
0x0 => Kind::Add (raw.i.truncated::<u16>().as_signed()),
0x1 => Kind::AddUnsigned (raw.i.truncated::<u16>().as_signed()),
0x2 => Kind::SetLessThan (raw.i.truncated::<u16>().as_signed()),
0x3 => Kind::SetLessThanUnsigned(raw.i.truncated::<u16>()),
0x4 => Kind::And (raw.i.truncated::<u16>()),
0x5 => Kind::Or (raw.i.truncated::<u16>()),
0x6 => Kind::Xor (raw.i.truncated::<u16>()),
_ => return None,
};
@ -121,20 +123,20 @@ impl AluImmInst {
/// Encodes this instruction
#[must_use]
pub fn encode(self) -> AluImmInstRaw {
pub fn encode(self) -> Raw {
#[rustfmt::skip]
let (p, i) = match self.kind {
AluImmInstKind::Add (rhs) => (0x0, rhs.zero_extended::<u32>()),
AluImmInstKind::AddUnsigned (rhs) => (0x1, rhs.zero_extended::<u32>()),
AluImmInstKind::SetLessThan (rhs) => (0x2, rhs.zero_extended::<u32>()),
AluImmInstKind::SetLessThanUnsigned(rhs) => (0x3, rhs.zero_extended::<u32>()),
AluImmInstKind::And (rhs) => (0x4, rhs.zero_extended::<u32>()),
AluImmInstKind::Or (rhs) => (0x5, rhs.zero_extended::<u32>()),
AluImmInstKind::Xor (rhs) => (0x6, rhs.zero_extended::<u32>()),
Kind::Add (rhs) => (0x0, rhs.as_unsigned().zero_extended::<u32>()),
Kind::AddUnsigned (rhs) => (0x1, rhs.as_unsigned().zero_extended::<u32>()),
Kind::SetLessThan (rhs) => (0x2, rhs.as_unsigned().zero_extended::<u32>()),
Kind::SetLessThanUnsigned(rhs) => (0x3, rhs.as_unsigned().zero_extended::<u32>()),
Kind::And (rhs) => (0x4, rhs.as_unsigned().zero_extended::<u32>()),
Kind::Or (rhs) => (0x5, rhs.as_unsigned().zero_extended::<u32>()),
Kind::Xor (rhs) => (0x6, rhs.as_unsigned().zero_extended::<u32>()),
};
let s = self.lhs.idx();
let t = self.dst.idx();
AluImmInstRaw { p, s, t, i }
Raw { p, s, t, i }
}
}

View File

@ -0,0 +1,141 @@
//! Alu register instructions
// Imports
use crate::exe::inst::Register;
/// Alu register instruction kind
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Kind {
/// Add signed with overflow trap
Add,
/// Add signed without overflow trap
AddUnsigned,
/// Sub signed with overflow trap
Sub,
/// Sub signed without overflow trap
SubUnsigned,
/// Bit and
And,
/// Bit or
Or,
/// Bit xor
Xor,
/// Bit nor
Nor,
/// Set on less than signed
SetLessThan,
/// Set on less than unsigned
SetLessThanUnsigned,
}
impl Kind {
/// Returns this kind's mnemonic
#[must_use]
pub const fn mnemonic(self) -> &'static str {
match self {
Self::Add => "add",
Self::AddUnsigned => "addu",
Self::Sub => "sub",
Self::SubUnsigned => "subu",
Self::And => "and",
Self::Or => "or",
Self::Xor => "xor",
Self::Nor => "nor",
Self::SetLessThan => "slt",
Self::SetLessThanUnsigned => "sltu",
}
}
}
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct Raw {
/// Rs
pub s: u32,
/// Rt
pub t: u32,
/// Rd
pub d: u32,
/// Func (lower 4 bits)
pub f: u32,
}
/// Alu register instructions
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
#[display(fmt = "{} {dst}, {lhs}, {rhs}", "kind.mnemonic()")]
pub struct Inst {
/// Destination register
pub dst: Register,
/// Lhs argument
pub lhs: Register,
/// Rhs argument
pub rhs: Register,
/// Kind
pub kind: Kind,
}
impl Inst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: Raw) -> Option<Self> {
let kind = match raw.f {
0x0 => Kind::Add,
0x1 => Kind::AddUnsigned,
0x2 => Kind::Sub,
0x3 => Kind::SubUnsigned,
0x4 => Kind::And,
0x5 => Kind::Or,
0x6 => Kind::Xor,
0x7 => Kind::Nor,
0xa => Kind::SetLessThan,
0xb => Kind::SetLessThanUnsigned,
_ => return None,
};
Some(Self {
dst: Register::new(raw.d)?,
lhs: Register::new(raw.s)?,
rhs: Register::new(raw.t)?,
kind,
})
}
/// Encodes this instruction
#[must_use]
pub const fn encode(self) -> Raw {
let f = match self.kind {
Kind::Add => 0x0,
Kind::AddUnsigned => 0x1,
Kind::Sub => 0x2,
Kind::SubUnsigned => 0x3,
Kind::And => 0x4,
Kind::Or => 0x5,
Kind::Xor => 0x6,
Kind::Nor => 0x7,
Kind::SetLessThan => 0xa,
Kind::SetLessThanUnsigned => 0xb,
};
let d = self.dst.idx();
let s = self.lhs.idx();
let t = self.rhs.idx();
Raw { f, t, d, s }
}
}

View File

@ -0,0 +1,132 @@
//! Condition branches
// Imports
use crate::exe::inst::Register;
use dcb_util::SignedHex;
use int_conv::{Signed, Truncated, ZeroExtended};
use std::fmt;
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct Raw {
/// 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 Kind {
/// 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 Inst {
/// Argument, `rs`
pub arg: Register,
/// Offset
pub offset: i16,
/// Kind
pub kind: Kind,
}
impl Inst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: Raw) -> Option<Self> {
let kind = match raw.p {
0x1 => match raw.t {
0b00000 => Kind::LessThanZero,
0b00001 => Kind::GreaterOrEqualZero,
0b10000 => Kind::LessThanZeroLink,
0b10001 => Kind::GreaterOrEqualZeroLink,
_ => return None,
},
0x4 => Kind::Equal(Register::new(raw.t)?),
0x5 => Kind::NotEqual(Register::new(raw.t)?),
0x6 => Kind::LessOrEqualZero,
0x7 => Kind::GreaterThanZero,
_ => return None,
};
Some(Self {
arg: Register::new(raw.s)?,
offset: raw.i.truncated::<u16>().as_signed(),
kind,
})
}
/// Encodes this instruction
#[must_use]
pub fn encode(self) -> Raw {
#[rustfmt::skip]
let (p, t) = match self.kind {
Kind::Equal(reg) => (0x4, reg.idx()),
Kind::NotEqual(reg) => (0x5, reg.idx()),
Kind::LessOrEqualZero => (0x6, 0),
Kind::GreaterThanZero => (0x7, 0),
Kind::LessThanZero => (0x1, 0b00000),
Kind::GreaterOrEqualZero => (0x1, 0b00001),
Kind::LessThanZeroLink => (0x1, 0b10000),
Kind::GreaterOrEqualZeroLink => (0x1, 0b10001),
};
let s = self.arg.idx();
let i = self.offset.as_unsigned().zero_extended();
Raw { p, s, t, i }
}
}
// TODO: Fmt given `pc` / label
impl fmt::Display for Inst {
#[rustfmt::skip]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Self { arg, offset, kind } = self;
match kind {
Kind::Equal(reg) => write!(f, "beq {arg}, {reg}, {}", SignedHex(offset)),
Kind::NotEqual(reg) => write!(f, "bne {arg}, {reg}, {}", SignedHex(offset)),
Kind::LessOrEqualZero => write!(f, "blez {arg}, {}" , SignedHex(offset)),
Kind::GreaterThanZero => write!(f, "bgtz {arg}, {}" , SignedHex(offset)),
Kind::LessThanZero => write!(f, "bltz {arg}, {}" , SignedHex(offset)),
Kind::GreaterOrEqualZero => write!(f, "bgez {arg}, {}" , SignedHex(offset)),
Kind::LessThanZeroLink => write!(f, "bltzal {arg}, {}" , SignedHex(offset)),
Kind::GreaterOrEqualZeroLink => write!(f, "bgezal {arg}, {}" , SignedHex(offset)),
}
}
}

View File

@ -12,12 +12,12 @@ pub struct InstIter<'a, I: Iterator<Item = u32> + Clone> {
impl<'a, I: Iterator<Item = u32> + Clone> InstIter<'a, I> {
/// Creates a new instruction iterator
pub fn new(iter: &mut I) -> Self {
pub fn new(iter: &'a mut I) -> Self {
Self { iter }
}
/// Reborrows this iterator with a smaller lifetime
pub fn reborrow<'b>(&mut self) -> InstIter<'b, I>
pub fn reborrow<'b>(&'b mut self) -> InstIter<'b, I>
where
'a: 'b,
{
@ -50,7 +50,7 @@ pub struct InstPeeker<'a, I: Iterator<Item = u32> + Clone> {
impl<'a, I: Iterator<Item = u32> + Clone> InstPeeker<'a, I> {
/// Creates a new peeker
pub(self) fn new(iter: &mut InstIter<'a, I>) -> Self {
pub(self) fn new(iter: InstIter<'a, I>) -> Self {
Self {
iter,
last_iter: None,

View File

@ -0,0 +1,47 @@
//! Jmp register instructions
// Modules
pub mod imm;
pub mod reg;
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::From)]
pub enum Raw {
/// Immediate
Imm(imm::Raw),
/// Register
Reg(reg::Raw),
}
/// Jmp register instructions
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
pub enum Inst {
/// Immediate
Imm(imm::Inst),
/// Register
Reg(reg::Inst),
}
impl Inst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: impl Into<Raw>) -> Option<Self> {
match raw.into() {
Raw::Imm(raw) => Some(Self::Imm(imm::Inst::decode(raw)?)),
Raw::Reg(raw) => Some(Self::Reg(reg::Inst::decode(raw)?)),
}
}
/// Encodes this instruction
#[must_use]
pub const fn encode(self) -> Raw {
match self {
Self::Imm(inst) => Raw::Imm(inst.encode()),
Self::Reg(inst) => Raw::Reg(inst.encode()),
}
}
}

View File

@ -2,7 +2,7 @@
/// Jmp immediate instruction kind
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum JmpImmInstKind {
pub enum Kind {
/// Jump
Jump,
@ -10,19 +10,20 @@ pub enum JmpImmInstKind {
JumpLink,
}
impl JmpImmInstKind {
impl Kind {
/// Returns this kind's mnemonic
pub fn mnemonic(self) -> &'static str {
#[must_use]
pub const fn mnemonic(self) -> &'static str {
match self {
JmpImmInstKind::Jump => "j",
JmpImmInstKind::JumpLink => "jal",
Self::Jump => "j",
Self::JumpLink => "jal",
}
}
}
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct JmpImmInstRaw {
pub struct Raw {
/// Opcode (lower bit)
pub p: u32,
@ -34,21 +35,21 @@ pub struct JmpImmInstRaw {
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
#[display(fmt = "{} {target}", "kind.mnemonic()")]
pub struct JmpImmInst {
pub struct Inst {
/// Target
pub target: u32,
/// Kind
pub kind: JmpImmInstKind,
pub kind: Kind,
}
impl JmpImmInst {
impl Inst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: JmpImmInstRaw) -> Option<Self> {
pub const fn decode(raw: Raw) -> Option<Self> {
let kind = match raw.p {
0 => JmpImmInstKind::Jump,
1 => JmpImmInstKind::JumpLink,
0 => Kind::Jump,
1 => Kind::JumpLink,
_ => return None,
};
@ -57,13 +58,13 @@ impl JmpImmInst {
/// Encodes this instruction
#[must_use]
pub fn encode(self) -> JmpImmInstRaw {
let (p, i) = match self.kind {
JmpImmInstKind::Jump => 0,
JmpImmInstKind::JumpLink => 1,
pub const fn encode(self) -> Raw {
let p = match self.kind {
Kind::Jump => 0,
Kind::JumpLink => 1,
};
let i = self.target;
JmpImmInstRaw { p, i }
Raw { p, i }
}
}

View File

@ -1,12 +1,12 @@
//! Jump register instructions
// Imports
use crate::exe::instruction::Register;
use crate::exe::inst::Register;
use std::fmt;
/// Jmp register instruction kind
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum JmpRegInstKind {
pub enum Kind {
/// Jump
Jump,
@ -14,46 +14,47 @@ pub enum JmpRegInstKind {
JumpLink(Register),
}
impl JmpRegInstKind {
impl Kind {
/// Returns this kind's mnemonic
pub fn mnemonic(self) -> &'static str {
#[must_use]
pub const fn mnemonic(self) -> &'static str {
match self {
JmpRegInstKind::Jump => "j",
JmpRegInstKind::JumpLink(_) => "jal",
Self::Jump => "jr",
Self::JumpLink(_) => "jalr",
}
}
}
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct JmpRegInstRaw {
pub struct Raw {
/// Rs
s: u32,
pub s: u32,
/// Rd
d: u32,
pub d: u32,
/// Func (lower bit)
f: u32,
pub f: u32,
}
/// Jmp register instructions
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct JmpRegInst {
pub struct Inst {
/// Target
pub target: Register,
/// Kind
pub kind: JmpRegInstKind,
pub kind: Kind,
}
impl JmpRegInst {
impl Inst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: JmpRegInstRaw) -> Option<Self> {
pub fn decode(raw: Raw) -> Option<Self> {
let kind = match raw.f {
0 => JmpRegInstKind::Jump,
1 => JmpRegInstKind::JumpLink(Register::new(raw.d)?),
0 => Kind::Jump,
1 => Kind::JumpLink(Register::new(raw.d)?),
_ => return None,
};
let target = Register::new(raw.s)?;
@ -63,25 +64,25 @@ impl JmpRegInst {
/// Encodes this instruction
#[must_use]
pub fn encode(self) -> JmpRegInstRaw {
pub const fn encode(self) -> Raw {
let (f, d) = match self.kind {
JmpRegInstKind::Jump => (0, 0),
JmpRegInstKind::JumpLink(reg) => (1, reg.idx()),
Kind::Jump => (0, 0),
Kind::JumpLink(reg) => (1, reg.idx()),
};
let s = self.target.idx();
JmpRegInstRaw { s, d, f }
Raw { s, d, f }
}
}
impl fmt::Display for JmpRegInst {
impl fmt::Display for Inst {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mnemonic = self.kind.mnemonic();
let target = self.target;
match self.kind {
JmpRegInstKind::Jump => write!(f, "{mnemonic} {target}"),
JmpRegInstKind::JumpLink(reg) => write!(f, "{mnemonic} {target}, {reg}"),
Kind::Jump => write!(f, "{mnemonic} {target}"),
Kind::JumpLink(reg) => write!(f, "{mnemonic} {target}, {reg}"),
}
}
}

View File

@ -1,7 +1,7 @@
//! Load instructions
// Imports
use crate::exe::instruction::Register;
use crate::exe::inst::Register;
use dcb_util::SignedHex;
use int_conv::{Signed, Truncated, ZeroExtended};
use std::convert::TryFrom;
@ -12,7 +12,7 @@ use std::convert::TryFrom;
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
#[repr(u8)]
pub enum LoadKind {
pub enum Kind {
/// Byte, `i8`
Byte = 0x0,
@ -35,7 +35,7 @@ pub enum LoadKind {
WordRight = 0x6,
}
impl LoadKind {
impl Kind {
/// Returns the mnemonic for this load kind
#[must_use]
pub const fn mnemonic(self) -> &'static str {
@ -53,7 +53,7 @@ impl LoadKind {
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct LoadRaw {
pub struct Raw {
/// Opcode (lower 3 bits)
pub p: u32,
@ -70,8 +70,8 @@ pub struct LoadRaw {
/// Load instructions
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
#[display(fmt = "{} {dst}, {:#x}({src})", "kind.mnemonic()", "SignedHex(offset)")]
pub struct LoadInst {
#[display(fmt = "{} {dst}, {}({src})", "kind.mnemonic()", "SignedHex(offset)")]
pub struct Inst {
/// Source register, `rt`
pub src: Register,
@ -82,14 +82,14 @@ pub struct LoadInst {
pub offset: i16,
/// Kind
pub kind: LoadKind,
pub kind: Kind,
}
impl LoadInst {
impl Inst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: LoadRaw) -> Option<Self> {
let op = LoadKind::try_from(raw.p.truncated::<u8>()).ok()?;
pub fn decode(raw: Raw) -> Option<Self> {
let op = Kind::try_from(raw.p.truncated::<u8>()).ok()?;
Some(Self {
src: Register::new(raw.t)?,
@ -101,12 +101,12 @@ impl LoadInst {
/// Encodes this instruction
#[must_use]
pub fn encode(self) -> LoadRaw {
pub fn encode(self) -> Raw {
let t = self.src.idx();
let s = self.dst.idx();
let i = self.offset.as_unsigned().zero_extended::<u32>();
let p = u8::from(self.kind).zero_extended::<u32>();
LoadRaw { p, s, t, i }
Raw { p, s, t, i }
}
}

View File

@ -1,12 +1,12 @@
//! Lui instruction
// Imports
use crate::exe::instruction::Register;
use crate::exe::inst::Register;
use int_conv::{Truncated, ZeroExtended};
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct LuiRaw {
pub struct Raw {
/// Rt
pub t: u32,
@ -18,7 +18,7 @@ pub struct LuiRaw {
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
#[display(fmt = "lui {dst}, {value:#x}")]
pub struct LuiInst {
pub struct Inst {
/// Destination register, `rt`
pub dst: Register,
@ -26,10 +26,10 @@ pub struct LuiInst {
pub value: u16,
}
impl LuiInst {
impl Inst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: LuiRaw) -> Option<Self> {
pub fn decode(raw: Raw) -> Option<Self> {
Some(Self {
dst: Register::new(raw.t)?,
value: raw.i.truncated::<u16>(),
@ -38,8 +38,8 @@ impl LuiInst {
/// Encodes this instruction
#[must_use]
pub fn encode(self) -> LuiRaw {
LuiRaw {
pub fn encode(self) -> Raw {
Raw {
t: self.dst.idx(),
i: self.value.zero_extended::<u32>(),
}

View File

@ -1,7 +1,7 @@
//! Multiplications
// Imports
use crate::exe::instruction::Register;
use crate::exe::inst::Register;
use std::fmt;
/// Operation func
@ -90,8 +90,8 @@ pub enum MultInst {
impl MultInst {
/// Decodes this instruction
#[must_use]
#[rustfmt::skip]
pub fn decode(raw: MultRaw) -> Option<Self> {
#[rustfmt::skip]
Some(match raw.f {
0x10 => Self::MoveFrom { dst: Register::new(raw.d)?, src: MultReg::Hi },
0x12 => Self::MoveFrom { dst: Register::new(raw.d)?, src: MultReg::Lo },
@ -110,7 +110,7 @@ impl MultInst {
/// Encodes this instruction
#[must_use]
pub fn encode(self) -> MultRaw {
pub const fn encode(self) -> MultRaw {
match self {
Self::Mult { kind, mode, lhs, rhs } => MultRaw {
s: lhs.idx(),

View File

@ -1,7 +1,7 @@
//! Store instructions
// Imports
use crate::exe::instruction::Register;
use crate::exe::inst::Register;
use dcb_util::SignedHex;
use int_conv::{Signed, Truncated, ZeroExtended};
use std::convert::TryFrom;
@ -12,7 +12,7 @@ use std::convert::TryFrom;
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(num_enum::IntoPrimitive, num_enum::TryFromPrimitive)]
#[repr(u8)]
pub enum StoreKind {
pub enum Kind {
/// Byte, `u8`
Byte = 0x0,
@ -29,7 +29,7 @@ pub enum StoreKind {
WordRight = 0x6,
}
impl StoreKind {
impl Kind {
/// Returns the mnemonic for this store kind
#[must_use]
pub const fn mnemonic(self) -> &'static str {
@ -45,7 +45,7 @@ impl StoreKind {
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct StoreRaw {
pub struct Raw {
/// Opcode (lower 3 bits)
pub p: u32,
@ -62,8 +62,8 @@ pub struct StoreRaw {
/// Store instructions
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
#[display(fmt = "{} {dst}, {:#x}({src})", "kind.mnemonic()", "SignedHex(offset)")]
pub struct StoreInst {
#[display(fmt = "{} {dst}, {}({src})", "kind.mnemonic()", "SignedHex(offset)")]
pub struct Inst {
/// Source register, `rt`
pub src: Register,
@ -74,14 +74,14 @@ pub struct StoreInst {
pub offset: i16,
/// Kind
pub kind: StoreKind,
pub kind: Kind,
}
impl StoreInst {
impl Inst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: StoreRaw) -> Option<Self> {
let kind = StoreKind::try_from(raw.p.truncated::<u8>()).ok()?;
pub fn decode(raw: Raw) -> Option<Self> {
let kind = Kind::try_from(raw.p.truncated::<u8>()).ok()?;
Some(Self {
src: Register::new(raw.t)?,
@ -93,12 +93,12 @@ impl StoreInst {
/// Encodes this instruction
#[must_use]
pub fn encode(self) -> StoreRaw {
pub fn encode(self) -> Raw {
let t = self.src.idx();
let s = self.dst.idx();
let i = self.offset.as_unsigned().zero_extended::<u32>();
let p = u8::from(self.kind).zero_extended::<u32>();
StoreRaw { p, s, t, i }
Raw { p, s, t, i }
}
}

View File

@ -2,7 +2,7 @@
/// Sys instruction func
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum SysInstKind {
pub enum Kind {
/// Syscall
Sys,
@ -10,19 +10,20 @@ pub enum SysInstKind {
Break,
}
impl SysInstKind {
impl Kind {
/// Returns the mnemonic associated with this syscall kind
pub fn mnemonic(self) -> &'static str {
#[must_use]
pub const fn mnemonic(self) -> &'static str {
match self {
SysInstKind::Sys => "sys",
SysInstKind::Break => "break",
Self::Sys => "sys",
Self::Break => "break",
}
}
}
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct SysInstRaw {
pub struct Raw {
/// Comment
pub c: u32,
@ -34,21 +35,21 @@ pub struct SysInstRaw {
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
#[display(fmt = "{}, {comment:#x}", "kind.mnemonic()")]
pub struct SysInst {
pub struct Inst {
/// Comment
pub comment: u32,
/// Kind
pub kind: SysInstKind,
pub kind: Kind,
}
impl SysInst {
impl Inst {
/// Decodes this instruction
#[must_use]
pub fn decode(SysInstRaw { c, f }: SysInstRaw) -> Option<Self> {
pub const fn decode(Raw { c, f }: Raw) -> Option<Self> {
let kind = match f {
0 => SysInstKind::Sys,
1 => SysInstKind::Break,
0 => Kind::Sys,
1 => Kind::Break,
_ => return None,
};
@ -58,13 +59,13 @@ impl SysInst {
/// Encodes this instruction
#[must_use]
#[allow(clippy::many_single_char_names)] // `Raw` has single character names
pub fn encode(self) -> SysInstRaw {
let c = self.comment.to_be_bytes();
pub const fn encode(self) -> Raw {
let c = self.comment;
let f = match self.kind {
SysInstKind::Sys => 0,
SysInstKind::Break => 1,
Kind::Sys => 0,
Kind::Break => 1,
};
SysInstRaw { c, f }
Raw { c, f }
}
}

View File

@ -0,0 +1,168 @@
//! Directives
// Imports
//use super::{FromRawIter, Instruction, Raw};
use super::Inst;
use crate::exe::Pos;
use ascii::AsciiChar;
use dcb_util::NextFromBytes;
use std::ops::{
Bound::{self, Excluded, Included, Unbounded},
RangeBounds,
};
/// A directive
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Directive {
/// Write word
Dw(u32),
/// Write half-word
Dh(u16),
/// Write byte
Db(u8),
/// Ascii string
Ascii {
/// String length
len: u32,
},
}
/// A force decode range
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct ForceDecodeRange {
/// Start bound
start: Bound<Pos>,
/// End bound
end: Bound<Pos>,
/// Decoding kind
kind: ForceDecodeKind,
}
impl RangeBounds<Pos> for ForceDecodeRange {
fn start_bound(&self) -> Bound<&Pos> {
match self.start {
Included(ref start) => Included(start),
Excluded(ref start) => Excluded(start),
Unbounded => Unbounded,
}
}
fn end_bound(&self) -> Bound<&Pos> {
match self.end {
Included(ref end) => Included(end),
Excluded(ref end) => Excluded(end),
Unbounded => Unbounded,
}
}
}
/// Force decode range kind
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum ForceDecodeKind {
/// Single Word
Word,
/// Half word
HalfWord,
/// Bytes
Byte,
}
impl Directive {
/// Positions that should be force decoded using a specific variant.
// TODO: Get this at run-time via a file.
pub const FORCE_DECODE_RANGES: &'static [ForceDecodeRange] = &[
ForceDecodeRange {
start: Included(Pos(0x80010000)),
end: Excluded(Pos(0x80010008)),
kind: ForceDecodeKind::Word,
},
ForceDecodeRange {
start: Included(Pos(0x8006fa20)),
end: Excluded(Pos(0x8006fa24)),
kind: ForceDecodeKind::HalfWord,
},
ForceDecodeRange {
start: Included(Inst::CODE_END),
end: Unbounded,
kind: ForceDecodeKind::Word,
},
];
/// Returns the size of this directive
#[must_use]
pub const fn size(self) -> u32 {
match self {
Self::Dw(_) => 4,
Self::Dh(_) => 2,
Self::Db(_) => 1,
// Round ascii strings' len up to the
// nearest word (or one after if exactly 1 word).
Self::Ascii { len } => len + 4 - (len % 4),
}
}
}
impl Directive {
/// Decodes a directive
#[must_use]
pub fn decode(pos: Pos, bytes: &[u8]) -> Option<(Self, usize)> {
// Check if we need to force decode it
if let Some(ForceDecodeRange { kind, .. }) = Self::FORCE_DECODE_RANGES.iter().find(|range| range.contains(&pos)) {
#[rustfmt::skip]
return match kind {
ForceDecodeKind::Word => bytes.next_u32().map(|value| (Self::Dw(value), 4)),
ForceDecodeKind::HalfWord => bytes.next_u16().map(|value| (Self::Dh(value), 2)),
ForceDecodeKind::Byte => bytes.next_u8 ().map(|value| (Self::Db(value), 1)),
};
}
// Else try to get a string
if let Some((str_len, with_nulls_len)) = self::read_ascii_until_null(pos, bytes) {
debug_assert!(with_nulls_len % 4 == 0, "Ascii string length wasn't multiple of 4");
return Some((Self::Ascii { len: str_len }, with_nulls_len));
}
// Else try to read a `u32`
if let Some(value) = bytes.next_u32() {
return Some((Self::Dw(value), 4));
}
// Else read a single byte
bytes.next_u8().map(|value| (Self::Db(value), 1))
}
}
/// Reads an ascii string from a byte slice until null.
///
/// Will always read in multiples of a word (4 bytes), including the null.
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)] // Our length will always fit into a `u32`.
fn read_ascii_until_null(pos: Pos, bytes: &[u8]) -> Option<(u32, usize)> {
// Get the next null or invalid character
let (idx, null) = bytes.iter().enumerate().find_map(|(idx, &byte)| match AsciiChar::from_ascii(byte) {
Ok(AsciiChar::Null) => Some((idx, true)),
Err(_) => Some((idx, false)),
_ => None,
})?;
// If it wasn't a null or the first character was a null, return None
if !null || idx == 0 {
return None;
}
// Else make sure until the end of the word it's all nulls
let nulls_len = 4 - ((pos.0 as usize + idx) % 4);
let nulls = bytes.get(idx..idx + nulls_len)?;
if !nulls.iter().all(|&byte| byte == 0) {
return None;
}
// Else return both lengths
Some((idx as u32, idx + nulls_len))
}

View File

@ -5,29 +5,23 @@
// Modules
pub mod alu_assign;
pub mod jmp;
pub mod load;
pub mod load_imm;
pub mod move_reg;
pub mod nop;
pub mod store;
// Exports
pub use alu_assign::AluAssignInst;
pub use jmp::JmpPseudoInst;
pub use load::LoadPseudoInst;
pub use load_imm::LoadImmInst;
pub use move_reg::MoveRegPseudoInst;
pub use nop::NopInst;
pub use store::StorePseudoInst;
//pub mod jmp;
//pub mod load;
//pub mod load_imm;
//pub mod move_reg;
//pub mod nop;
//pub mod store;
// Imports
use crate::exe::instruction::basic::InstIter;
use super::basic;
/// A pseudo instruction
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
pub enum PseudoInst {
/// Alu self-assign
AluAssign(alu_assign::Inst),
/*
/// Load
Load(LoadPseudoInst),
@ -43,8 +37,7 @@ pub enum PseudoInst {
/// No-op
Nop(NopInst),
/// Alu self-assign
AluAssign(AluAssignInst),
*/
/*
/// Subtract immediate
/// Alias for `addi $rt, $rs, -imm`
@ -68,6 +61,16 @@ pub enum PseudoInst {
*/
}
impl PseudoInst {
/// Attempts to parse a pseudo instruction from a start
/// basic instruction and remaining bytes
#[must_use]
pub fn decode(inst: basic::Inst, bytes: &[u8]) -> Option<(Self, usize)> {
alu_assign::Inst::decode(inst, bytes).map(|(inst, len)| (Self::AluAssign(inst), len))
}
}
/*
impl PseudoInst {
pub fn decode(iter: InstIter<'_, impl Iterator<Item = u32> + Clone>) -> Option<Self> {
LoadPseudoInst::decode(iter)
@ -79,3 +82,4 @@ impl PseudoInst {
.or_else(|| JmpPseudoInst::decode(iter))
}
}
*/

View File

@ -0,0 +1,96 @@
//! Alu self-assign instructions
// Imports
use crate::exe::inst::{
basic::{self, alu},
Register,
};
use std::fmt;
/// Alu assign kind
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum Kind {
/// Immediate
Imm {
/// Kind
kind: alu::imm::Kind,
},
/// Register
Reg {
/// Kind
kind: alu::reg::Kind,
/// Argument
rhs: Register,
},
}
impl Kind {
/// Returns this kind's mnemonic
#[must_use]
pub const fn mnemonic(self) -> &'static str {
match self {
Self::Imm { kind } => kind.mnemonic(),
Self::Reg { kind, .. } => kind.mnemonic(),
}
}
/// Returns a displayable with the value of this kind
#[must_use]
pub fn value_fmt(self) -> impl fmt::Display {
/// Display wrapper
struct FmtValue(Kind);
impl fmt::Display for FmtValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
Kind::Imm { kind } => write!(f, "{}", kind.value_fmt()),
Kind::Reg { rhs, .. } => write!(f, "{}", rhs),
}
}
}
FmtValue(self)
}
}
/// Alu self-assign instructions
///
/// Alias for
/// ```mips
/// [alu] $dst, $dst, $i
/// ```
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
#[display(fmt = "{} {dst}, {}", "kind.mnemonic()", "kind.value_fmt()")]
pub struct Inst {
/// Destination and source register
pub dst: Register,
/// Kind
pub kind: Kind,
}
impl Inst {
/// Decodes this pseudo instruction
#[must_use]
pub fn decode(inst: basic::Inst, _bytes: &[u8]) -> Option<(Self, usize)> {
let inst = match inst {
basic::Inst::Alu(inst) => match inst {
alu::Inst::Imm(alu::imm::Inst { dst, lhs, kind }) if dst == lhs => Some(Self {
dst,
kind: Kind::Imm { kind },
}),
alu::Inst::Reg(alu::reg::Inst { dst, lhs, rhs, kind }) if dst == lhs => Some(Self {
dst,
kind: Kind::Reg { kind, rhs },
}),
_ => None,
},
_ => None,
};
inst.map(|inst| (inst, 0))
}
}

View File

@ -1,7 +1,7 @@
//! Jump pseudo instructions
// Imports
use crate::exe::instruction::{
use crate::exe::inst::{
basic::{
cond::CondKind,
special::{jmp::JmpKind, JmpInst},

View File

@ -1,7 +1,7 @@
//! Load instructions
// Imports
use crate::exe::instruction::{
use crate::exe::inst::{
basic::{load::LoadKind, InstIter},
BasicInst, Register,
};

View File

@ -1,7 +1,7 @@
//! Load immediate
// Imports
use crate::exe::instruction::{
use crate::exe::inst::{
basic::{alu_imm::AluImmKind, AluImmInst, InstIter},
BasicInst, Register,
};
@ -48,13 +48,13 @@ impl LoadImmKind {
struct FmtValue(Self);
impl fmt::Display for FmtValue {
#[rustfmt::skip]
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
#[rustfmt::skip]
match self.0 {
LoadImmKind::Address(address) => write!(f, "{address:#x}"),
LoadImmKind::Word(value) => write!(f, "{value:#x}"),
LoadImmKind::HalfWordUnsigned(value) => write!(f, "{value:#x}"),
LoadImmKind::HalfWordSigned(value) => write!(f, "{:#x}", SignedHex(value)),
LoadImmKind::HalfWordSigned(value) => write!(f, "{}", SignedHex(value)),
}
}
}

View File

@ -1,7 +1,7 @@
//! Move register instruction
// Imports
use crate::exe::instruction::{
use crate::exe::inst::{
basic::{
alu_imm::AluImmKind,
special::{

View File

@ -1,7 +1,7 @@
//! Nop
// Imports
use crate::exe::instruction::{
use crate::exe::inst::{
basic::{
special::{
shift::{reg::ShiftRegFunc, ShiftImmInst},

View File

@ -1,7 +1,7 @@
//! Store instructions
// Imports
use crate::exe::instruction::{
use crate::exe::inst::{
basic::{store::StoreKind, InstIter},
BasicInst, Register,
};

View File

@ -13,6 +13,7 @@ pub struct Raw {
pub pos: Pos,
}
/*
/// Raw instruction decoding
///
/// Implementors should be atomic about the consumed and
@ -25,3 +26,4 @@ pub trait FromRawIter: Sized {
/// Attempts to decode an instruction from an iterator of raw instructions
fn decode<I: Iterator<Item = Raw> + Clone>(iter: &mut I) -> Self::Decoded;
}
*/

View File

@ -66,7 +66,7 @@ macro_rules! generate_register {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.trim() {
match s {
$(
$fmt => Ok(Self::$variant),
)*
@ -95,7 +95,7 @@ impl Register {
/// Checks if this register is an argument register
#[must_use]
pub fn is_arg(self) -> bool {
pub const fn is_arg(self) -> bool {
self.arg_idx().is_some()
}
}

View File

@ -1,133 +0,0 @@
//! Psx cpu instructions
// Modules
pub mod basic;
//pub mod directive;
//pub mod pseudo;
pub mod raw;
pub mod reg;
// Exports
pub use basic::BasicInst;
//pub use directive::Directive;
//pub use pseudo::PseudoInst;
pub use raw::{FromRawIter, Raw};
pub use reg::Register;
// Imports
use crate::exe::Pos;
/// An assembler instruction
#[derive(PartialEq, Eq, Clone, Debug)]
#[derive(derive_more::Display)]
pub struct Instruction {
/*
/// A basic instruction
Basic(BasicInst),
/// A pseudo instruction
Pseudo(PseudoInst),
/// A directive
Directive(Directive),
*/}
impl Instruction {
/// End of the code itself in the executable.
pub const CODE_END: Pos = Pos(0x8006dd3c);
/// Start of the code itself in the executable.
pub const CODE_START: Pos = Pos(0x80013e4c);
}
/// Iterator adaptor for converting [`RawInstruction`]s into [`Instruction`]s.
pub struct Iter<I: Iterator<Item = Raw> + Clone> {
/// Underlying iterator
iter: I,
/// Remaining items from last iterator
remaining: Option<Box<dyn Iterator<Item = (Pos, Instruction)>>>,
}
impl<I: Iterator<Item = Raw> + Clone> Iter<I> {
/*
/// Helper function to try to decode without consuming the iterator
fn try_decode<T: FromRawIter>(iter: &I) -> (I, T::Decoded) {
let mut cloned_iter = iter.clone();
let decoded = T::decode(&mut cloned_iter);
(cloned_iter, decoded)
}
/// Helper function to try to get instructions from `T`.
fn try_next_from<T: FromRawIter + 'static>(&mut self, to_instruction: fn(T) -> Instruction) -> Option<(Pos, Instruction)> {
// Try to decode it and get all instructions
let (iter, instructions) = Self::try_decode::<T>(&self.iter);
// Map the instructions to be an iterator over `Instruction` and peekable
let mut instructions = instructions
.into_iter()
.map(move |(pos, decoded)| (pos, to_instruction(decoded)))
.peekable();
// Then check if we got any from the decode
match instructions.next() {
// If we did, set our iter, set any remaining instructions and return the instruction
Some(instruction) => {
self.iter = iter;
// If there are any instructions left, set remaining, else just leave it
if instructions.peek().is_some() {
self.remaining = Some(Box::new(instructions));
}
Some(instruction)
},
// Else we didn't get anything, don't update the iterator.
None => None,
}
}
*/
/// Returns the current position of the iterator
fn cur_pos(&self) -> Option<Pos> {
self.iter.clone().next().map(|raw| raw.pos)
}
}
impl<I: Iterator<Item = Raw> + Clone> Iterator for Iter<I> {
type Item = (Pos, Instruction);
fn next(&mut self) -> Option<Self::Item> {
// If we have remaining instruction, supply them
if let Some(remaining) = self.remaining.as_mut() {
if let Some(instruction) = remaining.next() {
return Some(instruction);
} else {
// Note: We set it to none in case `next` is expensive to check.
self.remaining = None;
}
}
// Else get the current position
let cur_pos = self.cur_pos()?;
// If we're before the code start, just read directives
if cur_pos < Instruction::CODE_START || cur_pos >= Instruction::CODE_END {
todo!();
//return self.try_next_from(Instruction::Directive);
}
// Else try to decode it as a pseudo, basic or directive, in that order.
todo!()
/*
self.try_next_from(Instruction::Pseudo)
.or_else(|| self.try_next_from(Instruction::Basic))
.or_else(|| self.try_next_from(Instruction::Directive))
*/
}
}
impl Instruction {
/// Adapts an iterator over raw words to an instruction iterator
pub fn new_iter<I: Iterator<Item = Raw> + Clone>(iter: I) -> Iter<I> {
Iter { iter, remaining: None }
}
}

View File

@ -1,107 +0,0 @@
//! Basic instructions
//!
//! This modules defines all the basic instructions from the psx.
//! They are all 1 word (`u32`) long.
// Modules
pub mod alu;
pub mod cond;
pub mod iter;
pub mod jmp;
pub mod load;
pub mod lui;
pub mod mult;
pub mod store;
pub mod sys;
// Exports
pub use alu::{AluInst, AluInstRaw};
pub use cond::{CondInst, CondRaw};
pub use iter::InstIter;
pub use jmp::{JmpInst, JmpInstRaw};
pub use load::{LoadInst, LoadRaw};
pub use lui::{LuiInst, LuiRaw};
//pub use special::{SpecialInst, SpecialRaw};
pub use store::{StoreInst, StoreRaw};
pub use sys::{SysInst, SysInstRaw};
/// All basic instructions
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
pub enum BasicInst {
/// Store
Store(StoreInst),
/// Load
Load(LoadInst),
/// Condition
Cond(CondInst),
/// Jump
Jmp(JmpInst),
/// Alu
Alu(AluInst),
/// Load upper immediate
Lui(LuiInst),
// Syscall
Sys(SysInst),
}
impl BasicInst {
/// 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<Self> {
use BasicInst::*;
let inst = #[bitmatch]
match raw {
// Jump
"00001p_iiiii_iiiii_iiiii_iiiii_iiiiii" => Jmp(JmpInst::decode(JmpInstRaw::Imm(jmp::JmpImmInstRaw { p, i }))),
"000000_sssss_?????_ddddd_?????_00100f" => Jmp(JmpInst::decode(JmpInstRaw::Reg(jmp::JmpRegInstRaw { s, d, f }))),
"000ppp_sssss_ttttt_iiiii_iiiii_iiiiii" => Cond(CondInst::decode(CondRaw { p, s, t, i })?),
"001111_?????_ttttt_iiiii_iiiii_iiiiii" => Lui(LuiInst::decode(LuiRaw { t, i })?),
// Alu
"000000_sssss_ttttt_ddddd_?????_10ffff" => Alu(AluInst::decode(AluInstRaw::Imm(alu::AluRegInstRaw { s, t, d, f }))?),
"001ppp_sssss_ttttt_iiiii_iiiii_iiiiii" => Alu(AluInst::decode(AluInstRaw::Reg(alu::AluImmInstRaw { p, s, t, i }))?),
// Syscall
"000000_ccccc_ccccc_ccccc_ccccc_00110f" => Sys(SysInst::decode(SysInstRaw { c, f })?),
// Store / Load
"100ppp_sssss_ttttt_iiiii_iiiii_iiiiii" => Store(StoreInst::decode(StoreRaw { p, s, t, i })?),
"101ppp_sssss_ttttt_iiiii_iiiii_iiiiii" => Load(LoadInst::decode(LoadRaw { p, s, t, i })?),
/*
"0100nn_1iiii_iiiii_iiiii_iiiii_iiiiii" => CopN { n: n.truncate(), imm: i},
"0100nn_00000_ttttt_ddddd_?????_000000" => MfcN { n: n.truncate(), rt: reg(t)?, rd: reg(d)? },
"0100nn_00010_ttttt_ddddd_?????_000000" => CfcN { n: n.truncate(), rt: reg(t)?, rd: reg(d)? },
"0100nn_00100_ttttt_ddddd_?????_000000" => MtcN { n: n.truncate(), rt: reg(t)?, rd: reg(d)? },
"0100nn_00110_ttttt_ddddd_?????_000000" => CtcN { n: n.truncate(), rt: reg(t)?, rd: reg(d)? },
"0100nn_01000_00000_iiiii_iiiii_iiiiii" => BcNf { n: n.truncate(), target: i.truncate() },
"0100nn_01000_00001_iiiii_iiiii_iiiiii" => BcNt { n: n.truncate(), target: i.truncate() },
"1100nn_sssss_ttttt_iiiii_iiiii_iiiiii" => LwcN { n: n.truncate(), rs: reg(s)?, rt: reg(t)?, imm: i.truncate() },
"1110nn_sssss_ttttt_iiiii_iiiii_iiiiii" => SwcN { n: n.truncate(), rs: reg(s)?, rt: reg(t)?, imm: i.truncate() },
*/
_ => return None,
};
Some(inst)
}
/// Encodes this instruction
#[must_use]
#[bitmatch::bitmatch]
pub fn encode(self) -> u32 {
#[rustfmt::skip]
match self {
_ => todo!(),
}
}
}

View File

@ -1,50 +0,0 @@
//! Alu instructions
// Modules
pub mod imm;
pub mod reg;
// Exports
pub use imm::{AluImmInst, AluImmInstKind, AluImmInstRaw};
pub use reg::{AluRegInst, AluRegInstKind, AluRegInstRaw};
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum AluInstRaw {
/// Immediate
Imm(AluImmInstRaw),
/// Register
Reg(AluRegInstRaw),
}
/// Alu register instructions
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
pub enum AluInst {
/// Immediate
Imm(AluImmInst),
/// Register
Reg(AluRegInst),
}
impl AluInst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: AluInstRaw) -> Option<Self> {
match raw {
AluInstRaw::Imm(raw) => Self::Imm(AluImmInst::decode(raw)?),
AluInstRaw::Reg(raw) => Self::Reg(AluRegInst::decode(raw)?),
}
}
/// Encodes this instruction
#[must_use]
pub fn encode(self) -> AluInstRaw {
match self {
AluInst::Imm(inst) => AluInstRaw::Imm(inst.encode()),
AluInst::Reg(inst) => AluInstRaw::Reg(inst.encode()),
}
}
}

View File

@ -1,133 +0,0 @@
//! Alu register instructions
// Imports
use crate::exe::instruction::Register;
/// Alu register instruction kind
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
pub enum AluRegInstKind {
/// Add signed with overflow trap
#[display(fmt = "add")]
Add,
/// Add signed without overflow trap
#[display(fmt = "addu")]
AddUnsigned,
/// Sub signed with overflow trap
#[display(fmt = "sub")]
Sub,
/// Sub signed without overflow trap
#[display(fmt = "subu")]
SubUnsigned,
/// Bit and
#[display(fmt = "and")]
And,
/// Bit or
#[display(fmt = "or")]
Or,
/// Bit xor
#[display(fmt = "xor")]
Xor,
/// Bit nor
#[display(fmt = "nor")]
Nor,
/// Set on less than signed
#[display(fmt = "slt")]
SetLessThan,
/// Set on less than unsigned
#[display(fmt = "sltu")]
SetLessThanUnsigned,
}
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct AluRegInstRaw {
/// Rs
s: u32,
/// Rt
t: u32,
/// Rd
d: u32,
/// Func (lower 4 bits)
f: u32,
}
/// Alu register instructions
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
#[display(fmt = "{kind} {dst}, {lhs}, {rhs}")]
pub struct AluRegInst {
/// Destination register
pub dst: Register,
/// Lhs argument
pub lhs: Register,
/// Rhs argument
pub rhs: Register,
/// Kind
pub kind: AluRegInstKind,
}
impl AluRegInst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: AluRegInstRaw) -> Option<Self> {
let kind = match raw.f {
0x0 => AluRegInstKind::Add,
0x1 => AluRegInstKind::AddUnsigned,
0x2 => AluRegInstKind::Sub,
0x3 => AluRegInstKind::SubUnsigned,
0x4 => AluRegInstKind::And,
0x5 => AluRegInstKind::Or,
0x6 => AluRegInstKind::Xor,
0x7 => AluRegInstKind::Nor,
0xa => AluRegInstKind::SetLessThan,
0xb => AluRegInstKind::SetLessThanUnsigned,
_ => return None,
};
Some(Self {
dst: Register::new(raw.d)?,
lhs: Register::new(raw.s)?,
rhs: Register::new(raw.t)?,
kind,
})
}
/// Encodes this instruction
#[must_use]
pub fn encode(self) -> AluRegInstRaw {
let f = match self.kind {
AluRegInstKind::Add => 0x0,
AluRegInstKind::AddUnsigned => 0x1,
AluRegInstKind::Sub => 0x2,
AluRegInstKind::SubUnsigned => 0x3,
AluRegInstKind::And => 0x4,
AluRegInstKind::Or => 0x5,
AluRegInstKind::Xor => 0x6,
AluRegInstKind::Nor => 0x7,
AluRegInstKind::SetLessThan => 0xa,
AluRegInstKind::SetLessThanUnsigned => 0xb,
};
let d = self.dst.idx();
let s = self.lhs.idx();
let t = self.rhs.idx();
AluRegInstRaw { f, t, d, s }
}
}

View File

@ -1,132 +0,0 @@
//! Condition branches
// Imports
use crate::exe::instruction::Register;
use dcb_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<Self> {
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::<u16>().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)),
}
}
}

View File

@ -1,50 +0,0 @@
//! Jmp register instructions
// Modules
pub mod imm;
pub mod reg;
// Exports
pub use imm::{JmpImmInst, JmpImmInstRaw};
pub use reg::{JmpRegInst, JmpRegInstRaw};
/// Raw representation
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum JmpInstRaw {
/// Immediate
Imm(JmpImmInstRaw),
/// Register
Reg(JmpRegInstRaw),
}
/// Jmp register instructions
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
pub enum JmpInst {
/// Immediate
Imm(JmpImmInst),
/// Register
Reg(JmpRegInst),
}
impl JmpInst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: JmpInstRaw) -> Option<Self> {
match raw {
JmpInstRaw::Imm(raw) => Self::Imm(JmpImmInst::decode(raw)?),
JmpInstRaw::Reg(raw) => Self::Reg(JmpRegInst::decode(raw)?),
}
}
/// Encodes this instruction
#[must_use]
pub fn encode(self) -> JmpInstRaw {
match self {
JmpInst::Imm(inst) => JmpInstRaw::Imm(inst.encode()),
JmpInst::Reg(inst) => JmpInstRaw::Reg(inst.encode()),
}
}
}

View File

@ -1,78 +0,0 @@
//! Jumps
// Imports
use crate::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<Self> {
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}"),
}
}
}

View File

@ -1,84 +0,0 @@
//! 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<ShiftRaw> for ShiftImmRaw {
fn from(ShiftRaw { t, d, i, f, .. }: ShiftRaw) -> Self {
Self { t, d, i, f }
}
}
impl From<ShiftImmRaw> for ShiftRaw {
fn from(ShiftImmRaw { t, d, i, f }: ShiftImmRaw) -> Self {
Self { t, d, s: 0, i, f }
}
}
impl From<ShiftRaw> for ShiftRegRaw {
fn from(ShiftRaw { t, d, s, f, .. }: ShiftRaw) -> Self {
Self { t, d, s, f }
}
}
impl From<ShiftRegRaw> 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<Self> {
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(),
}
}
}

View File

@ -1,93 +0,0 @@
//! Immediate shifts
// Imports
use crate::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 {
/// Destination register, `rt`
pub dst: Register,
/// Lhs argument, `rd`
pub lhs: Register,
/// Rhs argument, immediate
pub rhs: i16,
/// Function
pub func: ShiftImmFunc,
}
impl ShiftImmInst {
/// Decodes this instruction
#[must_use]
pub fn decode(raw: ShiftImmRaw) -> Option<Self> {
let func = ShiftImmFunc::try_from(raw.f.truncated::<u8>()).ok()?;
Some(Self {
lhs: Register::new(raw.t)?,
dst: Register::new(raw.d)?,
rhs: raw.i.truncated::<u16>().as_signed(),
func,
})
}
/// Encodes this instruction
#[must_use]
pub fn encode(self) -> ShiftImmRaw {
let t = self.lhs.idx();
let d = self.dst.idx();
let i = self.rhs.as_unsigned().zero_extended::<u32>();
let f = u8::from(self.func).zero_extended::<u32>();
ShiftImmRaw { f, t, d, i }
}
}
impl fmt::Display for ShiftImmInst {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Self { lhs, dst, rhs, func } = self;
let mnemonic = match func {
ShiftImmFunc::LeftLogical => "sll",
ShiftImmFunc::RightLogical => "srl",
ShiftImmFunc::RightArithmetic => "sra",
};
write!(f, "{mnemonic} {dst}, {lhs}, {rhs:#x}")
}
}

View File

@ -1,93 +0,0 @@
//! Register shifts
// Imports
use crate::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 dst: 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<Self> {
let func = ShiftRegFunc::try_from(raw.f.truncated::<u8>()).ok()?;
Some(Self {
dst: 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.dst.idx();
let t = self.lhs.idx();
let s = self.rhs.idx();
let f = u8::from(self.func).zero_extended::<u32>();
ShiftRegRaw { f, t, d, s }
}
}
impl fmt::Display for ShiftRegInst {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let Self { dst, lhs, rhs, func } = self;
let mnemonic = match func {
ShiftRegFunc::LeftLogical => "sllv",
ShiftRegFunc::RightLogical => "srlv",
ShiftRegFunc::RightArithmetic => "srav",
};
write!(f, "{mnemonic} {dst}, {lhs}, {rhs}")
}
}

View File

@ -1,235 +0,0 @@
//! Directives
// Imports
use super::{FromRawIter, Instruction, Raw};
use crate::exe::Pos;
use ascii::{AsciiChar, AsciiStr, AsciiString};
use int_conv::Split;
use smallvec::{smallvec, SmallVec};
use std::ops::{
Bound::{self, Excluded, Included, Unbounded},
RangeBounds,
};
use AsciiChar::Null;
/// A directive
#[derive(PartialEq, Eq, Clone, Debug)]
#[derive(derive_more::Display)]
pub enum Directive {
/// Write word
#[display(fmt = "dw {_0:#x}")]
Dw(u32),
/// Write half-word
#[display(fmt = "dh {_0:#x}")]
Dh(u16),
/// Write byte
#[display(fmt = "db {_0:#x}")]
Db(u8),
/// Ascii string
#[display(fmt = ".ascii {_0:?}")]
Ascii(AsciiString),
}
/// A force decode range
pub struct ForceDecodeRange {
/// Start bound
start: Bound<Pos>,
/// End bound
end: Bound<Pos>,
/// Decoding kind
kind: ForceDecodeKind,
}
impl RangeBounds<Pos> for ForceDecodeRange {
fn start_bound(&self) -> Bound<&Pos> {
match self.start {
Included(ref start) => Included(start),
Excluded(ref start) => Excluded(start),
Unbounded => Unbounded,
}
}
fn end_bound(&self) -> Bound<&Pos> {
match self.end {
Included(ref end) => Included(end),
Excluded(ref end) => Excluded(end),
Unbounded => Unbounded,
}
}
}
/// Force decode range kind
pub enum ForceDecodeKind {
/// Single Word
W,
/// Two half-words
HH,
/// Half-word followed by bytes
HBB,
/// Bytes followed by half-word
BBH,
/// Bytes
BBBB,
}
impl Directive {
/// Positions that should be force decoded using a specific variant.
pub const FORCE_DECODE_RANGES: &'static [ForceDecodeRange] = &[
ForceDecodeRange {
start: Included(Pos(0x80010000)),
end: Excluded(Pos(0x80010008)),
kind: ForceDecodeKind::W,
},
ForceDecodeRange {
start: Included(Pos(0x8006fa20)),
end: Excluded(Pos(0x8006fa24)),
kind: ForceDecodeKind::HH,
},
ForceDecodeRange {
start: Included(Instruction::CODE_END),
end: Unbounded,
kind: ForceDecodeKind::W,
},
];
/// Returns the size of this instruction
#[must_use]
pub fn size(&self) -> u32 {
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)] // Our length will always fit into a `u32`.
match self {
Self::Dw(_) => 4,
Self::Dh(_) => 2,
Self::Db(_) => 1,
// Round ascii strings' len up to the
// nearest word.
Self::Ascii(ascii) => {
let len = ascii.len() as u32;
len + 4 - (len % 4)
},
}
}
}
/// Helper function to check if a string has null and if everything after the first
/// null is also null (or if there were no nulls).
fn check_nulls<S: AsRef<AsciiStr>>(s: S) -> (S, usize, bool) {
let null_idx = s
.as_ref()
.as_slice()
.iter()
.position(|&ch| ch == Null)
.unwrap_or_else(|| s.as_ref().len());
#[allow(clippy::indexing_slicing)] // `null_idx <= len`
let uniform_null = s.as_ref()[null_idx..].chars().all(|ch| ch == Null);
(s, null_idx, uniform_null)
}
impl FromRawIter for Directive {
//type Decoded = Option<(Pos, Self)>;
// Note: We return at most 4 directives.
type Decoded = SmallVec<[(Pos, Self); 4]>;
fn decode<I: Iterator<Item = Raw> + Clone>(iter: &mut I) -> Self::Decoded {
// Get the first raw
let raw = match iter.next() {
Some(raw) => raw,
None => return smallvec![],
};
// If we're past all the code, there are no more strings,
// so just decode a `dw`.
// Note: We're working in big endian when returning these.
if let Some(ForceDecodeRange { kind, .. }) = Self::FORCE_DECODE_RANGES.iter().find(|range| range.contains(&raw.pos)) {
return match kind {
ForceDecodeKind::W => smallvec![(raw.pos, Self::Dw(raw.repr))],
ForceDecodeKind::HH => {
let (lo, hi) = raw.repr.lo_hi();
smallvec![(raw.pos, Self::Dh(hi)), (raw.pos + 2, Self::Dh(lo))]
},
ForceDecodeKind::HBB => {
let (lo, hi) = raw.repr.lo_hi();
let (lo_lo, lo_hi) = lo.lo_hi();
smallvec![(raw.pos, Self::Dh(hi)), (raw.pos + 2, Self::Db(lo_hi)), (raw.pos + 3, Self::Db(lo_lo))]
},
ForceDecodeKind::BBH => {
let (lo, hi) = raw.repr.lo_hi();
let (hi_lo, hi_hi) = hi.lo_hi();
smallvec![(raw.pos, Self::Db(hi_hi)), (raw.pos + 1, Self::Db(hi_lo)), (raw.pos + 2, Self::Dh(lo))]
},
ForceDecodeKind::BBBB => {
let (lo, hi) = raw.repr.lo_hi();
let (lo_lo, lo_hi) = lo.lo_hi();
let (hi_lo, hi_hi) = hi.lo_hi();
smallvec![
(raw.pos, Self::Db(hi_hi)),
(raw.pos + 1, Self::Db(hi_lo)),
(raw.pos + 2, Self::Db(lo_hi)),
(raw.pos + 3, Self::Db(lo_lo))
]
},
};
}
// Try to get an ascii string from the raw and check for nulls
match AsciiString::from_ascii(raw.repr.to_ne_bytes()).map(check_nulls) {
// If we got a string with at least 1 non-null, but
// at least 1 null and uniformly null, return just it
Ok((mut ascii_string, null_idx @ 1..=3, true)) => {
ascii_string.truncate(null_idx);
smallvec![(raw.pos, Self::Ascii(ascii_string))]
},
// If we got a string without any nulls, keep
// filling the string until we find one.
Ok((mut ascii_string, 4, true)) => {
let ascii_string = loop {
let mut cur_iter = iter.clone();
match cur_iter.next() {
// If we don't have a next character, return the string as-is
// Note: No need to update the iterator, it returned `None`.
None => break ascii_string,
// Else try to get it as a string and check for nulls
Some(next_raw) => match AsciiStr::from_ascii(&next_raw.repr.to_ne_bytes()).map(check_nulls) {
// If we got it and it wasn't null, update the iterator, add it and continue
Ok((new_ascii_str, 4, _)) => {
*iter = cur_iter;
ascii_string.push_str(new_ascii_str);
},
// If we got it, but there was a uniform null, update the iterator,
// add the non-null parts and return.
#[allow(clippy::indexing_slicing)] // `null_idx < len`
Ok((new_ascii_str, null_idx, true)) => {
*iter = cur_iter;
ascii_string.push_str(&new_ascii_str[..null_idx]);
break ascii_string;
},
// If we didn't get it or it was a non-uniform null, return the string we have so far
// Note: We don't update the iterator, as we want to leave
// the next value to `dw`.
Err(_) | Ok((_, _, false)) => break ascii_string,
},
}
};
smallvec![(raw.pos, Self::Ascii(ascii_string))]
},
// Else if it was full null, non-uniformly null or non-ascii,
// just return a normal word.
_ => smallvec![(raw.pos, Self::Dw(raw.repr))],
}
}
}

View File

@ -1,87 +0,0 @@
//! Alu self-assign instructions
// Imports
use crate::exe::instruction::{
basic::{
alu_imm::AluImmKind,
special::{alu_reg::AluRegKind, AluRegInst},
AluImmInst, InstIter, SpecialInst,
},
BasicInst, Register,
};
use std::fmt;
/// Alu assign kind
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum AluAssignKind {
/// Immediate
Imm(AluImmKind),
/// Register
Reg(AluRegKind, Register),
}
impl AluAssignKind {
/// Returns this kind's mnemonic
pub fn mnemonic(self) -> &'static str {
match self {
AluAssignKind::Imm(kind) => kind.mnemonic(),
AluAssignKind::Reg(kind, _) => kind.mnemonic(),
}
}
/// Returns a displayable with the value of this kind
pub fn value_fmt(self) -> impl fmt::Display {
struct FmtValue(Self);
impl fmt::Display for FmtValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self.0 {
AluAssignKind::Imm(kind) => write!(f, "{}", kind.value_fmt()),
AluAssignKind::Reg(kind, reg) => write!(f, "{}", reg),
}
}
}
FmtValue(self)
}
}
/// Alu self-assign instructions
///
/// Alias for
/// ```mips
/// [alu] $dst, $dst, $i
/// ```
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(derive_more::Display)]
#[display(fmt = "{} {dst}, {}", "kind.mnemonic()", "kind.value_fmt()")]
pub struct AluAssignInst {
/// Destination and source register
pub dst: Register,
/// Kind
pub kind: AluAssignKind,
}
impl AluAssignInst {
/// Decodes this pseudo instruction
#[must_use]
pub fn decode(iter: InstIter<'_, impl Iterator<Item = u32> + Clone>) -> Option<Self> {
let peeker = iter.peeker();
let inst = match peeker.next()?? {
BasicInst::Special(SpecialInst::Alu(AluRegInst { dst, lhs, rhs, kind })) if dst == lhs => Self {
dst,
kind: AluAssignKind::Reg(kind, rhs),
},
BasicInst::AluImm(AluImmInst { dst, lhs, kind }) if dst == lhs => Self {
dst,
kind: AluAssignKind::Imm(kind),
},
_ => return None,
};
peeker.apply();
Some(inst)
}
}

View File

@ -8,6 +8,8 @@ use std::{convert::TryFrom, fmt, ops};
/// An instruction position
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Hash, Debug)]
#[derive(ref_cast::RefCast)]
#[derive(derive_more::Display)]
#[display(fmt = "{_0:#x?}")]
#[repr(transparent)]
pub struct Pos(pub u32);
@ -49,6 +51,23 @@ impl ops::Add<i32> for Pos {
}
}
impl ops::Add<i16> for Pos {
type Output = Self;
fn add(self, rhs: i16) -> Self::Output {
self + rhs.sign_extended::<i32>()
}
}
impl<T> ops::AddAssign<T> for Pos
where
Pos: ops::Add<T, Output = Self>,
{
fn add_assign(&mut self, rhs: T) {
*self = *self + rhs;
}
}
impl ops::BitAnd<u32> for Pos {
type Output = Self;

View File

@ -1,60 +1,7 @@
//! `dcb` is a library for interacting with the game file of `Digimon Digital Card Battle`.
//!
//! # Modules
//! `dcb` is split across 2 main modules, [`io`] and [`game`].
//!
//! ## Io
//! The Io module is responsible for interacting with the game file. In the future it may be responsible
//! for also interacting with the game extracted database, once work on that is complete.
//!
//! ## Game
//! The game module is responsible for representing in-game structures such as cards, sprites, text, and
//! others. The trait has various interfaces to be able to deserialize these structures from both the game
//! file, database or even other sources, depending on the structure.
//!
//! # Example
//!
//! The following is an example of how to use the `dcb` library.
//! This example extracts the card table and prints it to screen
//!
//! ```no_run
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # use std::fs::File;
//! let mut game_file = dcb::GameFile::from_reader( File::open("Digimon Digital Card Battle.bin")? )?;
//! let card_table = dcb::CardTable::deserialize(&mut game_file)?;
//! println!("Card table: {:?}", card_table);
//! # Ok(())
//! # }
//! ```
//! `dcb` executable debugging
// Features
#![feature(
seek_convenience,
never_type,
bool_to_option,
decl_macro,
stmt_expr_attributes,
unwrap_infallible,
external_doc,
format_args_capture,
const_fn,
const_panic,
min_const_generics,
exclusive_range_pattern,
unsafe_block_in_unsafe_fn,
maybe_uninit_uninit_array,
maybe_uninit_slice,
array_map,
const_mut_refs,
core_intrinsics,
const_assume,
bindings_after_at,
array_value_iter,
or_patterns,
once_cell,
box_syntax,
str_split_once
)]
#![feature(unsafe_block_in_unsafe_fn, format_args_capture, never_type, or_patterns)]
// Lints
#![warn(clippy::restriction, clippy::pedantic, clippy::nursery)]
// Instead of `unwrap`, we must use `expect` and provide a reason
@ -107,7 +54,9 @@
// A `match Option / Result / Bool` can sometimes look cleaner than a `if let / else`
#![allow(clippy::single_match_else, clippy::match_bool)]
// We're usually fine with missing future variants
#![allow(clippy::wildcard_enum_match_arm)]
#![allow(clippy::wildcard_enum_match_arm, clippy::match_wildcard_for_single_variants)]
// We only use globs in small scopes
#![allow(clippy::enum_glob_use)]
// Modules
pub mod exe;

View File

@ -12,14 +12,15 @@ path = "src/extractor/main.rs"
name = "patcher"
path = "src/patcher/main.rs"
#[[bin]]
#name = "decompiler"
#path = "src/decompiler/main.rs"
[[bin]]
name = "decompiler"
path = "src/decompiler/main.rs"
[dependencies]
# Dcb
dcb = { path = "../dcb" }
dcb-io = { path = "../dcb-io" }
dcb-exe = { path = "../dcb-exe" }
# Text
ascii = "1.0"

View File

@ -76,7 +76,7 @@ mod logger;
// Imports
use anyhow::Context;
use dcb::{exe::data::DataTable, GameFile};
use dcb_io::GameFile;
#[allow(clippy::cognitive_complexity, clippy::too_many_lines)] // TODO: Refactor
fn main() -> Result<(), anyhow::Error> {
@ -88,47 +88,18 @@ fn main() -> Result<(), anyhow::Error> {
// Open the game file
let input_file = std::fs::File::open(&game_file_path).context("Unable to open input file")?;
let _game_file = GameFile::from_reader(input_file).context("Unable to parse input file as dcb")?;
let mut game_file = GameFile::from_reader(input_file).context("Unable to parse input file as dcb")?;
// Read the executable
log::debug!("Deserializing executable");
//let exe = dcb::Exe::deserialize(&mut game_file).context("Unable to parse game executable")?;
let exe = dcb_exe::Exe::deserialize(&mut game_file).context("Unable to parse game executable")?;
//log::info!("Header:\n{}\n", exe.header);
println!("Header:\n{}", exe.header);
/*
// Get all instructions
log::debug!("Retrieving all instructions");
let instructions: Vec<(Pos, Instruction)> = Instruction::new_iter(
exe.data
.array_chunks::<4>()
.map(|bytes| LittleEndian::read_u32(bytes))
.zip(0..)
.map(|(word, offset)| Raw {
repr: word,
pos: Pos(exe.header.dest + 4 * offset),
}),
)
.collect();
*/
println!("Data table:\n{:#?}", exe.data_table);
/*
// Get all functions
log::debug!("Retrieving all functions");
let functions: FuncTable = FuncTable::get_known()
.context("Unable to get known function table")?
.merge(FuncTable::from_instructions(
&instructions.iter().map(|(pos, instruction)| (*pos, instruction)),
));
*/
// Get all data
log::debug!("Retrieving all locations");
let data_pos: DataTable = DataTable::get_known().context("Unable to get known function table")?;
#[allow(clippy::use_debug)]
{
println!("{data_pos:#?}");
for (pos, inst) in exe.parse_iter() {
println!("{}: {:?}", pos, inst);
}
/*

View File

@ -13,12 +13,6 @@ macro_rules! array_split {
),* $(,)?
) => {{
#![allow(
clippy::used_underscore_binding,
clippy::ptr_offset_with_cast,
clippy::indexing_slicing,
)]
// Struct holding all fields
struct Fields<'a, T> {
$(
@ -30,6 +24,11 @@ macro_rules! array_split {
}
// Get everything from `array_refs`
#[allow(
clippy::used_underscore_binding,
clippy::ptr_offset_with_cast,
clippy::indexing_slicing,
)]
let (
$(
$name

View File

@ -20,7 +20,6 @@ pub struct DiscardingSortedMergeIter<T: Ord, Li: Iterator<Item = T>, Ri: Iterato
impl<T: Ord, Li: Iterator<Item = T>, Ri: Iterator<Item = T>> DiscardingSortedMergeIter<T, Li, Ri> {
/// Creates a new merging iterator
#[allow(dead_code)] // TODO: Remove
pub fn new(lhs: Li, rhs: Ri) -> Self {
Self { lhs, rhs, last: None }
}

View File

@ -1,31 +1,4 @@
//! `dcb` is a library for interacting with the game file of `Digimon Digital Card Battle`.
//!
//! # Modules
//! `dcb` is split across 2 main modules, [`io`] and [`game`].
//!
//! ## Io
//! The Io module is responsible for interacting with the game file. In the future it may be responsible
//! for also interacting with the game extracted database, once work on that is complete.
//!
//! ## Game
//! The game module is responsible for representing in-game structures such as cards, sprites, text, and
//! others. The trait has various interfaces to be able to deserialize these structures from both the game
//! file, database or even other sources, depending on the structure.
//!
//! # Example
//!
//! The following is an example of how to use the `dcb` library.
//! This example extracts the card table and prints it to screen
//!
//! ```no_run
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! # use std::fs::File;
//! let mut game_file = dcb::GameFile::from_reader( File::open("Digimon Digital Card Battle.bin")? )?;
//! let card_table = dcb::CardTable::deserialize(&mut game_file)?;
//! println!("Card table: {:?}", card_table);
//! # Ok(())
//! # }
//! ```
//! Dcb utilities
// Features
#![feature(
@ -53,7 +26,8 @@
or_patterns,
once_cell,
box_syntax,
str_split_once
str_split_once,
try_trait
)]
// Lints
#![warn(clippy::restriction, clippy::pedantic, clippy::nursery)]
@ -117,13 +91,17 @@ pub mod null_ascii_string;
#[macro_use]
pub mod impl_bytes;
pub mod discarding_sorted_merge_iter;
pub mod peekable_iter;
pub mod signed_hex;
pub mod next_from_bytes;
// Exports
//pub use array_split::{array_split, array_split_mut};
pub use ascii_str_arr::AsciiStrArr;
pub use discarding_sorted_merge_iter::DiscardingSortedMergeIter;
pub use peekable_iter::PeekableIter;
pub use signed_hex::SignedHex;
pub use next_from_bytes::NextFromBytes;
/// Returns the absolute different between `a` and `b`, `a - b` as a `i64`.
///

View File

@ -0,0 +1,36 @@
//! Next type from bytes
/// Parses some types from bytes
pub trait NextFromBytes {
/// Parses the next `u8` from bytes
fn next_u8(&self) -> Option<u8>;
/// Parses the next `u16` from bytes
fn next_u16(&self) -> Option<u16>;
/// Parses the next `u32` from bytes
fn next_u32(&self) -> Option<u32>;
}
impl NextFromBytes for [u8] {
fn next_u8(&self) -> Option<u8> {
match *self {
[a, ..] => Some(a),
_ => None,
}
}
fn next_u16(&self) -> Option<u16> {
match *self {
[a, b, ..] => Some(u16::from_ne_bytes([a, b])),
_ => None,
}
}
fn next_u32(&self) -> Option<u32> {
match *self {
[a, b, c, d, ..] => Some(u32::from_ne_bytes([a, b, c, d])),
_ => None,
}
}
}

View File

@ -0,0 +1,34 @@
//! Peekable iterators
/// Iterators which are peekable
pub trait PeekableIter: Iterator {
/// Peeks the next element
fn peek(&self) -> Option<Self::Item>;
/// Consumes the next element if `f` returns true
fn next_if(&mut self, f: impl FnOnce(Self::Item) -> bool) -> bool;
/// Consumes the next element if `f` returns `Some`
fn try_next<T: std::ops::Try>(&mut self, f: impl FnOnce(Self::Item) -> T) -> Option<Result<T::Ok, T::Error>>;
}
impl<I: Iterator + Clone> PeekableIter for I {
fn peek(&self) -> Option<Self::Item> {
self.clone().next()
}
fn next_if(&mut self, f: impl FnOnce(Self::Item) -> bool) -> bool {
matches!(self.try_next(move |value| f(value).then_some(())), Some(Ok(())))
}
fn try_next<T: std::ops::Try>(&mut self, f: impl FnOnce(Self::Item) -> T) -> Option<Result<T::Ok, T::Error>> {
let mut iter = self.clone();
match iter.next().map(f)?.into_result() {
Ok(value) => {
*self = iter;
Some(Ok(value))
},
Err(err) => Some(Err(err)),
}
}
}

View File

@ -4,7 +4,7 @@
// Imports
use int_conv::Extended;
use ref_cast::RefCast;
use std::fmt::{self, Formatter, LowerHex};
use std::fmt;
/// A signed numeric type that uses signed hexadecimal formatting.
#[derive(ref_cast::RefCast)]
@ -13,20 +13,20 @@ pub struct SignedHex<T>(pub T);
// All references implement it for their underlying type.
#[allow(clippy::use_self)] // We're using a generic version `SignedHex`, not `Self`
impl<'a, T> LowerHex for SignedHex<&'a T>
impl<'a, T> fmt::Display for SignedHex<&'a T>
where
SignedHex<T>: LowerHex,
SignedHex<T>: fmt::Display,
{
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
<SignedHex<T> as LowerHex>::fmt(SignedHex::<T>::ref_cast(self.0), f)
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
<SignedHex<T> as fmt::Display>::fmt(SignedHex::<T>::ref_cast(self.0), f)
}
}
/// Macro to help implement [`SignedHex`]
macro impl_signed_hex($($T:ty => $TBigger:ty),* $(,)?) {
$(
impl LowerHex for SignedHex<$T> {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
impl fmt::Display for SignedHex<$T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let sign = match (self.0 < 0, f.sign_plus()) {
(true, _) => "-",
(false, true) => "+",
@ -40,7 +40,7 @@ macro impl_signed_hex($($T:ty => $TBigger:ty),* $(,)?) {
// TODO: Remove `+` from the formatter flags when we do
// this to fully support the `+` flag.
LowerHex::fmt(&self.0.extended::<$TBigger>().abs(), f)
fmt::LowerHex::fmt(&self.0.extended::<$TBigger>().abs(), f)
}
}
)*