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.
This commit is contained in:
2020-11-05 18:40:33 +00:00
parent 94bb2d780d
commit bda1755eed
10 changed files with 254 additions and 112 deletions

View File

@@ -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;

View File

@@ -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]) |

View File

@@ -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<Self, Self::FromError> {
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(())
}
}

View File

@@ -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),
}

View File

@@ -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`) |

View File

@@ -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<Digimon>,
@@ -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 = <Header as 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::<<Digimon as Bytes>::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::<<Item as Bytes>::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::<<Digivolve as Bytes>::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

View File

@@ -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

View File

@@ -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 | |

View File

@@ -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<Self, Self::FromError> {
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(())
}
}

View File

@@ -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],
},
}