diff --git a/src/ascii_str_arr.rs b/src/ascii_str_arr.rs new file mode 100644 index 0000000..daeb485 --- /dev/null +++ b/src/ascii_str_arr.rs @@ -0,0 +1,212 @@ +//! Ascii string backed by an array + +// Modules +pub mod error; +mod visitor; + +// Exports +pub use error::{FromByteStringError, NotAsciiError, TooLongError}; + +// Imports +use ascii::{AsciiChar, AsciiStr}; +use std::{cmp::Ordering, convert::TryFrom, fmt, hash::Hash}; +use visitor::DeserializerVisitor; + +/// An ascii string backed by an array +#[derive(Eq, Clone, Copy)] +pub struct AsciiStrArr { + /// Characters + chars: [AsciiChar; N], + + /// Size + len: usize, +} + +impl AsciiStrArr { + /// Returns the length of this string + #[must_use] + pub const fn len(&self) -> usize { + self.len + } + + /// Returns if this string is empty + #[must_use] + pub const fn is_empty(&self) -> bool { + self.len() == 0 + } + + /// Converts this string to a `&[AsciiStr]` + #[must_use] + pub fn as_ascii_str(&self) -> &AsciiStr { + <&AsciiStr>::from(&self.chars[..self.len]) + } + + /// Converts this string to a `&mut [AsciiStr]` + #[must_use] + pub fn as_ascii_str_mut(&mut self) -> &mut AsciiStr { + <&mut AsciiStr>::from(&mut self.chars[..self.len]) + } + + /// Converts this string to a `&[u8]` + #[must_use] + pub fn as_bytes(&self) -> &[u8] { + self.as_ascii_str().as_bytes() + } + + /// Converts this string to a `&str` + #[must_use] + pub fn as_str(&self) -> &str { + self.as_ascii_str().as_str() + } + + /// Returns the `n`th character + #[must_use] + pub fn get(&self, n: usize) -> Option<&AsciiChar> { + self.chars.get(n) + } + + /// Returns the `n`th character mutably + #[must_use] + pub fn get_mut(&mut self, n: usize) -> Option<&mut AsciiChar> { + self.chars.get_mut(n) + } +} + +impl AsRef for AsciiStrArr { + fn as_ref(&self) -> &AsciiStr { + self.as_ascii_str() + } +} + +impl AsMut for AsciiStrArr { + fn as_mut(&mut self) -> &mut AsciiStr { + self.as_ascii_str_mut() + } +} + +impl std::ops::Index for AsciiStrArr { + type Output = AsciiChar; + + fn index(&self, idx: usize) -> &Self::Output { + self.get(idx).expect("Invalid index access") + } +} + +impl std::ops::IndexMut for AsciiStrArr { + fn index_mut(&mut self, idx: usize) -> &mut Self::Output { + self.get_mut(idx).expect("Invalid index access") + } +} + +impl PartialEq for AsciiStrArr { + fn eq(&self, other: &Self) -> bool { + AsciiStr::eq(self.as_ascii_str(), other.as_ascii_str()) + } +} + +impl PartialOrd for AsciiStrArr { + fn partial_cmp(&self, other: &Self) -> Option { + AsciiStr::partial_cmp(self.as_ascii_str(), other.as_ascii_str()) + } +} + +impl Ord for AsciiStrArr { + fn cmp(&self, other: &Self) -> Ordering { + AsciiStr::cmp(self.as_ascii_str(), other.as_ascii_str()) + } +} + +impl Hash for AsciiStrArr { + fn hash(&self, state: &mut H) { + AsciiStr::hash(self.as_ascii_str(), state) + } +} + +impl fmt::Debug for AsciiStrArr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + AsciiStr::fmt(self.as_ascii_str(), f) + } +} + +impl fmt::Display for AsciiStrArr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + AsciiStr::fmt(self.as_ascii_str(), f) + } +} + +impl<'de, const N: usize> serde::Deserialize<'de> for AsciiStrArr { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + match deserializer.deserialize_str(DeserializerVisitor) { + Ok(string) => Ok(string), + Err(err) => Err(err), + } + } +} + +impl serde::Serialize for AsciiStrArr { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_ascii_str().as_str()) + } +} + +// TODO: Generalize this to `impl From<&[AsciiChar; M]> for AsciiStrArr where M <= N` +impl From<&[AsciiChar; N]> for AsciiStrArr { + fn from(src: &[AsciiChar; N]) -> Self { + let mut chars = [AsciiChar::Null; N]; + chars.copy_from_slice(src); + Self { chars, len: N } + } +} + +// TODO: Generalize this to `impl From<[AsciiChar; M]> for AsciiStrArr where M <= N` +impl From<[AsciiChar; N]> for AsciiStrArr { + fn from(chars: [AsciiChar; N]) -> Self { + Self { chars, len: N } + } +} + +// TODO: Generalize this to `impl TryFrom<&[u8; M]> for AsciiStrArr where M <= N` +impl TryFrom<&[u8; N]> for AsciiStrArr { + type Error = NotAsciiError; + + fn try_from(byte_str: &[u8; N]) -> Result { + let mut ascii_str = [AsciiChar::Null; N]; + for (pos, &byte) in byte_str.iter().enumerate() { + ascii_str[pos] = AsciiChar::from_ascii(byte).map_err(|_| NotAsciiError { pos })?; + } + + Ok(Self::from(ascii_str)) + } +} + +impl TryFrom<&AsciiStr> for AsciiStrArr { + type Error = TooLongError; + + fn try_from(ascii_str: &AsciiStr) -> Result { + // Note: No space for null, this isn't null terminated + let len = ascii_str.len(); + if len > N { + return Err(TooLongError::); + } + + let mut chars = [AsciiChar::Null; N]; + chars[0..len].copy_from_slice(ascii_str.as_ref()); + Ok(Self { chars, len }) + } +} + +impl TryFrom<&[u8]> for AsciiStrArr { + type Error = FromByteStringError; + + fn try_from(byte_str: &[u8]) -> Result { + let ascii_str = AsciiStr::from_ascii(byte_str).map_err(FromByteStringError::NotAscii)?; + + Self::try_from(ascii_str).map_err(FromByteStringError::TooLong) + } +} diff --git a/src/ascii_str_arr/error.rs b/src/ascii_str_arr/error.rs new file mode 100644 index 0000000..5797f0c --- /dev/null +++ b/src/ascii_str_arr/error.rs @@ -0,0 +1,26 @@ +//! Errors + +/// Error returned when a string was too long to be converted +#[derive(Debug, thiserror::Error)] +#[error("String was too long (max is {} characters)", LEN)] +pub struct TooLongError; + +/// Error returned when an input string contained non-ascii characters +#[derive(Debug, thiserror::Error)] +#[error("String contained non-ascii characters (first found at {pos})")] +pub struct NotAsciiError { + /// Index that contained the first non-ascii character + pub pos: usize, +} + +/// Error returned from [`TryFrom<&[u8]> for AsciiStrArr`] +#[derive(Debug, thiserror::Error)] +pub enum FromByteStringError { + /// Too long + #[error("String was too long")] + TooLong(TooLongError), + + /// Not ascii + #[error("String contained non-ascii characters")] + NotAscii(ascii::AsAsciiStrError), +} diff --git a/src/ascii_str_arr/visitor.rs b/src/ascii_str_arr/visitor.rs new file mode 100644 index 0000000..336ba8c --- /dev/null +++ b/src/ascii_str_arr/visitor.rs @@ -0,0 +1,26 @@ +//! Visitor for [`AsciiStrArr`] + +// Imports +use super::AsciiStrArr; +use ascii::AsciiStr; +use std::{convert::TryFrom, fmt}; + +/// Visitor implementation +pub(super) struct DeserializerVisitor; + +impl<'de, const N: usize> serde::de::Visitor<'de> for DeserializerVisitor { + type Value = AsciiStrArr; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + // TODO: Maybe get the full string at compile time and use `write_str` + f.write_fmt(format_args!("An ascii string of length {} or less", N)) + } + + fn visit_str(self, value: &str) -> Result { + // Convert it to ascii + let ascii_str = AsciiStr::from_ascii(value).map_err(E::custom)?; + + // Try to convert it + AsciiStrArr::try_from(ascii_str).map_err(E::custom) + } +} diff --git a/src/game/card/digimon.rs b/src/game/card/digimon.rs index 1f59b30..163d7ae 100644 --- a/src/game/card/digimon.rs +++ b/src/game/card/digimon.rs @@ -10,9 +10,17 @@ use crate::{ array_split, array_split_mut, null_ascii_string::{self, NullAsciiString}, }, + AsciiStrArr, }; use byteorder::{ByteOrder, LittleEndian}; +// TODO: Remove these +/// Name alias for [`Digimon`] +type NameString = AsciiStrArr<0x14>; + +/// Effect description alias for [`Digimon`] +type EffectDescriptionString = AsciiStrArr<0x14>; + /// A digimon card /// /// Contains all information about each digimon card stored in the [`Card Table`](crate::game::card::table::Table) @@ -20,9 +28,7 @@ use byteorder::{ByteOrder, LittleEndian}; #[derive(serde::Serialize, serde::Deserialize)] pub struct Digimon { /// The digimon's name - /// - /// An ascii string with 20 characters at most - pub name: ascii::AsciiString, + pub name: NameString, /// The digimon's speciality /// @@ -62,9 +68,8 @@ pub struct Digimon { /// 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 description is split along 4 lines + pub effect_description: [EffectDescriptionString; 4], /// The effect's arrow color #[serde(default)] @@ -97,19 +102,19 @@ pub enum FromBytesError { /// Unable to read the first support effect description #[error("Unable to read the first line of the effect description")] - EffectDescriptionFirst(#[source] null_ascii_string::ReadError), + EffectDescription1(#[source] null_ascii_string::ReadError), /// Unable to read the second support effect description #[error("Unable to read the second line of the effect description")] - EffectDescriptionSecond(#[source] null_ascii_string::ReadError), + EffectDescription2(#[source] null_ascii_string::ReadError), /// Unable to read the third support effect description #[error("Unable to read the third line of the effect description")] - EffectDescriptionThird(#[source] null_ascii_string::ReadError), + EffectDescription3(#[source] null_ascii_string::ReadError), /// Unable to read the fourth support effect description #[error("Unable to read the fourth line of the effect description")] - EffectDescriptionFourth(#[source] null_ascii_string::ReadError), + EffectDescription4(#[source] null_ascii_string::ReadError), /// An unknown speciality was found #[error("Unknown speciality found")] @@ -162,39 +167,8 @@ pub enum FromBytesError { /// Error type for [`Bytes::to_bytes`] #[derive(PartialEq, Eq, Clone, Copy, Debug, thiserror::Error)] +#[allow(clippy::pub_enum_variant_names)] // This is a general error, not a specific effect error pub enum ToBytesError { - /// Unable to write the digimon name - #[error("Unable to write the digimon name")] - Name(#[source] null_ascii_string::WriteError), - - /// Unable to write the first support effect description - #[error("Unable to write the first line of the effect description")] - EffectDescriptionFirst(#[source] null_ascii_string::WriteError), - - /// Unable to write the second support effect description - #[error("Unable to write the second line of the effect description")] - EffectDescriptionSecond(#[source] null_ascii_string::WriteError), - - /// Unable to write the third support effect description - #[error("Unable to write the third line of the effect description")] - EffectDescriptionThird(#[source] null_ascii_string::WriteError), - - /// Unable to write the fourth support effect description - #[error("Unable to write the fourth line of the effect description")] - EffectDescriptionFourth(#[source] null_ascii_string::WriteError), - - /// Unable to write the circle move - #[error("Unable to write the circle move")] - MoveCircle(#[source] property::moves::ToBytesError), - - /// Unable to write the triangle move - #[error("Unable to write the triangle move")] - MoveTriangle(#[source] property::moves::ToBytesError), - - /// Unable to write the cross move - #[error("Unable to write the cross move")] - MoveCross(#[source] property::moves::ToBytesError), - /// Unable to write the first effect #[error("Unable to write the first effect")] EffectFirst(#[source] property::effect::ToBytesError), @@ -242,7 +216,7 @@ impl Bytes for Digimon { // Return the struct after building it Ok(Self { - name: bytes.name.read_string().map_err(FromBytesError::Name)?.to_ascii_string(), + name: NullAsciiString::read_string(bytes.name).map_err(FromBytesError::Name)?, speciality: Speciality::from_bytes(&((bytes.speciality_level & 0xF0) >> 4)).map_err(FromBytesError::Speciality)?, @@ -275,26 +249,10 @@ impl Bytes for Digimon { effect_arrow_color: Option::::from_bytes(bytes.effect_arrow_color).map_err(FromBytesError::ArrowColor)?, effect_description: [ - bytes - .effect_description_0 - .read_string() - .map_err(FromBytesError::EffectDescriptionFirst)? - .to_ascii_string(), - bytes - .effect_description_1 - .read_string() - .map_err(FromBytesError::EffectDescriptionSecond)? - .to_ascii_string(), - bytes - .effect_description_2 - .read_string() - .map_err(FromBytesError::EffectDescriptionThird)? - .to_ascii_string(), - bytes - .effect_description_3 - .read_string() - .map_err(FromBytesError::EffectDescriptionFourth)? - .to_ascii_string(), + bytes.effect_description_0.read_string().map_err(FromBytesError::EffectDescription1)?, + bytes.effect_description_1.read_string().map_err(FromBytesError::EffectDescription2)?, + bytes.effect_description_2.read_string().map_err(FromBytesError::EffectDescription3)?, + bytes.effect_description_3.read_string().map_err(FromBytesError::EffectDescription4)?, ], // Unknown @@ -332,7 +290,7 @@ impl Bytes for Digimon { ); // Name - bytes.name.write_string(&self.name).map_err(ToBytesError::Name)?; + bytes.name.write_string(&self.name); // Speciality / Level { @@ -354,9 +312,9 @@ impl Bytes for Digimon { 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).into_ok(); + self.move_triangle.to_bytes(bytes.move_triangle).into_ok(); + self.move_cross.to_bytes(bytes.move_cross).into_ok(); // Effects self.effect_conditions[0].to_bytes(bytes.condition_first).into_ok(); @@ -370,22 +328,10 @@ impl Bytes for Digimon { Option::::to_bytes(&self.effect_arrow_color, bytes.effect_arrow_color).into_ok(); - bytes - .effect_description_0 - .write_string(&self.effect_description[0]) - .map_err(ToBytesError::EffectDescriptionFirst)?; - bytes - .effect_description_1 - .write_string(&self.effect_description[1]) - .map_err(ToBytesError::EffectDescriptionSecond)?; - bytes - .effect_description_2 - .write_string(&self.effect_description[2]) - .map_err(ToBytesError::EffectDescriptionThird)?; - bytes - .effect_description_3 - .write_string(&self.effect_description[3]) - .map_err(ToBytesError::EffectDescriptionFourth)?; + bytes.effect_description_0.write_string(&self.effect_description[0]); + bytes.effect_description_1.write_string(&self.effect_description[1]); + bytes.effect_description_2.write_string(&self.effect_description[2]); + bytes.effect_description_3.write_string(&self.effect_description[3]); // Unknown LittleEndian::write_u16(bytes.unknown_15, self.unknown_15); diff --git a/src/game/card/digivolve.rs b/src/game/card/digivolve.rs index d554ff7..716c182 100644 --- a/src/game/card/digivolve.rs +++ b/src/game/card/digivolve.rs @@ -7,8 +7,16 @@ use crate::{ array_split, array_split_mut, null_ascii_string::{self, NullAsciiString}, }, + AsciiStrArr, }; +// TODO: Remove these +/// Name alias for [`Digimon`] +type NameString = AsciiStrArr<0x14>; + +/// Effect description alias for [`Digimon`] +type EffectDescriptionString = AsciiStrArr<0x14>; + /// A digivolve card /// /// Contains all information about each digivolve card stored in the [`Card Table`](crate::game::card::table::Table) @@ -16,15 +24,12 @@ use crate::{ #[derive(serde::Serialize, serde::Deserialize)] pub struct Digivolve { /// The item's name - /// - /// An ascii string with 20 characters at most - pub name: ascii::AsciiString, + pub name: NameString, /// 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 description is split along 4 lines + pub effect_description: [EffectDescriptionString; 4], /// Unknown field at `0x15` pub unknown_15: [u8; 3], @@ -39,49 +44,25 @@ pub enum FromBytesError { /// Unable to read the first support effect description #[error("Unable to read the first line of the effect description")] - EffectDescriptionFirst(#[source] null_ascii_string::ReadError), + EffectDescription1(#[source] null_ascii_string::ReadError), /// Unable to read the second support effect description #[error("Unable to read the second line of the effect description")] - EffectDescriptionSecond(#[source] null_ascii_string::ReadError), + EffectDescription2(#[source] null_ascii_string::ReadError), /// Unable to read the third support effect description #[error("Unable to read the third line of the effect description")] - EffectDescriptionThird(#[source] null_ascii_string::ReadError), + EffectDescription3(#[source] null_ascii_string::ReadError), /// Unable to read the fourth support effect description #[error("Unable to read the fourth line of the effect description")] - EffectDescriptionFourth(#[source] null_ascii_string::ReadError), -} - -/// Error type for [`Bytes::to_bytes`] -#[derive(PartialEq, Eq, Clone, Copy, Debug, thiserror::Error)] -pub enum ToBytesError { - /// Unable to write the digimon name - #[error("Unable to write the digimon name")] - Name(#[source] null_ascii_string::WriteError), - - /// Unable to write the first support effect description - #[error("Unable to write the first line of the effect description")] - EffectDescriptionFirst(#[source] null_ascii_string::WriteError), - - /// Unable to write the second support effect description - #[error("Unable to write the second line of the effect description")] - EffectDescriptionSecond(#[source] null_ascii_string::WriteError), - - /// Unable to write the third support effect description - #[error("Unable to write the third line of the effect description")] - EffectDescriptionThird(#[source] null_ascii_string::WriteError), - - /// Unable to write the fourth support effect description - #[error("Unable to write the fourth line of the effect description")] - EffectDescriptionFourth(#[source] null_ascii_string::WriteError), + EffectDescription4(#[source] null_ascii_string::ReadError), } impl Bytes for Digivolve { type ByteArray = [u8; 0x6c]; type FromError = FromBytesError; - type ToError = ToBytesError; + type ToError = !; fn from_bytes(bytes: &Self::ByteArray) -> Result { // Split bytes @@ -96,30 +77,14 @@ impl Bytes for Digivolve { Ok(Self { // Name - name: bytes.name.read_string().map_err(FromBytesError::Name)?.to_ascii_string(), + name: bytes.name.read_string().map_err(FromBytesError::Name)?, // Effect effect_description: [ - bytes - .effect_description_0 - .read_string() - .map_err(FromBytesError::EffectDescriptionFirst)? - .to_ascii_string(), - bytes - .effect_description_1 - .read_string() - .map_err(FromBytesError::EffectDescriptionSecond)? - .to_ascii_string(), - bytes - .effect_description_2 - .read_string() - .map_err(FromBytesError::EffectDescriptionThird)? - .to_ascii_string(), - bytes - .effect_description_3 - .read_string() - .map_err(FromBytesError::EffectDescriptionFourth)? - .to_ascii_string(), + bytes.effect_description_0.read_string().map_err(FromBytesError::EffectDescription1)?, + bytes.effect_description_1.read_string().map_err(FromBytesError::EffectDescription2)?, + bytes.effect_description_2.read_string().map_err(FromBytesError::EffectDescription3)?, + bytes.effect_description_3.read_string().map_err(FromBytesError::EffectDescription4)?, ], // Unknown @@ -139,25 +104,13 @@ impl Bytes for Digivolve { ); // Name - bytes.name.write_string(&self.name).map_err(ToBytesError::Name)?; + bytes.name.write_string(&self.name); // Effects - bytes - .effect_description_0 - .write_string(&self.effect_description[0]) - .map_err(ToBytesError::EffectDescriptionFirst)?; - bytes - .effect_description_1 - .write_string(&self.effect_description[1]) - .map_err(ToBytesError::EffectDescriptionSecond)?; - bytes - .effect_description_2 - .write_string(&self.effect_description[2]) - .map_err(ToBytesError::EffectDescriptionThird)?; - bytes - .effect_description_3 - .write_string(&self.effect_description[3]) - .map_err(ToBytesError::EffectDescriptionFourth)?; + bytes.effect_description_0.write_string(&self.effect_description[0]); + bytes.effect_description_1.write_string(&self.effect_description[1]); + bytes.effect_description_2.write_string(&self.effect_description[2]); + bytes.effect_description_3.write_string(&self.effect_description[3]); // Unknown *bytes.unknown_15 = self.unknown_15; diff --git a/src/game/card/item.rs b/src/game/card/item.rs index 74902e6..3743e91 100644 --- a/src/game/card/item.rs +++ b/src/game/card/item.rs @@ -10,9 +10,17 @@ use crate::{ array_split, array_split_mut, null_ascii_string::{self, NullAsciiString}, }, + AsciiStrArr, }; use byteorder::{ByteOrder, LittleEndian}; +// TODO: Remove these +/// Name alias for [`Digimon`] +type NameString = AsciiStrArr<0x14>; + +/// Effect description alias for [`Digimon`] +type EffectDescriptionString = AsciiStrArr<0x14>; + /// An item card /// /// Contains all information about each item card stored in the [`Card Table`](crate::game::card::table::Table) @@ -22,13 +30,13 @@ pub struct Item { /// The item's name /// /// An ascii string with 20 characters at most - pub name: ascii::AsciiString, + pub name: NameString, /// 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], + pub effect_description: [EffectDescriptionString; 4], /// The effect's arrow color #[serde(default)] @@ -55,19 +63,19 @@ pub enum FromBytesError { /// Unable to read the first support effect description #[error("Unable to read the first line of the effect description")] - EffectDescriptionFirst(#[source] null_ascii_string::ReadError), + EffectDescription1(#[source] null_ascii_string::ReadError), /// Unable to read the second support effect description #[error("Unable to read the second line of the effect description")] - EffectDescriptionSecond(#[source] null_ascii_string::ReadError), + EffectDescription2(#[source] null_ascii_string::ReadError), /// Unable to read the third support effect description #[error("Unable to read the third line of the effect description")] - EffectDescriptionThird(#[source] null_ascii_string::ReadError), + EffectDescription3(#[source] null_ascii_string::ReadError), /// Unable to read the fourth support effect description #[error("Unable to read the fourth line of the effect description")] - EffectDescriptionFourth(#[source] null_ascii_string::ReadError), + EffectDescription4(#[source] null_ascii_string::ReadError), /// An unknown effect arrow color was found #[error("Unknown effect arrow color found")] @@ -96,27 +104,8 @@ pub enum FromBytesError { /// Error type for [`Bytes::to_bytes`] #[derive(PartialEq, Eq, Clone, Copy, Debug, thiserror::Error)] +#[allow(clippy::pub_enum_variant_names)] // This is a general error, not a specific effect error pub enum ToBytesError { - /// Unable to write the digimon name - #[error("Unable to write the digimon name")] - Name(#[source] null_ascii_string::WriteError), - - /// Unable to write the first support effect description - #[error("Unable to write the first line of the effect description")] - EffectDescriptionFirst(#[source] null_ascii_string::WriteError), - - /// Unable to write the second support effect description - #[error("Unable to write the second line of the effect description")] - EffectDescriptionSecond(#[source] null_ascii_string::WriteError), - - /// Unable to write the third support effect description - #[error("Unable to write the third line of the effect description")] - EffectDescriptionThird(#[source] null_ascii_string::WriteError), - - /// Unable to write the fourth support effect description - #[error("Unable to write the fourth line of the effect description")] - EffectDescriptionFourth(#[source] null_ascii_string::WriteError), - /// Unable to write the first effect #[error("Unable to write the first effect")] EffectFirst(#[source] property::effect::ToBytesError), @@ -154,7 +143,7 @@ impl Bytes for Item { // And return the struct Ok(Self { - name: bytes.name.read_string().map_err(FromBytesError::Name)?.to_ascii_string(), + name: bytes.name.read_string().map_err(FromBytesError::Name)?, // Effects effect_conditions: [ @@ -171,26 +160,10 @@ impl Bytes for Item { effect_arrow_color: Option::::from_bytes(bytes.effect_arrow_color).map_err(FromBytesError::ArrowColor)?, effect_description: [ - bytes - .effect_description_0 - .read_string() - .map_err(FromBytesError::EffectDescriptionFirst)? - .to_ascii_string(), - bytes - .effect_description_1 - .read_string() - .map_err(FromBytesError::EffectDescriptionSecond)? - .to_ascii_string(), - bytes - .effect_description_2 - .read_string() - .map_err(FromBytesError::EffectDescriptionThird)? - .to_ascii_string(), - bytes - .effect_description_3 - .read_string() - .map_err(FromBytesError::EffectDescriptionFourth)? - .to_ascii_string(), + bytes.effect_description_0.read_string().map_err(FromBytesError::EffectDescription1)?, + bytes.effect_description_1.read_string().map_err(FromBytesError::EffectDescription2)?, + bytes.effect_description_2.read_string().map_err(FromBytesError::EffectDescription3)?, + bytes.effect_description_3.read_string().map_err(FromBytesError::EffectDescription4)?, ], // Unknown @@ -216,7 +189,7 @@ impl Bytes for Item { ); // Name - bytes.name.write_string(&self.name).map_err(ToBytesError::Name)?; + bytes.name.write_string(&self.name); // Effects self.effect_conditions[0].to_bytes(bytes.condition_first).into_ok(); @@ -228,22 +201,10 @@ impl Bytes for Item { Option::::to_bytes(&self.effect_arrow_color, bytes.effect_arrow_color).into_ok(); - bytes - .effect_description_0 - .write_string(&self.effect_description[0]) - .map_err(ToBytesError::EffectDescriptionFirst)?; - bytes - .effect_description_1 - .write_string(&self.effect_description[1]) - .map_err(ToBytesError::EffectDescriptionSecond)?; - bytes - .effect_description_2 - .write_string(&self.effect_description[2]) - .map_err(ToBytesError::EffectDescriptionThird)?; - bytes - .effect_description_3 - .write_string(&self.effect_description[3]) - .map_err(ToBytesError::EffectDescriptionFourth)?; + bytes.effect_description_0.write_string(&self.effect_description[0]); + bytes.effect_description_1.write_string(&self.effect_description[1]); + bytes.effect_description_2.write_string(&self.effect_description[2]); + bytes.effect_description_3.write_string(&self.effect_description[3]); // Unknown LittleEndian::write_u32(bytes.unknown_15, self.unknown_15); diff --git a/src/game/card/property/moves.rs b/src/game/card/property/moves.rs index 76e5a1d..9d0fe0f 100644 --- a/src/game/card/property/moves.rs +++ b/src/game/card/property/moves.rs @@ -11,15 +11,20 @@ use crate::{ array_split, array_split_mut, null_ascii_string::{self, NullAsciiString}, }, + AsciiStrArr, }; use byteorder::{ByteOrder, LittleEndian}; +// TODO: Remove these +/// Name alias for [`Digimon`] +type NameString = AsciiStrArr<0x15>; + /// A digimon's move #[derive(PartialEq, Eq, Clone, Hash, Debug)] #[derive(serde::Serialize, serde::Deserialize)] pub struct Move { /// The move's name - name: ascii::AsciiString, + name: NameString, /// The move's power power: u16, @@ -36,18 +41,10 @@ pub enum FromBytesError { Name(#[source] null_ascii_string::ReadError), } -/// Error type for [`Bytes::to_bytes`] -#[derive(PartialEq, Eq, Clone, Copy, Debug, thiserror::Error)] -pub enum ToBytesError { - /// Unable to write the move name - #[error("Unable to write the move name")] - Name(#[source] null_ascii_string::WriteError), -} - impl Bytes for Move { type ByteArray = [u8; 0x1c]; type FromError = FromBytesError; - type ToError = ToBytesError; + type ToError = !; fn from_bytes(bytes: &Self::ByteArray) -> Result { // Get all byte arrays we need @@ -59,7 +56,7 @@ impl Bytes for Move { // Return the move Ok(Self { - name: bytes.name.read_string().map_err(FromBytesError::Name)?.to_ascii_string(), + name: bytes.name.read_string().map_err(FromBytesError::Name)?, power: LittleEndian::read_u16(bytes.power), unknown: LittleEndian::read_u32(bytes.unknown), }) @@ -74,7 +71,7 @@ impl Bytes for Move { ); // Write the name - bytes.name.write_string(&self.name).map_err(ToBytesError::Name)?; + bytes.name.write_string(&self.name); // Then write the power and the unknown LittleEndian::write_u16(bytes.power, self.power); @@ -93,11 +90,6 @@ impl Validatable for Move { // Create the initial validation let mut validation = Validation::new(); - // If our name is longer or equal to `0x16` bytes, emit error - if self.name.len() >= 0x16 { - validation.emit_error(ValidationError::NameTooLong); - } - // If the power isn't a multiple of 10, warn, as we don't know how the game handles // powers that aren't multiples of 10. // TODO: Verify if the game can handle non-multiple of 10 powers. diff --git a/src/game/card/property/moves/test.rs b/src/game/card/property/moves/test.rs index 7a41227..12a3a28 100644 --- a/src/game/card/property/moves/test.rs +++ b/src/game/card/property/moves/test.rs @@ -4,6 +4,8 @@ #![allow(clippy::panic)] // Unit tests are supposed to panic on error. // Imports +use std::convert::TryFrom; + use super::*; use crate::Validatable; @@ -11,10 +13,11 @@ use crate::Validatable; fn valid_bytes() { // Valid moves with no warnings #[rustfmt::skip] + #[allow(clippy::as_conversions)] let valid_moves: &[(Move, ::ByteArray)] = &[ ( Move { - name: ascii::AsciiString::from_ascii("Digimon").expect("Unable to convert string to ascii"), + name: AsciiStrArr::try_from(b"Digimon" as &[u8]).expect("Unable to convert string to ascii"), power: LittleEndian::read_u16(&[4, 1]), unknown: LittleEndian::read_u32(&[1, 2, 3, 4]), }, @@ -27,7 +30,7 @@ fn valid_bytes() { ), ( Move { - name: ascii::AsciiString::from_ascii("123456789012345678901").expect("Unable to convert string to ascii"), + name: AsciiStrArr::try_from(b"123456789012345678901" as &[u8]).expect("Unable to convert string to ascii"), power: 0, unknown: 0, }, diff --git a/src/game/card/table.rs b/src/game/card/table.rs index 3b68792..358260d 100644 --- a/src/game/card/table.rs +++ b/src/game/card/table.rs @@ -228,7 +228,7 @@ impl Table { // Macro to help write all cards to file macro_rules! write_card { - ($cards:expr, $prev_ids:expr, $card_type:ident, $ErrVariant:ident) => { + ($cards:expr, $prev_ids:expr, $card_type:ident, $($on_err:tt)*) => { for (rel_id, card) in $cards.iter().enumerate() { // Current id through the whole table let cur_id = $prev_ids + rel_id; @@ -247,9 +247,10 @@ impl Table { CardType::$card_type.to_bytes(bytes.header_type)?; // Write the card + #[allow(unreachable_code)] // FIXME: Remove this card .to_bytes(bytes.card) - .map_err(|err| SerializeError::$ErrVariant { id: cur_id, err })?; + .map_err(|err| {$($on_err)*}(err, cur_id))?; // Write the footer *bytes.footer = 0; @@ -268,9 +269,9 @@ impl Table { // Write all cards #[rustfmt::skip] { // Buffer , Offset , Type , Error variant - write_card! { self.digimons , 0 , Digimon , SerializeDigimonCard } - write_card! { self.items , self.digimons.len() , Item , SerializeItemCard } - write_card! { self.digivolves, self.digimons.len() + self.items.len(), Digivolve, SerializeDigivolveCard } + write_card! { self.digimons , 0 , Digimon , |err, cur_id| SerializeError::SerializeDigimonCard { id: cur_id, err } } + write_card! { self.items , self.digimons.len() , Item , |err, cur_id| SerializeError::SerializeItemCard { id: cur_id, err } } + write_card! { self.digivolves, self.digimons.len() + self.items.len(), Digivolve, |err, _| err } } // And return Ok diff --git a/src/game/card/table/error.rs b/src/game/card/table/error.rs index 622e0cc..a9c0e8b 100644 --- a/src/game/card/table/error.rs +++ b/src/game/card/table/error.rs @@ -186,15 +186,4 @@ pub enum SerializeError { #[source] err: card::item::ToBytesError, }, - - /// Unable to serialize a digivolve card - #[error("Unable to serialize digivolve card with id {}", id)] - SerializeDigivolveCard { - /// Id of card - id: usize, - - /// Underlying error - #[source] - err: card::digivolve::ToBytesError, - }, } diff --git a/src/game/deck/deck.rs b/src/game/deck/deck.rs index 2ae79ef..b0d1f02 100644 --- a/src/game/deck/deck.rs +++ b/src/game/deck/deck.rs @@ -10,21 +10,29 @@ use crate::{ array_split, array_split_mut, null_ascii_string::{self, NullAsciiString}, }, + AsciiStrArr, }; use byteorder::{ByteOrder, LittleEndian}; /// Card id type pub type CardId = u16; +// TODO: Remove these +/// Name alias for [`Digimon`] +type NameString = AsciiStrArr<0x12>; + +/// Owner alias for [`Digimon`] +type OwnerString = AsciiStrArr<0x14>; + /// A deck #[derive(PartialEq, Eq, Clone, Debug)] #[derive(serde::Serialize, serde::Deserialize)] pub struct Deck { /// Name of this deck - pub name: ascii::AsciiString, + pub name: NameString, /// Digimon who plays this deck - pub owner: ascii::AsciiString, + pub owner: OwnerString, /// All of the card ids that make up this deck pub cards: [CardId; 30], @@ -79,22 +87,10 @@ pub enum FromBytesError { PolygonMusic(#[source] music::FromBytesError), } -/// Error type for [`Bytes::to_bytes`] -#[derive(PartialEq, Eq, Clone, Copy, Debug, thiserror::Error)] -pub enum ToBytesError { - /// Unable to write the deck name - #[error("Unable to write the deck name")] - Name(#[source] null_ascii_string::WriteError), - - /// Unable to write the deck owner - #[error("Unable to write the deck owner")] - Owner(#[source] null_ascii_string::WriteError), -} - impl Bytes for Deck { type ByteArray = [u8; 0x6e]; type FromError = FromBytesError; - type ToError = ToBytesError; + type ToError = !; fn from_bytes(bytes: &Self::ByteArray) -> Result { // Split the bytes @@ -120,8 +116,8 @@ impl Bytes for Deck { } Ok(Self { - name: bytes.name.read_string().map_err(FromBytesError::Name)?.to_ascii_string(), - owner: bytes.owner.read_string().map_err(FromBytesError::Owner)?.to_ascii_string(), + name: bytes.name.read_string().map_err(FromBytesError::Name)?, + owner: bytes.owner.read_string().map_err(FromBytesError::Owner)?, cards, city: Option::::from_bytes(bytes.city).map_err(FromBytesError::City)?, armor_evo: Option::::from_bytes(bytes.armor_evo).map_err(FromBytesError::ArmorEvo)?, @@ -149,8 +145,8 @@ impl Bytes for Deck { ); // Name / Owner - bytes.name.write_string(&self.name).map_err(ToBytesError::Name)?; - bytes.owner.write_string(&self.owner).map_err(ToBytesError::Owner)?; + bytes.name.write_string(&self.name); + bytes.owner.write_string(&self.owner); // Deck for (card_id, card) in self.cards.iter().enumerate() { diff --git a/src/game/deck/table.rs b/src/game/deck/table.rs index eca733e..0340672 100644 --- a/src/game/deck/table.rs +++ b/src/game/deck/table.rs @@ -131,7 +131,7 @@ impl Table { for (id, deck) in self.decks.iter().enumerate() { // Parse each deck into bytes let mut bytes = [0; 0x6e]; - deck.to_bytes(&mut bytes).map_err(|err| SerializeError::SerializeDeck { id, err })?; + deck.to_bytes(&mut bytes).into_ok(); // And write them to file file.write(&bytes).map_err(|err| SerializeError::WriteDeck { id, err })?; diff --git a/src/game/deck/table/error.rs b/src/game/deck/table/error.rs index 7fb14f1..e0e8996 100644 --- a/src/game/deck/table/error.rs +++ b/src/game/deck/table/error.rs @@ -78,17 +78,6 @@ pub enum SerializeError { #[error("Unable to write table header")] WriteHeader(#[source] std::io::Error), - /// Could not deserialize a deck entry - #[error("Unable to deserialize deck entry with id {}", id)] - SerializeDeck { - /// Id of card - id: usize, - - /// Underlying error - #[source] - err: deck::ToBytesError, - }, - /// Could not write a deck entry #[error("Unable to read deck entry with id {}", id)] WriteDeck { diff --git a/src/lib.rs b/src/lib.rs index 2419a74..0104735 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,10 +91,12 @@ #![allow(clippy::indexing_slicing)] // Modules +pub mod ascii_str_arr; pub mod game; pub mod io; mod util; // Exports +pub use ascii_str_arr::AsciiStrArr; pub use game::{Bytes, CardTable, Deck, DeckTable, Digimon, Digivolve, Item, Validatable, Validation}; pub use io::GameFile; diff --git a/src/util/null_ascii_string.rs b/src/util/null_ascii_string.rs index ae0379e..7052948 100644 --- a/src/util/null_ascii_string.rs +++ b/src/util/null_ascii_string.rs @@ -4,44 +4,56 @@ pub mod error; // Exports -pub use error::{ReadError, WriteError}; +pub use error::ReadError; + +// Imports +use crate::AsciiStrArr; +use std::convert::TryInto; /// Trait for reading null terminated ascii strings from a buffer -pub trait NullAsciiString { +pub trait NullAsciiString { /// Reads a null terminated ascii string from this buffer and returns it - fn read_string(&self) -> Result<&ascii::AsciiStr, ReadError>; + fn read_string(&self) -> Result, ReadError>; /// Writes a null terminated ascii string to this buffer and returns it - fn write_string(&mut self, s: &ascii::AsciiStr) -> Result<&Self, WriteError>; + fn write_string(&mut self, s: &AsciiStrArr); } -impl NullAsciiString for [u8] { - fn read_string(&self) -> Result<&ascii::AsciiStr, ReadError> { - // Find the first null and trim the buffer until it - let buf = match self.iter().position(|&b| b == b'\0') { - Some(idx) => &self[0..idx], - None => return Err(ReadError::NoNull), - }; +// TODO: Get rid of this once we're able to use `{N + 1}` +macro impl_null_ascii_string($($N:expr),* $(,)?) { + $( + impl NullAsciiString<$N> for [u8; $N + 1] { + fn read_string(&self) -> Result, ReadError> { + // Find the first null and trim the buffer until it + let buf = match self.iter().position(|&b| b == b'\0') { + Some(idx) => &self[0..idx], + None => return Err(ReadError::NoNull), + }; - // Then convert it from Ascii - ascii::AsciiStr::from_ascii(buf).map_err(ReadError::NotAscii) - } + // Then convert it to the ascii string array + Ok(ascii::AsciiStr::from_ascii(buf) + .map_err(ReadError::NotAscii)? + .try_into() + .expect("Null terminated `[u8; N+1]` didn't fit into `AsciiStringArr`") + ) + } - fn write_string(&mut self, input: &ascii::AsciiStr) -> Result<&Self, WriteError> { - // If the input string doesn't fit into the buffer (excluding the null byte), return Err - if input.len() >= self.len() { - return Err(WriteError::TooLarge { - input_len: input.len(), - buffer_len: self.len(), - }); + fn write_string(&mut self, input: &AsciiStrArr<$N>) { + // Copy everything over and set the last byte to 0 + // Note: Panic cannot occur, as `len < N` + // Note: No need to override the remaining bytes + let len = input.len(); + self[0..len].copy_from_slice(input.as_bytes()); + self[len] = 0; + } } - - // 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 - self[0..input.len()].copy_from_slice(input.as_bytes()); - self[input.len()] = 0; - - // And return Ok with the buffer - Ok(self) - } + )* } + +#[rustfmt::skip] +impl_null_ascii_string!( + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, +); diff --git a/src/util/null_ascii_string/error.rs b/src/util/null_ascii_string/error.rs index 14c1922..aa7beef 100644 --- a/src/util/null_ascii_string/error.rs +++ b/src/util/null_ascii_string/error.rs @@ -11,12 +11,3 @@ pub enum ReadError { #[error("The buffer did not contain valid Ascii")] NotAscii(#[source] ascii::AsAsciiStrError), } - -/// Error type for [`write`](super::read) -#[derive(PartialEq, Eq, Clone, Copy, Debug, thiserror::Error)] -#[allow(clippy::missing_docs_in_private_items)] -pub enum WriteError { - /// The input string was too large - #[error("Input string was too large for buffer. ({}+1 / {})", input_len, buffer_len)] - TooLarge { input_len: usize, buffer_len: usize }, -}