From bda1755eed62a4ce96590e363637b7e2eace0c46 Mon Sep 17 00:00:00 2001 From: Filipe Rodrigues Date: Thu, 5 Nov 2020 18:40:33 +0000 Subject: [PATCH] Added `dcb::game::card::table::Header` and `dcb::game::card::Header` for the table header and card header in the table, respectively. Modified `dcb::game::card::Table` to use these new headers. --- dcb/src/game/card.rs | 2 + dcb/src/game/card/header.md | 9 ++ dcb/src/game/card/header.rs | 55 ++++++++++ dcb/src/game/card/header/error.rs | 12 +++ dcb/src/game/card/table.md | 33 ++---- dcb/src/game/card/table.rs | 129 +++++++++--------------- dcb/src/game/card/table/error.rs | 19 ++-- dcb/src/game/card/table/header.md | 15 +++ dcb/src/game/card/table/header.rs | 77 ++++++++++++++ dcb/src/game/card/table/header/error.rs | 15 +++ 10 files changed, 254 insertions(+), 112 deletions(-) create mode 100644 dcb/src/game/card/header.md create mode 100644 dcb/src/game/card/header.rs create mode 100644 dcb/src/game/card/header/error.rs create mode 100644 dcb/src/game/card/table/header.md create mode 100644 dcb/src/game/card/table/header.rs create mode 100644 dcb/src/game/card/table/header/error.rs diff --git a/dcb/src/game/card.rs b/dcb/src/game/card.rs index 76b62b0..dcfabbb 100644 --- a/dcb/src/game/card.rs +++ b/dcb/src/game/card.rs @@ -6,6 +6,7 @@ // Modules pub mod digimon; pub mod digivolve; +pub mod header; pub mod item; pub mod property; pub mod table; @@ -13,5 +14,6 @@ pub mod table; // Exports pub use digimon::Digimon; pub use digivolve::Digivolve; +pub use header::CardHeader; pub use item::Item; pub use table::Table; diff --git a/dcb/src/game/card/header.md b/dcb/src/game/card/header.md new file mode 100644 index 0000000..b99a08b --- /dev/null +++ b/dcb/src/game/card/header.md @@ -0,0 +1,9 @@ +Header for each card in the table. + +# Details +Each header contains the id of the card, as well as it's type. + +| Offset | Size | Type | Name | Details | +| ------ | ---- | ------------ | --------- | ------------------------------------------------ | +| 0x0 | 0x2 | u16 | Card id | This card's ID | +| 0x2 | 0x1 | [`CardType`] | Card type | The card type ([Digimon], [Item] or [Digivolve]) | diff --git a/dcb/src/game/card/header.rs b/dcb/src/game/card/header.rs new file mode 100644 index 0000000..3857d58 --- /dev/null +++ b/dcb/src/game/card/header.rs @@ -0,0 +1,55 @@ +#![doc(include = "header.md")] + +// Modules +pub mod error; + +// Exports +pub use error::FromBytesError; + +// Includes +use crate::{ + game::card::property::CardType, + util::{array_split, array_split_mut}, +}; +use byteorder::{ByteOrder, LittleEndian}; +use dcb_bytes::Bytes; + +/// Card header +pub struct CardHeader { + /// Card id + pub id: u16, + + /// Card type + pub ty: CardType, +} + + +impl Bytes for CardHeader { + type ByteArray = [u8; 0x3]; + type FromError = FromBytesError; + type ToError = !; + + fn from_bytes(bytes: &Self::ByteArray) -> Result { + let bytes = array_split!(bytes, + id: [0x2], + ty: 0x1, + ); + + let id = LittleEndian::read_u16(bytes.id); + let ty = CardType::from_bytes(bytes.ty).map_err(FromBytesError::CardType)?; + + Ok(Self { id, ty }) + } + + fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { + let bytes = array_split_mut!(bytes, + id: [0x2], + ty: 0x1, + ); + + LittleEndian::write_u16(bytes.id, self.id); + self.ty.to_bytes(bytes.ty).into_ok(); + + Ok(()) + } +} diff --git a/dcb/src/game/card/header/error.rs b/dcb/src/game/card/header/error.rs new file mode 100644 index 0000000..348ed83 --- /dev/null +++ b/dcb/src/game/card/header/error.rs @@ -0,0 +1,12 @@ +//! Errors + +// Imports +use crate::game::card::property::card_type; + +/// Error type for [`Bytes::from_bytes`](dcb_bytes::Bytes::from_bytes) +#[derive(PartialEq, Eq, Clone, Copy, Debug, thiserror::Error)] +pub enum FromBytesError { + /// Unable to parse card type + #[error("Unable to parse the card type")] + CardType(#[source] card_type::FromBytesError), +} diff --git a/dcb/src/game/card/table.md b/dcb/src/game/card/table.md index c737afa..0f9d114 100644 --- a/dcb/src/game/card/table.md +++ b/dcb/src/game/card/table.md @@ -1,34 +1,23 @@ The table of all digimon in the game # Details -At address [0x216d000](Table::START_ADDRESS) of the game file, the card table begins -with a small header of `0xb` and then the table itself. +The card table begins at address [0x216d000](Table::START_ADDRESS) of the game file, +containing a header with the number of each card, and then a contiguous array of card entries. # Table Layout The digimon table has a max size of [0x14950](Table::MAX_BYTE_SIZE), but does not necessary use all of this space, but it does follow this layout: -| Offset | Size | Type | Name | Details | -| ------ | -------- | --------------------------------- | -------------------- | ----------------------------------------------------------------------- | -| 0x0 | 0x4 | u32 | Magic | Always contains the string "0ACD" (= [0x44434130](Table::HEADER_MAGIC)) | -| 0x4 | 0x2 | u16 | Number of digimon | | -| 0x6 | 0x1 | u8 | Number of items | | -| 0x7 | 0x1 | u8 | Number of digivolves | | -| 0x8 | variable | [`CardEntry`](#card-entry-layout) | Card Entries | A contiguous array of [Card Entry](#card-entry-layout) | +| Offset | Size | Type | Name | Details | +| ------ | -------- | --------------------------------- | ------------ | ------------------------------------------------------ | +| 0x0 | 0x8 | u32 | Header | The [table header](TableHeader) | +| 0x8 | variable | [`CardEntry`](#card-entry-layout) | Card Entries | A contiguous array of [Card Entry](#card-entry-layout) | # Card Entry Layout Each card entry consists of a header of the card -| Offset | Size | Type | Name | Details | -| ------ | -------- | ------------------------------------ | --------------- | -------------------------------------------- | -| 0x0 | 0x3 | [`Card Header`](#card-header-layout) | Card Header | The card's header | -| 0x3 | variable | | Card | Either a [Digimon], [Item] or [Digivolve] | -| ... | 0x1 | u8 | Null terminator | A null terminator for the card (must be `0`) | - -# Card Header Layout -The card header determines which type of card this card entry has. - -| Offset | Size | Type | Name | Details | -| ------ | ---- | ------------ | --------- | ------------------------------------------------ | -| 0x0 | 0x2 | u16 | Card id | This card's ID | -| 0x2 | 0x1 | [`CardType`] | Card type | The card type ([Digimon], [Item] or [Digivolve]) | +| Offset | Size | Type | Name | Details | +| ------ | -------- | -------------- | --------------- | -------------------------------------------- | +| 0x0 | 0x3 | [`CardHeader`] | Card Header | The card's header | +| 0x3 | variable | | Card | Either a [Digimon], [Item] or [Digivolve] | +| ... | 0x1 | u8 | Null terminator | A null terminator for the card (must be `0`) | diff --git a/dcb/src/game/card/table.rs b/dcb/src/game/card/table.rs index 8bf4e49..9a6e03c 100644 --- a/dcb/src/game/card/table.rs +++ b/dcb/src/game/card/table.rs @@ -2,34 +2,28 @@ // Modules pub mod error; +pub mod header; // Exports pub use error::{DeserializeError, SerializeError}; +pub use header::Header; // Imports use crate::{ - game::card::{ - self, - property::{self, CardType}, - Digimon, Digivolve, Item, - }, + game::card::{self, property::CardType, CardHeader, Digimon, Digivolve, Item}, io::{address::Data, GameFile}, - util::{array_split, array_split_mut}, + util::array_split_mut, }; -use byteorder::{ByteOrder, LittleEndian}; use dcb_bytes::Bytes; use std::{ convert::TryInto, io::{Read, Seek, Write}, }; -/// The table storing all cards -/// -/// See the [module containing](self) this struct for more details -/// on where the table is deserialized from and it's features / restrictions. +/// Table storing all cards. #[derive(PartialEq, Eq, Clone, Debug)] #[derive(serde::Serialize, serde::Deserialize)] -#[allow(clippy::unsafe_derive_deserialize)] // We don't have any `unsafe` methods +#[allow(clippy::unsafe_derive_deserialize)] // False positive pub struct Table { /// All digimons in this table pub digimons: Vec, @@ -43,11 +37,6 @@ pub struct Table { // Constants impl Table { - /// Table header size - pub const HEADER_BYTE_SIZE: usize = 0x8; - /// The magic in the table header - /// = "0ACD" - pub const HEADER_MAGIC: u32 = 0x44434130; /// The max size of the card table // TODO: Check the theoretical max, which is currently thought to be `0x14ff5` pub const MAX_BYTE_SIZE: usize = 0x14970; @@ -81,26 +70,14 @@ impl Table { .map_err(DeserializeError::Seek)?; // Read header - let mut header_bytes = [0u8; Self::HEADER_BYTE_SIZE]; + let mut header_bytes =
::ByteArray::default(); file.read_exact(&mut header_bytes).map_err(DeserializeError::ReadHeader)?; - let header = array_split! {&header_bytes, - magic: [0x4], - - digimons_len: [0x2], - items_len: 1, - digivolves_len: 1, - }; - - // Check if the magic is right - let magic = LittleEndian::read_u32(header.magic); - if magic != Self::HEADER_MAGIC { - return Err(DeserializeError::HeaderMagic { magic }); - } + let header = Header::from_bytes(&header_bytes).map_err(DeserializeError::Header)?; // Then check the number of each card - let digimon_cards: usize = LittleEndian::read_u16(header.digimons_len).into(); - let item_cards: usize = (*header.items_len).into(); - let digivolve_cards: usize = (*header.digivolves_len).into(); + let digimon_cards: usize = header.digimons_len.into(); + let item_cards: usize = header.items_len.into(); + let digivolve_cards: usize = header.digivolves_len.into(); log::trace!("Found {digimon_cards} digimon, {item_cards} item, {digivolve_cards} digivolve cards"); // And calculate the number of cards @@ -124,43 +101,51 @@ impl Table { let mut digivolves = Vec::with_capacity(digivolve_cards); // Read until the table is over - for cur_id in 0..cards_len { + for card_id in 0..cards_len { // Read card header bytes let mut card_header_bytes = [0u8; 0x3]; file.read_exact(&mut card_header_bytes) - .map_err(|err| DeserializeError::ReadCardHeader { id: cur_id, err })?; + .map_err(|err| DeserializeError::ReadCardHeader { id: card_id, err })?; // Read the header - let card_id = LittleEndian::read_u16(&card_header_bytes[0x0..0x2]); - let card_type = CardType::from_bytes(&card_header_bytes[0x2]).map_err(|err| DeserializeError::UnknownCardType { id: cur_id, err })?; + let card_header = CardHeader::from_bytes(&card_header_bytes).map_err(|err| DeserializeError::ParseCardHeader { id: card_id, err })?; - log::trace!("Found #{}: {}", card_id, card_type); + log::trace!("Found #{}: {}", card_id, card_header.ty); // If the card id isn't what we expected, log warning - if usize::from(card_id) != cur_id { - log::warn!("Card with id {} had unexpected id {}", cur_id, card_id); + if usize::from(card_header.id) != card_id { + log::warn!("Card with id {} had unexpected id {}", card_id, card_header.id); } // And create / push the card - match card_type { + match card_header.ty { CardType::Digimon => { let mut digimon_bytes = [0; std::mem::size_of::<::ByteArray>()]; - file.read_exact(&mut digimon_bytes) - .map_err(|err| DeserializeError::ReadCard { id: cur_id, card_type, err })?; - let digimon = Digimon::from_bytes(&digimon_bytes).map_err(|err| DeserializeError::DigimonCard { id: cur_id, err })?; + file.read_exact(&mut digimon_bytes).map_err(|err| DeserializeError::ReadCard { + id: card_id, + card_type: card_header.ty, + err, + })?; + let digimon = Digimon::from_bytes(&digimon_bytes).map_err(|err| DeserializeError::DigimonCard { id: card_id, err })?; digimons.push(digimon); }, CardType::Item => { let mut item_bytes = [0; std::mem::size_of::<::ByteArray>()]; - file.read_exact(&mut item_bytes) - .map_err(|err| DeserializeError::ReadCard { id: cur_id, card_type, err })?; - let item = Item::from_bytes(&item_bytes).map_err(|err| DeserializeError::ItemCard { id: cur_id, err })?; + file.read_exact(&mut item_bytes).map_err(|err| DeserializeError::ReadCard { + id: card_id, + card_type: card_header.ty, + err, + })?; + let item = Item::from_bytes(&item_bytes).map_err(|err| DeserializeError::ItemCard { id: card_id, err })?; items.push(item); }, CardType::Digivolve => { let mut digivolve_bytes = [0; std::mem::size_of::<::ByteArray>()]; - file.read_exact(&mut digivolve_bytes) - .map_err(|err| DeserializeError::ReadCard { id: cur_id, card_type, err })?; - let digivolve = Digivolve::from_bytes(&digivolve_bytes).map_err(|err| DeserializeError::DigivolveCard { id: cur_id, err })?; + file.read_exact(&mut digivolve_bytes).map_err(|err| DeserializeError::ReadCard { + id: card_id, + card_type: card_header.ty, + err, + })?; + let digivolve = Digivolve::from_bytes(&digivolve_bytes).map_err(|err| DeserializeError::DigivolveCard { id: card_id, err })?; digivolves.push(digivolve); }, } @@ -168,9 +153,9 @@ impl Table { // Skip null terminator let mut null_terminator = [0; 1]; file.read_exact(&mut null_terminator) - .map_err(|err| DeserializeError::ReadCardFooter { id: cur_id, err })?; + .map_err(|err| DeserializeError::ReadCardFooter { id: card_id, err })?; if null_terminator[0] != 0 { - log::warn!("Card with id {}'s null terminator was {} instead of 0", cur_id, null_terminator[0]); + log::warn!("Card with id {}'s null terminator was {} instead of 0", card_id, null_terminator[0]); } } @@ -198,27 +183,12 @@ impl Table { // Write header let mut header_bytes = [0u8; 0x8]; - let header = array_split_mut!(&mut header_bytes, - magic: [0x4], - - digimons_len: [0x2], - items_len: 1, - digivolves_len: 1, - ); - - // Set magic - LittleEndian::write_u32(header.magic, Self::HEADER_MAGIC); - - // Write card lens - log::trace!("Writing {} digimon cards", self.digimons.len()); - log::trace!("Writing {} item cards", self.items.len()); - log::trace!("Writing {} digivolve cards", self.digivolves.len()); - LittleEndian::write_u16( - header.digimons_len, - self.digimons.len().try_into().expect("Number of digimon cards exceeded `u16`"), - ); - *header.items_len = self.items.len().try_into().expect("Number of item cards exceeded `u8`"); - *header.digivolves_len = self.digivolves.len().try_into().expect("Number of digivolve cards exceeded `u8`"); + let header = Header { + digimons_len: self.digimons.len().try_into().expect("Number of digimon cards exceeded `u16`"), + items_len: self.items.len().try_into().expect("Number of item cards exceeded `u8`"), + digivolves_len: self.digivolves.len().try_into().expect("Number of digivolve cards exceeded `u8`"), + }; + header.to_bytes(&mut header_bytes).into_ok(); // And write the header file.write_all(&header_bytes).map_err(SerializeError::WriteHeader)?; @@ -233,15 +203,16 @@ impl Table { // Card bytes let mut card_bytes = [0; 0x3 + CardType::$card_type.byte_size() + 0x1]; let bytes = array_split_mut!(&mut card_bytes, - header_id : [0x2], - header_type: 1, + header : [0x3], card : [CardType::$card_type.byte_size()], footer : 1, ); - // Write the header - LittleEndian::write_u16(bytes.header_id, cur_id.try_into().expect("Card ID exceeded `u16`")); - CardType::$card_type.to_bytes(bytes.header_type)?; + let card_header = CardHeader { + id: cur_id.try_into().expect("Card id didn't fit into a `u16`"), + ty: CardType::$card_type, + }; + card_header.to_bytes(bytes.header).into_ok(); // Write the card #[allow(unreachable_code)] // FIXME: Remove this diff --git a/dcb/src/game/card/table/error.rs b/dcb/src/game/card/table/error.rs index a9c0e8b..a19a870 100644 --- a/dcb/src/game/card/table/error.rs +++ b/dcb/src/game/card/table/error.rs @@ -1,7 +1,7 @@ //! Errors // Imports -use super::{card, property, CardType, Table}; +use super::{card, header, CardType, Table}; /// Error type for [`Table::deserialize`] #[derive(Debug, thiserror::Error)] @@ -14,12 +14,9 @@ pub enum DeserializeError { #[error("Unable to read table header")] ReadHeader(#[source] std::io::Error), - /// The magic of the table was wrong - #[error("Found wrong table header magic (expected {:#x}, found {:#x})", Table::HEADER_MAGIC, magic)] - HeaderMagic { - /// Magic we found - magic: u32, - }, + /// Unable to parse table header + #[error("Unable to parse table header")] + Header(#[source] header::FromBytesError), /// There were too many cards #[error( @@ -51,15 +48,15 @@ pub enum DeserializeError { err: std::io::Error, }, - /// An unknown card type was found - #[error("Unknown card type for card id {}", id)] - UnknownCardType { + /// Unable to parse a card header + #[error("Unable to parse a card header for card id {id}")] + ParseCardHeader { /// Id of card id: usize, /// Underlying error #[source] - err: property::card_type::FromBytesError, + err: card::header::FromBytesError, }, /// Unable to read a card diff --git a/dcb/src/game/card/table/header.md b/dcb/src/game/card/table/header.md new file mode 100644 index 0000000..17e65ed --- /dev/null +++ b/dcb/src/game/card/table/header.md @@ -0,0 +1,15 @@ +Header for the digimon table. + +# Details +The header contains a magic number to identify the table, as well as the number of each card +available. + +The number of digimons is measured with a `u16`, but the items and digivolves are only measured using +a `u8`, meaning they have a limit of `256`. + +| Offset | Size | Type | Name | Details | +| ------ | ---- | ---- | -------------------- | ---------------------------------------------------------- | +| 0x0 | 0x4 | u32 | Magic | Always contains the string ["0ACD"]((Table::HEADER_MAGIC)) | +| 0x4 | 0x2 | u16 | Number of digimon | | +| 0x6 | 0x1 | u8 | Number of items | | +| 0x7 | 0x1 | u8 | Number of digivolves | | diff --git a/dcb/src/game/card/table/header.rs b/dcb/src/game/card/table/header.rs new file mode 100644 index 0000000..ae805b5 --- /dev/null +++ b/dcb/src/game/card/table/header.rs @@ -0,0 +1,77 @@ +#![doc(include = "header.md")] + +// Modules +pub mod error; + +// Exports +pub use error::FromBytesError; + +// Imports +use crate::util::{array_split, array_split_mut}; +use byteorder::{ByteOrder, LittleEndian}; +use dcb_bytes::Bytes; + +/// The header +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +#[derive(serde::Serialize, serde::Deserialize)] +pub struct Header { + /// Number of digimon + pub digimons_len: u16, + + /// Number of items + pub items_len: u8, + + /// Number of digivolves + pub digivolves_len: u8, +} + +impl Header { + /// Magic of this header. + /// = "0ACD" + pub const MAGIC: [u8; 4] = *b"0ACD"; +} + +impl Bytes for Header { + type ByteArray = [u8; 0x8]; + type FromError = FromBytesError; + type ToError = !; + + fn from_bytes(bytes: &Self::ByteArray) -> Result { + let bytes = array_split!(bytes, + magic: [0x4], + digimons_len: [0x2], + items_len: 1, + digivolves_len: 1, + ); + + if *bytes.magic != Self::MAGIC { + return Err(FromBytesError::Magic { magic: *bytes.magic }); + } + + let digimons_len = LittleEndian::read_u16(bytes.digimons_len); + let items_len = *bytes.items_len; + let digivolves_len = *bytes.digivolves_len; + + Ok(Self { + digimons_len, + items_len, + digivolves_len, + }) + } + + fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { + let bytes = array_split_mut!(bytes, + magic: [0x4], + digimons_len: [0x2], + items_len: 1, + digivolves_len: 1, + ); + + *bytes.magic = Self::MAGIC; + LittleEndian::write_u16(bytes.digimons_len, self.digimons_len); + *bytes.items_len = self.items_len; + *bytes.digivolves_len = self.digivolves_len; + + Ok(()) + } +} diff --git a/dcb/src/game/card/table/header/error.rs b/dcb/src/game/card/table/header/error.rs new file mode 100644 index 0000000..1bb8b9c --- /dev/null +++ b/dcb/src/game/card/table/header/error.rs @@ -0,0 +1,15 @@ +//! Errors + +// Imports +use super::Header; + +/// Error type for [`Bytes::from_bytes`](dcb_bytes::Bytes::from_bytes) +#[derive(PartialEq, Eq, Clone, Copy, Debug, thiserror::Error)] +pub enum FromBytesError { + /// Magic + #[error("Magic did not match (found {magic:#x?}, expected {:#x?})", Header::MAGIC)] + Magic { + /// Magic that was found + magic: [u8; 4], + }, +}