From 06c88e506de78b5e9b3769989fa6ac5e8795d7ab Mon Sep 17 00:00:00 2001 From: Filipe Rodrigues Date: Fri, 23 Oct 2020 03:00:37 +0100 Subject: [PATCH] Added new deserializer for the game executable. --- dcb/src/game.rs | 2 + dcb/src/game/exe.rs | 58 ++++++++++++++++ dcb/src/game/exe/error.rs | 24 +++++++ dcb/src/game/exe/header.rs | 108 ++++++++++++++++++++++++++++++ dcb/src/game/exe/header/error.rs | 23 +++++++ dcb/src/util/null_ascii_string.rs | 1 + 6 files changed, 216 insertions(+) create mode 100644 dcb/src/game/exe.rs create mode 100644 dcb/src/game/exe/error.rs create mode 100644 dcb/src/game/exe/header.rs create mode 100644 dcb/src/game/exe/header/error.rs diff --git a/dcb/src/game.rs b/dcb/src/game.rs index 86b6550..e25c7d4 100644 --- a/dcb/src/game.rs +++ b/dcb/src/game.rs @@ -17,9 +17,11 @@ // Modules pub mod card; pub mod deck; +pub mod exe; pub mod validation; // Exports pub use card::{Digimon, Digivolve, Item, Table as CardTable}; pub use deck::{Deck, Table as DeckTable}; +pub use exe::{Exe, Header as ExeHeader}; pub use validation::{Validatable, Validation}; diff --git a/dcb/src/game/exe.rs b/dcb/src/game/exe.rs new file mode 100644 index 0000000..93d4c71 --- /dev/null +++ b/dcb/src/game/exe.rs @@ -0,0 +1,58 @@ +//! Executable +//! +//! This module contains the executable portion of the game, +//! as well as tools to decompile and recompile it. + +// Modules +pub mod error; +pub mod header; + +// Exports +pub use error::DeserializeError; +pub use header::Header; + +// Imports +use crate::{io::address::Data, GameFile}; +use dcb_bytes::{ByteArray, Bytes}; +use std::{ + convert::TryFrom, + io::{Read, Seek, Write}, +}; + +/// The game executable +/// +/// This type holds all of the executable code +/// of the game. +#[derive(PartialEq, Eq, Clone, Hash, Debug)] +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Exe { + /// The executable header + pub header: Header, + + /// All data + pub data: Vec, +} + +impl Exe { + /// Start address of the executable + const START_ADDRESS: Data = Data::from_u64(0x58b9000); +} + +impl Exe { + /// Deserializes the card table from a game file + pub fn deserialize(file: &mut GameFile) -> Result { + // Seek to the table + file.seek(std::io::SeekFrom::Start(Self::START_ADDRESS.as_u64())) + .map_err(DeserializeError::Seek)?; + + // Read header + let mut header_bytes = [0u8; <
::ByteArray as ByteArray>::SIZE]; + file.read_exact(&mut header_bytes).map_err(DeserializeError::ReadHeader)?; + let header = Header::from_bytes(&header_bytes).map_err(DeserializeError::ParseHeader)?; + + 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)?; + + Ok(Self { header, data }) + } +} diff --git a/dcb/src/game/exe/error.rs b/dcb/src/game/exe/error.rs new file mode 100644 index 0000000..c91d737 --- /dev/null +++ b/dcb/src/game/exe/error.rs @@ -0,0 +1,24 @@ +//! Errors + +// Imports +use super::header; + +/// Error type for [`Table::deserialize`] +#[derive(Debug, thiserror::Error)] +pub enum DeserializeError { + /// Unable to seek game file + #[error("Unable to seek game file to executable")] + Seek(#[source] std::io::Error), + + /// Unable to read header + #[error("Unable to read header")] + ReadHeader(#[source] std::io::Error), + + /// Unable to parse header + #[error("Unable to parse header")] + ParseHeader(#[source] header::FromBytesError), + + /// Unable to read data + #[error("Unable to read data")] + ReadData(#[source] std::io::Error), +} diff --git a/dcb/src/game/exe/header.rs b/dcb/src/game/exe/header.rs new file mode 100644 index 0000000..5b68844 --- /dev/null +++ b/dcb/src/game/exe/header.rs @@ -0,0 +1,108 @@ +//! Executable header + +// Modules +pub mod error; + +// Exports +pub use error::{FromBytesError, ToBytesError}; + +// Import +use crate::{ + util::{array_split, null_ascii_string::NullAsciiString}, + AsciiStrArr, +}; +use byteorder::{ByteOrder, LittleEndian}; +use dcb_bytes::Bytes; + +/// The header of the executable. +#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Header { + /// Initial program counter + pub initial_pc: u32, + + /// Initial global pointer + pub initial_gp: u32, + + /// Destination in memory for the executable + pub dest: u32, + + /// Size of the executable + pub size: u32, + + /// Unknown at `0x20` + pub unknown20: u32, + + /// Unknown at `0x24` + pub unknown24: u32, + + /// Where to start mem filling + pub memfill_start: u32, + + /// Size to mem fill + pub memfill_size: u32, + + /// Initial stack pointer + pub initial_sp_base: u32, + + /// Offset from initial stack pointer + pub initial_sp_offset: u32, + + /// Executable region marker + pub marker: AsciiStrArr<0x7b4>, +} + +impl Header { + /// Magic + pub const MAGIC: &'static [u8; 8] = b"PS-X EXE"; +} + +impl Bytes for Header { + type ByteArray = [u8; 0x800]; + type FromError = FromBytesError; + type ToError = ToBytesError; + + fn from_bytes(bytes: &Self::ByteArray) -> Result { + let bytes = array_split!(bytes, + magic : [0x8], + _zero : [0x8], + initial_pc : [0x4], + initial_gp : [0x4], + dest : [0x4], + size : [0x4], + unknown20 : [0x4], + unknown24 : [0x4], + memfill_start : [0x4], + memfill_size : [0x4], + initial_sp_base : [0x4], + initial_sp_offset: [0x4], + _zero2 : [0x13], + marker : [0x7b5], + ); + + // If the magic is wrong, return Err + if bytes.magic != Self::MAGIC { + return Err(FromBytesError::Magic { magic: *bytes.magic }); + } + + // TODO: Maybe check if `zero` and `zero2` are actually zero? + + Ok(Self { + initial_pc: LittleEndian::read_u32(bytes.initial_pc), + initial_gp: LittleEndian::read_u32(bytes.initial_gp), + dest: LittleEndian::read_u32(bytes.dest), + size: LittleEndian::read_u32(bytes.size), + unknown20: LittleEndian::read_u32(bytes.unknown20), + unknown24: LittleEndian::read_u32(bytes.unknown24), + memfill_start: LittleEndian::read_u32(bytes.memfill_start), + memfill_size: LittleEndian::read_u32(bytes.memfill_size), + initial_sp_base: LittleEndian::read_u32(bytes.initial_sp_base), + initial_sp_offset: LittleEndian::read_u32(bytes.initial_sp_offset), + marker: NullAsciiString::read_string(bytes.marker).map_err(FromBytesError::Name)?, + }) + } + + fn to_bytes(&self, _bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { + todo!() + } +} diff --git a/dcb/src/game/exe/header/error.rs b/dcb/src/game/exe/header/error.rs new file mode 100644 index 0000000..8796c33 --- /dev/null +++ b/dcb/src/game/exe/header/error.rs @@ -0,0 +1,23 @@ +//! Errors + +// Imports +use super::Header; +use crate::util::null_ascii_string; + +/// Error type for [`Bytes::from_bytes`](dcb_bytes::Bytes::from_bytes) +#[derive(PartialEq, Eq, Clone, Copy, Debug, thiserror::Error)] +pub enum FromBytesError { + /// The magic of the table was wrong + #[error("Found wrong header magic (expected {:?}, found {:?})", Header::MAGIC, magic)] + Magic { + /// Magic we found + magic: [u8; 8], + }, + + /// Unable to read region marker + #[error("Unable to read the region marker")] + Name(#[source] null_ascii_string::ReadError), +} + +/// Error type for [`Bytes::to_bytes`](dcb_bytes::Bytes::to_bytes) +pub type ToBytesError = !; diff --git a/dcb/src/util/null_ascii_string.rs b/dcb/src/util/null_ascii_string.rs index c52cfcc..8f5f511 100644 --- a/dcb/src/util/null_ascii_string.rs +++ b/dcb/src/util/null_ascii_string.rs @@ -58,4 +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 );