All code is now formatted.

Started using `err_impl::Error` where possible.
This commit is contained in:
2020-05-01 11:29:01 +01:00
parent c031d151a7
commit 280bdac119
21 changed files with 1135 additions and 1350 deletions

View File

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

View File

@@ -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<Self, Self::FromError>;
/// Writes this structure to `bytes`
fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError>;
}

View File

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

View File

@@ -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<CrossMoveEffect>,
/// 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<ArrowColor>,
/// The effect's conditions
#[serde(default)]
pub effect_conditions: [Option<EffectCondition>; 2],
/// The effects
#[serde(default)]
pub effects: [Option<Effect>; 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<Self, Self::FromError>
{
type ToError = ToBytesError;
fn from_bytes(bytes: &Self::ByteArray) -> Result<Self, Self::FromError> {
// 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::<EffectCondition>::from_bytes( bytes.condition_first )
.map_err(FromBytesError::EffectConditionFirst)?,
Option::<EffectCondition>::from_bytes( bytes.condition_second )
.map_err(FromBytesError::EffectConditionSecond)?,
Option::<EffectCondition>::from_bytes(bytes.condition_first).map_err(FromBytesError::EffectConditionFirst)?,
Option::<EffectCondition>::from_bytes(bytes.condition_second).map_err(FromBytesError::EffectConditionSecond)?,
],
effects: [
Option::<Effect>::from_bytes( bytes.effect_first )
.map_err(FromBytesError::EffectFirst)?,
Option::<Effect>::from_bytes( bytes.effect_second )
.map_err(FromBytesError::EffectSecond)?,
Option::<Effect>::from_bytes( bytes.effect_third )
.map_err(FromBytesError::EffectThird)?,
Option::<Effect>::from_bytes(bytes.effect_first).map_err(FromBytesError::EffectFirst)?,
Option::<Effect>::from_bytes(bytes.effect_second).map_err(FromBytesError::EffectSecond)?,
Option::<Effect>::from_bytes(bytes.effect_third).map_err(FromBytesError::EffectThird)?,
],
cross_move_effect: Option::<CrossMoveEffect>::from_bytes(bytes.cross_move_effect)
.map_err(FromBytesError::CrossMoveEffect)?,
effect_arrow_color: Option::<ArrowColor>::from_bytes(bytes.effect_arrow_color)
.map_err(FromBytesError::ArrowColor)?,
cross_move_effect: Option::<CrossMoveEffect>::from_bytes(bytes.cross_move_effect).map_err(FromBytesError::CrossMoveEffect)?,
effect_arrow_color: Option::<ArrowColor>::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::<CrossMoveEffect>::to_bytes(&self.cross_move_effect, bytes.cross_move_effect).into_ok();
Option::<ArrowColor>::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(())
}

View File

@@ -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<Self, Self::FromError>
{
type ToError = ToBytesError;
fn from_bytes(bytes: &Self::ByteArray) -> Result<Self, Self::FromError> {
// 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(())
}

View File

@@ -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<ArrowColor>,
/// The effect's conditions
#[serde(default)]
pub effect_conditions: [Option<EffectCondition>; 2],
/// The effects
#[serde(default)]
pub effects: [Option<Effect>; 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<Self, Self::FromError>
{
type ToError = ToBytesError;
fn from_bytes(bytes: &Self::ByteArray) -> Result<Self, Self::FromError> {
// 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::<EffectCondition>::from_bytes( bytes.condition_first )
.map_err(FromBytesError::EffectConditionFirst)?,
Option::<EffectCondition>::from_bytes( bytes.condition_second )
.map_err(FromBytesError::EffectConditionSecond)?,
Option::<EffectCondition>::from_bytes(bytes.condition_first).map_err(FromBytesError::EffectConditionFirst)?,
Option::<EffectCondition>::from_bytes(bytes.condition_second).map_err(FromBytesError::EffectConditionSecond)?,
],
effects: [
Option::<Effect>::from_bytes( bytes.effect_first )
.map_err(FromBytesError::EffectFirst)?,
Option::<Effect>::from_bytes( bytes.effect_second )
.map_err(FromBytesError::EffectSecond)?,
Option::<Effect>::from_bytes( bytes.effect_third )
.map_err(FromBytesError::EffectThird)?,
Option::<Effect>::from_bytes(bytes.effect_first).map_err(FromBytesError::EffectFirst)?,
Option::<Effect>::from_bytes(bytes.effect_second).map_err(FromBytesError::EffectSecond)?,
Option::<Effect>::from_bytes(bytes.effect_third).map_err(FromBytesError::EffectThird)?,
],
effect_arrow_color: Option::<ArrowColor>::from_bytes(bytes.effect_arrow_color)
.map_err(FromBytesError::ArrowColor)?,
effect_arrow_color: Option::<ArrowColor>::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::<ArrowColor>::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(())
}

View File

@@ -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<Self, Self::FromError>
{
@@ -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<E>` 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<Self, Self::FromError>
{
@@ -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;

View File

@@ -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:
///
///
/// `<property> = ( <A> + <Y> ) + ( <C> <op> ( <B> + <X> ) )`
#[serde(rename = "Change property")]
ChangeProperty {
property: DigimonProperty,
a: Option<DigimonProperty>,
b: Option<DigimonProperty>,
c: Option<DigimonProperty>,
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:
///
///
/// `<temp slot> = <A> + (<B> <op> <C>)`
#[serde(rename = "Set temp slot")]
SetTempSlot {
a: Option<DigimonProperty>,
b: Option<DigimonProperty>,
c: Option<DigimonProperty>,
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<Self, Self::FromError>
{
type ToError = ToBytesError;
fn from_bytes(bytes: &Self::ByteArray) -> Result<Self, Self::FromError> {
// 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<DigimonProperty>| if let Some(a) = a {
a.to_bytes(bytes_a).into_ok();
} else {
*bytes_a = 0;
let mut set_a = |a: &Option<DigimonProperty>| {
if let Some(a) = a {
a.to_bytes(bytes_a).into_ok();
} else {
*bytes_a = 0;
}
};
let mut set_b = |b: &Option<DigimonProperty>| if let Some(b) = b {
b.to_bytes(bytes_b).into_ok();
} else {
*bytes_b = 0;
let mut set_b = |b: &Option<DigimonProperty>| {
if let Some(b) = b {
b.to_bytes(bytes_b).into_ok();
} else {
*bytes_b = 0;
}
};
let mut set_c = |c: &Option<DigimonProperty>| if let Some(c) = c {
c.to_bytes(bytes_c).into_ok();
} else {
*bytes_c = 0;
let mut set_c = |c: &Option<DigimonProperty>| {
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<Effect>
{
impl Bytes for Option<Effect> {
type ByteArray = [u8; 0x10];
type FromError = FromBytesError;
type ToError = ToBytesError;
// `bytes` should include the `exists` byte
fn from_bytes(bytes: &Self::ByteArray) -> Result<Self, Self::FromError>
{
fn from_bytes(bytes: &Self::ByteArray) -> Result<Self, Self::FromError> {
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(())
}

View File

@@ -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<DigimonProperty>,
/// 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<Self, Self::FromError>
{
type ToError = !;
fn from_bytes(bytes: &Self::ByteArray) -> Result<Self, Self::FromError> {
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::<DigimonProperty>::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::<DigimonProperty>::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<EffectCondition>
{
impl Bytes for Option<EffectCondition> {
type ByteArray = [u8; 0x20];
type FromError = FromBytesError;
fn from_bytes(bytes: &Self::ByteArray) -> Result<Self, Self::FromError>
{
type ToError = <EffectCondition as crate::game::Bytes>::ToError;
fn from_bytes(bytes: &Self::ByteArray) -> Result<Self, Self::FromError> {
// 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 = <EffectCondition as crate::game::Bytes>::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(())
}

View File

@@ -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<Self, Self::FromError>
{
type ToError = ToBytesError;
fn from_bytes(bytes: &Self::ByteArray) -> Result<Self, Self::FromError> {
// 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(())
}

View File

@@ -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<Digimon >,
pub items : Vec<Item >,
/// All digimons in this table
pub digimons: Vec<Digimon>,
/// All items in this table
pub items: Vec<Item>,
/// All digivolves in this table
pub digivolves: Vec<Digivolve>,
}
// 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<R: Read + Write + Seek>(file: &mut GameFile<R>) -> Result<Self, DeserializeError> {
// 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::< <Digimon as Bytes>::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::<<Digimon as Bytes>::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::< <Item as Bytes>::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::<<Item as Bytes>::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::< <Digivolve as Bytes>::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::<<Digivolve as Bytes>::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<R: Read + Write + Seek>(&self, file: &mut GameFile<R>) -> Result<(), SerializeError> {
// Get the final table size
let table_size = self. digimons.len() * (0x3 + CardType::Digimon .byte_size() + 0x1) +
self. items.len() * (0x3 + CardType::Item .byte_size() + 0x1) +
self.digivolves.len() * (0x3 + CardType::Digivolve.byte_size() + 0x1);
let table_size = self.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(())
}

View File

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

View File

@@ -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<Deck>,
}
// 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<F>(game_file: &mut GameFile<F>) -> Result<Self, NewError>
where
F: Read + Write + Seek,
{
decks: Vec<Deck>
}
#[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<F>(game_file: &mut GameFile<F>) -> Result<Self, NewError>
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<F>(&self, _game_file: &mut GameFile<F>) -> 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<F>(&self, _game_file: &mut GameFile<F>) -> 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(())
}
*/
}

View File

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

View File

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

View File

@@ -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<Real> 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<Real> for Data {
type Error = RealToDataError;
fn try_from(real_address: Real) -> Result<Self, Self::Error>
{
fn try_from(real_address: Real) -> Result<Self, Self::Error> {
// 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<Data> for Real
{
fn from(data_address: Data) -> Self
{
impl From<Data> 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
)
}
}

View File

@@ -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<Data> for u64 { fn from(address: Data) -> Self { address.0 } }
impl From<u64 > for Data { fn from(address: u64 ) -> Self { Self(address) } }
impl From<Data> for u64 {
fn from(address: Data) -> Self {
address.0
}
}
impl From<u64> for Data {
fn from(address: u64) -> Self {
Self(address)
}
}
// Data + Offset
impl std::ops::Add<i64> for Data
{
impl std::ops::Add<i64> 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<i64> for Data
}
// Data += Offset
impl std::ops::AddAssign<i64> for Data
{
fn add_assign(&mut self, offset: i64) { *self = *self + offset; }
impl std::ops::AddAssign<i64> for Data {
fn add_assign(&mut self, offset: i64) {
*self = *self + offset;
}
}
// Data + absolute
impl std::ops::Add<u64> 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<u64> 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<u64> 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<u64> 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<Data> 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
}
}

View File

@@ -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<u64> = Self::DATA_START .. Self::DATA_END;
pub const DATA_RANGE: std::ops::Range<u64> = 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<i64> 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<i64> 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<Real> for Real
{
impl std::ops::Sub<Real> 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))
}
}

View File

@@ -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<R: Read + Write + Seek> {
/// 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<R: Read + Write + Seek> GameFile<R> {
/// Constructs a `GameFile` given a reader
pub fn from_reader(mut reader: R) -> Result<Self, NewGameFileError> {
// 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<R: Read + Write + Seek> Read for GameFile<R>
{
fn read(&mut self, mut buf: &mut [u8]) -> std::io::Result<usize>
{
impl<R: Read + Write + Seek> Read for GameFile<R> {
fn read(&mut self, mut buf: &mut [u8]) -> std::io::Result<usize> {
// 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<R: Read + Write + Seek> Write for GameFile<R>
{
fn write(&mut self, mut buf: &[u8]) -> std::io::Result<usize>
{
impl<R: Read + Write + Seek> Write for GameFile<R> {
fn write(&mut self, mut buf: &[u8]) -> std::io::Result<usize> {
// 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<R: Read + Write + Seek> Write for GameFile<R>
/// 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<R: Read + Write + Seek> Seek for GameFile<R>
{
impl<R: Read + Write + Seek> Seek for GameFile<R> {
fn seek(&mut self, data_pos: std::io::SeekFrom) -> std::io::Result<u64> {
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))
}
}

View File

@@ -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<T>` / `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;