Added bounds on the Bytes error types.

Started using `err-impl`, fork of `err-derive` that does not implement `Display` or `From`.
This commit is contained in:
Filipe Rodrigues 2020-04-20 21:56:35 +01:00
parent 18e68dc0c6
commit e943819853
12 changed files with 466 additions and 289 deletions

View File

@ -17,3 +17,4 @@ serde = { version = "1.0", features = ["derive"] }
# Derives
derive_more = "0.99"
err-impl = { path = "../err-impl" }

View File

@ -9,13 +9,13 @@ where
const BUF_BYTE_SIZE: usize;
/// The error type used for the operation
type FromError;
type FromError: std::fmt::Debug + std::error::Error;
/// Reads `bytes` and returns a result with `Self`
fn from_bytes(bytes: &[u8]) -> Result<Self, Self::FromError>;
/// The error type used for the operation
type ToError;
type ToError: std::fmt::Debug + std::error::Error;
/// Writes bytes into `bytes` from self
fn to_bytes(&self, bytes: &mut [u8]) -> Result<(), Self::ToError>;

View File

@ -14,5 +14,4 @@ pub mod table;
pub use digimon ::Digimon;
pub use item ::Item;
pub use digivolve::Digivolve;
pub use table::Table;

View File

@ -7,13 +7,13 @@
//!
//! | Offset | Size | Type | Name | Location | Details |
//! |--------|------|----------------------|---------------------------|--------------------------------|-------------------------------------------------------------------------------------|
//! | 0x0 | 0x15 | `char[0x15]` | Name | `basic.name` | |
//! | 0x15 | 0x2 | `u16` | Unknown | `basic.unknown_1` | Most likely contains the digimon's model |
//! | 0x17 | 0x1 | `u8` | Speciality & Level | `basic.speciality basic.level` | The bottom nibble of this byte is the level, while the top nibble is the speciality |
//! | 0x18 | 0x1 | `u8` | DP | `basic.dp_cost` | |
//! | 0x19 | 0x1 | `u8` | +P | `basic.dp_give` | |
//! | 0x1a | 0x1 | `u8` | Unknown | `basic.unknown_0` | Is` 0` for all digimon |
//! | 0x1b | 0x2 | `u16` | Health | `basic.hp` | |
//! | 0x0 | 0x15 | `char[0x15]` | Name | `name` | |
//! | 0x15 | 0x2 | `u16` | Unknown | `unknown_1` | Most likely contains the digimon's model |
//! | 0x17 | 0x1 | `u8` | Speciality & Level | `speciality level` | The bottom nibble of this byte is the level, while the top nibble is the speciality |
//! | 0x18 | 0x1 | `u8` | DP | `dp_cost` | |
//! | 0x19 | 0x1 | `u8` | +P | `dp_give` | |
//! | 0x1a | 0x1 | `u8` | Unknown | `unknown_0` | Is` 0` for all digimon |
//! | 0x1b | 0x2 | `u16` | Health | `hp` | |
//! | 0x1d | 0x1c | [`Move`] | Circle Move | `moves.circle` | |
//! | 0x39 | 0x1c | [`Move`] | Triangle move | `moves.triangle` | |
//! | 0x55 | 0x1c | [`Move`] | Cross move | `moves.cross` | |
@ -39,26 +39,12 @@ use crate::game::{
}
};
/// A digimon card
/// The digimon card itself
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Digimon
{
/// The basic info of the digimon
pub basic: Basic,
/// The moves
pub moves: Moves,
/// The support
pub support: Support,
}
/// The basic properties of a digimon
#[derive(PartialEq, Eq, Clone, Hash, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct Basic
{
/// The digimon's name
pub name: String,
pub speciality: Speciality,
pub level: Level,
@ -73,6 +59,12 @@ pub struct Basic
// Unknown fields
pub unknown_0: u8,
pub unknown_1: u16,
/// The moves
pub moves: Moves,
/// The support
pub support: Support,
}
/// The moves a digimon has
@ -94,38 +86,23 @@ pub struct Support
pub unknown: u8,
/// The cross move effect
#[serde(default)]
pub cross_move: Option<CrossMoveEffect>,
/// The effect description
pub description: [String; 4],
/// The effect arrow color
#[serde(default)]
pub arrow_color: Option<ArrowColor>,
/// The effect conditions
pub conditions: SupportConditions,
#[serde(default)]
pub conditions: [Option<SupportCondition>; 2],
/// The effects themselves
pub effects: SupportSupport,
}
/// All of the support effects
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct SupportSupport
{
pub first : Option<SupportEffect>,
pub second: Option<SupportEffect>,
pub third : Option<SupportEffect>,
}
/// All of the support effect conditions
#[derive(PartialEq, Eq, Clone, Copy, Hash, Debug)]
#[derive(serde::Serialize, serde::Deserialize)]
pub struct SupportConditions
{
pub first : Option<SupportCondition>,
pub second: Option<SupportCondition>,
#[serde(default)]
pub effects: [Option<SupportEffect>; 3],
}
/// The error type thrown by [`FromBytes`]
@ -140,8 +117,6 @@ pub enum FromBytesError
#[display(fmt = "The {} support effect description could not be converted to a string", rank)]
SupportEffectDescriptionToString {
rank: &'static str,
err: util::ReadNullTerminatedStringError,
},
@ -165,8 +140,6 @@ pub enum FromBytesError
#[display(fmt = "Unable to read the {0} support effect condition", rank)]
SupportCondition {
rank: &'static str,
err: crate::game::card::property::support_condition::FromBytesError,
},
@ -174,8 +147,6 @@ pub enum FromBytesError
#[display(fmt = "Unable to read the {} support effect", rank)]
SupportEffect {
rank: &'static str,
err: crate::game::card::property::support_effect::FromBytesError,
},
@ -183,12 +154,26 @@ pub enum FromBytesError
#[display(fmt = "Unable to read the {} move", name)]
Move {
name: &'static str,
err: crate::game::card::property::moves::FromBytesError,
},
}
impl std::error::Error for FromBytesError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::NameToString(err) |
Self::SupportEffectDescriptionToString{ err, .. } => Some(err),
Self::UnknownSpeciality(err) => Some(err),
Self::UnknownLevel(err) => Some(err),
Self::UnknownEffectArrowColor(err) => Some(err),
Self::UnknownCrossMoveEffect(err) => Some(err),
Self::SupportCondition{err, ..} => Some(err),
Self::SupportEffect{err, ..} => Some(err),
Self::Move{err, ..} => Some(err),
}
}
}
/// The error type thrown by `ToBytes`
#[derive(Debug, derive_more::Display)]
pub enum ToBytesError
@ -197,8 +182,6 @@ pub enum ToBytesError
#[display(fmt = r#"The name "{}" is too long to be written to file"#, name)]
NameTooLong {
name: String,
err: crate::game::util::WriteNullTerminatedStringError,
},
@ -208,15 +191,11 @@ pub enum ToBytesError
name: String,
},
/// A support effect description was too long to be written to file
#[display(fmt = r#"The {0} support effect description "{1}" is too long to be written to file"#, rank, string)]
SupportEffectDescriptionTooLong {
string: String,
rank: String,
err: crate::game::util::WriteNullTerminatedStringError,
},
@ -227,196 +206,199 @@ pub enum ToBytesError
rank: String,
},
/// Unable to write a move
#[display(fmt = "Unable to write the {} move", name)]
Move {
name: &'static str,
err: crate::game::card::property::moves::ToBytesError,
},
}
// Impl
//--------------------------------------------------------------------------------------------------
// Bytes
impl Bytes for Digimon
{
const BUF_BYTE_SIZE : usize = 0x138;
type FromError = FromBytesError;
fn from_bytes(bytes: &[u8]) -> Result<Self, Self::FromError>
{
// Note: We can't use `TryInto` because it only supports arrays up to 32
// SAFETY: Safe as we checked the length
assert!(bytes.len() == Self::BUF_BYTE_SIZE);
let bytes: &[u8; Self::BUF_BYTE_SIZE] = unsafe {
#[allow(clippy::as_conversions)]
&*( bytes.as_ptr() as *const [u8; Self::BUF_BYTE_SIZE] )
};
impl std::error::Error for ToBytesError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::NameTooLong{err, ..} |
Self::SupportEffectDescriptionTooLong{ err, .. } => Some(err),
// Return the struct after building it
Ok( Self {
// 0x0 - 0x1d
basic: Basic {
name : util::read_null_terminated_string( &bytes[0x0..0x15] ) .map_err(FromBytesError::NameToString)?.to_string(),
unknown_1 : LittleEndian::read_u16( &bytes[0x15..0x17] ),
speciality: Speciality::from_bytes( &[(bytes[0x17] & 0xF0) >> 4] ) .map_err(FromBytesError::UnknownSpeciality)?,
level : Level::from_bytes( &[(bytes[0x17] & 0x0F) >> 0] ) .map_err(FromBytesError::UnknownLevel )?,
dp_cost : bytes[0x18],
dp_give : bytes[0x19],
unknown_0 : bytes[0x1a],
hp : LittleEndian::read_u16( &bytes[0x1b..0x1d] ),
},
// 0x1d - 0x71
moves: Moves {
circle : Move::from_bytes( &bytes[0x1d..0x39] ) .map_err(|err| FromBytesError::Move{ name: "circle" , err })?,
triangle: Move::from_bytes( &bytes[0x39..0x55] ) .map_err(|err| FromBytesError::Move{ name: "triangle", err })?,
cross : Move::from_bytes( &bytes[0x55..0x71] ) .map_err(|err| FromBytesError::Move{ name: "cross" , err })?,
},
// 0x71 - 0x138
support: Support {
conditions: SupportConditions {
first: if bytes[0x73] != 0 { Some(
SupportCondition::from_bytes( &bytes[0x71..0x91] ) .map_err(|err| FromBytesError::SupportCondition{ rank: "1st", err })?
)} else { None },
second: if bytes[0x93] != 0 { Some(
SupportCondition::from_bytes( &bytes[0x91..0xb1] ) .map_err(|err| FromBytesError::SupportCondition{ rank: "2nd", err })?
)} else { None },
},
effects: SupportSupport {
first: if bytes[0xb1] != 0 { Some(
SupportEffect::from_bytes( &bytes[0xb1..0xc1] ) .map_err(|err| FromBytesError::SupportEffect{ rank: "1st", err })?
)} else { None },
second: if bytes[0xc1] != 0 { Some(
SupportEffect::from_bytes( &bytes[0xc1..0xd1] ) .map_err(|err| FromBytesError::SupportEffect{ rank: "2nd", err })?
)} else { None },
third: if bytes[0xd1] != 0 { Some(
SupportEffect::from_bytes( &bytes[0xd1..0xe1] ) .map_err(|err| FromBytesError::SupportEffect{ rank: "3rd", err })?
)} else { None },
},
cross_move: if bytes[0xe1] != 0 { Some(
CrossMoveEffect::from_bytes( &[ bytes[0xe1] ] ) .map_err(FromBytesError::UnknownCrossMoveEffect)?
)} else { None },
unknown: bytes[0xe2],
arrow_color: if bytes[0xe3] != 0 {
Some( ArrowColor::from_bytes( &bytes[0xe3..0xe4] ) .map_err(FromBytesError::UnknownEffectArrowColor)? )
} else { None },
description: [
util::read_null_terminated_string( &bytes[0x0e4..0x0f9] ) .map_err(|err| FromBytesError::SupportEffectDescriptionToString{ rank: "1st", err })?.to_string(),
util::read_null_terminated_string( &bytes[0x0f9..0x10e] ) .map_err(|err| FromBytesError::SupportEffectDescriptionToString{ rank: "2nd", err })?.to_string(),
util::read_null_terminated_string( &bytes[0x10e..0x123] ) .map_err(|err| FromBytesError::SupportEffectDescriptionToString{ rank: "3rd", err })?.to_string(),
util::read_null_terminated_string( &bytes[0x123..0x138] ) .map_err(|err| FromBytesError::SupportEffectDescriptionToString{ rank: "4th", err })?.to_string(),
],
},
})
}
type ToError = ToBytesError;
fn to_bytes(&self, bytes: &mut [u8]) -> Result<(), Self::ToError>
{
// Basic
//--------------------------------------------------------------------------------------------------
// Name
// If it's not valid ascii, return Err
// If we cannot write it to the buffer, return Err
if !self.basic.name.chars().all(|c| c.is_ascii() && !c.is_ascii_control()) { return Err( ToBytesError::NameNotAscii{name: self.basic.name.clone()} ); }
bytes[0x0..0x15].copy_from_slice(
util::write_null_terminated_string(&self.basic.name, &mut [0u8; 0x15])
.map_err(|err| ToBytesError::NameTooLong{ name: self.basic.name.clone(), err })?
);
// Unknown 1
LittleEndian::write_u16(&mut bytes[0x15..0x17], self.basic.unknown_1);
// Speciality / Level
{
let (mut speciality_byte, mut level_byte) = ( [0u8], [0u8] );
self.basic.speciality.to_bytes(&mut speciality_byte)?;
self.basic.level .to_bytes(&mut level_byte)?;
// Merge them
bytes[0x17] = (speciality_byte[0] << 4) | level_byte[0];
}
// DP / +P
bytes[0x18] = self.basic.dp_cost;
bytes[0x19] = self.basic.dp_give;
// Unknown
bytes[0x1a] = self.basic.unknown_0;
// Health
LittleEndian::write_u16(&mut bytes[0x1b..0x1d], self.basic.hp);
//--------------------------------------------------------------------------------------------------
// Moves
self.moves.circle .to_bytes(&mut bytes[0x1d..0x39]).map_err(|err| ToBytesError::Move{ name: "circle" , err })?;
self.moves.triangle.to_bytes(&mut bytes[0x39..0x55]).map_err(|err| ToBytesError::Move{ name: "triangle", err })?;
self.moves.cross .to_bytes(&mut bytes[0x55..0x71]).map_err(|err| ToBytesError::Move{ name: "cross" , err })?;
// Support
// Note: Although support conditions and effects aren't written if they're None,
// a bit pattern of all 0s is a valid pattern and means "None" to the game.
//--------------------------------------------------------------------------------------------------
// Support conditions
if let Some(support_condition) = &self.support.conditions.first { support_condition.to_bytes(&mut bytes[0x71..0x91])?; }
if let Some(support_condition) = &self.support.conditions.second { support_condition.to_bytes(&mut bytes[0x91..0xb1])?; }
// Support effects
if let Some(support_effect) = &self.support.effects.first { support_effect.to_bytes(&mut bytes[0xb1..0xc1])?; }
if let Some(support_effect) = &self.support.effects.second { support_effect.to_bytes(&mut bytes[0xc1..0xd1])?; }
if let Some(support_effect) = &self.support.effects.third { support_effect.to_bytes(&mut bytes[0xd1..0xe1])?; }
// Cross move
if let Some(cross_move) = self.support.cross_move { cross_move.to_bytes(&mut bytes[0xe1..0xe2])? };
// Unknown
bytes[0xe2] = self.support.unknown;
// Support arrow color
if let Some(arrow_color) = self.support.arrow_color { arrow_color.to_bytes( &mut bytes[0xe3..0xe4] )?; }
// Write the support effects
for (index, line) in self.support.description.iter().enumerate()
{
// If it's not valid ascii, return Err
if !line.chars().all(|c| c.is_ascii() && !c.is_ascii_control()) {
return Err( ToBytesError::SupportEffectDescriptionNotAscii{
name: line.clone(),
rank: util::as_ordinal((index+1) as u64),
});
}
// If we cannot write it to the buffer, return Err
bytes[0x0e4 + (0x15 * index) .. 0x0f9 + (0x15 * index)].copy_from_slice(
util::write_null_terminated_string(line, &mut [0u8; 0x15])
.map_err(|err| ToBytesError::SupportEffectDescriptionTooLong {
string: line.clone(),
rank: util::as_ordinal((index+1) as u64),
err
})?
);
}
//--------------------------------------------------------------------------------------------------
// Return Ok
Ok(())
Self::NameNotAscii{ .. } |
Self::SupportEffectDescriptionNotAscii{ .. } => None,
Self::Move{err, ..} => Some(err),
}
}
//--------------------------------------------------------------------------------------------------
}
impl Bytes for Digimon
{
const BUF_BYTE_SIZE : usize = 0x138;
type FromError = FromBytesError;
fn from_bytes(bytes: &[u8]) -> Result<Self, Self::FromError>
{
// Note: We can't use `TryInto` because it only supports arrays up to 32
// SAFETY: Safe as we checked the length
assert!(bytes.len() == Self::BUF_BYTE_SIZE);
let bytes: &[u8; Self::BUF_BYTE_SIZE] = unsafe {
#[allow(clippy::as_conversions)]
&*( bytes.as_ptr() as *const [u8; Self::BUF_BYTE_SIZE] )
};
// Return the struct after building it
Ok( Self {
// 0x0 - 0x1d
name : util::read_null_terminated_string( &bytes[0x0..0x15] ) .map_err(FromBytesError::NameToString)?.to_string(),
unknown_1 : LittleEndian::read_u16( &bytes[0x15..0x17] ),
speciality: Speciality::from_bytes( &[(bytes[0x17] & 0xF0) >> 4] ) .map_err(FromBytesError::UnknownSpeciality)?,
level : Level::from_bytes( &[(bytes[0x17] & 0x0F) >> 0] ) .map_err(FromBytesError::UnknownLevel )?,
dp_cost : bytes[0x18],
dp_give : bytes[0x19],
unknown_0 : bytes[0x1a],
hp : LittleEndian::read_u16( &bytes[0x1b..0x1d] ),
// 0x1d - 0x71
moves: Moves {
circle : Move::from_bytes( &bytes[0x1d..0x39] ) .map_err(|err| FromBytesError::Move{ name: "circle" , err })?,
triangle: Move::from_bytes( &bytes[0x39..0x55] ) .map_err(|err| FromBytesError::Move{ name: "triangle", err })?,
cross : Move::from_bytes( &bytes[0x55..0x71] ) .map_err(|err| FromBytesError::Move{ name: "cross" , err })?,
},
// 0x71 - 0x138
support: Support {
conditions: [
if bytes[0x73] != 0 { Some(
SupportCondition::from_bytes( &bytes[0x71..0x91] ) .map_err(|err| FromBytesError::SupportCondition{ rank: "1st", err })?
)} else { None },
if bytes[0x93] != 0 { Some(
SupportCondition::from_bytes( &bytes[0x91..0xb1] ) .map_err(|err| FromBytesError::SupportCondition{ rank: "2nd", err })?
)} else { None },
],
effects: [
if bytes[0xb1] != 0 { Some(
SupportEffect::from_bytes( &bytes[0xb1..0xc1] ) .map_err(|err| FromBytesError::SupportEffect{ rank: "1st", err })?
)} else { None },
if bytes[0xc1] != 0 { Some(
SupportEffect::from_bytes( &bytes[0xc1..0xd1] ) .map_err(|err| FromBytesError::SupportEffect{ rank: "2nd", err })?
)} else { None },
if bytes[0xd1] != 0 { Some(
SupportEffect::from_bytes( &bytes[0xd1..0xe1] ) .map_err(|err| FromBytesError::SupportEffect{ rank: "3rd", err })?
)} else { None },
],
cross_move: if bytes[0xe1] != 0 { Some(
CrossMoveEffect::from_bytes( &[ bytes[0xe1] ] ) .map_err(FromBytesError::UnknownCrossMoveEffect)?
)} else { None },
unknown: bytes[0xe2],
arrow_color: if bytes[0xe3] != 0 {
Some( ArrowColor::from_bytes( &bytes[0xe3..0xe4] ) .map_err(FromBytesError::UnknownEffectArrowColor)? )
} else { None },
description: [
util::read_null_terminated_string( &bytes[0x0e4..0x0f9] ) .map_err(|err| FromBytesError::SupportEffectDescriptionToString{ rank: "1st", err })?.to_string(),
util::read_null_terminated_string( &bytes[0x0f9..0x10e] ) .map_err(|err| FromBytesError::SupportEffectDescriptionToString{ rank: "2nd", err })?.to_string(),
util::read_null_terminated_string( &bytes[0x10e..0x123] ) .map_err(|err| FromBytesError::SupportEffectDescriptionToString{ rank: "3rd", err })?.to_string(),
util::read_null_terminated_string( &bytes[0x123..0x138] ) .map_err(|err| FromBytesError::SupportEffectDescriptionToString{ rank: "4th", err })?.to_string(),
],
},
})
}
type ToError = ToBytesError;
fn to_bytes(&self, bytes: &mut [u8]) -> Result<(), Self::ToError>
{
// Basic
//--------------------------------------------------------------------------------------------------
// Name
// If it's not valid ascii, return Err
// If we cannot write it to the buffer, return Err
if !self.name.chars().all(|c| c.is_ascii() && !c.is_ascii_control()) { return Err( ToBytesError::NameNotAscii{name: self.name.clone()} ); }
bytes[0x0..0x15].copy_from_slice(
util::write_null_terminated_string(&self.name, &mut [0u8; 0x15])
.map_err(|err| ToBytesError::NameTooLong{ name: self.name.clone(), err })?
);
// Unknown 1
LittleEndian::write_u16(&mut bytes[0x15..0x17], self.unknown_1);
// Speciality / Level
{
let (mut speciality_byte, mut level_byte) = ( [0u8], [0u8] );
self.speciality.to_bytes(&mut speciality_byte)?;
self.level .to_bytes(&mut level_byte)?;
// Merge them
bytes[0x17] = (speciality_byte[0] << 4) | level_byte[0];
}
// DP / +P
bytes[0x18] = self.dp_cost;
bytes[0x19] = self.dp_give;
// Unknown
bytes[0x1a] = self.unknown_0;
// Health
LittleEndian::write_u16(&mut bytes[0x1b..0x1d], self.hp);
//--------------------------------------------------------------------------------------------------
// Moves
self.moves.circle .to_bytes(&mut bytes[0x1d..0x39]).map_err(|err| ToBytesError::Move{ name: "circle" , err })?;
self.moves.triangle.to_bytes(&mut bytes[0x39..0x55]).map_err(|err| ToBytesError::Move{ name: "triangle", err })?;
self.moves.cross .to_bytes(&mut bytes[0x55..0x71]).map_err(|err| ToBytesError::Move{ name: "cross" , err })?;
// Support
// Note: Although support conditions and effects aren't written if they're None,
// a bit pattern of all 0s is a valid pattern and means "None" to the game.
//--------------------------------------------------------------------------------------------------
// Support conditions
if let Some(support_condition) = &self.support.conditions[0] { support_condition.to_bytes(&mut bytes[0x71..0x91])?; }
if let Some(support_condition) = &self.support.conditions[1] { support_condition.to_bytes(&mut bytes[0x91..0xb1])?; }
// Support effects
if let Some(support_effect) = &self.support.effects[0] { support_effect.to_bytes(&mut bytes[0xb1..0xc1])?; }
if let Some(support_effect) = &self.support.effects[1] { support_effect.to_bytes(&mut bytes[0xc1..0xd1])?; }
if let Some(support_effect) = &self.support.effects[2] { support_effect.to_bytes(&mut bytes[0xd1..0xe1])?; }
// Cross move
if let Some(cross_move) = self.support.cross_move { cross_move.to_bytes(&mut bytes[0xe1..0xe2])? };
// Unknown
bytes[0xe2] = self.support.unknown;
// Support arrow color
if let Some(arrow_color) = self.support.arrow_color { arrow_color.to_bytes( &mut bytes[0xe3..0xe4] )?; }
// Write the support effects
for (index, line) in self.support.description.iter().enumerate()
{
// If it's not valid ascii, return Err
if !line.chars().all(|c| c.is_ascii() && !c.is_ascii_control()) {
return Err( ToBytesError::SupportEffectDescriptionNotAscii{
name: line.clone(),
rank: util::as_ordinal((index+1) as u64),
});
}
// If we cannot write it to the buffer, return Err
bytes[0x0e4 + (0x15 * index) .. 0x0f9 + (0x15 * index)].copy_from_slice(
util::write_null_terminated_string(line, &mut [0u8; 0x15])
.map_err(|err| ToBytesError::SupportEffectDescriptionTooLong {
string: line.clone(),
rank: util::as_ordinal((index+1) as u64),
err
})?
);
}
//--------------------------------------------------------------------------------------------------
// Return Ok
Ok(())
}
}

