From 280bdac119b4ecde76e06226f2c463d4f5b0ec46 Mon Sep 17 00:00:00 2001 From: Filipe Rodrigues Date: Fri, 1 May 2020 11:29:01 +0100 Subject: [PATCH] All code is now formatted. Started using `err_impl::Error` where possible. --- rustfmt.toml | 5 + src/game.rs | 8 +- src/game/bytes.rs | 12 +- src/game/card.rs | 8 +- src/game/card/digimon.rs | 338 +++++++++----------- src/game/card/digivolve.rs | 124 ++++---- src/game/card/item.rs | 215 ++++++------- src/game/card/property.rs | 121 ++++--- src/game/card/property/effect.rs | 348 ++++++++++---------- src/game/card/property/effect_condition.rs | 133 ++++---- src/game/card/property/moves.rs | 61 ++-- src/game/card/table.rs | 319 +++++++++---------- src/game/deck.rs | 9 +- src/game/deck/table.rs | 351 +++++++++------------ src/game/util.rs | 61 ++-- src/io.rs | 4 +- src/io/address.rs | 51 ++- src/io/address/data.rs | 54 ++-- src/io/address/real.rs | 59 ++-- src/io/game_file.rs | 188 +++++------ src/lib.rs | 16 +- 21 files changed, 1135 insertions(+), 1350 deletions(-) diff --git a/rustfmt.toml b/rustfmt.toml index 24a9240..844da63 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -11,3 +11,8 @@ newline_style = "Unix" reorder_impl_items = true overflow_delimited_expr = true use_field_init_shorthand = true +enum_discrim_align_threshold = 100 +fn_args_layout = "Compressed" +merge_derives = false +merge_imports = true +max_width = 150 diff --git a/src/game.rs b/src/game.rs index b3d07ac..c746254 100644 --- a/src/game.rs +++ b/src/game.rs @@ -1,11 +1,11 @@ //! Game Data -//! +//! //! The game module is where all of the game data is defined, this game data //! can read from the [`GameFile`](crate::io::GameFile) in the `io` module. -//! +//! //! Some notable types within this module are [`CardTable`](crate::game::card::Table), the table which //! stores all cards and [`DeckTable`](crate::game::deck::Table), the table which stores all cards available. -//! +//! //! # Strings //! A lot of types in this module have strings that they must read and write from the game. //! All these strings must only contain ascii characters, thus on read and on write, if any @@ -21,8 +21,6 @@ pub mod bytes; pub mod card; pub mod deck; - - // Exports pub use bytes::Bytes; pub use card::Digimon; diff --git a/src/game/bytes.rs b/src/game/bytes.rs index ba8a48c..2c98192 100644 --- a/src/game/bytes.rs +++ b/src/game/bytes.rs @@ -3,22 +3,22 @@ /// Convertions to and from bytes for the game file pub trait Bytes where - Self: Sized + Self: Sized, { /// The type of array required by this structure - /// + /// /// *MUST* be a `[u8; N]` type ByteArray: Sized; - + /// The error type used for the operation type FromError: std::fmt::Debug + std::error::Error; - + /// The error type used for the operation type ToError: std::fmt::Debug + std::error::Error; - + /// Constructs this structure from `bytes` fn from_bytes(bytes: &Self::ByteArray) -> Result; - + /// Writes this structure to `bytes` fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError>; } diff --git a/src/game/card.rs b/src/game/card.rs index af8131f..76b62b0 100644 --- a/src/game/card.rs +++ b/src/game/card.rs @@ -1,17 +1,17 @@ //! Cards -//! +//! //! This module contains all cards and card properties that are stored within the game, //! as well as the card table itself, of all of the cards in the game. // Modules pub mod digimon; -pub mod item; pub mod digivolve; +pub mod item; pub mod property; pub mod table; // Exports -pub use digimon ::Digimon; -pub use item ::Item; +pub use digimon::Digimon; pub use digivolve::Digivolve; +pub use item::Item; pub use table::Table; diff --git a/src/game/card/digimon.rs b/src/game/card/digimon.rs index 8d577a7..2778f78 100644 --- a/src/game/card/digimon.rs +++ b/src/game/card/digimon.rs @@ -1,10 +1,10 @@ //! A digimon card -//! +//! //! This module contains the [`Digimon`] struct, which describes a digimon card. -//! +//! //! # Layout //! The digimon card has a size of `0x138` bytes, and it's layout is the following: -//! +//! //! | Offset | Size | Type | Name | Location | Details | //! |--------|------|---------------------|---------------------------|------------------------|-------------------------------------------------------------------------------------| //! | 0x0 | 0x15 | `[char; 0x15]` | Name | `name` | Null-terminated | @@ -32,86 +32,75 @@ use byteorder::{ByteOrder, LittleEndian}; // Crate use crate::game::{ - util, - Bytes, - card::property::{ - self, - Speciality, - Level, - Move, - CrossMoveEffect, - EffectCondition, - Effect, - ArrowColor, - } + card::property::{self, ArrowColor, CrossMoveEffect, Effect, EffectCondition, Level, Move, Speciality}, + util, Bytes, }; /// A digimon card -/// +/// /// Contains all information about each digimon card stored in the [`Card Table`](crate::game::card::table::Table) #[derive(PartialEq, Eq, Clone, Hash, Debug)] #[derive(serde::Serialize, serde::Deserialize)] -pub struct Digimon -{ +pub struct Digimon { /// The digimon's name - /// + /// /// An ascii string with 20 characters at most pub name: ascii::AsciiString, - + /// The digimon's speciality - /// + /// /// Stored alongside with the level in a single byte pub speciality: Speciality, - + /// The digimon's level - /// + /// /// Stored alongside with the speciality in a single byte pub level: Level, - + /// The digimon's health points pub hp: u16, - + /// The DP cost to play this digimon card - /// + /// /// `DP` in the game. pub dp_cost: u8, - + /// The number of DP given when discarded - /// + /// /// `+P` in the game. pub dp_give: u8, - + /// The digimon's circle move pub move_circle: Move, - + /// The digimon's triangle move pub move_triangle: Move, - + /// The digimon's cross move pub move_cross: Move, - + /// The digimon's cross move effect, if any #[serde(default)] pub cross_move_effect: Option, - + /// The effect's description. - /// + /// /// The description is split along 4 lines, each /// being an ascii string with 20 characters at most. pub effect_description: [ascii::AsciiString; 4], - + /// The effect's arrow color #[serde(default)] pub effect_arrow_color: Option, - + /// The effect's conditions #[serde(default)] pub effect_conditions: [Option; 2], - + /// The effects #[serde(default)] pub effects: [Option; 3], - + // Unknown fields pub unknown_1a: u8, pub unknown_15: u16, @@ -121,134 +110,131 @@ pub struct Digimon /// Error type for [`Bytes::from_bytes`] #[derive(Debug)] #[derive(derive_more::Display, err_impl::Error)] -pub enum FromBytesError -{ +pub enum FromBytesError { /// Unable to read the digimon name #[display(fmt = "Unable to read the digimon name")] - Name( #[error(source)] util::ReadNullAsciiStringError ), - + Name(#[error(source)] util::ReadNullAsciiStringError), + /// Unable to read the first support effect description #[display(fmt = "Unable to read the first line of the effect description")] - EffectDescriptionFirst( #[error(source)] util::ReadNullAsciiStringError ), - + EffectDescriptionFirst(#[error(source)] util::ReadNullAsciiStringError), + /// Unable to read the second support effect description #[display(fmt = "Unable to read the second line of the effect description")] - EffectDescriptionSecond( #[error(source)] util::ReadNullAsciiStringError ), - + EffectDescriptionSecond(#[error(source)] util::ReadNullAsciiStringError), + /// Unable to read the third support effect description #[display(fmt = "Unable to read the third line of the effect description")] - EffectDescriptionThird( #[error(source)] util::ReadNullAsciiStringError ), - + EffectDescriptionThird(#[error(source)] util::ReadNullAsciiStringError), + /// Unable to read the fourth support effect description #[display(fmt = "Unable to read the fourth line of the effect description")] - EffectDescriptionFourth( #[error(source)] util::ReadNullAsciiStringError ), - + EffectDescriptionFourth(#[error(source)] util::ReadNullAsciiStringError), + /// An unknown speciality was found #[display(fmt = "Unknown speciality found")] - Speciality( #[error(source)] property::speciality::FromBytesError ), - + Speciality(#[error(source)] property::speciality::FromBytesError), + /// An unknown level was found #[display(fmt = "Unknown level found")] - Level( #[error(source)] property::level::FromBytesError ), - + Level(#[error(source)] property::level::FromBytesError), + /// An unknown effect arrow color was found #[display(fmt = "Unknown effect arrow color found")] - ArrowColor( #[error(source)] property::arrow_color::FromBytesError ), - + ArrowColor(#[error(source)] property::arrow_color::FromBytesError), + /// An unknown cross move effect was found #[display(fmt = "Unknown cross move effect found")] - CrossMoveEffect( #[error(source)] property::cross_move_effect::FromBytesError ), - + CrossMoveEffect(#[error(source)] property::cross_move_effect::FromBytesError), + /// Unable to read the circle move #[display(fmt = "Unable to read the circle move")] - MoveCircle( #[error(source)] property::moves::FromBytesError ), - + MoveCircle(#[error(source)] property::moves::FromBytesError), + /// Unable to read the triangle move #[display(fmt = "Unable to read the triangle move")] - MoveTriangle( #[error(source)] property::moves::FromBytesError ), - + MoveTriangle(#[error(source)] property::moves::FromBytesError), + /// Unable to read the cross move #[display(fmt = "Unable to read the cross move")] - MoveCross( #[error(source)] property::moves::FromBytesError ), - + MoveCross(#[error(source)] property::moves::FromBytesError), + /// Unable to read the first effect condition #[display(fmt = "Unable to read the first effect condition")] - EffectConditionFirst( #[error(source)] property::effect_condition::FromBytesError ), - + EffectConditionFirst(#[error(source)] property::effect_condition::FromBytesError), + /// Unable to read the second effect condition #[display(fmt = "Unable to read the second effect condition")] - EffectConditionSecond( #[error(source)] property::effect_condition::FromBytesError ), - + EffectConditionSecond(#[error(source)] property::effect_condition::FromBytesError), + /// Unable to read the first effect #[display(fmt = "Unable to read the first effect")] - EffectFirst( #[error(source)] property::effect::FromBytesError ), - + EffectFirst(#[error(source)] property::effect::FromBytesError), + /// Unable to read the second effect #[display(fmt = "Unable to read the second effect")] - EffectSecond( #[error(source)] property::effect::FromBytesError ), - + EffectSecond(#[error(source)] property::effect::FromBytesError), + /// Unable to read the third effect #[display(fmt = "Unable to read the third effect")] - EffectThird( #[error(source)] property::effect::FromBytesError ), + EffectThird(#[error(source)] property::effect::FromBytesError), } /// Error type for [`Bytes::to_bytes`] #[derive(Debug)] #[derive(derive_more::Display, err_impl::Error)] -pub enum ToBytesError -{ +pub enum ToBytesError { /// Unable to write the digimon name #[display(fmt = "Unable to write the digimon name")] - Name( #[error(source)] util::WriteNullAsciiStringError ), - + Name(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the first support effect description #[display(fmt = "Unable to write the first line of the effect description")] - EffectDescriptionFirst( #[error(source)] util::WriteNullAsciiStringError ), - + EffectDescriptionFirst(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the second support effect description #[display(fmt = "Unable to write the second line of the effect description")] - EffectDescriptionSecond( #[error(source)] util::WriteNullAsciiStringError ), - + EffectDescriptionSecond(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the third support effect description #[display(fmt = "Unable to write the third line of the effect description")] - EffectDescriptionThird( #[error(source)] util::WriteNullAsciiStringError ), - + EffectDescriptionThird(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the fourth support effect description #[display(fmt = "Unable to write the fourth line of the effect description")] - EffectDescriptionFourth( #[error(source)] util::WriteNullAsciiStringError ), - + EffectDescriptionFourth(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the circle move #[display(fmt = "Unable to write the circle move")] - MoveCircle( #[error(source)] property::moves::ToBytesError ), - + MoveCircle(#[error(source)] property::moves::ToBytesError), + /// Unable to write the triangle move #[display(fmt = "Unable to write the triangle move")] - MoveTriangle( #[error(source)] property::moves::ToBytesError ), - + MoveTriangle(#[error(source)] property::moves::ToBytesError), + /// Unable to write the cross move #[display(fmt = "Unable to write the cross move")] - MoveCross( #[error(source)] property::moves::ToBytesError ), - + MoveCross(#[error(source)] property::moves::ToBytesError), + /// Unable to write the first effect #[display(fmt = "Unable to write the first effect")] - EffectFirst( #[error(source)] property::effect::ToBytesError ), - + EffectFirst(#[error(source)] property::effect::ToBytesError), + /// Unable to write the second effect #[display(fmt = "Unable to write the second effect")] - EffectSecond( #[error(source)] property::effect::ToBytesError ), - + EffectSecond(#[error(source)] property::effect::ToBytesError), + /// Unable to write the third effect #[display(fmt = "Unable to write the third effect")] - EffectThird( #[error(source)] property::effect::ToBytesError ), + EffectThird(#[error(source)] property::effect::ToBytesError), } -impl Bytes for Digimon -{ +impl Bytes for Digimon { type ByteArray = [u8; 0x138]; - type FromError = FromBytesError; - fn from_bytes(bytes: &Self::ByteArray) -> Result - { + type ToError = ToBytesError; + + fn from_bytes(bytes: &Self::ByteArray) -> Result { // Split bytes let bytes = util::array_split!(bytes, name : [0x15], @@ -274,83 +260,68 @@ impl Bytes for Digimon effect_description_2: [0x15], effect_description_3: [0x15], ); - + // Return the struct after building it - Ok( Self { - name: util::read_null_ascii_string(bytes.name) - .map_err(FromBytesError::Name)? - .chars().collect(), - - speciality: Speciality::from_bytes( &( (bytes.speciality_level & 0xF0) >> 4 ) ) - .map_err(FromBytesError::Speciality)?, - - level: Level::from_bytes( &( (bytes.speciality_level & 0x0F) >> 0 ) ) - .map_err(FromBytesError::Level)?, - - dp_cost : *bytes.dp_cost, - dp_give : *bytes.dp_give, - - hp: LittleEndian::read_u16( bytes.hp ), - + Ok(Self { + name: util::read_null_ascii_string(bytes.name).map_err(FromBytesError::Name)?.chars().collect(), + + speciality: Speciality::from_bytes(&((bytes.speciality_level & 0xF0) >> 4)).map_err(FromBytesError::Speciality)?, + + level: Level::from_bytes(&((bytes.speciality_level & 0x0F) >> 0)).map_err(FromBytesError::Level)?, + + dp_cost: *bytes.dp_cost, + dp_give: *bytes.dp_give, + + hp: LittleEndian::read_u16(bytes.hp), + // Moves - move_circle: Move::from_bytes( bytes.move_circle ) - .map_err(FromBytesError::MoveCircle)?, - move_triangle: Move::from_bytes( bytes.move_triangle ) - .map_err(FromBytesError::MoveTriangle)?, - move_cross: Move::from_bytes( bytes.move_cross ) - .map_err(FromBytesError::MoveCross)?, - + move_circle: Move::from_bytes(bytes.move_circle).map_err(FromBytesError::MoveCircle)?, + move_triangle: Move::from_bytes(bytes.move_triangle).map_err(FromBytesError::MoveTriangle)?, + move_cross: Move::from_bytes(bytes.move_cross).map_err(FromBytesError::MoveCross)?, + // Effects effect_conditions: [ - Option::::from_bytes( bytes.condition_first ) - .map_err(FromBytesError::EffectConditionFirst)?, - - Option::::from_bytes( bytes.condition_second ) - .map_err(FromBytesError::EffectConditionSecond)?, + Option::::from_bytes(bytes.condition_first).map_err(FromBytesError::EffectConditionFirst)?, + Option::::from_bytes(bytes.condition_second).map_err(FromBytesError::EffectConditionSecond)?, ], - + effects: [ - Option::::from_bytes( bytes.effect_first ) - .map_err(FromBytesError::EffectFirst)?, - - Option::::from_bytes( bytes.effect_second ) - .map_err(FromBytesError::EffectSecond)?, - - Option::::from_bytes( bytes.effect_third ) - .map_err(FromBytesError::EffectThird)?, + Option::::from_bytes(bytes.effect_first).map_err(FromBytesError::EffectFirst)?, + Option::::from_bytes(bytes.effect_second).map_err(FromBytesError::EffectSecond)?, + Option::::from_bytes(bytes.effect_third).map_err(FromBytesError::EffectThird)?, ], - - cross_move_effect: Option::::from_bytes(bytes.cross_move_effect) - .map_err(FromBytesError::CrossMoveEffect)?, - - effect_arrow_color: Option::::from_bytes(bytes.effect_arrow_color) - .map_err(FromBytesError::ArrowColor)?, - + + cross_move_effect: Option::::from_bytes(bytes.cross_move_effect).map_err(FromBytesError::CrossMoveEffect)?, + + effect_arrow_color: Option::::from_bytes(bytes.effect_arrow_color).map_err(FromBytesError::ArrowColor)?, + effect_description: [ - util::read_null_ascii_string( bytes.effect_description_0 ) + util::read_null_ascii_string(bytes.effect_description_0) .map_err(FromBytesError::EffectDescriptionFirst)? - .chars().collect(), - util::read_null_ascii_string( bytes.effect_description_1 ) + .chars() + .collect(), + util::read_null_ascii_string(bytes.effect_description_1) .map_err(FromBytesError::EffectDescriptionSecond)? - .chars().collect(), - util::read_null_ascii_string( bytes.effect_description_2 ) + .chars() + .collect(), + util::read_null_ascii_string(bytes.effect_description_2) .map_err(FromBytesError::EffectDescriptionThird)? - .chars().collect(), - util::read_null_ascii_string( bytes.effect_description_3 ) + .chars() + .collect(), + util::read_null_ascii_string(bytes.effect_description_3) .map_err(FromBytesError::EffectDescriptionFourth)? - .chars().collect(), + .chars() + .collect(), ], - + // Unknown unknown_15: LittleEndian::read_u16(bytes.unknown_15), unknown_1a: *bytes.unknown_1a, unknown_e2: *bytes.unknown_e2, }) } - - type ToError = ToBytesError; - fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> - { + + fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { // Split bytes let bytes = util::array_split_mut!(bytes, name : [0x15], @@ -376,47 +347,46 @@ impl Bytes for Digimon effect_description_2: [0x15], effect_description_3: [0x15], ); - + // Name - util::write_null_ascii_string(self.name.as_ref(), bytes.name) - .map_err(ToBytesError::Name)?; - + util::write_null_ascii_string(self.name.as_ref(), bytes.name).map_err(ToBytesError::Name)?; + // Speciality / Level { - let (mut speciality_byte, mut level_byte) = ( 0u8, 0u8 ); - + let (mut speciality_byte, mut level_byte) = (0u8, 0u8); + // Note: Buffers have 1 byte, so this can't fail self.speciality.to_bytes(&mut speciality_byte)?; self.level.to_bytes(&mut level_byte)?; - + // Merge them *bytes.speciality_level = (speciality_byte << 4) | level_byte; } - + // DP / +P *bytes.dp_cost = self.dp_cost; *bytes.dp_give = self.dp_give; - + // Health LittleEndian::write_u16(bytes.hp, self.hp); - + // Moves - self.move_circle .to_bytes( bytes.move_circle ).map_err(ToBytesError::MoveCircle )?; - self.move_triangle.to_bytes( bytes.move_triangle ).map_err(ToBytesError::MoveTriangle)?; - self.move_cross .to_bytes( bytes.move_cross ).map_err(ToBytesError::MoveCross )?; - + self.move_circle.to_bytes(bytes.move_circle).map_err(ToBytesError::MoveCircle)?; + self.move_triangle.to_bytes(bytes.move_triangle).map_err(ToBytesError::MoveTriangle)?; + self.move_cross.to_bytes(bytes.move_cross).map_err(ToBytesError::MoveCross)?; + // Effects - self.effect_conditions[0].to_bytes( bytes.condition_first ).into_ok(); - self.effect_conditions[1].to_bytes( bytes.condition_second ).into_ok(); - - self.effects[0].to_bytes( bytes.effect_first ).map_err(ToBytesError::EffectFirst )?; - self.effects[1].to_bytes( bytes.effect_second ).map_err(ToBytesError::EffectSecond)?; - self.effects[2].to_bytes( bytes.effect_third ).map_err(ToBytesError::EffectThird )?; - + self.effect_conditions[0].to_bytes(bytes.condition_first).into_ok(); + self.effect_conditions[1].to_bytes(bytes.condition_second).into_ok(); + + self.effects[0].to_bytes(bytes.effect_first).map_err(ToBytesError::EffectFirst)?; + self.effects[1].to_bytes(bytes.effect_second).map_err(ToBytesError::EffectSecond)?; + self.effects[2].to_bytes(bytes.effect_third).map_err(ToBytesError::EffectThird)?; + Option::::to_bytes(&self.cross_move_effect, bytes.cross_move_effect).into_ok(); - + Option::::to_bytes(&self.effect_arrow_color, bytes.effect_arrow_color).into_ok(); - + util::write_null_ascii_string(self.effect_description[0].as_ref(), bytes.effect_description_0) .map_err(ToBytesError::EffectDescriptionFirst)?; util::write_null_ascii_string(self.effect_description[1].as_ref(), bytes.effect_description_1) @@ -425,12 +395,12 @@ impl Bytes for Digimon .map_err(ToBytesError::EffectDescriptionThird)?; util::write_null_ascii_string(self.effect_description[3].as_ref(), bytes.effect_description_3) .map_err(ToBytesError::EffectDescriptionFourth)?; - + // Unknown LittleEndian::write_u16(bytes.unknown_15, self.unknown_15); *bytes.unknown_1a = self.unknown_1a; *bytes.unknown_e2 = self.unknown_e2; - + // Return Ok Ok(()) } diff --git a/src/game/card/digivolve.rs b/src/game/card/digivolve.rs index 5233573..ede6bbc 100644 --- a/src/game/card/digivolve.rs +++ b/src/game/card/digivolve.rs @@ -1,10 +1,10 @@ //! A digivolve card -//! +//! //! This module contains the [`Digivolve`] struct, which describes a digivolve card. -//! +//! //! # Layout //! The digivolve card has a size of `0x6c` bytes, and it's layout is the following: -//! +//! //! | Offset | Size | Type | Name | Location | Details | //! |--------|------|---------------------|---------------------------|------------------------|---------------------------------------------------------------------| //! | 0x0 | 0x15 | `[char; 0x15]` | Name | `name` | Null-terminated | @@ -12,29 +12,25 @@ //! | 0x8a | 0x54 | `[[char; 0x15]; 4]` | Effect description lines | `effect_description` | Each line is` 0x15` bytes, split over 4 lines, each null terminated | // Crate -use crate::game::{ - util, - Bytes, -}; +use crate::game::{util, Bytes}; /// A digivolve card -/// +/// /// Contains all information about each digivolve card stored in the [`Card Table`](crate::game::card::table::Table) #[derive(PartialEq, Eq, Clone, Hash, Debug)] #[derive(serde::Serialize, serde::Deserialize)] -pub struct Digivolve -{ +pub struct Digivolve { /// The item's name - /// + /// /// An ascii string with 20 characters at most pub name: ascii::AsciiString, - + /// The effect's description. - /// + /// /// The description is split along 4 lines, each /// being an ascii string with 20 characters at most. pub effect_description: [ascii::AsciiString; 4], - + // Unknown pub unknown_15: [u8; 3], } @@ -42,62 +38,59 @@ pub struct Digivolve /// Error type for [`Bytes::from_bytes`] #[derive(Debug)] #[derive(derive_more::Display, err_impl::Error)] -pub enum FromBytesError -{ +pub enum FromBytesError { /// Unable to read the digimon name #[display(fmt = "Unable to read the digimon name")] - Name( #[error(source)] util::ReadNullAsciiStringError ), - + Name(#[error(source)] util::ReadNullAsciiStringError), + /// Unable to read the first support effect description #[display(fmt = "Unable to read the first line of the effect description")] - EffectDescriptionFirst( #[error(source)] util::ReadNullAsciiStringError ), - + EffectDescriptionFirst(#[error(source)] util::ReadNullAsciiStringError), + /// Unable to read the second support effect description #[display(fmt = "Unable to read the second line of the effect description")] - EffectDescriptionSecond( #[error(source)] util::ReadNullAsciiStringError ), - + EffectDescriptionSecond(#[error(source)] util::ReadNullAsciiStringError), + /// Unable to read the third support effect description #[display(fmt = "Unable to read the third line of the effect description")] - EffectDescriptionThird( #[error(source)] util::ReadNullAsciiStringError ), - + EffectDescriptionThird(#[error(source)] util::ReadNullAsciiStringError), + /// Unable to read the fourth support effect description #[display(fmt = "Unable to read the fourth line of the effect description")] - EffectDescriptionFourth( #[error(source)] util::ReadNullAsciiStringError ), + EffectDescriptionFourth(#[error(source)] util::ReadNullAsciiStringError), } /// Error type for [`Bytes::to_bytes`] #[derive(Debug)] #[derive(derive_more::Display, err_impl::Error)] -pub enum ToBytesError -{ +pub enum ToBytesError { /// Unable to write the digimon name #[display(fmt = "Unable to write the digimon name")] - Name( #[error(source)] util::WriteNullAsciiStringError ), - + Name(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the first support effect description #[display(fmt = "Unable to write the first line of the effect description")] - EffectDescriptionFirst( #[error(source)] util::WriteNullAsciiStringError ), - + EffectDescriptionFirst(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the second support effect description #[display(fmt = "Unable to write the second line of the effect description")] - EffectDescriptionSecond( #[error(source)] util::WriteNullAsciiStringError ), - + EffectDescriptionSecond(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the third support effect description #[display(fmt = "Unable to write the third line of the effect description")] - EffectDescriptionThird( #[error(source)] util::WriteNullAsciiStringError ), - + EffectDescriptionThird(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the fourth support effect description #[display(fmt = "Unable to write the fourth line of the effect description")] - EffectDescriptionFourth( #[error(source)] util::WriteNullAsciiStringError ), + EffectDescriptionFourth(#[error(source)] util::WriteNullAsciiStringError), } -impl Bytes for Digivolve -{ +impl Bytes for Digivolve { type ByteArray = [u8; 0x6c]; - type FromError = FromBytesError; - fn from_bytes(bytes: &Self::ByteArray) -> Result - { + type ToError = ToBytesError; + + fn from_bytes(bytes: &Self::ByteArray) -> Result { // Split bytes let bytes = util::array_split!(bytes, name : [0x15], @@ -107,37 +100,37 @@ impl Bytes for Digivolve effect_description_2: [0x15], effect_description_3: [0x15], ); - - Ok( Self { + + Ok(Self { // Name - name: util::read_null_ascii_string(bytes.name) - .map_err(FromBytesError::Name)? - .chars().collect(), - + name: util::read_null_ascii_string(bytes.name).map_err(FromBytesError::Name)?.chars().collect(), + // Effect effect_description: [ - util::read_null_ascii_string( bytes.effect_description_0 ) + util::read_null_ascii_string(bytes.effect_description_0) .map_err(FromBytesError::EffectDescriptionFirst)? - .chars().collect(), - util::read_null_ascii_string( bytes.effect_description_1 ) + .chars() + .collect(), + util::read_null_ascii_string(bytes.effect_description_1) .map_err(FromBytesError::EffectDescriptionSecond)? - .chars().collect(), - util::read_null_ascii_string( bytes.effect_description_2 ) + .chars() + .collect(), + util::read_null_ascii_string(bytes.effect_description_2) .map_err(FromBytesError::EffectDescriptionThird)? - .chars().collect(), - util::read_null_ascii_string( bytes.effect_description_3 ) + .chars() + .collect(), + util::read_null_ascii_string(bytes.effect_description_3) .map_err(FromBytesError::EffectDescriptionFourth)? - .chars().collect(), + .chars() + .collect(), ], - + // Unknown unknown_15: *bytes.unknown_15, }) } - - type ToError = ToBytesError; - fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> - { + + fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { // Split bytes let bytes = util::array_split_mut!(bytes, name : [0x15], @@ -147,11 +140,10 @@ impl Bytes for Digivolve effect_description_2: [0x15], effect_description_3: [0x15], ); - + // Name - util::write_null_ascii_string(self.name.as_ref(), bytes.name) - .map_err(ToBytesError::Name)?; - + util::write_null_ascii_string(self.name.as_ref(), bytes.name).map_err(ToBytesError::Name)?; + // Effects util::write_null_ascii_string(self.effect_description[0].as_ref(), bytes.effect_description_0) .map_err(ToBytesError::EffectDescriptionFirst)?; @@ -161,10 +153,10 @@ impl Bytes for Digivolve .map_err(ToBytesError::EffectDescriptionThird)?; util::write_null_ascii_string(self.effect_description[3].as_ref(), bytes.effect_description_3) .map_err(ToBytesError::EffectDescriptionFourth)?; - + // Unknown *bytes.unknown_15 = self.unknown_15; - + // Return Ok Ok(()) } diff --git a/src/game/card/item.rs b/src/game/card/item.rs index c477bc0..da79ae5 100644 --- a/src/game/card/item.rs +++ b/src/game/card/item.rs @@ -1,10 +1,10 @@ //! An item card -//! +//! //! This module contains the [`Item`] struct, which describes an item card. -//! +//! //! # Layout //! The item card has a size of `0xde` bytes, and it's layout is the following: -//! +//! //! | Offset | Size | Type | Name | Location | Details | //! |--------|------|---------------------|---------------------------|------------------------|-------------------------------------------------------------------------------------| //! | 0x0 | 0x15 | `[char; 0x15]` | Name | `name` | Null-terminated | @@ -22,46 +22,39 @@ use byteorder::{ByteOrder, LittleEndian}; // Crate use crate::game::{ - util, - Bytes, - card::property::{ - self, - EffectCondition, - Effect, - ArrowColor, - } + card::property::{self, ArrowColor, Effect, EffectCondition}, + util, Bytes, }; /// An item card -/// +/// /// Contains all information about each item card stored in the [`Card Table`](crate::game::card::table::Table) #[derive(PartialEq, Eq, Clone, Hash, Debug)] #[derive(serde::Serialize, serde::Deserialize)] -pub struct Item -{ +pub struct Item { /// The item's name - /// + /// /// An ascii string with 20 characters at most pub name: ascii::AsciiString, - + /// The effect's description. - /// + /// /// The description is split along 4 lines, each /// being an ascii string with 20 characters at most. pub effect_description: [ascii::AsciiString; 4], - + /// The effect's arrow color #[serde(default)] pub effect_arrow_color: Option, - + /// The effect's conditions #[serde(default)] pub effect_conditions: [Option; 2], - + /// The effects #[serde(default)] pub effects: [Option; 3], - + // Unknown fields pub unknown_15: u32, } @@ -69,99 +62,95 @@ pub struct Item /// Error type for [`Bytes::from_bytes`] #[derive(Debug)] #[derive(derive_more::Display, err_impl::Error)] -pub enum FromBytesError -{ +pub enum FromBytesError { /// Unable to read the digimon name #[display(fmt = "Unable to read the digimon name")] - Name( #[error(source)] util::ReadNullAsciiStringError ), - + Name(#[error(source)] util::ReadNullAsciiStringError), + /// Unable to read the first support effect description #[display(fmt = "Unable to read the first line of the effect description")] - EffectDescriptionFirst( #[error(source)] util::ReadNullAsciiStringError ), - + EffectDescriptionFirst(#[error(source)] util::ReadNullAsciiStringError), + /// Unable to read the second support effect description #[display(fmt = "Unable to read the second line of the effect description")] - EffectDescriptionSecond( #[error(source)] util::ReadNullAsciiStringError ), - + EffectDescriptionSecond(#[error(source)] util::ReadNullAsciiStringError), + /// Unable to read the third support effect description #[display(fmt = "Unable to read the third line of the effect description")] - EffectDescriptionThird( #[error(source)] util::ReadNullAsciiStringError ), - + EffectDescriptionThird(#[error(source)] util::ReadNullAsciiStringError), + /// Unable to read the fourth support effect description #[display(fmt = "Unable to read the fourth line of the effect description")] - EffectDescriptionFourth( #[error(source)] util::ReadNullAsciiStringError ), - + EffectDescriptionFourth(#[error(source)] util::ReadNullAsciiStringError), + /// An unknown effect arrow color was found #[display(fmt = "Unknown effect arrow color found")] - ArrowColor( #[error(source)] property::arrow_color::FromBytesError ), - + ArrowColor(#[error(source)] property::arrow_color::FromBytesError), + /// Unable to read the first effect condition #[display(fmt = "Unable to read the first effect condition")] - EffectConditionFirst( #[error(source)] property::effect_condition::FromBytesError ), - + EffectConditionFirst(#[error(source)] property::effect_condition::FromBytesError), + /// Unable to read the second effect condition #[display(fmt = "Unable to read the second effect condition")] - EffectConditionSecond( #[error(source)] property::effect_condition::FromBytesError ), - + EffectConditionSecond(#[error(source)] property::effect_condition::FromBytesError), + /// Unable to read the first effect #[display(fmt = "Unable to read the first effect")] - EffectFirst( #[error(source)] property::effect::FromBytesError ), - + EffectFirst(#[error(source)] property::effect::FromBytesError), + /// Unable to read the second effect #[display(fmt = "Unable to read the second effect")] - EffectSecond( #[error(source)] property::effect::FromBytesError ), - + EffectSecond(#[error(source)] property::effect::FromBytesError), + /// Unable to read the third effect #[display(fmt = "Unable to read the third effect")] - EffectThird( #[error(source)] property::effect::FromBytesError ), + EffectThird(#[error(source)] property::effect::FromBytesError), } /// Error type for [`Bytes::to_bytes`] #[derive(Debug)] #[derive(derive_more::Display, err_impl::Error)] -pub enum ToBytesError -{ +pub enum ToBytesError { /// Unable to write the digimon name #[display(fmt = "Unable to write the digimon name")] - Name( #[error(source)] util::WriteNullAsciiStringError ), - + Name(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the first support effect description #[display(fmt = "Unable to write the first line of the effect description")] - EffectDescriptionFirst( #[error(source)] util::WriteNullAsciiStringError ), - + EffectDescriptionFirst(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the second support effect description #[display(fmt = "Unable to write the second line of the effect description")] - EffectDescriptionSecond( #[error(source)] util::WriteNullAsciiStringError ), - + EffectDescriptionSecond(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the third support effect description #[display(fmt = "Unable to write the third line of the effect description")] - EffectDescriptionThird( #[error(source)] util::WriteNullAsciiStringError ), - + EffectDescriptionThird(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the fourth support effect description #[display(fmt = "Unable to write the fourth line of the effect description")] - EffectDescriptionFourth( #[error(source)] util::WriteNullAsciiStringError ), - + EffectDescriptionFourth(#[error(source)] util::WriteNullAsciiStringError), + /// Unable to write the first effect #[display(fmt = "Unable to write the first effect")] - EffectFirst( #[error(source)] property::effect::ToBytesError ), - + EffectFirst(#[error(source)] property::effect::ToBytesError), + /// Unable to write the second effect #[display(fmt = "Unable to write the second effect")] - EffectSecond( #[error(source)] property::effect::ToBytesError ), - + EffectSecond(#[error(source)] property::effect::ToBytesError), + /// Unable to write the third effect #[display(fmt = "Unable to write the third effect")] - EffectThird( #[error(source)] property::effect::ToBytesError ), + EffectThird(#[error(source)] property::effect::ToBytesError), } - -impl Bytes for Item -{ +impl Bytes for Item { type ByteArray = [u8; 0xde]; - type FromError = FromBytesError; - fn from_bytes(bytes: &Self::ByteArray) -> Result - { + type ToError = ToBytesError; + + fn from_bytes(bytes: &Self::ByteArray) -> Result { // Split bytes let bytes = util::array_split!(bytes, name : [0x15], @@ -177,59 +166,50 @@ impl Bytes for Item effect_description_2: [0x15], effect_description_3: [0x15], ); - + // And return the struct - Ok( Self { - name: util::read_null_ascii_string(bytes.name) - .map_err(FromBytesError::Name)? - .chars().collect(), - + Ok(Self { + name: util::read_null_ascii_string(bytes.name).map_err(FromBytesError::Name)?.chars().collect(), + // Effects effect_conditions: [ - Option::::from_bytes( bytes.condition_first ) - .map_err(FromBytesError::EffectConditionFirst)?, - - Option::::from_bytes( bytes.condition_second ) - .map_err(FromBytesError::EffectConditionSecond)?, + Option::::from_bytes(bytes.condition_first).map_err(FromBytesError::EffectConditionFirst)?, + Option::::from_bytes(bytes.condition_second).map_err(FromBytesError::EffectConditionSecond)?, ], - + effects: [ - Option::::from_bytes( bytes.effect_first ) - .map_err(FromBytesError::EffectFirst)?, - - Option::::from_bytes( bytes.effect_second ) - .map_err(FromBytesError::EffectSecond)?, - - Option::::from_bytes( bytes.effect_third ) - .map_err(FromBytesError::EffectThird)?, + Option::::from_bytes(bytes.effect_first).map_err(FromBytesError::EffectFirst)?, + Option::::from_bytes(bytes.effect_second).map_err(FromBytesError::EffectSecond)?, + Option::::from_bytes(bytes.effect_third).map_err(FromBytesError::EffectThird)?, ], - - effect_arrow_color: Option::::from_bytes(bytes.effect_arrow_color) - .map_err(FromBytesError::ArrowColor)?, - + + effect_arrow_color: Option::::from_bytes(bytes.effect_arrow_color).map_err(FromBytesError::ArrowColor)?, + effect_description: [ - util::read_null_ascii_string( bytes.effect_description_0 ) + util::read_null_ascii_string(bytes.effect_description_0) .map_err(FromBytesError::EffectDescriptionFirst)? - .chars().collect(), - util::read_null_ascii_string( bytes.effect_description_1 ) + .chars() + .collect(), + util::read_null_ascii_string(bytes.effect_description_1) .map_err(FromBytesError::EffectDescriptionSecond)? - .chars().collect(), - util::read_null_ascii_string( bytes.effect_description_2 ) + .chars() + .collect(), + util::read_null_ascii_string(bytes.effect_description_2) .map_err(FromBytesError::EffectDescriptionThird)? - .chars().collect(), - util::read_null_ascii_string( bytes.effect_description_3 ) + .chars() + .collect(), + util::read_null_ascii_string(bytes.effect_description_3) .map_err(FromBytesError::EffectDescriptionFourth)? - .chars().collect(), + .chars() + .collect(), ], - + // Unknown unknown_15: LittleEndian::read_u32(bytes.unknown_15), }) } - - type ToError = ToBytesError; - fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> - { + + fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { // Split bytes let bytes = util::array_split_mut!(bytes, name : [0x15], @@ -245,21 +225,20 @@ impl Bytes for Item effect_description_2: [0x15], effect_description_3: [0x15], ); - + // Name - util::write_null_ascii_string(self.name.as_ref(), bytes.name) - .map_err(ToBytesError::Name)?; - + util::write_null_ascii_string(self.name.as_ref(), bytes.name).map_err(ToBytesError::Name)?; + // Effects - self.effect_conditions[0].to_bytes( bytes.condition_first ).into_ok(); - self.effect_conditions[1].to_bytes( bytes.condition_second ).into_ok(); - - self.effects[0].to_bytes( bytes.effect_first ).map_err(ToBytesError::EffectFirst )?; - self.effects[1].to_bytes( bytes.effect_second ).map_err(ToBytesError::EffectSecond)?; - self.effects[2].to_bytes( bytes.effect_third ).map_err(ToBytesError::EffectThird )?; - + self.effect_conditions[0].to_bytes(bytes.condition_first).into_ok(); + self.effect_conditions[1].to_bytes(bytes.condition_second).into_ok(); + + self.effects[0].to_bytes(bytes.effect_first).map_err(ToBytesError::EffectFirst)?; + self.effects[1].to_bytes(bytes.effect_second).map_err(ToBytesError::EffectSecond)?; + self.effects[2].to_bytes(bytes.effect_third).map_err(ToBytesError::EffectThird)?; + Option::::to_bytes(&self.effect_arrow_color, bytes.effect_arrow_color).into_ok(); - + util::write_null_ascii_string(self.effect_description[0].as_ref(), bytes.effect_description_0) .map_err(ToBytesError::EffectDescriptionFirst)?; util::write_null_ascii_string(self.effect_description[1].as_ref(), bytes.effect_description_1) @@ -268,10 +247,10 @@ impl Bytes for Item .map_err(ToBytesError::EffectDescriptionThird)?; util::write_null_ascii_string(self.effect_description[3].as_ref(), bytes.effect_description_3) .map_err(ToBytesError::EffectDescriptionFourth)?; - + // Unknown LittleEndian::write_u32(bytes.unknown_15, self.unknown_15); - + // Return Ok Ok(()) } diff --git a/src/game/card/property.rs b/src/game/card/property.rs index 9575113..02ebac2 100644 --- a/src/game/card/property.rs +++ b/src/game/card/property.rs @@ -14,7 +14,7 @@ macro_rules! generate_enum_property_mod { // Enum attributes $( #[$enum_attr:meta] )* - + // Enum enum $enum_name:ident { @@ -22,26 +22,26 @@ macro_rules! generate_enum_property_mod $( // Attributes $( #[$enum_variant_attr:meta] )* - + // Variant // Note: Must have no data $enum_variant_name:ident - + // `Display` convertion name ($enum_variant_rename:literal) - + => - + // Variant value $enum_variant_value:literal, )* - + // Error _ => $error_unknown_value_display:literal - + $(,)? } - + // Any further definitions inside the module $( $extra_defs:tt )* } @@ -67,23 +67,23 @@ macro_rules! generate_enum_property_mod $enum_variant_name = $enum_variant_value, )* } - + /// Error type for [`$crate::game::Bytes::from_bytes`] #[derive(Debug)] #[derive(::derive_more::Display, ::err_impl::Error)] pub enum FromBytesError { - + /// Unknown value #[display(fmt = $error_unknown_value_display, "byte")] UnknownValue { byte: u8, } } - + impl $crate::game::Bytes for $enum_name { type ByteArray = u8; - + type FromError = FromBytesError; fn from_bytes(byte: &Self::ByteArray) -> Result { @@ -92,11 +92,11 @@ macro_rules! generate_enum_property_mod $enum_variant_value => Ok( <$enum_name>::$enum_variant_name ), )* - + &byte => Err( Self::FromError::UnknownValue{ byte } ), } } - + type ToError = !; #[allow(unreachable_code, unused_variables)] // For when there are multiple values fn to_bytes(&self, byte: &mut Self::ByteArray) -> Result<(), Self::ToError> @@ -106,11 +106,11 @@ macro_rules! generate_enum_property_mod <$enum_name>::$enum_variant_name => $enum_variant_value, )* }; - + Ok(()) } } - + // Extra definitions $( $extra_defs )* } @@ -120,7 +120,7 @@ macro_rules! generate_enum_property_mod /// Implements [`Bytes`](crate::game::Bytes) for `Option` where `E` /// is the first argument of this macro and an enum. -/// +/// /// This is done by suppling a sentinel value which is read/written as `None`. macro generate_enum_property_option { ( @@ -130,7 +130,7 @@ macro generate_enum_property_option { #[allow(clippy::diverging_sub_expression)] // Errors might be `!` impl $crate::game::Bytes for Option<$enum_name> { type ByteArray = <$enum_name as $crate::game::Bytes>::ByteArray; - + type FromError = <$enum_name as $crate::game::Bytes>::FromError; fn from_bytes(bytes: &Self::ByteArray) -> Result { @@ -139,7 +139,7 @@ macro generate_enum_property_option { _ => Ok( Some( $crate::game::Bytes::from_bytes(bytes)? ) ), } } - + type ToError = <$enum_name as $crate::game::Bytes>::ToError; fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { @@ -147,7 +147,7 @@ macro generate_enum_property_option { Some(value) => $crate::game::Bytes::to_bytes(value, bytes)?, None => *bytes = $sentinel_value, } - + Ok(()) } } @@ -155,7 +155,6 @@ macro generate_enum_property_option { } } - generate_enum_property_mod!( pub mod slot { /// A player's card slots @@ -165,11 +164,11 @@ generate_enum_property_mod!( Dp ("Dp" ) => 1, Online ("Online" ) => 2, Offline("Offline") => 3, - + _ => "Unknown byte 0x{:x} for a slot" } } - + pub mod arrow_color { /// A digimon effect's arrow color enum ArrowColor @@ -177,11 +176,11 @@ generate_enum_property_mod!( Red ("Red" ) => 1, Green("Green") => 2, Blue ("Blue" ) => 3, - + _ => "Unknown byte 0x{:x} for an arrow color" } } - + pub mod attack_type { /// A digimon's attack type enum AttackType @@ -189,11 +188,11 @@ generate_enum_property_mod!( Circle ("Circle" ) => 0, Triangle("Triangle") => 1, Cross ("Cross" ) => 2, - + _ => "Unknown byte 0x{:x} for an attack type" } } - + pub mod card_type { /// A card type enum CardType @@ -201,10 +200,10 @@ generate_enum_property_mod!( Digimon ("Digimon" ) => 0, Item ("Item" ) => 1, Digivolve("Digivolve") => 2, - + _ => "Unknown byte 0x{:x} for a card type" } - + impl CardType { /// Returns the byte size of the corresponding card @@ -220,18 +219,18 @@ generate_enum_property_mod!( } } } - + pub mod player_type { /// A player type enum PlayerType { Opponent("Opponent") => 0, Player ("Player" ) => 1, - + _ => "Unknown byte 0x{:x} for a player type", } } - + pub mod level { /// A digimon's level enum Level @@ -240,11 +239,11 @@ generate_enum_property_mod!( Armor ("Armor" ) => 1, Champion("Champion") => 2, Ultimate("Ultimate") => 3, - + _ => "Unknown byte 0x{:x} for a level", } } - + pub mod speciality { /// A digimon's speciality enum Speciality @@ -254,11 +253,11 @@ generate_enum_property_mod!( Nature ("Nature" ) => 2, Darkness("Darkness") => 3, Rare ("Rare" ) => 4, - + _ => "Unknown byte 0x{:x} for a speciality", } } - + pub mod effect_operation { /// A digimon's support effect operation enum EffectOperation @@ -267,14 +266,14 @@ generate_enum_property_mod!( Subtraction ("Subtraction" ) => 1, Multiplication("Multiplication") => 2, Division ("Division" ) => 3, - + _ => "Unknown byte 0x{:x} for a support effect operation", } } - + pub mod effect_condition_operation { /// A digimon's support condition operation - /// + /// /// # Todo /// These don't seem to be 100% right, the less than property, sometimes does less than number, might be a range check enum EffectConditionOperation @@ -285,39 +284,39 @@ generate_enum_property_mod!( MoreThanNumber ("More than number" ) => 3, DifferentFromNumber("Different from number") => 4, EqualToNumber ("Equal to number" ) => 5, - + _ => "Unknown byte 0x{:x} for a support condition operation", } } - + pub mod cross_move_effect { /// A digimon's cross move effect enum CrossMoveEffect { FirstAttack("Attack first") => 1, - + CircleTo0("Circle to 0" ) => 2, TriangleTo0("Triangle to 0") => 3, CrossTo0("Cross to 0" ) => 4, - + CircleCounter("Circle counter" ) => 5, TriangleCounter("Triangle counter") => 6, CrossCounter("Cross counter" ) => 7, - + Crash ("Crash" ) => 8, EatUpHP("Eat Up HP") => 9, Jamming("Jamming" ) => 10, - + FireFoe3x("Fire Foe x3" ) => 11, IceFoe3x("Ice Foe x3" ) => 12, NatureFoe3x("Nature Foe x3" ) => 13, DarknessFoe3x("Darkness Foe x3") => 14, RareFoe3x("Rare Foe x3" ) => 15, - + _ => "Unknown byte 0x{:x} for a cross move effect", } } - + pub mod digimon_property { /// A digimon's property enum DigimonProperty @@ -336,10 +335,10 @@ generate_enum_property_mod!( OpnAttack ("Opponent attack" ) => 12, OwnLevel ("Own level" ) => 13, OpnLevel ("Opponent level" ) => 14, - + OwnAttackType("Own attack type" ) => 17, OpnAttackType("Opponent attack type") => 18, - + AttackOrder ("Attack order" ) => 20, CardsInOwnHand ("Cards in own hand" ) => 21, CardsInOpnHand ("Cards in opponent hand" ) => 22, @@ -349,7 +348,7 @@ generate_enum_property_mod!( TempSlot ("Temp slot" ) => 26, CardsInOwnOnDeck ("Cards in own online deck" ) => 27, CardsInOpnOnDeck ("Cards in opponent online deck") => 28, - + _ => "Unknown byte 0x{:x} for a digimon property", } } @@ -362,22 +361,22 @@ generate_enum_property_option!( ); // Complex -pub mod moves; // Note: Can't be `move`, as it's a keyword pub mod effect; pub mod effect_condition; +pub mod moves; // Note: Can't be `move`, as it's a keyword // Exports -pub use level::Level; -pub use speciality::Speciality; -pub use cross_move_effect::CrossMoveEffect; -pub use digimon_property::DigimonProperty; -pub use effect_operation::EffectOperation; -pub use effect_condition_operation::EffectConditionOperation; -pub use card_type::CardType; pub use arrow_color::ArrowColor; pub use attack_type::AttackType; -pub use player_type::PlayerType; -pub use slot::Slot; -pub use moves::Move; +pub use card_type::CardType; +pub use cross_move_effect::CrossMoveEffect; +pub use digimon_property::DigimonProperty; pub use effect::Effect; pub use effect_condition::EffectCondition; +pub use effect_condition_operation::EffectConditionOperation; +pub use effect_operation::EffectOperation; +pub use level::Level; +pub use moves::Move; +pub use player_type::PlayerType; +pub use slot::Slot; +pub use speciality::Speciality; diff --git a/src/game/card/property/effect.rs b/src/game/card/property/effect.rs index d3c2c88..b20ef20 100644 --- a/src/game/card/property/effect.rs +++ b/src/game/card/property/effect.rs @@ -1,10 +1,10 @@ //! A digimon's support effect -//! +//! //! This module contains the [`Effect`] struct, which describes a support effect. -//! +//! //! # Layout //! Each support effect has a size of `0x10` bytes, and it's general layout is the following: -//! +//! //! | Offset | Size | Type | Name | Location | Details | //! |--------|------|----------------------|---------------------------|------------------------|--------------------------------------------------------| //! | 0x0 | 0x1 | `bool` | Exists | N/A | If `0`, the effect does not exist | @@ -16,15 +16,12 @@ use byteorder::{ByteOrder, LittleEndian}; // Crate use crate::game::{ - Bytes, - util, - card::property::{ - self, DigimonProperty, EffectOperation, AttackType, PlayerType, Slot - }, + card::property::{self, AttackType, DigimonProperty, EffectOperation, PlayerType, Slot}, + util, Bytes, }; /// A digimon's support effects -/// +/// /// As this type is wildly volatile in which arguments it uses and from where, /// it is an `enum` with struct variants instead of a struct. This simplifices argument /// verification and, from a language perspective, makes more sense as an implementation. @@ -33,10 +30,9 @@ use crate::game::{ #[serde(tag = "type")] // TODO: Move this `allow` to the variant once clippy allows #[allow(clippy::pub_enum_variant_names)] // `Effect` on `VoidOpponentSupportEffect` isn't refering to the enum -pub enum Effect -{ +pub enum Effect { /// Changes a property of either digimon - /// + /// /// # Valid properties /// Only the following properties are valid for this effect: /// - `OwnSpeciality` / `OpnSpeciality` , @@ -46,51 +42,48 @@ pub enum Effect /// - `OwnCrossAttack` / `OpnCrossAttack` , /// - `OwnAttack` / `OpnAttack` , /// - `OwnLevel` / `OpnLevel` , - /// + /// /// # Equation /// This variant uses the following equation /// to calculate the property: - /// + /// /// ` = ( + ) + ( ( + ) )` #[serde(rename = "Change property")] ChangeProperty { property: DigimonProperty, - + a: Option, b: Option, c: Option, - + x: u16, y: u16, - + op: EffectOperation, }, - + /// A player uses an attack type #[serde(rename = "Use attack")] - UseAttack { - player: PlayerType, - attack: AttackType, - }, - + UseAttack { player: PlayerType, attack: AttackType }, + /// Set the temp slot - /// + /// /// # Equation /// This variant uses the following equation /// to calculate the property: - /// + /// /// ` = + ( )` #[serde(rename = "Set temp slot")] SetTempSlot { a: Option, b: Option, c: Option, - + op: EffectOperation, }, - + /// Moves cards from a slot to another - /// + /// /// # Valid moves /// Only the following moves are valid for this effect, for both the player and opponent: /// - `Hand` -> `Offline` @@ -100,33 +93,31 @@ pub enum Effect /// - `Dp` -> `Offline` #[serde(rename = "Move cards")] MoveCards { - player : PlayerType, - source : Slot, + player: PlayerType, + source: Slot, destination: Slot, - + count: u16, }, - + /// Shuffles a player's online deck #[serde(rename = "Shuffle online deck")] - ShuffleOnlineDeck { - player: PlayerType, - }, - + ShuffleOnlineDeck { player: PlayerType }, + /// Voids the opponent's support effect #[serde(rename = "Void opponent support effect")] VoidOpponentSupportEffect, - + /// Voids the opponent's support option effect #[serde(rename = "Void opponent support option effect")] VoidOpponentSupportOptionEffect, - + /// Picks the partner from the online deck and puts it onto the hand #[serde(rename = "Pick partner card")] PickPartnerCard, - + /// Cycles the opponent's attack types - /// + /// /// # Order /// The order is the following: /// - `Circle` -> `Triangle` @@ -134,56 +125,48 @@ pub enum Effect /// - `Cross` -> `Circle` #[serde(rename = "Cycle opponent attack type")] CycleOpponentAttackType, - + /// If the digimon is Ko'd it revives with health #[serde(rename = "Ko'd digimon revives")] - KoDigimonRevives { - health: u16, - }, - + KoDigimonRevives { health: u16 }, + /// A player draws cards #[serde(rename = "Draw cards")] - DrawCards { - player: PlayerType, - count: u16, - }, - + DrawCards { player: PlayerType, count: u16 }, + /// Own attack becomes Eat Up HP #[serde(rename = "Own attack becomes Eat Up HP")] OwnAttackBecomesEatUpHP, - + /// A player attacks first #[serde(rename = "Attack first")] - AttackFirst { - player: PlayerType - }, + AttackFirst { player: PlayerType }, } /// Error type for [`Bytes::from_bytes`] #[derive(Debug)] #[derive(derive_more::Display, err_impl::Error)] -pub enum FromBytesError -{ +pub enum FromBytesError { /// Unknown property for first property argument #[display(fmt = "Unknown property for first property argument")] - FirstProperty( #[error(source)] property::digimon_property::FromBytesError ), - + FirstProperty(#[error(source)] property::digimon_property::FromBytesError), + /// Unknown property for second property argument #[display(fmt = "Unknown property for second property argument")] - SecondProperty( #[error(source)] property::digimon_property::FromBytesError ), - + SecondProperty(#[error(source)] property::digimon_property::FromBytesError), + /// Unknown property for third property argument #[display(fmt = "Unknown property for third property argument")] - ThirdProperty( #[error(source)] property::digimon_property::FromBytesError ), - + ThirdProperty(#[error(source)] property::digimon_property::FromBytesError), + /// Unknown operation argument #[display(fmt = "Unknown operation argument")] - Operation( #[error(source)] property::effect_operation::FromBytesError ), - + Operation(#[error(source)] property::effect_operation::FromBytesError), + /// Unknown attack type for [`Effect::UseAttack`] #[display(fmt = "Unknown attack type")] - UseAttackAttackType( #[error(source)] property::attack_type::FromBytesError ), - + UseAttackAttackType(#[error(source)] property::attack_type::FromBytesError), + /// Unknown effect type #[display(fmt = "Unknown byte for an effect type: {}", "byte")] EffectType { byte: u8 }, @@ -192,28 +175,22 @@ pub enum FromBytesError /// Error type for [`Bytes::from_bytes`] #[derive(Debug)] #[derive(derive_more::Display, err_impl::Error)] -pub enum ToBytesError -{ +pub enum ToBytesError { /// Invalid move [`Effect::MoveCards`] effect #[display(fmt = "Invalid move cards effect ({} => {})", source, destination)] - InvalidMoveCards { - source : Slot, - destination: Slot, - } + InvalidMoveCards { source: Slot, destination: Slot }, } -impl Bytes for Effect -{ +impl Bytes for Effect { type ByteArray = [u8; 0xf]; - type FromError = FromBytesError; - - fn from_bytes(bytes: &Self::ByteArray) -> Result - { + type ToError = ToBytesError; + + fn from_bytes(bytes: &Self::ByteArray) -> Result { // Utility uses - use PlayerType::{Player, Opponent}; - use Slot::{Hand, Online as OnlineDeck, Offline as OfflineDeck, Dp as DpSlot}; - + use PlayerType::{Opponent, Player}; + use Slot::{Dp as DpSlot, Hand, Offline as OfflineDeck, Online as OnlineDeck}; + // Get all byte arrays we need let bytes = util::array_split!(bytes, effect_type: 0x1, @@ -228,102 +205,101 @@ impl Bytes for Effect _unknown_e : 0x1, op : 0x1, ); - + // Else create getters for all arguments - let get_a = || (*bytes.a != 0) - .then(|| DigimonProperty::from_bytes(bytes.a)) - .transpose() - .map_err(FromBytesError::FirstProperty); - let get_b = || (*bytes.b != 0) - .then(|| DigimonProperty::from_bytes(bytes.b)) - .transpose() - .map_err(FromBytesError::SecondProperty); - let get_c = || (*bytes.c != 0) - .then(|| DigimonProperty::from_bytes(bytes.c)) - .transpose() - .map_err(FromBytesError::ThirdProperty); - + let get_a = || { + (*bytes.a != 0) + .then(|| DigimonProperty::from_bytes(bytes.a)) + .transpose() + .map_err(FromBytesError::FirstProperty) + }; + let get_b = || { + (*bytes.b != 0) + .then(|| DigimonProperty::from_bytes(bytes.b)) + .transpose() + .map_err(FromBytesError::SecondProperty) + }; + let get_c = || { + (*bytes.c != 0) + .then(|| DigimonProperty::from_bytes(bytes.c)) + .transpose() + .map_err(FromBytesError::ThirdProperty) + }; + // The number arguments - let x = LittleEndian::read_u16( bytes.x ); - let y = LittleEndian::read_u16( bytes.y ); - + let x = LittleEndian::read_u16(bytes.x); + let y = LittleEndian::read_u16(bytes.y); + // Attack type // Lower byte of `x` - let get_attack_type = || AttackType::from_bytes( &x.to_le_bytes()[0] ) - .map_err(FromBytesError::UseAttackAttackType); - + let get_attack_type = || AttackType::from_bytes(&x.to_le_bytes()[0]).map_err(FromBytesError::UseAttackAttackType); + // The operation argument - let get_op = || EffectOperation::from_bytes( bytes.op ) - .map_err(FromBytesError::Operation); - + let get_op = || EffectOperation::from_bytes(bytes.op).map_err(FromBytesError::Operation); + // And check what the effect type is - let effect = match bytes.effect_type - { + #[rustfmt::skip] + let effect = match bytes.effect_type { 0..=13 => Self::ChangeProperty { // Note: unwrapping is fine here because we know that `effect_type_byte+1` is between 1 and 14 inclusive property: DigimonProperty::from_bytes( &(bytes.effect_type+1) ) .expect("Unable to get digimon property from bytes"), a: get_a()?, b: get_b()?, c: get_c()?, x, y, op: get_op()?, }, - - + 16 => Self::UseAttack{ player: Player , attack: get_attack_type()? }, 17 => Self::UseAttack{ player: Opponent, attack: get_attack_type()? }, - - + 25 => Self::SetTempSlot{ a: get_a()?, b: get_b()?, c: get_c()?, op: get_op()? }, - + 26 => Self::MoveCards{ player: Player , source: Hand, destination: OfflineDeck, count: y }, 27 => Self::MoveCards{ player: Opponent, source: Hand, destination: OfflineDeck, count: y }, - + 30 => Self::MoveCards{ player: Player , source: Hand, destination: OnlineDeck, count: y }, 31 => Self::MoveCards{ player: Opponent, source: Hand, destination: OnlineDeck, count: y }, - + 32 => Self::MoveCards{ player: Player , source: OnlineDeck, destination: OfflineDeck, count: y }, 33 => Self::MoveCards{ player: Opponent, source: OnlineDeck, destination: OfflineDeck, count: y }, - + 34 => Self::MoveCards{ player: Player , source: OfflineDeck, destination: OnlineDeck, count: y }, 35 => Self::MoveCards{ player: Opponent, source: OfflineDeck, destination: OnlineDeck, count: y }, - + 36 => Self::MoveCards{ player: Player , source: DpSlot, destination: OfflineDeck, count: y }, 37 => Self::MoveCards{ player: Opponent, source: DpSlot, destination: OfflineDeck, count: y }, - - + 42 => Self::ShuffleOnlineDeck{ player: Player }, 43 => Self::ShuffleOnlineDeck{ player: Opponent }, - + 44 => Self::VoidOpponentSupportEffect, 45 => Self::VoidOpponentSupportOptionEffect, - + 46 => Self::PickPartnerCard, - + 47 => Self::CycleOpponentAttackType, - + 48 => Self::KoDigimonRevives{ health: y }, - + 49 => Self::DrawCards{ player: Player , count: y }, 50 => Self::DrawCards{ player: Opponent, count: y }, - + 51 => Self::OwnAttackBecomesEatUpHP, - + 52 => Self::AttackFirst{ player: Player }, 53 => Self::AttackFirst{ player: Opponent }, - + &byte => return Err( FromBytesError::EffectType { byte } ), }; - + // And return the effect - Ok( effect ) + Ok(effect) } - - type ToError = ToBytesError; + #[allow(clippy::too_many_lines)] // It's a single match, we can't really split it - fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> - { + fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { // Utility uses - use PlayerType::{Player, Opponent}; - use Slot::{Hand, Online as OnlineDeck, Offline as OfflineDeck, Dp as DpSlot}; - + use PlayerType::{Opponent, Player}; + use Slot::{Dp as DpSlot, Hand, Offline as OfflineDeck, Online as OnlineDeck}; + // Get all byte arrays we need let bytes = util::array_split_mut!(bytes, effect_type: 0x1, @@ -338,37 +314,43 @@ impl Bytes for Effect _unknown_e : 0x1, op : 0x1, ); - + // Setters let bytes_a = bytes.a; let bytes_b = bytes.b; let bytes_c = bytes.c; - let mut set_a = |a: &Option| if let Some(a) = a { - a.to_bytes(bytes_a).into_ok(); - } else { - *bytes_a = 0; + let mut set_a = |a: &Option| { + if let Some(a) = a { + a.to_bytes(bytes_a).into_ok(); + } else { + *bytes_a = 0; + } }; - let mut set_b = |b: &Option| if let Some(b) = b { - b.to_bytes(bytes_b).into_ok(); - } else { - *bytes_b = 0; + let mut set_b = |b: &Option| { + if let Some(b) = b { + b.to_bytes(bytes_b).into_ok(); + } else { + *bytes_b = 0; + } }; - let mut set_c = |c: &Option| if let Some(c) = c { - c.to_bytes(bytes_c).into_ok(); - } else { - *bytes_c = 0; + let mut set_c = |c: &Option| { + if let Some(c) = c { + c.to_bytes(bytes_c).into_ok(); + } else { + *bytes_c = 0; + } }; let bytes_attack_type = &mut bytes.x[0]; - let mut set_attack_type = |attack: &AttackType| attack.to_bytes( bytes_attack_type ).into_ok(); - + let mut set_attack_type = |attack: &AttackType| attack.to_bytes(bytes_attack_type).into_ok(); + // Check our variant and fill `bytes` with info - #[allow(clippy::unneeded_field_pattern)] // Placeholder + #[rustfmt::skip] match self { Self::ChangeProperty { property, a, b, c, x, y, op } => { // Write the property minus one property.to_bytes(bytes.effect_type).into_ok(); *bytes.effect_type -= 1; - + // Write all arguments set_a(a); set_b(b); @@ -377,7 +359,7 @@ impl Bytes for Effect LittleEndian::write_u16(bytes.y, *y); op.to_bytes(bytes.op).into_ok(); }, - + Self::UseAttack { player, attack } => { *bytes.effect_type = match player { Player => 16, @@ -385,7 +367,7 @@ impl Bytes for Effect }; set_attack_type(attack); }, - + Self::SetTempSlot { a, b, c, op } => { *bytes.effect_type = 25; set_a(a); @@ -393,45 +375,45 @@ impl Bytes for Effect set_c(c); op.to_bytes(bytes.op).into_ok(); } - + Self::MoveCards { player, source, destination, count } => { *bytes.effect_type = match (player, source, destination) { (Player , Hand, OfflineDeck) => 26, (Opponent, Hand, OfflineDeck) => 27, - + (Player , Hand, OnlineDeck) => 30, (Opponent, Hand, OnlineDeck) => 31, - + (Player , OnlineDeck, OfflineDeck) => 32, (Opponent, OnlineDeck, OfflineDeck) => 33, - + (Player , OfflineDeck, OnlineDeck) => 34, (Opponent, OfflineDeck, OnlineDeck) => 35, - + (Player , DpSlot, OfflineDeck) => 36, (Opponent, DpSlot, OfflineDeck) => 37, - + (_, &source, &destination) => return Err( ToBytesError::InvalidMoveCards { source, destination } ), }; LittleEndian::write_u16(bytes.y, *count); } - + Self::ShuffleOnlineDeck { player } => *bytes.effect_type = match player { Player => 42, Opponent => 43, }, - + Self::VoidOpponentSupportEffect => *bytes.effect_type = 42, Self::VoidOpponentSupportOptionEffect => *bytes.effect_type = 43, - + Self::PickPartnerCard => *bytes.effect_type = 46, - + Self::CycleOpponentAttackType => *bytes.effect_type = 47, - + Self::KoDigimonRevives { health } => { LittleEndian::write_u16(bytes.y, *health); }, - + Self::DrawCards { player, count } => { *bytes.effect_type = match player { Player => 49, @@ -439,62 +421,58 @@ impl Bytes for Effect }; LittleEndian::write_u16(bytes.y, *count); } - + Self::OwnAttackBecomesEatUpHP => *bytes.effect_type = 51, - + Self::AttackFirst { player } => *bytes.effect_type = match player { Player => 52, Opponent => 53, }, } - + // And return Ok Ok(()) } } -impl Bytes for Option -{ +impl Bytes for Option { type ByteArray = [u8; 0x10]; - type FromError = FromBytesError; - + type ToError = ToBytesError; + // `bytes` should include the `exists` byte - fn from_bytes(bytes: &Self::ByteArray) -> Result - { + fn from_bytes(bytes: &Self::ByteArray) -> Result { let bytes = util::array_split!(bytes, exists : 0x1, effect : [0xf], ); - + // If the exists byte is 0, return None if *bytes.exists == 0 { return Ok(None); } - + // Else get the effect - Ok( Some( Effect::from_bytes(bytes.effect)? ) ) + Ok(Some(Effect::from_bytes(bytes.effect)?)) } - - type ToError = ToBytesError; - fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> - { + + fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { let bytes = util::array_split_mut!(bytes, exists: 0x1, effect: [0xf], ); - + // Check if we exist match self { Some(effect) => { *bytes.exists = 1; effect.to_bytes(bytes.effect)?; - } + }, None => { *bytes.exists = 0; - } + }, }; - + // An return Ok Ok(()) } diff --git a/src/game/card/property/effect_condition.rs b/src/game/card/property/effect_condition.rs index c90da39..fc8f782 100644 --- a/src/game/card/property/effect_condition.rs +++ b/src/game/card/property/effect_condition.rs @@ -1,10 +1,10 @@ //! A digimon's effect condition -//! +//! //! This module contains the [`EffectCondition`] struct, which describes a condition for an effect. -//! +//! //! # Layout //! Each support condition has a size of `0x20` bytes, and it's layout is the following: -//! +//! //! | Offset | Size | Type | Name | Location | Details | //! |--------|------|------------------------------|---------------------------|--------------- |------------------------------------------------------------------------------------| //! | 0x0 | 0x1 | `bool` | Misfire | `misfire` | If the condition throws a misfire when false | @@ -23,37 +23,33 @@ use byteorder::{ByteOrder, LittleEndian}; // Crate use crate::game::{ - Bytes, - card::property::{ - self, DigimonProperty, EffectConditionOperation - }, - util, + card::property::{self, DigimonProperty, EffectConditionOperation}, + util, Bytes, }; /// A digimon's support effect condition #[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)] #[derive(serde::Serialize, serde::Deserialize)] -pub struct EffectCondition -{ +pub struct EffectCondition { /// If the effect should throw a misfire when false misfire: bool, - + /// The property to compare to property_cmp: DigimonProperty, - + /// The property argument arg_property: Option, - + /// The number argument arg_num: u16, - + /// The operation operation: EffectConditionOperation, - + // Unknown - unknown_1 : u8, - unknown_3 : [u8; 0x5], - unknown_9 : [u8; 0xb], + unknown_1: u8, + unknown_3: [u8; 0x5], + unknown_9: [u8; 0xb], unknown_16: [u8; 0x4], unknown_1b: [u8; 0x5], } @@ -61,28 +57,26 @@ pub struct EffectCondition /// The error type thrown by `FromBytes` #[derive(Debug)] #[derive(derive_more::Display, err_impl::Error)] -pub enum FromBytesError -{ +pub enum FromBytesError { /// Unable to read the condition #[display(fmt = "Unable to read the effect condition")] - Condition( #[error(source)] property::digimon_property::FromBytesError ), - + Condition(#[error(source)] property::digimon_property::FromBytesError), + /// Unable to read a property argument #[display(fmt = "Unable to read the property argument")] - PropertyArgument( #[error(source)] property::digimon_property::FromBytesError ), - + PropertyArgument(#[error(source)] property::digimon_property::FromBytesError), + /// Unable to read the effect operation #[display(fmt = "Unable to read the effect operation")] - Operation( #[error(source)] property::effect_condition_operation::FromBytesError ), + Operation(#[error(source)] property::effect_condition_operation::FromBytesError), } -impl Bytes for EffectCondition -{ +impl Bytes for EffectCondition { type ByteArray = [u8; 0x20]; - type FromError = FromBytesError; - fn from_bytes(bytes: &Self::ByteArray) -> Result - { + type ToError = !; + + fn from_bytes(bytes: &Self::ByteArray) -> Result { let bytes = util::array_split!(bytes, misfire : 0x1, unknown_1 : 0x1, @@ -95,31 +89,26 @@ impl Bytes for EffectCondition operation : 1, unknown_1b : [0x5], ); - - Ok( Self { + + Ok(Self { misfire: (*bytes.misfire != 0), - property_cmp: DigimonProperty::from_bytes( bytes.property_cmp ) - .map_err(FromBytesError::Condition)?, - - arg_property: Option::::from_bytes( bytes.arg_property ) - .map_err(FromBytesError::PropertyArgument)?, - - arg_num: LittleEndian::read_u16( bytes.arg_num ), - - operation: EffectConditionOperation::from_bytes( bytes.operation ) - .map_err(FromBytesError::Operation)?, - - unknown_1 : *bytes.unknown_1, - unknown_3 : *bytes.unknown_3, - unknown_9 : *bytes.unknown_9, + property_cmp: DigimonProperty::from_bytes(bytes.property_cmp).map_err(FromBytesError::Condition)?, + + arg_property: Option::::from_bytes(bytes.arg_property).map_err(FromBytesError::PropertyArgument)?, + + arg_num: LittleEndian::read_u16(bytes.arg_num), + + operation: EffectConditionOperation::from_bytes(bytes.operation).map_err(FromBytesError::Operation)?, + + unknown_1: *bytes.unknown_1, + unknown_3: *bytes.unknown_3, + unknown_9: *bytes.unknown_9, unknown_16: *bytes.unknown_16, unknown_1b: *bytes.unknown_1b, }) } - - type ToError = !; - fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> - { + + fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { let bytes = util::array_split_mut!(bytes, misfire : 0x1, unknown_1 : 0x1, @@ -132,57 +121,53 @@ impl Bytes for EffectCondition operation : 1, unknown_1b : [0x5], ); - + // Misfire *bytes.misfire = if self.misfire { 1 } else { 0 }; - + // Condition - self.property_cmp.to_bytes( bytes.property_cmp ).into_ok(); - + self.property_cmp.to_bytes(bytes.property_cmp).into_ok(); + // Arguments - self.arg_property.to_bytes( bytes.arg_property ).into_ok(); + self.arg_property.to_bytes(bytes.arg_property).into_ok(); LittleEndian::write_u16(bytes.arg_num, self.arg_num); self.operation.to_bytes(bytes.operation).into_ok(); - + // Unknowns - *bytes.unknown_1 = self.unknown_1; - *bytes.unknown_3 = self.unknown_3; - *bytes.unknown_9 = self.unknown_9; + *bytes.unknown_1 = self.unknown_1; + *bytes.unknown_3 = self.unknown_3; + *bytes.unknown_9 = self.unknown_9; *bytes.unknown_16 = self.unknown_16; *bytes.unknown_1b = self.unknown_1b; - - + // And return OK Ok(()) } } -impl Bytes for Option -{ +impl Bytes for Option { type ByteArray = [u8; 0x20]; - type FromError = FromBytesError; - fn from_bytes(bytes: &Self::ByteArray) -> Result - { + type ToError = ::ToError; + + fn from_bytes(bytes: &Self::ByteArray) -> Result { // If we have no property comparation, return None if bytes[0x2] == 0 { return Ok(None); } - + // Else build the type - Ok( Some( EffectCondition::from_bytes(bytes)? )) + Ok(Some(EffectCondition::from_bytes(bytes)?)) } - - type ToError = ::ToError; + #[allow(clippy::diverging_sub_expression)] // For if we ever change `EffectCondition::ToError` - fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> - { + fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { // Check if we exist match self { Some(cond) => cond.to_bytes(bytes)?, - None => bytes[0x2] = 0, + None => bytes[0x2] = 0, }; - + // And return Ok Ok(()) } diff --git a/src/game/card/property/moves.rs b/src/game/card/property/moves.rs index 1744792..7527bdb 100644 --- a/src/game/card/property/moves.rs +++ b/src/game/card/property/moves.rs @@ -1,10 +1,10 @@ //! A digimon's move -//! +//! //! This module contains the [`Move`] struct, which describes a generic move over the triangle, circle or cross. -//! +//! //! # Layout //! Each move has a size of `0x1c` bytes, and it's layout is the following: -//! +//! //! | Offset | Size | Type | Name | Location | Details | //! |--------|------|----------------------|---------------------------|------------------------|-----------------------------------| //! | 0x0 | 0x2 | `u16` | Power | `power` | | @@ -20,79 +20,70 @@ use crate::game::{util, Bytes}; /// A digimon's move #[derive(PartialEq, Eq, Clone, Hash, Debug)] #[derive(serde::Serialize, serde::Deserialize)] -pub struct Move -{ +pub struct Move { /// The move's name name: ascii::AsciiString, - + /// The move's power power: u16, - + /// The unknown data unknown: u32, } /// Error type for [`Bytes::from_bytes`] #[derive(Debug, derive_more::Display, err_impl::Error)] -pub enum FromBytesError -{ +pub enum FromBytesError { /// Unable to read the move name #[display(fmt = "Unable to read the move name")] - Name( #[error(source)] util::ReadNullAsciiStringError ), + Name(#[error(source)] util::ReadNullAsciiStringError), } /// Error type for [`Bytes::to_bytes`] #[derive(Debug, derive_more::Display, err_impl::Error)] -pub enum ToBytesError -{ +pub enum ToBytesError { /// Unable to write the move name #[display(fmt = "Unable to write the move name")] - Name( #[error(source)] util::WriteNullAsciiStringError ), + Name(#[error(source)] util::WriteNullAsciiStringError), } // Bytes -impl Bytes for Move -{ +impl Bytes for Move { type ByteArray = [u8; 0x1c]; - type FromError = FromBytesError; - fn from_bytes(bytes: &Self::ByteArray) -> Result - { + type ToError = ToBytesError; + + fn from_bytes(bytes: &Self::ByteArray) -> Result { // Get all byte arrays we need let bytes = util::array_split!(bytes, power : [0x2], unknown: [0x4], name : [0x16], ); - + // Return the move - Ok( Self { - name : util::read_null_ascii_string( bytes.name ) - .map_err(FromBytesError::Name)? - .chars().collect(), - power : LittleEndian::read_u16( bytes.power ), - unknown: LittleEndian::read_u32( bytes.unknown ), + Ok(Self { + name: util::read_null_ascii_string(bytes.name).map_err(FromBytesError::Name)?.chars().collect(), + power: LittleEndian::read_u16(bytes.power), + unknown: LittleEndian::read_u32(bytes.unknown), }) } - - type ToError = ToBytesError; - fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> - { + + fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> { // Get all byte arrays we need let bytes = util::array_split_mut!(bytes, power : [0x2], unknown: [0x4], name : [0x16], ); - + // Write the name - util::write_null_ascii_string(self.name.as_ref(), bytes.name) - .map_err(ToBytesError::Name)?; - + util::write_null_ascii_string(self.name.as_ref(), bytes.name).map_err(ToBytesError::Name)?; + // Then write the power and the unknown - LittleEndian::write_u16(bytes.power , self.power); + LittleEndian::write_u16(bytes.power, self.power); LittleEndian::write_u32(bytes.unknown, self.unknown); - + // And return Ok Ok(()) } diff --git a/src/game/card/table.rs b/src/game/card/table.rs index 04474f3..a4ad2ff 100644 --- a/src/game/card/table.rs +++ b/src/game/card/table.rs @@ -1,5 +1,5 @@ //! 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. @@ -7,7 +7,7 @@ //! # 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)) | @@ -15,67 +15,68 @@ //! | 0x6 | 0x1 | u8 | Number of items | | //! | 0x7 | 0x1 | u8 | Number of digivolves | | //! | 0x8 | variable | \[`CardEntry`\] | Card Entries | A contigous 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]) | -// Io Traits -use std::io::{Read, Write, Seek}; +// Std +use std::io::{Read, Seek, Write}; // byteorder use byteorder::{ByteOrder, LittleEndian}; -// Dcb +// Crate use crate::{ - io::{address::Data, GameFile}, game::{ card::{ self, - Digimon, Item, Digivolve, property::{self, CardType}, + Digimon, Digivolve, Item, }, - Bytes, - util, - } + util, Bytes, + }, + io::{address::Data, GameFile}, }; /// The table storing all cards #[derive(Debug)] #[derive(serde::Serialize, serde::Deserialize)] pub struct Table { - pub digimons : Vec, - pub items : Vec, + /// All digimons in this table + pub digimons: Vec, + + /// All items in this table + pub items: Vec, + + /// All digivolves in this table pub digivolves: Vec, } // Constants impl Table { - /// The start address of the card table - pub const START_ADDRESS: Data = Data::from_u64(0x216d000); - /// Table header size pub const HEADER_BYTE_SIZE: usize = 0x8; - + /// The magic in the table header + 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; - - /// The magic in the table header - pub const HEADER_MAGIC: u32 = 0x44434130; + /// The start address of the card table + pub const START_ADDRESS: Data = Data::from_u64(0x216d000); } // Utils @@ -83,33 +84,29 @@ impl Table { /// Returns how many cards are in this table #[must_use] pub fn card_count(&self) -> usize { - self.digimons .len() + - self.items .len() + - self.digivolves.len() + self.digimons.len() + self.items.len() + self.digivolves.len() } } - /// Error type for [`Table::deserialize`] #[derive(Debug)] #[derive(derive_more::Display, err_impl::Error)] pub enum DeserializeError { /// Unable to seek game file #[display(fmt = "Unable to seek game file to card table")] - Seek( #[error(source)] std::io::Error ), - + Seek(#[error(source)] std::io::Error), + /// Unable to read table header #[display(fmt = "Unable to read table header")] - ReadHeader( #[error(source)] std::io::Error ), - + ReadHeader(#[error(source)] std::io::Error), + /// The magic of the table was wrong #[display(fmt = "Found wrong table header magic (expected {:x}, found {:x})", Table::HEADER_MAGIC, "magic")] - HeaderMagic { - magic: u32, - }, - + HeaderMagic { magic: u32 }, + /// There were too many cards - #[display(fmt = "Too many cards in table ({} digimon, {} item, {} digivolve, {} / {} bytes max)", + #[display( + fmt = "Too many cards in table ({} digimon, {} item, {} digivolve, {} / {} bytes max)", "digimon_cards", "item_cards", "digivolve_cards", @@ -119,11 +116,11 @@ pub enum DeserializeError { Table::MAX_BYTE_SIZE )] TooManyCards { - digimon_cards: usize, - item_cards: usize, + digimon_cards: usize, + item_cards: usize, digivolve_cards: usize, }, - + /// Unable to read card header #[display(fmt = "Unable to read card header for card id {}", id)] ReadCardHeader { @@ -131,7 +128,7 @@ pub enum DeserializeError { #[error(source)] err: std::io::Error, }, - + /// An unknown card type was found #[display(fmt = "Unknown card type for card id {}", id)] UnknownCardType { @@ -139,7 +136,7 @@ pub enum DeserializeError { #[error(source)] err: property::card_type::FromBytesError, }, - + /// Unable to read card footer #[display(fmt = "Unable to read card footer for card id {}", id)] ReadCardFooter { @@ -155,14 +152,15 @@ pub enum DeserializeError { pub enum SerializeError { /// Unable to seek game file #[display(fmt = "Unable to seek game file to card table")] - Seek( #[error(source)] std::io::Error ), - + Seek(#[error(source)] std::io::Error), + /// Unable to write table header #[display(fmt = "Unable to write table header")] - WriteHeader( #[error(source)] std::io::Error ), - + WriteHeader(#[error(source)] std::io::Error), + /// There were too many cards - #[display(fmt = "Too many cards in table ({} digimon, {} item, {} digivolve, {} / {} bytes max)", + #[display( + fmt = "Too many cards in table ({} digimon, {} item, {} digivolve, {} / {} bytes max)", "digimon_cards", "item_cards", "digivolve_cards", @@ -172,11 +170,11 @@ pub enum SerializeError { Table::MAX_BYTE_SIZE )] TooManyCards { - digimon_cards: usize, - item_cards: usize, + digimon_cards: usize, + item_cards: usize, digivolve_cards: usize, }, - + /// Unable to write a card #[display(fmt = "Unable to write card with id {}", id)] WriteCard { @@ -184,7 +182,7 @@ pub enum SerializeError { #[error(source)] err: std::io::Error, }, - + /// Unable to serialize a digimon card #[display(fmt = "Unable to serialize digimon card with id {}", id)] DigimonCard { @@ -192,7 +190,7 @@ pub enum SerializeError { #[error(source)] err: card::digimon::ToBytesError, }, - + /// Unable to write an item card #[display(fmt = "Unable to write item card with id {}", id)] ItemCard { @@ -200,7 +198,7 @@ pub enum SerializeError { #[error(source)] err: card::item::ToBytesError, }, - + /// Unable to write a digivolve card #[display(fmt = "Unable to write digivolve card with id {}", id)] DigivolveCard { @@ -214,93 +212,87 @@ impl Table { /// Deserializes the card table from a game file pub fn deserialize(file: &mut GameFile) -> Result { // Seek to the table - file.seek( std::io::SeekFrom::Start( u64::from( Self::START_ADDRESS ) ) ) + file.seek(std::io::SeekFrom::Start(u64::from(Self::START_ADDRESS))) .map_err(DeserializeError::Seek)?; - + // Read header let mut header_bytes = [0u8; 0x8]; - file.read_exact(&mut header_bytes) - .map_err(DeserializeError::ReadHeader)?; - + 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 } ); } - + let magic = LittleEndian::read_u32(&header_bytes[0x0..0x4]); + if magic != Self::HEADER_MAGIC { + return Err(DeserializeError::HeaderMagic { magic }); + } + // Then check the number of each card - let digimon_cards = LittleEndian::read_u16( &header_bytes[0x4..0x6] ) as usize; - let item_cards = header_bytes[0x6] as usize; + let digimon_cards = LittleEndian::read_u16(&header_bytes[0x4..0x6]) as usize; + let item_cards = header_bytes[0x6] as usize; let digivolve_cards = header_bytes[0x7] as usize; log::debug!("[Table Header] Found {} digimon cards", digimon_cards); log::debug!("[Table Header] Found {} item cards", item_cards); log::debug!("[Table Header] Found {} digivolve cards", digivolve_cards); - + // 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); + 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::debug!("[Table Header] {} total bytes of cards", table_size); - if table_size > Self::MAX_BYTE_SIZE { return Err( DeserializeError::TooManyCards { - digimon_cards, - item_cards, - digivolve_cards, - } ); } - + 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); + let mut digimons = Vec::with_capacity(digimon_cards); + let mut items = Vec::with_capacity(item_cards); let mut digivolves = Vec::with_capacity(digivolve_cards); - + // Read until the table is over - for cur_id in 0..cards_len - { + for cur_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 })?; - + // 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_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::debug!("[Card Header] Found {} with id {}", card_type, card_id); - + // 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); } // And create / push the card - match card_type - { + match card_type { CardType::Digimon => { - let mut digimon_bytes = [0; std::mem::size_of::< ::ByteArray>()]; - file.read_exact(&mut digimon_bytes) - .expect("Unable to read digimon bytes"); - let digimon = Digimon::from_bytes(&digimon_bytes) - .expect("Unable to parse digimon bytes"); + let mut digimon_bytes = [0; std::mem::size_of::<::ByteArray>()]; + file.read_exact(&mut digimon_bytes).expect("Unable to read digimon bytes"); + let digimon = Digimon::from_bytes(&digimon_bytes).expect("Unable to parse digimon bytes"); digimons.push(digimon); }, CardType::Item => { - let mut item_bytes = [0; std::mem::size_of::< ::ByteArray>()]; - file.read_exact(&mut item_bytes) - .expect("Unable to read item bytes"); - let item = Item::from_bytes(&item_bytes) - .expect("Unable to parse item bytes"); + let mut item_bytes = [0; std::mem::size_of::<::ByteArray>()]; + file.read_exact(&mut item_bytes).expect("Unable to read item bytes"); + let item = Item::from_bytes(&item_bytes).expect("Unable to parse item bytes"); items.push(item); }, CardType::Digivolve => { - let mut digivolve_bytes = [0; std::mem::size_of::< ::ByteArray>()]; - file.read_exact(&mut digivolve_bytes) - .expect("Unable to read digivolve bytes"); - let digivolve = Digivolve::from_bytes(&digivolve_bytes) - .expect("Unable to parse digivolve bytes"); + let mut digivolve_bytes = [0; std::mem::size_of::<::ByteArray>()]; + file.read_exact(&mut digivolve_bytes).expect("Unable to read digivolve bytes"); + let digivolve = Digivolve::from_bytes(&digivolve_bytes).expect("Unable to parse digivolve bytes"); digivolves.push(digivolve); }, } - + // Skip null terminator let mut null_terminator = [0; 1]; file.read_exact(&mut null_terminator) @@ -309,64 +301,61 @@ impl Table { log::warn!("Card with id {}'s null terminator was {} instead of 0", cur_id, null_terminator[0]); } } - + // Return the table - Ok( Self { - digimons, - items, - digivolves, - }) + Ok(Self { digimons, items, digivolves }) } - + pub fn serialize(&self, file: &mut GameFile) -> 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.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); + // 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(), - } ); } - + 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( u64::from( Self::START_ADDRESS ) ) ) + file.seek(std::io::SeekFrom::Start(u64::from(Self::START_ADDRESS))) .map_err(SerializeError::Seek)?; - + // Write header let mut header_bytes = [0u8; 0x8]; { let bytes = util::array_split_mut!(&mut header_bytes, magic: [0x4], - - digimons_len: [0x2], - items_len: 1, + + digimons_len: [0x2], + items_len: 1, digivolves_len: 1, ); - + // Set magic LittleEndian::write_u32(bytes.magic, Self::HEADER_MAGIC); - + // Write card lens use std::convert::TryInto; - log::debug!("[Table Header] Writing {} digimon cards" , self.digimons .len()); - log::debug!("[Table Header] Writing {} item cards" , self.items .len()); + log::debug!("[Table Header] Writing {} digimon cards", self.digimons.len()); + log::debug!("[Table Header] Writing {} item cards", self.items.len()); log::debug!("[Table Header] Writing {} digivolve cards", self.digivolves.len()); - LittleEndian::write_u16( bytes.digimons_len, self.digimons.len().try_into().expect("Too many digimons")); - *bytes. items_len = self.items .len().try_into().expect("Too many items"); + LittleEndian::write_u16(bytes.digimons_len, self.digimons.len().try_into().expect("Too many digimons")); + *bytes.items_len = self.items.len().try_into().expect("Too many items"); *bytes.digivolves_len = self.digivolves.len().try_into().expect("Too many digivolves"); } - - file.write_all(&header_bytes) - .map_err(SerializeError::WriteHeader)?; - + + 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; - + // Card bytes let mut card_bytes = [0; 0x3 + CardType::Digimon.byte_size() + 0x1]; let bytes = util::array_split_mut!(&mut card_bytes, @@ -375,26 +364,26 @@ impl Table { digimon : [CardType::Digimon.byte_size()], footer : 1, ); - + // Write the header - LittleEndian::write_u16( bytes.header_id, cur_id as u16 ); - CardType::Digimon.to_bytes( bytes.header_type )?; - + LittleEndian::write_u16(bytes.header_id, cur_id as u16); + CardType::Digimon.to_bytes(bytes.header_type)?; + // Write the digimon - digimon.to_bytes( bytes.digimon ) + digimon + .to_bytes(bytes.digimon) .map_err(|err| SerializeError::DigimonCard { id: cur_id, err })?; - + // Write the footer *bytes.footer = 0; - + log::debug!("[Card Header] Writing Digimon with id {}", cur_id); - file.write_all(&card_bytes) - .map_err(|err| SerializeError::WriteCard { id: cur_id, err })?; + file.write_all(&card_bytes).map_err(|err| SerializeError::WriteCard { id: cur_id, 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 = util::array_split_mut!(&mut card_bytes, @@ -403,26 +392,24 @@ impl Table { item : [CardType::Item.byte_size()], footer : 1, ); - + // Write the header - LittleEndian::write_u16( bytes.header_id, cur_id as u16 ); - CardType::Item.to_bytes( bytes.header_type )?; - + LittleEndian::write_u16(bytes.header_id, cur_id as u16); + CardType::Item.to_bytes(bytes.header_type)?; + // Write the item - item.to_bytes( bytes.item ) - .map_err(|err| SerializeError::ItemCard { id: cur_id, err })?; - + item.to_bytes(bytes.item).map_err(|err| SerializeError::ItemCard { id: cur_id, err })?; + // Write the footer *bytes.footer = 0; - + log::debug!("[Card Header] Writing Item with id {}", cur_id); - file.write_all(&card_bytes) - .map_err(|err| SerializeError::WriteCard { id: cur_id, err })?; + file.write_all(&card_bytes).map_err(|err| SerializeError::WriteCard { 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 = util::array_split_mut!(&mut card_bytes, @@ -431,23 +418,23 @@ impl Table { item : [CardType::Digivolve.byte_size()], footer : 1, ); - + // Write the header - LittleEndian::write_u16( bytes.header_id, cur_id as u16 ); - CardType::Digivolve.to_bytes( bytes.header_type )?; - + LittleEndian::write_u16(bytes.header_id, cur_id as u16); + CardType::Digivolve.to_bytes(bytes.header_type)?; + // Write the digivolve - digivolve.to_bytes( bytes.item ) + digivolve + .to_bytes(bytes.item) .map_err(|err| SerializeError::DigivolveCard { id: cur_id, err })?; - + // Write the footer *bytes.footer = 0; - + log::debug!("[Card Header] Writing Digivolve with id {}", cur_id); - file.write_all(&card_bytes) - .map_err(|err| SerializeError::WriteCard { id: cur_id, err })?; + file.write_all(&card_bytes).map_err(|err| SerializeError::WriteCard { id: cur_id, err })?; } - + // And return Ok Ok(()) } diff --git a/src/game/deck.rs b/src/game/deck.rs index 9bc7923..2f12654 100644 --- a/src/game/deck.rs +++ b/src/game/deck.rs @@ -1,14 +1,7 @@ //! Deck // Modules -//-------------------------------------------------------------------------------------------------- - //pub mod deck; - - //pub mod property; - pub mod table; -//-------------------------------------------------------------------------------------------------- +pub mod table; // Exports -//pub use deck::Deck; - pub use table::Table; diff --git a/src/game/deck/table.rs b/src/game/deck/table.rs index 4ac610b..a32537d 100644 --- a/src/game/deck/table.rs +++ b/src/game/deck/table.rs @@ -1,211 +1,160 @@ -// Dcb -//-------------------------------------------------------------------------------------------------- - // Io - use crate::io::address::Data; - use crate::io::GameFile; - - // Game - //use crate::game::deck::Deck; - //use crate::game::Bytes; - //use crate::game::FromBytes; - //use crate::game::ToBytes; -//-------------------------------------------------------------------------------------------------- +//! The table of all decks in the game -// Read / Write -use std::io::Read; -use std::io::Write; -use std::io::Seek; +// Std +use std::io::{Read, Seek, Write}; // byteorder -use byteorder::ByteOrder; -use byteorder::LittleEndian; +use byteorder::{ByteOrder, LittleEndian}; -// Macros +// Crate +use crate::io::{address::Data, GameFile}; -use serde::Serialize; -use serde::Deserialize; +/// The decks table, where all decks are stored +/// +/// # Details +/// This type serves as an interface to this table, being able to read +/// and write to it, it is the only type able to do so, as each deck +/// type may only be converted to and from bytes. +#[derive(Debug)] +#[derive(::serde::Serialize, ::serde::Deserialize)] +pub struct Table { + decks: Vec, +} -// Types -//-------------------------------------------------------------------------------------------------- - /// The decks table, where all decks are stored - /// - /// # Details - /// This type serves as an interface to this table, being able to read - /// and write to it, it is the only type able to do so, as each deck - /// type may only be converted to and from bytes. - #[derive(Debug, Serialize, Deserialize)] - pub struct Table +#[derive(Debug)] +#[derive(::serde::Serialize, ::serde::Deserialize)] +pub struct Deck { + cards: [u16; 30], +} + +// Constants +impl Table { + /// The start address of the decks table + const DECK_TABLE_START_ADDRESS: Data = Data::from_u64(0x21a6808); +} + +/// Error type for `Table::new` +#[derive(Debug)] +#[derive(derive_more::Display, err_impl::Error)] +pub enum NewError { + /// Could not seek tothe beginning of the deck table + #[display(fmt = "Could not seek to the beginning of the deck table")] + SeekTableBegin(#[error(source)] std::io::Error), + + /// Could not read a deck entry from the deck table + #[display(fmt = "Unable to fully read a deck entry (The file was too small)")] + DeckEntry(#[error(source)] std::io::Error), + + /* + /// Could not constructs a deck + #[display(fmt = "Could not construct a deck from the deck table")] + DeckConstruction( crate::game::deck::deck::FromBytesError ), + */ + /// Could not read the next entry info + #[display(fmt = "Unable to fully read next entry info (The file was too small)")] + NextEntryInfo(#[error(source)] std::io::Error), + /* + /// The deck table was malformed + #[display(fmt = "The deck table is malformed")] + MalformedTable( crate::game::deck::property::deck_type::UnknownDeckType ), + */ +} + +/// Error type for `Table::write_to_file` +#[derive(Debug, derive_more::Display)] +pub enum WriteError { + /// The deck table was too big + #[display(fmt = "The deck table was too big (is {}, should be 65536 max)", _0)] + TooManyDeck(usize), + /* + /// Unable to convert a deck to bytes + #[display(fmt = "Unable to convert deck with id {} to bytes", id)] + UnableToConvertDeckToBytes { + id: u16, + err: crate::game::deck::deck::ToBytesError, + }, + + /// Unable to write deck entry + #[display(fmt = "Unable to write deck entry with id {}", id)] + UnableToWriteDeckEntry { + id: u16, + err: std::io::Error, + }, + */ +} + +impl Table { + pub fn new(game_file: &mut GameFile) -> Result + where + F: Read + Write + Seek, { - decks: Vec - } - - #[derive(Debug, Serialize, Deserialize)] - pub struct Deck - { - cards: [u16; 30], - } - - /// Error type for `Table::new` - #[derive(Debug, derive_more::Display)] - pub enum NewError - { - /// Could not seek tothe beginning of the deck table - #[display(fmt = "Could not seek to the beginning of the deck table")] - SeekTableBegin( std::io::Error ), - - - - /// Could not read a deck entry from the deck table - #[display(fmt = "Unable to fully read a deck entry (The file was too small)")] - DeckEntry( std::io::Error ), - - - /* - /// Could not constructs a deck - #[display(fmt = "Could not construct a deck from the deck table")] - DeckConstruction( crate::game::deck::deck::FromBytesError ), - */ - - - /// Could not read the next entry info - #[display(fmt = "Unable to fully read next entry info (The file was too small)")] - NextEntryInfo( std::io::Error ), - - /* - /// The deck table was malformed - #[display(fmt = "The deck table is malformed")] - MalformedTable( crate::game::deck::property::deck_type::UnknownDeckType ), - */ - } - - impl std::error::Error for NewError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::SeekTableBegin(err) | - Self::DeckEntry(err) | - Self::NextEntryInfo(err) => Some(err), - } + // The deck array + let mut decks = vec![]; + + // Seek to the beginning of the deck table + game_file + .seek(std::io::SeekFrom::Start(u64::from(Self::DECK_TABLE_START_ADDRESS))) + .map_err(NewError::SeekTableBegin)?; + + // Then loop until we're at the end of the table + //'table_loop: loop + for _ in 0..100 { + // Read the deck + let mut buf = [0u8; 110]; + game_file.read_exact(&mut buf).map_err(NewError::DeckEntry)?; + + // And construct the deck + let deck = Deck { + cards: { + let mut cards_buf = [0u16; 30]; + + for card_id in 0..30 { + cards_buf[card_id] = LittleEndian::read_u16(&buf[0x0 + card_id * 2..0x2 + card_id * 2]); + } + + cards_buf + }, + }; + + // And add it + decks.push(deck); } - } - - /// Error type for `Table::write_to_file` - #[derive(Debug, derive_more::Display)] - pub enum WriteError - { - /// The deck table was too big - #[display(fmt = "The deck table was too big (is {}, should be 65536 max)", _0)] - TooManyDeck( usize ), - - /* - /// Unable to convert a deck to bytes - #[display(fmt = "Unable to convert deck with id {} to bytes", id)] - UnableToConvertDeckToBytes { - id: u16, - err: crate::game::deck::deck::ToBytesError, - }, - - /// Unable to write deck entry - #[display(fmt = "Unable to write deck entry with id {}", id)] - UnableToWriteDeckEntry { - id: u16, - err: std::io::Error, - }, - */ - } -//-------------------------------------------------------------------------------------------------- -// Impl -//-------------------------------------------------------------------------------------------------- - impl Table - { - // Constants - //-------------------------------------------------------------------------------------------------- - /// The start address of the decks table - const DECK_TABLE_START_ADDRESS : Data = Data::from_u64(0x21a6808); - //-------------------------------------------------------------------------------------------------- - - // Constructors - //-------------------------------------------------------------------------------------------------- - /// Reads the deck table from a dcb bin file - pub fn new(game_file: &mut GameFile) -> Result - where - F: Read + Write + Seek - { - // The deck array - let mut decks = vec![]; - - - // Seek to the beginning of the deck table - game_file.seek( std::io::SeekFrom::Start( u64::from( Self::DECK_TABLE_START_ADDRESS) ) ).map_err(NewError::SeekTableBegin)?; - - // Then loop until we're at the end of the table - //'table_loop: loop - for _ in 0..100 - { - // Read the deck - let mut buf = [0u8; 110]; - game_file.read_exact(&mut buf) - .map_err(NewError::DeckEntry)?; - - // And construct the deck - let deck = Deck { - cards: { - let mut cards_buf = [0u16; 30]; - - for card_id in 0..30 { - cards_buf[card_id] = LittleEndian::read_u16( &buf[0x0 + card_id*2 .. 0x2 + card_id*2] ); - } - - cards_buf - } - }; - - // And add it - decks.push(deck); - } - - // And return the table - Ok( Self { - decks, - }) - } - //-------------------------------------------------------------------------------------------------- - - // Write - //-------------------------------------------------------------------------------------------------- - /* - /// Writes this table to a dcb bin file - pub fn write_to_file(&self, _game_file: &mut GameFile) -> Result<(), WriteError> - where - F: Read + Write + Seek - { - /* - // If the table length is bigger than 0xFFFF, return err - if self.decks.len() > 0xFFFF { return Err( TableWriteError::TooManyDeck( self.decks.len() ) ); } - - // Go through all deck and write them - // Note: We write them in the order they appear in the array, - // because this is the same way we read them. - for (id, deck) in self.decks.iter().enumerate() - { - // Convert `id` to a u16 - let id = id as u16; - - // Get the bytes - let mut bytes = [0u8; Deck::BUF_BYTE_SIZE]; - deck.to_bytes(&mut bytes).map_err(|err| TableWriteError::UnableToConvertDeckToBytes{id, err})?; - - // Seek to the right address in the table - Self::seek_deck_table(game_file, id as u16)?; - - // And write the deck buffer - game_file.write_all(&bytes).map_err(|err| TableWriteError::UnableToWriteDeckEntry{id, err})?; - } - */ - - Ok(()) - } - */ - //-------------------------------------------------------------------------------------------------- + // And return the table + Ok(Self { decks }) } -//-------------------------------------------------------------------------------------------------- + + /* + /// Writes this table to a dcb bin file + pub fn write_to_file(&self, _game_file: &mut GameFile) -> Result<(), WriteError> + where + F: Read + Write + Seek + { + /* + // If the table length is bigger than 0xFFFF, return err + if self.decks.len() > 0xFFFF { return Err( TableWriteError::TooManyDeck( self.decks.len() ) ); } + + // Go through all deck and write them + // Note: We write them in the order they appear in the array, + // because this is the same way we read them. + for (id, deck) in self.decks.iter().enumerate() + { + // Convert `id` to a u16 + let id = id as u16; + + // Get the bytes + let mut bytes = [0u8; Deck::BUF_BYTE_SIZE]; + deck.to_bytes(&mut bytes).map_err(|err| TableWriteError::UnableToConvertDeckToBytes{id, err})?; + + // Seek to the right address in the table + Self::seek_deck_table(game_file, id as u16)?; + + // And write the deck buffer + game_file.write_all(&bytes).map_err(|err| TableWriteError::UnableToWriteDeckEntry{id, err})?; + } + */ + + Ok(()) + } + */ +} diff --git a/src/game/util.rs b/src/game/util.rs index 03979a9..cf67b98 100644 --- a/src/game/util.rs +++ b/src/game/util.rs @@ -1,36 +1,35 @@ //! Utility macros and functions -//! +//! //! This modules is used for miscellaneous macros, functions that have //! not been moved to a more permanent location. -//! +//! //! All items in this module will eventually be depracated and moved //! somewhere else, but this change might take some time. - pub macro array_split { ( $arr:expr, $( $name:ident : - + $( [$arr_size:expr] )? $( $val_size:literal )? - + ),* $(,)? ) => {{ #![allow(clippy::used_underscore_binding)] #![allow(clippy::ptr_offset_with_cast )] - + // Struct holding all fields struct Fields<'a, T> { $( $name: - + $( &'a [T; $arr_size], )? $( &'a T, #[cfg(os = "Os that does not exist")] __field: [u8; $val_size], )? )* } - + // Get everything from `array_refs` let ( $( @@ -43,7 +42,7 @@ pub macro array_split { $( $val_size )? ),* ); - + // And return the fields Fields { $( @@ -60,25 +59,25 @@ pub macro array_split_mut { $arr:expr, $( $name:ident : - + $( [$arr_size:expr] )? $( $val_size:literal )? - + ),* $(,)? ) => {{ #![allow(clippy::used_underscore_binding)] #![allow(clippy::ptr_offset_with_cast )] - + // Struct holding all fields struct Fields<'a, T> { $( $name: - + $( &'a mut [T; $arr_size], )? $( &'a mut T, #[cfg(os = "Os that does not exist")] __field: [u8; $val_size], )? )* } - + // Get everything from `array_refs` let ( $( @@ -91,7 +90,7 @@ pub macro array_split_mut { $( $val_size )? ),* ); - + // And return the fields Fields { $( @@ -103,7 +102,6 @@ pub macro array_split_mut { }} } - /// Error type for [`read_null_ascii_string`] #[derive(Debug)] #[derive(derive_more::Display, err_impl::Error)] @@ -111,10 +109,10 @@ pub enum ReadNullAsciiStringError { /// No null was found in the string #[display(fmt = "No null was found on the buffer")] NoNull, - + /// The string was not ascii #[display(fmt = "The buffer did not contain valid Ascii")] - NotAscii( #[error(source)] ascii::AsAsciiStrError ), + NotAscii(#[error(source)] ascii::AsAsciiStrError), } /// Reads a null-terminated ascii string from a buffer. @@ -123,12 +121,11 @@ pub fn read_null_ascii_string(buf: &impl AsRef<[u8]>) -> Result<&ascii::AsciiStr let buf = buf.as_ref(); let buf = match buf.iter().position(|&b| b == 0) { Some(null_idx) => &buf[0..null_idx], - None => return Err( ReadNullAsciiStringError::NoNull ), + None => return Err(ReadNullAsciiStringError::NoNull), }; - + // Then convert it from Ascii - ascii::AsciiStr::from_ascii(buf) - .map_err(ReadNullAsciiStringError::NotAscii) + ascii::AsciiStr::from_ascii(buf).map_err(ReadNullAsciiStringError::NotAscii) } /// Error type for [`write_null_ascii_string`] @@ -137,24 +134,24 @@ pub fn read_null_ascii_string(buf: &impl AsRef<[u8]>) -> Result<&ascii::AsciiStr pub enum WriteNullAsciiStringError { /// The input string was too large #[display(fmt = "Input string was too large for buffer. ({}+1 / {})", "input_len", "buffer_len")] - TooLarge { - input_len : usize, - buffer_len: usize, - }, + TooLarge { input_len: usize, buffer_len: usize }, } /// Writes a null-terminated ascii string to a buffer and returns it pub fn write_null_ascii_string<'a>(input: &ascii::AsciiStr, buf: &'a mut [u8]) -> Result<&'a mut [u8], WriteNullAsciiStringError> { // If the input string doesn't fit into the buffer (excluding the null byte), return Err if input.len() >= buf.len() { - return Err(WriteNullAsciiStringError::TooLarge{ input_len: input.len(), buffer_len: buf.len() }); + return Err(WriteNullAsciiStringError::TooLarge { + input_len: input.len(), + buffer_len: buf.len(), + }); } - + // Else copy everything over and set the last byte to null // Note: We leave all other bytes as they are, no need to set them to 0 - buf[ 0..input.len() ].copy_from_slice( input.as_bytes() ); - buf[ input.len() ] = 0; - + buf[0..input.len()].copy_from_slice(input.as_bytes()); + buf[input.len()] = 0; + // And return Ok with the buffer - Ok( buf ) + Ok(buf) } diff --git a/src/io.rs b/src/io.rs index 488542c..b86303f 100644 --- a/src/io.rs +++ b/src/io.rs @@ -1,13 +1,13 @@ //! Input / Output -//! +//! //! The Io module takes care of interacting with the game file itself, such //! as ensuring that only the data sections in the game file are written to. //! As well as making convertions between coordinates in data to real file //! coordinates. (For more details, visit the [`address`] module) // Modules -pub mod game_file; pub mod address; +pub mod game_file; // Exports pub use game_file::GameFile; diff --git a/src/io/address.rs b/src/io/address.rs index 4d0fd8b..4933001 100644 --- a/src/io/address.rs +++ b/src/io/address.rs @@ -1,72 +1,65 @@ //! Addressing modes of the game file -//! +//! //! The game file, as explained in `GameFile`, is separated //! into real addresses, which correspond to actual file //! offsets, and data addresses, which correspond to offsets //! inside the data section of each sector. // Modules -pub mod real; pub mod data; +pub mod real; // Exports -pub use real::Real; pub use data::Data; +pub use real::Real; /// Error type for `TryFrom for Data` -#[derive(Debug, derive_more::Display)] +#[derive(Debug)] +#[derive(derive_more::Display, err_impl::Error)] pub enum RealToDataError { /// Occurs when the Real is outside of the data section of the sector #[display(fmt = "The real address {} could not be converted to a data address as it is not in the data section", _0)] OutsideDataSection(Real), } -impl std::error::Error for RealToDataError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::OutsideDataSection(..) => None, - } - } -} - // Real -> Data impl std::convert::TryFrom for Data { type Error = RealToDataError; - - fn try_from(real_address: Real) -> Result - { + + fn try_from(real_address: Real) -> Result { // If the real address isn't in the data section, then return err - if !real_address.in_data_section() { return Err( Self::Error::OutsideDataSection(real_address) ); } - + if !real_address.in_data_section() { + return Err(Self::Error::OutsideDataSection(real_address)); + } + // Else get the sector and offset - let real_sector = real_address.sector(); + let real_sector = real_address.sector(); let real_sector_offset = real_address.offset(); - + // The data address is just converting the real_sector // to a data_sector and subtracting the header from the - // real offset to get the data offset - Ok( Self::from( + // real offset to get the data offset + Ok(Self::from( Real::SECTOR_BYTE_SIZE * real_sector + // Base of data sector - real_sector_offset - Real::HEADER_BYTE_SIZE // Data offset + real_sector_offset - + Real::HEADER_BYTE_SIZE, // Data offset )) } } // Data -> Real -impl From for Real -{ - fn from(data_address: Data) -> Self - { +impl From for Real { + fn from(data_address: Data) -> Self { // Get the sector and offset - let data_sector = data_address.sector(); + let data_sector = data_address.sector(); let data_sector_offset = data_address.offset(); - + // Then the real address is just convering the data_sector // to a real_sector and adding the header plus the offset Self::from( Self::SECTOR_BYTE_SIZE * data_sector + // Base of real sector Self::HEADER_BYTE_SIZE + // Skip header - data_sector_offset // Offset inside data sector + data_sector_offset, // Offset inside data sector ) } } diff --git a/src/io/address/data.rs b/src/io/address/data.rs index 4254e73..b3de59d 100644 --- a/src/io/address/data.rs +++ b/src/io/address/data.rs @@ -4,28 +4,27 @@ use crate::io::address::Real; /// A type for defining addresses on the data parts of `.bin` file. -/// +/// /// # Details /// All addresses of type `Data` will represent the position /// within *only* the data sections on the file. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct Data(u64); -impl Data -{ +impl Data { /// Constructs a data address from it's `u64` representation #[must_use] pub const fn from_u64(address: u64) -> Self { Self(address) } - + /// Returns the sector associated with this address #[must_use] #[allow(clippy::integer_division)] // We want to get the whole division pub fn sector(self) -> u64 { u64::from(self) / Real::DATA_BYTE_SIZE } - + /// Returns the offset into the data section of this address #[must_use] pub fn offset(self) -> u64 { @@ -34,14 +33,21 @@ impl Data } // Conversions from and into u64 -impl From for u64 { fn from(address: Data) -> Self { address.0 } } -impl From for Data { fn from(address: u64 ) -> Self { Self(address) } } +impl From for u64 { + fn from(address: Data) -> Self { + address.0 + } +} +impl From for Data { + fn from(address: u64) -> Self { + Self(address) + } +} // Data + Offset -impl std::ops::Add for Data -{ +impl std::ops::Add for Data { type Output = Self; - + fn add(self, offset: i64) -> Self { if offset > 0 { self + (offset as u64) @@ -52,46 +58,50 @@ impl std::ops::Add for Data } // Data += Offset -impl std::ops::AddAssign for Data -{ - fn add_assign(&mut self, offset: i64) { *self = *self + offset; } +impl std::ops::AddAssign for Data { + fn add_assign(&mut self, offset: i64) { + *self = *self + offset; + } } // Data + absolute impl std::ops::Add for Data { type Output = Self; - + fn add(self, absolute: u64) -> Self { - Self::from( self.0 + absolute ) + Self::from(self.0 + absolute) } } // Data += absolute impl std::ops::AddAssign for Data { - fn add_assign(&mut self, absolute: u64) { *self = *self + absolute; } + fn add_assign(&mut self, absolute: u64) { + *self = *self + absolute; + } } // Data - absolute impl std::ops::Sub for Data { type Output = Self; - + fn sub(self, absolute: u64) -> Self { - Self::from( self.0 - absolute ) + Self::from(self.0 - absolute) } } // Data -= absolute impl std::ops::SubAssign for Data { - fn sub_assign(&mut self, absolute: u64) { *self = *self - absolute; } + fn sub_assign(&mut self, absolute: u64) { + *self = *self - absolute; + } } // Data - Data impl std::ops::Sub for Data { type Output = i64; - + fn sub(self, address: Self) -> i64 { - self.0 as i64 - - address.0 as i64 + self.0 as i64 - address.0 as i64 } } diff --git a/src/io/address/real.rs b/src/io/address/real.rs index e8d6404..6164fdc 100644 --- a/src/io/address/real.rs +++ b/src/io/address/real.rs @@ -1,7 +1,7 @@ //! File real addresses /// A type for defining addresses on the `.bin` file. -/// +/// /// All real addresses will depict the actual position /// within the game file, including headers from the `.bin` file format. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -10,26 +10,20 @@ pub struct Real(u64); // Constants impl Real { - /// The number of bytes within a whole sector - pub const SECTOR_BYTE_SIZE: u64 = 2352; - /// The number of bytes the data section takes up in the sector pub const DATA_BYTE_SIZE: u64 = 2048; - - /// The number of bytes the header takes up in the sector - pub const HEADER_BYTE_SIZE: u64 = 24; - - /// The number of bytes the footer takes up in the sector - pub const FOOTER_BYTE_SIZE: u64 = 280; - - /// The start of the data section - pub const DATA_START: u64 = Self::HEADER_BYTE_SIZE; - /// The end of the data section (one-past) pub const DATA_END: u64 = Self::HEADER_BYTE_SIZE + Self::DATA_BYTE_SIZE; - /// The range of the data section - pub const DATA_RANGE: std::ops::Range = Self::DATA_START .. Self::DATA_END; + pub const DATA_RANGE: std::ops::Range = Self::DATA_START..Self::DATA_END; + /// The start of the data section + pub const DATA_START: u64 = Self::HEADER_BYTE_SIZE; + /// The number of bytes the footer takes up in the sector + pub const FOOTER_BYTE_SIZE: u64 = 280; + /// The number of bytes the header takes up in the sector + pub const HEADER_BYTE_SIZE: u64 = 24; + /// The number of bytes within a whole sector + pub const SECTOR_BYTE_SIZE: u64 = 2352; } impl Real { @@ -39,66 +33,63 @@ impl Real { pub fn sector(self) -> u64 { u64::from(self) / Self::SECTOR_BYTE_SIZE } - + /// Returns the offset into the sector of this address #[must_use] pub fn offset(self) -> u64 { u64::from(self) % Self::SECTOR_BYTE_SIZE } - + /// Returns the address of the end of the data section in this sector. #[must_use] pub fn data_section_end(self) -> Self { // Get the sector let real_sector = self.sector(); - + // The end of the real data section is after the header and data sections Self::from( Self::SECTOR_BYTE_SIZE * real_sector + // Beginning of sector Self::HEADER_BYTE_SIZE + // Skip Header - Self:: DATA_BYTE_SIZE // Skip Data + Self:: DATA_BYTE_SIZE, // Skip Data ) } - + /// Checks if this address is within the real data section #[must_use] pub fn in_data_section(self) -> bool { // If our offset is within the data range - Self::DATA_RANGE.contains( &self.offset() ) + Self::DATA_RANGE.contains(&self.offset()) } } - // Real + Offset impl std::ops::Add for Real { type Output = Self; - + fn add(self, offset: i64) -> Self { - Self::from( ( u64::from(self) as i64 + offset) as u64 ) + Self::from((u64::from(self) as i64 + offset) as u64) } } // Real += Offset impl std::ops::AddAssign for Real { - fn add_assign(&mut self, offset: i64) { *self = *self + offset; } + fn add_assign(&mut self, offset: i64) { + *self = *self + offset; + } } // Real - Real -impl std::ops::Sub for Real -{ +impl std::ops::Sub for Real { type Output = i64; - + fn sub(self, address: Self) -> i64 { u64::from(self) as i64 - u64::from(address) as i64 } } - // Display -impl std::fmt::Display for Real -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result - { +impl std::fmt::Display for Real { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:x}", u64::from(*self)) } } diff --git a/src/io/game_file.rs b/src/io/game_file.rs index 81788d9..5c34c3b 100644 --- a/src/io/game_file.rs +++ b/src/io/game_file.rs @@ -1,28 +1,28 @@ //! Abstraction over the game file. -//! +//! //! See [`GameFile`] for details // Addresses -use crate::io::address::{Real as RealAddress, Data as DataAddress, RealToDataError}; +use crate::io::address::{Data as DataAddress, Real as RealAddress, RealToDataError}; // Read / Write -use std::io::{Read, Write, Seek}; +use std::io::{Read, Seek, Write}; /// A type that abstracts over a the game reader. -/// +/// /// # Game reader /// The game file is a `.bin` file, of the type `MODE2/2352`. -/// +/// /// This means that the file is divided into sectors of size /// 2352 bytes, each with it's data structure. -/// +/// /// For us the only thing that matters is the data section /// of each sector, which is 2048 bytes long. -/// +/// /// This type allows reading and writing in `DataAddress` addresses, /// which are reader offsets in terms of the 2048 byte data section, /// instead of the 2352 byte sectors. -/// +/// /// # Parameters /// `GameFile` is generic over `R`, this being any type that implements /// `Read`, `Write` and `Seek`, thus being able to read from either a @@ -35,19 +35,11 @@ pub struct GameFile { /// Error type for [`GameFile::from_reader`] #[derive(Debug)] -#[derive(derive_more::Display)] +#[derive(derive_more::Display, err_impl::Error)] pub enum NewGameFileError { /// Unable to seek reader to data section #[display(fmt = "Unable to seek reader to data section")] - SeekData( std::io::Error ), -} - -impl std::error::Error for NewGameFileError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::SeekData(err) => Some(err), - } - } + SeekData(#[error(source)] std::io::Error), } // Constructors @@ -55,153 +47,149 @@ impl GameFile { /// Constructs a `GameFile` given a reader pub fn from_reader(mut reader: R) -> Result { // Seek the reader to the beginning of the data section - reader.seek( std::io::SeekFrom::Start( - RealAddress::DATA_START - )).map_err(NewGameFileError::SeekData)?; - - Ok( Self { reader } ) + reader + .seek(std::io::SeekFrom::Start(RealAddress::DATA_START)) + .map_err(NewGameFileError::SeekData)?; + + Ok(Self { reader }) } } /// `Read` for `GameFile` -/// +/// /// # Implementation guarantees /// Currently `Read` guarantees that if an error is returned, then /// the buffer isn't modified, but this implementation cannot make /// that guarantee. -impl Read for GameFile -{ - fn read(&mut self, mut buf: &mut [u8]) -> std::io::Result - { +impl Read for GameFile { + fn read(&mut self, mut buf: &mut [u8]) -> std::io::Result { // Total length of the buffer to fill let total_buf_len = buf.len(); - + // While the buffer isn't empty - while !buf.is_empty() - { + while !buf.is_empty() { // Get the current real address we're at in the reader // Note: If we can't get the position, we return immediatly - let cur_real_address = RealAddress::from( self.reader.stream_position()? ); - + let cur_real_address = RealAddress::from(self.reader.stream_position()?); + // Get the data section end let data_section_end = cur_real_address.data_section_end(); - + // If we're at the end of the data section, seek to the next data section if cur_real_address == data_section_end { // Seek ahead by skiping the footer and next header - self.reader.seek( std::io::SeekFrom::Current( - (RealAddress::FOOTER_BYTE_SIZE + - RealAddress::HEADER_BYTE_SIZE) as i64 + self.reader.seek(std::io::SeekFrom::Current( + (RealAddress::FOOTER_BYTE_SIZE + RealAddress::HEADER_BYTE_SIZE) as i64, ))?; - + // And restart this loop continue; } - + // We always guarantee that the current address lies within the data sections // Note: We only check it here, because `cur_real_address` may be `data_section_end` // during seeking. - assert!( cur_real_address.in_data_section(), "Real offset {} [Sector {}, Offset {}] could not be read as it was not in the data section", + assert!( + cur_real_address.in_data_section(), + "Real offset {} [Sector {}, Offset {}] could not be read as it was not in the data section", cur_real_address, cur_real_address.sector(), cur_real_address.offset() ); - + // Check how many bytes we can read let mut bytes_to_read = (data_section_end - cur_real_address) as usize; - + // If we were to read more bytes than the buffer has, read less if bytes_to_read > buf.len() { bytes_to_read = buf.len(); } - + // Read either until the end of the data section or until buffer is full // Note: If any fail, we immediatly return Err - let bytes_read = self.reader.read( &mut buf[0..bytes_to_read] )?; - + let bytes_read = self.reader.read(&mut buf[0..bytes_to_read])?; + // If 0 bytes were read, EOF was reached, so return with however many we've read if bytes_read == 0 { - return Ok( total_buf_len - buf.len() ); + return Ok(total_buf_len - buf.len()); } - + // Discard what we've already read buf = &mut buf[bytes_read..]; // If `bytes_to_read == buf.len()` this does not panic } - + // And return the bytes we read - Ok( total_buf_len ) + Ok(total_buf_len) } } /// Write for `GameFile` -/// +/// /// # Implementation guarantees /// Currently `Read` guarantees that if an error is returned, then /// the buffer isn't modified, but this implementation cannot make /// that guarantee. -impl Write for GameFile -{ - fn write(&mut self, mut buf: &[u8]) -> std::io::Result - { +impl Write for GameFile { + fn write(&mut self, mut buf: &[u8]) -> std::io::Result { // Total length of the buffer to write let total_buf_len = buf.len(); - + // While the buffer isn't empty - while !buf.is_empty() - { + while !buf.is_empty() { // Get the current real address we're at in the reader // Note: If we can't get the position, we return immediatly - let cur_real_address = RealAddress::from( self.reader.stream_position()? ); - + let cur_real_address = RealAddress::from(self.reader.stream_position()?); + // Get the data section end let data_section_end = cur_real_address.data_section_end(); - + // If we're at the end of the data section, seek to the next data section if cur_real_address == data_section_end { // Seek ahead by skiping the footer and next header - self.reader.seek( std::io::SeekFrom::Current( - (RealAddress::FOOTER_BYTE_SIZE + - RealAddress::HEADER_BYTE_SIZE) as i64 + self.reader.seek(std::io::SeekFrom::Current( + (RealAddress::FOOTER_BYTE_SIZE + RealAddress::HEADER_BYTE_SIZE) as i64, ))?; - + // And restart this loop continue; } - + // We always guarantee that the current address lies within the data sections // Note: We only check it here, because `cur_real_address` may be `data_section_end` // during seeking. - assert!( cur_real_address.in_data_section(), "Real offset {} [Sector {}, Offset {}] could not be written as it was not in the data section", + assert!( + cur_real_address.in_data_section(), + "Real offset {} [Sector {}, Offset {}] could not be written as it was not in the data section", cur_real_address, cur_real_address.sector(), cur_real_address.offset() ); - + // Check how many bytes we can write let mut bytes_to_write = (data_section_end - cur_real_address) as usize; - + // If we were to write more bytes than the buffer has, write less if bytes_to_write > buf.len() { bytes_to_write = buf.len(); } - + // Write either until the end of the data section or until buffer runs out // Note: If this fails, we immediatly return Err - let bytes_written = self.reader.write( &buf[0..bytes_to_write] )?; - + let bytes_written = self.reader.write(&buf[0..bytes_to_write])?; + // If 0 bytes were written, EOF was reached, so return with however many we've read if bytes_written == 0 { - return Ok( total_buf_len - buf.len() ); + return Ok(total_buf_len - buf.len()); } - + // Discard what we've already written buf = &buf[bytes_to_write..]; // If `bytes_to_write == buf.len()` this does not panic } - + // And return the bytes we read - Ok( total_buf_len ) + Ok(total_buf_len) } - + fn flush(&mut self) -> std::io::Result<()> { self.reader.flush() } @@ -210,49 +198,37 @@ impl Write for GameFile /// Error type for `Seek for GameFile`. /// Returned when, after seeking, we ended up in a non-data section #[derive(Debug)] -#[derive(derive_more::Display)] +#[derive(derive_more::Display, err_impl::Error)] #[display(fmt = "Reader seeked into a non-data section")] -pub struct SeekNonDataError(RealToDataError); +pub struct SeekNonDataError(#[error(source)] RealToDataError); -impl std::error::Error for SeekNonDataError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&self.0) - } -} - -impl Seek for GameFile -{ +impl Seek for GameFile { fn seek(&mut self, data_pos: std::io::SeekFrom) -> std::io::Result { - use std::{ - io::SeekFrom, - convert::TryFrom, - }; - + use std::{convert::TryFrom, io::SeekFrom}; + // Calculate the real position let real_pos = match data_pos { - SeekFrom::Start(data_address) => SeekFrom::Start( u64::from( RealAddress::from( DataAddress::from(data_address) ) ) ), - SeekFrom::Current(data_offset) => SeekFrom::Start( - u64::from(RealAddress::from( - DataAddress::try_from( RealAddress::from( self.reader.stream_position()? ) ) - .map_err(SeekNonDataError) - .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))? + - data_offset - )) - ), + SeekFrom::Start(data_address) => SeekFrom::Start(u64::from(RealAddress::from(DataAddress::from(data_address)))), + SeekFrom::Current(data_offset) => SeekFrom::Start(u64::from(RealAddress::from( + DataAddress::try_from(RealAddress::from(self.reader.stream_position()?)) + .map_err(SeekNonDataError) + .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))? + + data_offset, + ))), SeekFrom::End(_) => { todo!("SeekFrom::End isn't currently implemented"); - } + }, }; - + // Seek to the real position and get where we are right now let cur_real_address = self.reader.seek(real_pos)?; - + // Get the data address - let data_address = DataAddress::try_from( RealAddress::from(cur_real_address) ) + let data_address = DataAddress::try_from(RealAddress::from(cur_real_address)) .map_err(SeekNonDataError) .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?; - + // And return the new data address - Ok( u64::from(data_address) ) + Ok(u64::from(data_address)) } } diff --git a/src/lib.rs b/src/lib.rs index c9eeea9..f8b6607 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ //! data types are defined. //! //! # Example -//! +//! //! The following is an example of how to use the `dcb` library. //! This example extracts the card table and prints it to screen //! @@ -32,15 +32,10 @@ stmt_expr_attributes, unwrap_infallible, const_if_match, - exclusive_range_pattern, + exclusive_range_pattern )] - // Lints -#![warn( - clippy::restriction, - clippy::pedantic, - clippy::nursery, -)] +#![warn(clippy::restriction, clippy::pedantic, clippy::nursery)] #![allow( clippy::missing_inline_in_public_items, // Dubious lint clippy::implicit_return, // We prefer tail returns where possible @@ -59,22 +54,19 @@ clippy::unreachable, // Some code should be unreachable and panic when reached. clippy::integer_arithmetic, // Come on now, we need to use numbers to program clippy::shadow_same, // Useful when taking arguments such as `value: impl AsRef` / `let value = value.as_ref();` - // TODO: Deal with casts eventually clippy::cast_possible_wrap, clippy::cast_sign_loss, clippy::cast_possible_truncation, - // TODO: Remove these once all modules are ported clippy::missing_docs_in_private_items, clippy::as_conversions, clippy::indexing_slicing, - )] // Modules -pub mod io; pub mod game; +pub mod io; // Exports pub use io::GameFile;