Improved card table.

Added some features to deck table, such as reading & writing the header.
This commit is contained in:
2020-07-18 22:03:47 +01:00
parent e89efc849d
commit 820b1e3225
6 changed files with 292 additions and 244 deletions

View File

@@ -17,7 +17,7 @@ use crate::{
Bytes,
},
io::{address::Data, GameFile},
util::array_split_mut,
util::{array_split, array_split_mut},
};
use byteorder::{ByteOrder, LittleEndian};
use std::{
@@ -26,6 +26,9 @@ use std::{
};
/// 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.
#[derive(PartialEq, Eq, Clone, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Table {
@@ -44,6 +47,7 @@ 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`
@@ -59,6 +63,15 @@ 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 {
@@ -69,22 +82,27 @@ impl Table {
.map_err(DeserializeError::Seek)?;
// Read header
let mut header_bytes = [0u8; 0x8];
let mut header_bytes = [0u8; Self::HEADER_BYTE_SIZE];
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_bytes[0x0..0x4]);
let magic = LittleEndian::read_u32(header.magic);
if magic != Self::HEADER_MAGIC {
return Err(DeserializeError::HeaderMagic { magic });
}
// Then check the number of each card
let digimon_cards: usize = LittleEndian::read_u16(&header_bytes[0x4..0x6]).into();
let item_cards: usize = header_bytes[0x6].into();
let digivolve_cards: usize = header_bytes[0x7].into();
log::trace!("Found {} digimon cards", digimon_cards);
log::trace!("Found {} item cards", item_cards);
log::trace!("Found {} digivolve cards", digivolve_cards);
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();
log::trace!("Found {digimon_cards} digimon, {item_cards} item, {digivolve_cards} digivolve cards");
// And calculate the number of cards
let cards_len = digimon_cards + item_cards + digivolve_cards;
@@ -93,7 +111,6 @@ impl Table {
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);
log::trace!("{} total bytes of cards", table_size);
if table_size > Self::MAX_BYTE_SIZE {
return Err(DeserializeError::TooManyCards {
digimon_cards,
@@ -118,7 +135,7 @@ impl Table {
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 })?;
log::trace!("Found {} with id {}", card_type, card_id);
log::trace!("Found #{}: {}", card_id, card_type);
// If the card id isn't what we expected, log warning
if usize::from(card_id) != cur_id {
@@ -165,9 +182,7 @@ impl Table {
/// Serializes this card table to `file`.
pub fn serialize<R: Read + Write + Seek>(&self, file: &mut GameFile<R>) -> Result<(), SerializeError> {
// Get the final table size
let table_size = 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);
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 {
@@ -184,118 +199,75 @@ impl Table {
// Write header
let mut header_bytes = [0u8; 0x8];
{
let bytes = array_split_mut!(&mut header_bytes,
magic: [0x4],
let header = array_split_mut!(&mut header_bytes,
magic: [0x4],
digimons_len: [0x2],
items_len: 1,
digivolves_len: 1,
);
digimons_len: [0x2],
items_len: 1,
digivolves_len: 1,
);
// Set magic
LittleEndian::write_u32(bytes.magic, Self::HEADER_MAGIC);
// 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(
bytes.digimons_len,
self.digimons.len().try_into().expect("Number of digimon cards exceeded `u16`"),
);
*bytes.items_len = self.items.len().try_into().expect("Number of item cards exceeded `u8`");
*bytes.digivolves_len = self.digivolves.len().try_into().expect("Number of digivolve cards exceeded `u8`");
}
// 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`");
// And write the header
file.write_all(&header_bytes).map_err(SerializeError::WriteHeader)?;
// Write all digimon, items and digivolves
for (rel_id, digimon) in self.digimons.iter().enumerate() {
// Current id through the whole table
let cur_id = rel_id;
// Macro to help write all cards to file
macro_rules! write_card {
($cards:expr, $prev_ids:expr, $card_type:ident, $ErrVariant:ident) => {
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::Digimon.byte_size() + 0x1];
let bytes = array_split_mut!(&mut card_bytes,
header_id : [0x2],
header_type: 1,
digimon : [CardType::Digimon.byte_size()],
footer : 1,
);
// 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,
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::Digimon.to_bytes(bytes.header_type)?;
// 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)?;
// Write the digimon
digimon
.to_bytes(bytes.digimon)
.map_err(|err| SerializeError::ParseDigimonCard { id: cur_id, err })?;
// Write the card
card
.to_bytes(bytes.card)
.map_err(|err| SerializeError::$ErrVariant { id: cur_id, err })?;
// Write the footer
*bytes.footer = 0;
// Write the footer
*bytes.footer = 0;
log::trace!("Writing Digimon with id {}", cur_id);
file.write_all(&card_bytes)
.map_err(|err| SerializeError::WriteDigimonCard { id: cur_id, err })?;
log::trace!("#{}: Writing {}", cur_id, CardType::$card_type);
file.write_all(&card_bytes)
.map_err(|err| SerializeError::WriteCard {
id: cur_id,
card_type: CardType::$card_type,
err
})?;
}
}
}
for (rel_id, item) in self.items.iter().enumerate() {
// Current id through the whole table
let cur_id = self.digimons.len() + rel_id;
// Card bytes
let mut card_bytes = [0; 0x3 + CardType::Item.byte_size() + 0x1];
let bytes = array_split_mut!(&mut card_bytes,
header_id : [0x2],
header_type: 1,
item : [CardType::Item.byte_size()],
footer : 1,
);
// Write the header
LittleEndian::write_u16(bytes.header_id, cur_id.try_into().expect("Card ID exceeded `u16`"));
CardType::Item.to_bytes(bytes.header_type)?;
// Write the item
item.to_bytes(bytes.item)
.map_err(|err| SerializeError::ParseItemCard { id: cur_id, err })?;
// Write the footer
*bytes.footer = 0;
log::trace!("Writing Item with id {}", cur_id);
file.write_all(&card_bytes)
.map_err(|err| SerializeError::WriteItemCard { id: cur_id, err })?;
}
for (rel_id, digivolve) in self.digivolves.iter().enumerate() {
// Current id through the whole table
let cur_id = self.digimons.len() + self.items.len() + rel_id;
// Card bytes
let mut card_bytes = [0; 0x3 + CardType::Digivolve.byte_size() + 0x1];
let bytes = array_split_mut!(&mut card_bytes,
header_id : [0x2],
header_type: 1,
item : [CardType::Digivolve.byte_size()],
footer : 1,
);
// Write the header
LittleEndian::write_u16(bytes.header_id, cur_id.try_into().expect("Card ID exceeded `u16`"));
CardType::Digivolve.to_bytes(bytes.header_type)?;
// Write the digivolve
digivolve
.to_bytes(bytes.item)
.map_err(|err| SerializeError::ParseDigivolveCard { id: cur_id, err })?;
// Write the footer
*bytes.footer = 0;
log::trace!("Writing Digivolve with id {}", cur_id);
file.write_all(&card_bytes)
.map_err(|err| SerializeError::WriteDigivolveCard { id: cur_id, err })?;
// Write all cards
#[rustfmt::skip] {
write_card! { self.digimons , 0 , Digimon , SerializeDigimonCard }
write_card! { self.items , self.digimons.len() , Item , SerializeItemCard }
write_card! { self.digivolves, self.digimons.len() + self.items.len(), Digivolve, SerializeDigivolveCard }
}
// And return Ok