View File

@ -85,8 +85,6 @@ use serde::Deserialize;
#[display(fmt = "Unable to convert the {} support effect description to a string", rank)]
SupportEffectDescriptionToString {
rank: &'static str,
err: util::ReadNullTerminatedStringError,
},
@ -95,8 +93,6 @@ use serde::Deserialize;
SupportEffectCondition {
rank: &'static str,
digivolve_pos: u64,
err: crate::game::card::property::support_condition::FromBytesError,
},
@ -104,12 +100,24 @@ use serde::Deserialize;
#[display(fmt = "Unable to read the {} support effect", rank)]
SupportEffect {
rank: &'static str,
err: crate::game::card::property::support_effect::FromBytesError,
},
}
impl std::error::Error for FromBytesError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::NameToString(err) |
Self::SupportEffectDescriptionToString{err, ..} => Some(err),
Self::EffectArrowColor(err) => Some(err),
Self::SupportEffectCondition{err, ..} => Some(err),
Self::SupportEffect{err, ..} => Some(err),
}
}
}
/// The error type thrown by `ToBytes`
#[derive(Debug, derive_more::Display)]
pub enum ToBytesError
@ -125,6 +133,15 @@ use serde::Deserialize;
string: String,
},
}
impl std::error::Error for ToBytesError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::NameTooLong(..) |
Self::SupportEffectDescriptionTooLong{ .. } => None,
}
}
}
//--------------------------------------------------------------------------------------------------
// Impl

