Revised dcb::card::Table.

This commit is contained in:
Filipe Rodrigues 2021-01-18 22:26:00 +00:00
parent 0ebd141373
commit 2408cece65
3 changed files with 73 additions and 150 deletions

View File

@ -1,20 +1,18 @@
The table of all digimon in the game
# Details
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.
The card table contains a small header detailing how many of each cards exist, following by 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 | 0x8 | u32 | Header | The [table header](TableHeader) |
| 0x0 | 0x8 | u32 | Header | The [table header](Header) |
| 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
Each card entry consists of a header of the card, the card itself and a null terminator.
| Offset | Size | Type | Name | Details |
| ------ | -------- | -------------- | --------------- | -------------------------------------------- |

View File

@ -5,12 +5,15 @@ pub mod error;
pub mod header;
// Exports
use std::{convert::TryInto, io};
use dcb_bytes::Bytes;
pub use error::{DeserializeError, SerializeError};
pub use header::Header;
// Imports
use super::CardHeader;
use crate::card::{self, property::CardType, Digimon, Digivolve, Item};
use dcb_io::GameFile;
/// Table storing all cards.
#[derive(PartialEq, Eq, Clone, Debug)]
@ -29,13 +32,8 @@ pub struct Table {
// Constants
impl Table {
/*
/// 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;
/// The start address of the card table
pub const START_ADDRESS: Data = Data::from_u64(0x216d000);
*/
/// The file of the card table
pub const PATH: &'static str = "B:\\CARD2.CDD";
}
// Utils
@ -45,30 +43,16 @@ impl Table {
pub fn card_count(&self) -> usize {
self.digimons.len() + self.items.len() + self.digivolves.len()
}
/// Returns the byte size of all cards in this table
#[must_use]
#[rustfmt::skip]
pub fn cards_byte_size(&self) -> usize {
self.digimons .len() * (0x3 + CardType::Digimon .byte_size() + 0x1) +
self.items .len() * (0x3 + CardType::Item .byte_size() + 0x1) +
self.digivolves.len() * (0x3 + CardType::Digivolve.byte_size() + 0x1)
}
}
impl Table {
/// Deserializes the card table from a game file
pub fn deserialize(_file: &mut GameFile) -> Result<Self, DeserializeError> {
todo!();
/*
// Seek to the table
file.seek(std::io::SeekFrom::Start(Self::START_ADDRESS.as_u64()))
.map_err(DeserializeError::Seek)?;
/// Deserializes the card table from it's file
#[allow(clippy::similar_names)] // Reader and Header are different.
pub fn deserialize<R: io::Read>(mut reader: R) -> Result<Self, DeserializeError> {
// Read header
let mut header_bytes = <Header as Bytes>::ByteArray::default();
file.read_exact(&mut header_bytes).map_err(DeserializeError::ReadHeader)?;
let header = Header::from_bytes(&header_bytes).map_err(DeserializeError::Header)?;
reader.read_exact(&mut header_bytes).map_err(DeserializeError::ReadHeader)?;
let header = Header::from_bytes(&header_bytes).map_err(DeserializeError::ParseHeader)?;
// Then check the number of each card
let digimon_cards: usize = header.digimons_len.into();
@ -79,18 +63,6 @@ impl Table {
// And calculate the number of cards
let cards_len = digimon_cards + item_cards + digivolve_cards;
// If there are too many cards, return Err
let table_size = digimon_cards * (0x3 + CardType::Digimon.byte_size() + 0x1) +
item_cards * (0x3 + CardType::Item.byte_size() + 0x1) +
digivolve_cards * (0x3 + CardType::Digivolve.byte_size() + 0x1);
if table_size > Self::MAX_BYTE_SIZE {
return Err(DeserializeError::TooManyCards {
digimon_cards,
item_cards,
digivolve_cards,
});
}
// Create the arrays with capacity
let mut digimons = Vec::with_capacity(digimon_cards);
let mut items = Vec::with_capacity(item_cards);
@ -100,7 +72,8 @@ impl Table {
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)
reader
.read_exact(&mut card_header_bytes)
.map_err(|err| DeserializeError::ReadCardHeader { id: card_id, err })?;
// Read the header
@ -116,39 +89,41 @@ impl Table {
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 {
reader.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 })?;
let digimon = Digimon::from_bytes(&digimon_bytes).map_err(|err| DeserializeError::ParseDigimonCard { 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 {
reader.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 })?;
let item = Item::from_bytes(&item_bytes).map_err(|err| DeserializeError::ParseItemCard { 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 {
reader.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 })?;
let digivolve =
Digivolve::from_bytes(&digivolve_bytes).map_err(|err| DeserializeError::ParseDigivolveCard { id: card_id, err })?;
digivolves.push(digivolve);
},
}
// Skip null terminator
let mut null_terminator = [0; 1];
file.read_exact(&mut null_terminator)
reader
.read_exact(&mut null_terminator)
.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", card_id, null_terminator[0]);
@ -157,52 +132,42 @@ impl Table {
// Return the table
Ok(Self { digimons, items, digivolves })
*/
}
/// Serializes this card table to `file`.
pub fn serialize(&self, _file: &mut GameFile) -> Result<(), SerializeError> {
let _ = self;
todo!()
/*
// Get the final table size
let table_size = self.cards_byte_size();
// If the total table size is bigger than the max, return Err
if table_size > Self::MAX_BYTE_SIZE {
return Err(SerializeError::TooManyCards {
digimon_cards: self.digimons.len(),
item_cards: self.items.len(),
digivolve_cards: self.digivolves.len(),
});
}
// Seek to the beginning of the card table
file.seek(std::io::SeekFrom::Start(Self::START_ADDRESS.as_u64()))
.map_err(SerializeError::Seek)?;
/// Serializes this card table to a file
pub fn serialize<R: io::Write>(&self, mut writer: R) -> Result<(), SerializeError> {
// Write header
let mut header_bytes = [0u8; 0x8];
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`"),
digimons_len: self
.digimons
.len()
.try_into()
.map_err(|_err| SerializeError::TooManyDigimon(self.digimons.len()))?,
items_len: self
.items
.len()
.try_into()
.map_err(|_err| SerializeError::TooManyItems(self.items.len()))?,
digivolves_len: self
.digivolves
.len()
.try_into()
.map_err(|_err| SerializeError::TooManyDigivolves(self.digivolves.len()))?,
};
let mut header_bytes = [0u8; 0x8];
header.to_bytes(&mut header_bytes).into_ok();
// And write the header
file.write_all(&header_bytes).map_err(SerializeError::WriteHeader)?;
writer.write_all(&header_bytes).map_err(SerializeError::WriteHeader)?;
// Macro to help write all cards to file
macro_rules! write_card {
($cards:expr, $prev_ids:expr, $card_type:ident, $($on_err:tt)*) => {
($cards:expr, $prev_ids:expr, $card_type:ident, $($on_err:tt)*) => {{
for (rel_id, card) in $cards.iter().enumerate() {
// Current id through the whole table
let cur_id = $prev_ids + rel_id;
// Card bytes
let mut card_bytes = [0; 0x3 + CardType::$card_type.byte_size() + 0x1];
let bytes = array_split_mut!(&mut card_bytes,
let bytes = dcb_util::array_split_mut!(&mut card_bytes,
header : [0x3],
card : [CardType::$card_type.byte_size()],
footer : 1,
@ -215,7 +180,7 @@ impl Table {
card_header.to_bytes(bytes.header).into_ok();
// Write the card
#[allow(unreachable_code)] // FIXME: Remove this
#[allow(unreachable_code)] // Might be `!`
card
.to_bytes(bytes.card)
.map_err(|err| {$($on_err)*}(err, cur_id))?;
@ -224,32 +189,28 @@ impl Table {
*bytes.footer = 0;
log::trace!("#{}: Writing {}", cur_id, CardType::$card_type);
file.write_all(&card_bytes)
writer.write_all(&card_bytes)
.map_err(|err| SerializeError::WriteCard {
id: cur_id,
card_type: CardType::$card_type,
err
})?;
}
}
}}
}
// Write all cards
{
// Buffer, Offset, Type, Error closure
write_card! { self.digimons , 0 , Digimon ,
|err, cur_id| SerializeError::SerializeDigimonCard { id: cur_id, err }
}
write_card! { self.items , self.digimons.len() , Item ,
|err, cur_id| SerializeError::SerializeItemCard { id: cur_id, err }
}
write_card! { self.digivolves, self.digimons.len() + self.items.len(), Digivolve,
|err, _| err
}
write_card! { self.digimons, 0, Digimon,
|err, cur_id| SerializeError::SerializeDigimonCard { id: cur_id, err }
}
write_card! { self.items, self.digimons.len(), Item,
|err, cur_id| SerializeError::SerializeItemCard { id: cur_id, err }
}
write_card! { self.digivolves, self.digimons.len() + self.items.len(), Digivolve,
|err, _| err
}
// And return Ok
Ok(())
*/
}
}

View File

@ -3,40 +3,16 @@
// Imports
use super::{card, header, CardType};
/// Error type for [`Table::deserialize`]
/// Error type for [`Table::deserialize`](super::Table::deserialize)
#[derive(Debug, thiserror::Error)]
pub enum DeserializeError {
/// Unable to seek game file
#[error("Unable to seek game file to card table")]
Seek(#[source] std::io::Error),
/// Unable to read table header
#[error("Unable to read table header")]
ReadHeader(#[source] std::io::Error),
/// Unable to parse table header
#[error("Unable to parse table header")]
Header(#[source] header::FromBytesError),
/// There were too many cards
#[error(
"Too many cards in table ({digimon_cards} digimon, {item_cards} item, {digivolve_cards} digivolve, {} / {} bytes max)",
digimon_cards * (0x3 + CardType::Digimon .byte_size() + 0x1) +
item_cards * (0x3 + CardType::Item .byte_size() + 0x1) +
digivolve_cards * (0x3 + CardType::Digivolve.byte_size() + 0x1),
0
//Table::MAX_BYTE_SIZE
)]
TooManyCards {
/// Number of digimon cards
digimon_cards: usize,
/// Number of item cards
item_cards: usize,
/// Number of digivolve cards
digivolve_cards: usize,
},
ParseHeader(#[source] header::FromBytesError),
/// Unable to read card header
#[error("Unable to read card header for card id {}", id)]
@ -76,7 +52,7 @@ pub enum DeserializeError {
/// Unable to deserialize a digimon card
#[error("Unable to deserialize digimon card with id {}", id)]
DigimonCard {
ParseDigimonCard {
/// Id of card
id: usize,
@ -87,7 +63,7 @@ pub enum DeserializeError {
/// Unable to deserialize an item card
#[error("Unable to deserialize item card with id {}", id)]
ItemCard {
ParseItemCard {
/// Id of card
id: usize,
@ -98,7 +74,7 @@ pub enum DeserializeError {
/// Unable to deserialize a digivolve card
#[error("Unable to deserialize digivolve card with id {}", id)]
DigivolveCard {
ParseDigivolveCard {
/// Id of card
id: usize,
@ -119,32 +95,20 @@ pub enum DeserializeError {
},
}
/// Error type for [`Table::serialize`]
/// Error type for [`Table::serialize`](super::Table::serialize)
#[derive(Debug, thiserror::Error)]
pub enum SerializeError {
/// There were too many cards
#[error(
"Too many cards in table ({digimon_cards} digimon, {item_cards} item, {digivolve_cards} digivolve, {} / {} bytes max)",
digimon_cards * (0x3 + CardType::Digimon .byte_size() + 0x1) +
item_cards * (0x3 + CardType::Item .byte_size() + 0x1) +
digivolve_cards * (0x3 + CardType::Digivolve.byte_size() + 0x1),
0
//Table::MAX_BYTE_SIZE
)]
TooManyCards {
/// Number of digimon cards
digimon_cards: usize,
/// Number of digimons must fit within a `u16`
#[error("Number of digimons must fit within a `u16` (was {_0})")]
TooManyDigimon(usize),
/// Number of item cards
item_cards: usize,
/// Number of items must fit within a `u8`
#[error("Number of items must fit within a `u8` (was {_0})")]
TooManyItems(usize),
/// Number of digivolve cards
digivolve_cards: usize,
},
/// Unable to seek game file
#[error("Unable to seek game file to card table")]
Seek(#[source] std::io::Error),
/// Number of digivolves must fit within a `u8`
#[error("Number of digivolves must fit within a `u8` (was {_0})")]
TooManyDigivolves(usize),
/// Unable to write table header
#[error("Unable to write table header")]