mirror of
https://github.com/Zenithsiz/dcb.git
synced 2026-02-09 03:40:23 +00:00
Improved card table.
Added some features to deck table, such as reading & writing the header.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
|
||||
@@ -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];
|
||||
|
||||
102
src/game/deck/table/error.rs
Normal file
102
src/game/deck/table/error.rs
Normal 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,
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user