View File

@ -81,8 +81,6 @@ use serde::Deserialize;
#[display(fmt = "Unable to convert the {} support effect description to a string", rank)]
SupportEffectDescriptionToString {
rank: &'static str,
err: util::ReadNullTerminatedStringError,
},
@ -91,8 +89,6 @@ use serde::Deserialize;
SupportCondition {
rank: &'static str,
item_pos: u64,
err: crate::game::card::property::support_condition::FromBytesError,
},
@ -100,12 +96,23 @@ use serde::Deserialize;
#[display(fmt = "Unable to read the {} support effect", rank)]
SupportEffect {
rank: &'static str,
err: crate::game::card::property::support_effect::FromBytesError,
},
}
impl std::error::Error for FromBytesError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::NameToString(err) |
Self::SupportEffectDescriptionToString{err, ..} => Some(err),
Self::EffectArrowColor(err) => Some(err),
Self::SupportCondition{err, ..} => Some(err),
Self::SupportEffect{err, ..} => Some(err),
}
}
}
/// The error type thrown by `ToBytes`
#[derive(Debug, derive_more::Display)]
pub enum ToBytesError
@ -121,6 +128,15 @@ use serde::Deserialize;
string: String,
},
}
impl std::error::Error for ToBytesError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::NameTooLong(..) |
Self::SupportEffectDescriptionTooLong{ .. } => None,
}
}
}
//--------------------------------------------------------------------------------------------------
// Impl

