mirror of
https://github.com/Zenithsiz/dcb.git
synced 2026-02-11 12:41:48 +00:00
All code is now formatted.
Started using `err_impl::Error` where possible.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
16
src/lib.rs
16
src/lib.rs
@@ -7,7 +7,7 @@
|
||||
//! data types are defined.
|
||||
//!
|
||||
//! # Example
|
||||
//!
|
||||
//!
|
||||
//! The following is an example of how to use the `dcb` library.
|
||||
//! This example extracts the card table and prints it to screen
|
||||
//!
|
||||
@@ -32,15 +32,10 @@
|
||||
stmt_expr_attributes,
|
||||
unwrap_infallible,
|
||||
const_if_match,
|
||||
exclusive_range_pattern,
|
||||
exclusive_range_pattern
|
||||
)]
|
||||
|
||||
// Lints
|
||||
#![warn(
|
||||
clippy::restriction,
|
||||
clippy::pedantic,
|
||||
clippy::nursery,
|
||||
)]
|
||||
#![warn(clippy::restriction, clippy::pedantic, clippy::nursery)]
|
||||
#![allow(
|
||||
clippy::missing_inline_in_public_items, // Dubious lint
|
||||
clippy::implicit_return, // We prefer tail returns where possible
|
||||
@@ -59,22 +54,19 @@
|
||||
clippy::unreachable, // Some code should be unreachable and panic when reached.
|
||||
clippy::integer_arithmetic, // Come on now, we need to use numbers to program
|
||||
clippy::shadow_same, // Useful when taking arguments such as `value: impl AsRef<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;
|
||||
|
||||
Reference in New Issue
Block a user