View File

@@ -15,7 +15,7 @@ pub enum DeserializeError {
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)]
#[error("Found wrong table header magic (expected {:#x}, found {:#x})", Table::HEADER_MAGIC, magic)]
HeaderMagic {
/// Magic we found
magic: u32,
@@ -23,10 +23,7 @@ pub enum DeserializeError {
/// There were too many cards
#[error(
"Too many cards in table ({} digimon, {} item, {} digivolve, {} / {} bytes max)",
digimon_cards,
item_cards,
digivolve_cards,
"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),
@@ -127,20 +124,9 @@ pub enum DeserializeError {
/// Error type for [`Table::serialize`]
#[derive(Debug, thiserror::Error)]
pub enum SerializeError {
/// Unable to seek game file
#[error("Unable to seek game file to card table")]
Seek(#[source] std::io::Error),
/// Unable to write table header
#[error("Unable to write table header")]
WriteHeader(#[source] std::io::Error),
/// There were too many cards
#[error(
"Too many cards in table ({} digimon, {} item, {} digivolve, {} / {} bytes max)",
digimon_cards,
item_cards,
digivolve_cards,
"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),
@@ -157,42 +143,31 @@ pub enum SerializeError {
digivolve_cards: usize,
},
/// Unable to write a digimon card
#[error("Unable to write digimon card with id {}", id)]
WriteDigimonCard {
/// Unable to seek game file
#[error("Unable to seek game file to card table")]
Seek(#[source] std::io::Error),
/// Unable to write table header
#[error("Unable to write table header")]
WriteHeader(#[source] std::io::Error),
/// Unable to write a card
#[error("Unable to write {} card with id {}", card_type, id)]
WriteCard {
/// Id of card
id: usize,
/// Card type
card_type: CardType,
/// Underlying error
#[source]
err: std::io::Error,
},
/// Unable to write an item card
#[error("Unable to write item card with id {}", id)]
WriteItemCard {
/// Id of card
id: usize,
/// Underlying error
#[source]
err: std::io::Error,
},
/// Unable to write a digivolve card
#[error("Unable to write digivolve card with id {}", id)]
WriteDigivolveCard {
/// Id of card
id: usize,
/// Underlying error
#[source]
err: std::io::Error,
},
/// Unable to parse a digimon card
#[error("Unable to parse digimon card with id {}", id)]
ParseDigimonCard {
/// Unable to serialize a digimon card
#[error("Unable to serialize digimon card with id {}", id)]
SerializeDigimonCard {
/// Id of card
id: usize,
@@ -201,9 +176,9 @@ pub enum SerializeError {
err: card::digimon::ToBytesError,
},
/// Unable to parse an item card
#[error("Unable to parse item card with id {}", id)]
ParseItemCard {
/// Unable to serialize an item card
#[error("Unable to serialize item card with id {}", id)]
SerializeItemCard {
/// Id of card
id: usize,
@@ -212,9 +187,9 @@ pub enum SerializeError {
err: card::item::ToBytesError,
},
/// Unable to parse a digivolve card
#[error("Unable to parse digivolve card with id {}", id)]
ParseDigivolveCard {
/// Unable to serialize a digivolve card
#[error("Unable to serialize digivolve card with id {}", id)]
SerializeDigivolveCard {
/// Id of card
id: usize,

View File

@@ -1,11 +1,22 @@
//! The table of all decks in the game
// Modules
pub mod error;
// Exports
pub use error::{DeserializeError, SerializeError};
// Imports
use crate::{
game::{deck::deck, Bytes, Deck},
game::{Bytes, Deck},
io::{address::Data, GameFile},
util::array_split_mut,
};
use byteorder::{ByteOrder, LittleEndian};
use std::{
convert::TryInto,
io::{Read, Seek, Write},
};
use std::io::{Read, Seek, Write};
/// The decks table, where all decks are stored
#[derive(PartialEq, Eq, Clone, Debug)]
@@ -17,76 +28,16 @@ pub struct Table {
// Constants
impl Table {
/// Table header size
pub const HEADER_BYTE_SIZE: usize = 0x8;
/// The magic in the table header
/// = "33KD"
pub const HEADER_MAGIC: u32 = 0x444b3033;
/// The max size of the deck table
// TODO: Verify this
pub const MAX_BYTE_SIZE: usize = 0x4452;
/// The start address of the decks table
const DECK_TABLE_START_ADDRESS: Data = Data::from_u64(0x21a6808);
}
/// Error type for [`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),
/// Could not read a deck entry
#[error("Unable to read deck entry with id {}", id)]
ReadDeck {
/// Id of card
id: usize,
/// Underlying error
#[source]
err: std::io::Error,
},
/// Could not deserialize a deck entry
#[error("Unable to serialize deck entry with id {}", id)]
DeserializeDeck {
/// Id of card
id: usize,
/// Underlying error
#[source]
err: deck::FromBytesError,
},
}
/// Error type for [`Table::serialize`]
#[derive(Debug, thiserror::Error)]
pub enum SerializeError {
/// 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")]
WriteHeader(#[source] std::io::Error),
/// Could not deserialize a deck entry
#[error("Unable to deserialize deck entry with id {}", id)]
SerializeDeck {
/// Id of card
id: usize,
/// Underlying error
#[source]
err: deck::ToBytesError,
},
/// Could not write a deck entry
#[error("Unable to read deck entry with id {}", id)]
WriteDeck {
/// Id of card
id: usize,
/// Underlying error
#[source]
err: std::io::Error,
},
const START_ADDRESS: Data = Data::from_u64(0x21a6808);
}
impl Table {
@@ -95,15 +46,32 @@ impl Table {
where
R: Read + Write + Seek,
{
// The deck array
let mut decks = vec![];
// Seek to the beginning of the deck table
file.seek(std::io::SeekFrom::Start(u64::from(Self::DECK_TABLE_START_ADDRESS)))
file.seek(std::io::SeekFrom::Start(u64::from(Self::START_ADDRESS)))
.map_err(DeserializeError::Seek)?;
// Read header
let mut header_bytes = [0u8; Self::HEADER_BYTE_SIZE];
file.read_exact(&mut header_bytes).map_err(DeserializeError::ReadHeader)?;
// Check if the magic is right
let magic = LittleEndian::read_u32(&header_bytes[0x0..0x4]);
if magic != Self::HEADER_MAGIC {
return Err(DeserializeError::HeaderMagic { magic });
}
// Extract the number of decks
let decks_count: usize = header_bytes[0x4].into();
log::trace!("Found {decks_count} decks");
// If there are too many decks, return Err
if decks_count * std::mem::size_of::<<Deck as Bytes>::ByteArray>() > Self::MAX_BYTE_SIZE {
return Err(DeserializeError::TooManyDecks { decks_count });
}
// Then get each deck
for id in 0..159 {
let mut decks = vec![];
for id in 0..decks_count {
// Read all bytes of the deck
let mut bytes = [0; 0x6e];
file.read_exact(&mut bytes).map_err(|err| DeserializeError::ReadDeck { id, err })?;
@@ -111,6 +79,9 @@ impl Table {
// And try to serialize the deck
let deck = Deck::from_bytes(&bytes).map_err(|err| DeserializeError::DeserializeDeck { id, err })?;
// Log the deck
log::trace!("Found deck #{}: {}", id, deck.name);
// And add it
decks.push(deck);
}
@@ -124,11 +95,37 @@ impl Table {
where
R: Read + Write + Seek,
{
// If the total table size is bigger than the max, return Err
if self.decks.len() * std::mem::size_of::<<Deck as Bytes>::ByteArray>() > Self::MAX_BYTE_SIZE {
return Err(SerializeError::TooManyDecks {
decks_count: self.decks.len(),
});
}
// Seek to the beginning of the deck table
file.seek(std::io::SeekFrom::Start(u64::from(Self::DECK_TABLE_START_ADDRESS)))
file.seek(std::io::SeekFrom::Start(u64::from(Self::START_ADDRESS)))
.map_err(SerializeError::Seek)?;
// Then get each deck
// Write header
let mut header_bytes = [0u8; 0x8];
let header = array_split_mut!(&mut header_bytes,
magic: [0x4],
decks_count: 1,
_unknown: [0x3],
);
// Set magic
LittleEndian::write_u32(header.magic, Self::HEADER_MAGIC);
// Write deck len
log::trace!("Writing {} decks", self.decks.len());
*header.decks_count = self.decks.len().try_into().expect("Number of decks exceeded `u8`");
// And write the header
file.write_all(&header_bytes).map_err(SerializeError::WriteHeader)?;
// Then write each deck
for (id, deck) in self.decks.iter().enumerate() {
// Parse each deck into bytes
let mut bytes = [0; 0x6e];

View File

@@ -0,0 +1,102 @@
//! Errors
// Imports
use super::{Bytes, Deck, Table};
use crate::game::deck::deck;
/// Error type for [`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),
/// The magic of the table was wrong
#[error("Found wrong table header magic (expected {:#}, found {:#x})", Table::HEADER_MAGIC, magic)]
HeaderMagic {
/// Magic we found
magic: u32,
},
/// There were too many decks
#[error(
"Too many decks in table ({decks_count} decks, {} / {} bytes max)",
decks_count * std::mem::size_of::<<Deck as Bytes>::ByteArray>(),
Table::MAX_BYTE_SIZE
)]
TooManyDecks {
/// Number of decks
decks_count: usize,
},
/// Could not read a deck entry
#[error("Unable to read deck entry with id {}", id)]
ReadDeck {
/// Id of card
id: usize,
/// Underlying error
#[source]
err: std::io::Error,
},
/// Could not deserialize a deck entry
#[error("Unable to serialize deck entry with id {}", id)]
DeserializeDeck {
/// Id of card
id: usize,
/// Underlying error
#[source]
err: deck::FromBytesError,
},
}
/// Error type for [`Table::serialize`]
#[derive(Debug, thiserror::Error)]
pub enum SerializeError {
/// Unable to seek game file
#[error("Unable to seek game file to card table")]
Seek(#[source] std::io::Error),
/// There were too many decks
#[error(
"Too many decks in table ({decks_count} decks, {} / {} bytes max)",
decks_count * std::mem::size_of::<<Deck as Bytes>::ByteArray>(),
Table::MAX_BYTE_SIZE
)]
TooManyDecks {
/// Number of decks
decks_count: usize,
},
/// Unable to write table header
#[error("Unable to write table header")]
WriteHeader(#[source] std::io::Error),
/// Could not deserialize a deck entry
#[error("Unable to deserialize deck entry with id {}", id)]
SerializeDeck {
/// Id of card
id: usize,
/// Underlying error
#[source]
err: deck::ToBytesError,
},
/// Could not write a deck entry
#[error("Unable to read deck entry with id {}", id)]
WriteDeck {
/// Id of card
id: usize,
/// Underlying error
#[source]
err: std::io::Error,
},
}

View File

@@ -35,7 +35,8 @@
decl_macro,
stmt_expr_attributes,
unwrap_infallible,
external_doc
external_doc,
format_args_capture
)]
// Lints
#![warn(clippy::restriction, clippy::pedantic, clippy::nursery)]
@@ -50,7 +51,7 @@
// it is the safest alternative.
#![allow(clippy::expect_used)]
// Like-wise with `.expect("...")`, we use `unreachable!` / `todo!` when we know a branch
// if unreachable, and if it ever does get reached, panicking would be the
// if unreachable or not yet finished, and if it ever does get reached, panicking would be the
// safest option
#![allow(clippy::unreachable, clippy::todo)]
// We find it more important to be able to copy paste literals such as `0xabcd1234` than

View File

@@ -69,6 +69,7 @@ pub macro array_split_mut {
clippy::used_underscore_binding,
clippy::ptr_offset_with_cast,
clippy::indexing_slicing,
dead_code
)]
// Struct holding all fields