View File

@ -27,16 +27,16 @@ use byteorder::LittleEndian;
}
/// The error type thrown by `FromBytes`
#[derive(Debug, derive_more::Display)]
#[derive(Debug, derive_more::Display, err_impl::Error)]
pub enum FromBytesError
{
/// Unable to convert name to a string
#[display(fmt = "Unable to convert name to a string")]
NameToString( util::ReadNullTerminatedStringError ),
NameToString( #[error(source)] util::ReadNullTerminatedStringError ),
}
/// The error type thrown by `ToBytes`
#[derive(Debug, derive_more::Display)]
#[derive(Debug, derive_more::Display, err_impl::Error)]
pub enum ToBytesError
{
/// The name was too big to be written to file

View File

@ -50,6 +50,16 @@ use byteorder::{ByteOrder, LittleEndian};
#[display(fmt = "Unable to read the effect operation")]
Operation( crate::game::card::property::support_condition_operation::UnknownSupportConditionOperation ),
}
impl std::error::Error for FromBytesError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Condition(err) |
Self::PropertyArgument(err) => Some(err),
Self::Operation(err) => Some(err),
}
}
}
//--------------------------------------------------------------------------------------------------
// Impl

View File

@ -180,6 +180,17 @@ use crate::{
#[display(fmt = "An unknown attack type was found")]
AttackType( crate::game::card::property::attack_type::UnknownAttackType ),
}
impl std::error::Error for FromBytesError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::UnknownEffectType{ .. } => None,
Self::PropertyArgument{ err, ..} => Some(err),
Self::Operation(err) => Some(err),
Self::AttackType(err) => Some(err),
}
}
}
//--------------------------------------------------------------------------------------------------
// Impl

View File

@ -88,7 +88,7 @@ impl Table {
}
/// Error type for [`Structure::DeserializeError`]
/// Error type for [`Table::DeserializeError`]
#[derive(Debug)]
#[derive(derive_more::Display)]
pub enum DeserializeError {
@ -158,6 +158,61 @@ impl std::error::Error for DeserializeError {
}
}
/// Error type for [`Table::DeserializeError`]
#[derive(Debug)]
#[derive(derive_more::Display)]
pub enum SerializeError {
/// Unable to seek game file
#[display(fmt = "Unable to seek game file to card table")]
Seek( std::io::Error ),
/// Unable to write table header
#[display(fmt = "Unable to write table header")]
WriteHeader( std::io::Error ),
/// There were too many cards
#[display(fmt = "Too many cards in table ({} digimon, {} item, {} digivolve, {} / {} bytes max)",
"digimon_cards",
"item_cards",
"digivolve_cards",
" digimon_cards * (0x3 + Digimon ::BUF_BYTE_SIZE + 0x1) +
item_cards * (0x3 + Item ::BUF_BYTE_SIZE + 0x1) +
digivolve_cards * (0x3 + Digivolve::BUF_BYTE_SIZE + 0x1)",
Table::MAX_BYTE_SIZE
)]
TooManyCards {
digimon_cards: usize,
item_cards: usize,
digivolve_cards: usize,
},
/// Unable to write card header
#[display(fmt = "Unable to write card header for card id {}", id)]
WriteCardHeader {
id: usize,
err: std::io::Error,
},
/// Unable to write card footer
#[display(fmt = "Unable to write card footer for card id {}", id)]
WriteCardFooter {
id: usize,
err: std::io::Error,
},
}
impl std::error::Error for SerializeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Seek(err) |
Self::WriteHeader(err) |
Self::WriteCardHeader { err, .. } |
Self::WriteCardFooter { err, .. } => Some(err),
Self::TooManyCards { .. } => None,
}
}
}
impl Table {
/// Deserializes the card table from a game file
pub fn deserialize<R: Read + Write + Seek>(file: &mut GameFile<R>) -> Result<Self, DeserializeError> {
@ -266,34 +321,109 @@ impl Table {
})
}
/*
pub fn serialize<R: Read + Write + Seek>(&self, _file: &mut GameFile<R>) -> Result<(), !> {
todo!();
/*
pub fn serialize<R: Read + Write + Seek>(&self, file: &mut GameFile<R>) -> Result<(), SerializeError> {
// Get the final table size
// Note: + 0x4 here is for the `next` section
let table_size = self.digimons .len() * (Digimon ::BUF_BYTE_SIZE + 0x4) +
self.items .len() * (Item ::BUF_BYTE_SIZE + 0x4) +
self.digivolves.len() * (Digivolve::BUF_BYTE_SIZE + 0x4);
let table_size = table_size as u64;
let table_size = self.digimons .len() * (0x3 + Digimon ::BUF_BYTE_SIZE + 0x1) +
self.items .len() * (0x3 + Item ::BUF_BYTE_SIZE + 0x1) +
self.digivolves.len() * (0x3 + Digivolve::BUF_BYTE_SIZE + 0x1);
// If the total table size is bigger than the max, return Err
assert!(table_size > Table::MAX_BYTE_SIZE as u64, "Table is too big");
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 ) + 0x8 ) )
.map_err(SerializeError::Seek)?;
// Function to write a card to file
fn write_card<R: Read + Write + Seek, C: Bytes>(_file: &mut GameFile<R>, _card: &C, _cur_id: usize) {
/*
// Get the bytes
let mut bytes = [0u8; <C as Bytes>::BUF_BYTE_SIZE];
card.to_bytes(&mut bytes)
.expect("Unable to get digimon as bytes");
// Write the digimon buffer
file.write_all(&bytes)
.expect("Unable to write digimon card");
// And write the 'next' section
let mut buf = [0u8; 0x4];
match idx {
num if num + 1 == self.digimons.len() => CardType::Item .to_bytes( &mut buf[0x3..0x4] )?,
_ => CardType::Digimon.to_bytes( &mut buf[0x3..0x4] )?,
}
LittleEndian::write_u16( &mut buf[0x1..0x3], cur_id+1);
file.write_all(&buf)
.expect("");
*/
}
// Write all digimon, items and digivolves
for (id, digimon) in self.digimons.iter().enumerate() {
write_card(file, digimon, id);
}
for (id, item) in self.items.iter().enumerate() {
write_card(file, item, self.digimons.len() + id);
}
for (id, digivolve) in self.digivolves.iter().enumerate() {
write_card(file, digivolve, self.digimons.len() + self.items.len() + id);
}
/*
if table_size > Table::MAX_BYTE_SIZE as u64
enum Card<'a> {
Digimon (&'a Digimon ),
Item (&'a Item ),
Digivolve(&'a Digivolve),
}
// Then write all cards
for (idx, card) in std::iter::empty()
.chain(self.digimons .iter().map(Card::Digimon ))
.chain(self.items .iter().map(Card::Item ))
.chain(self.digivolves.iter().map(Card::Digivolve))
.enumerate()
{
return Err( WriteError::TableTooBig{size: table_size, max: Table::MAX_BYTE_SIZE as u64} );
let bytes = match card {
Card::Digimon(digimon) => {
let mut bytes = [0; Digimon::BUF_BYTE_SIZE];
digimon.to_bytes(&mut bytes);
&bytes as &[u8]
},
_ => &[],
};
// Write the buffer
file.write_all(&bytes)
.expect("Unable to write card");
// And write the 'next' section
let mut buf = [0u8; 0x4];
match idx {
num if num + 1 == self.digimons.len() => CardType::Item .to_bytes( &mut buf[0x3..0x4] )?,
_ => CardType::Digimon.to_bytes( &mut buf[0x3..0x4] )?,
}
LittleEndian::write_u16( &mut buf[0x1..0x3], (idx+1) as u16);
file.write_all(&buf)
.expect("");
}
*/
/*
// The current id
let mut cur_id = 0u16;
// Seek to the beginning of the card table
game_file.seek( std::io::SeekFrom::Start( u64::from( Table::START_ADDRESS ) + 0x8 ) )
.expect("Unable to seek to card table");
// Then write all cards, first digimon, then items, then digivolves
for (idx, digimon) in self.digimons.iter().enumerate()
@ -304,21 +434,21 @@ impl Table {
.expect("Unable to get digimon as bytes");
// Write the digimon buffer
game_file.write_all(&bytes)
file.write_all(&bytes)
.expect("Unable to write digimon card");
// And write the 'next' section
let mut buf = [0u8; 0x4];
match idx {
num if num + 1 == self.digimons.len() => { CardType::Item .to_bytes( &mut buf[0x3..0x4] )?; }
_ => { CardType::Digimon.to_bytes( &mut buf[0x3..0x4] )?; }
num if num + 1 == self.digimons.len() => CardType::Item .to_bytes( &mut buf[0x3..0x4] )?,
_ => CardType::Digimon.to_bytes( &mut buf[0x3..0x4] )?,
}
LittleEndian::write_u16( &mut buf[0x1..0x3], cur_id+1);
game_file.write_all(&buf)
.expect("")
file.write_all(&buf)
.expect("");
cur_id += 1;
}
@ -327,10 +457,10 @@ impl Table {
{
// Get the bytes
let mut bytes = [0u8; Item::BUF_BYTE_SIZE as usize];
item.to_bytes(&mut bytes).map_err(|err| WriteError::ConvertItem{id: cur_id, err})?;
item.to_bytes(&mut bytes).unwrap();//.map_err(|err| SerializeError::ConvertItem{id: cur_id, err})?;
// Write the item buffer
game_file.write_all(&bytes).map_err(|err| WriteError::WriteItem{id: cur_id, err})?;
file.write_all(&bytes).unwrap();//.map_err(|err| SerializeError::WriteItem{id: cur_id, err})?;
// And write the 'next' section
let mut buf = [0u8; 0x4];
@ -342,7 +472,7 @@ impl Table {
LittleEndian::write_u16( &mut buf[0x1..0x3], cur_id+1);
game_file.write_all(&buf).map_err(|err| WriteError::NextEntryInfo{ id: cur_id, err })?;
file.write_all(&buf).unwrap();//.map_err(|err| SerializeError::NextEntryInfo{ id: cur_id, err })?;
cur_id += 1;
}
@ -351,10 +481,10 @@ impl Table {
{
// Get the bytes
let mut bytes = [0u8; Digivolve::BUF_BYTE_SIZE as usize];
digivolve.to_bytes(&mut bytes).map_err(|err| WriteError::ConvertDigivolve{id: cur_id, err})?;
digivolve.to_bytes(&mut bytes).unwrap();//.map_err(|err| SerializeError::ConvertDigivolve{id: cur_id, err})?;
// Write the digimon buffer
game_file.write_all(&bytes).map_err(|err| WriteError::WriteDigivolve{id: cur_id, err})?;
file.write_all(&bytes).unwrap();//.map_err(|err| SerializeError::WriteDigivolve{id: cur_id, err})?;
// And write the 'next' section
let mut buf = [0u8; 0x4];
@ -364,15 +494,13 @@ impl Table {
_ => { CardType::Digivolve.to_bytes( &mut buf[0x3..0x4] )?; LittleEndian::write_u16( &mut buf[0x1..0x3], cur_id+1); }
}
game_file.write_all(&buf).map_err(|err| WriteError::NextEntryInfo{ id: cur_id, err })?;
file.write_all(&buf).unwrap();//.map_err(|err| SerializeError::NextEntryInfo{ id: cur_id, err })?;
cur_id += 1;
}
*/
// And return Ok
Ok(())
*/
}
*/
}

View File

@ -61,6 +61,15 @@
Utf8( std::str::Utf8Error ),
}
impl std::error::Error for ReadNullTerminatedStringError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::NoNull => None,
Self::Utf8(err) => Some(err),
}
}
}
/// Error type for `write_null_terminated_string`
#[derive(Debug, derive_more::Display)]
#[display(fmt = "The string was too long to write to the buffer ({0} / {1})", string_size, buf_size)]
@ -72,6 +81,9 @@
/// The buffer size
buf_size: usize,
}
// No source
impl std::error::Error for WriteNullTerminatedStringError { }
//--------------------------------------------------------------------------------------------------
// Impl

View File

@ -45,6 +45,7 @@
clippy::unreadable_literal, // More important to be able to copy the number with no formatting than it being readable
clippy::multiple_inherent_impl, // We prefer to separate certain methods by type and insert error types in between methods
clippy::identity_op, // Makes sense sometimes for symmetry
clippy::items_after_statements, // Sometimes we only introduce items when we first use them.
// TODO: Deal with casts eventually
clippy::cast_possible_wrap,