Added AsciiStrArr and modified all modules to use it.

This commit is contained in:
2020-09-18 01:45:58 +01:00
parent 65deb1fe84
commit cdaf7047f4
16 changed files with 424 additions and 325 deletions

212
src/ascii_str_arr.rs Normal file
View File

@@ -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<const N: usize> {
/// Characters
chars: [AsciiChar; N],
/// Size
len: usize,
}
impl<const N: usize> AsciiStrArr<N> {
/// 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<const N: usize> AsRef<AsciiStr> for AsciiStrArr<N> {
fn as_ref(&self) -> &AsciiStr {
self.as_ascii_str()
}
}
impl<const N: usize> AsMut<AsciiStr> for AsciiStrArr<N> {
fn as_mut(&mut self) -> &mut AsciiStr {
self.as_ascii_str_mut()
}
}
impl<const N: usize> std::ops::Index<usize> for AsciiStrArr<N> {
type Output = AsciiChar;
fn index(&self, idx: usize) -> &Self::Output {
self.get(idx).expect("Invalid index access")
}
}
impl<const N: usize> std::ops::IndexMut<usize> for AsciiStrArr<N> {
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
self.get_mut(idx).expect("Invalid index access")
}
}
impl<const N: usize> PartialEq for AsciiStrArr<N> {
fn eq(&self, other: &Self) -> bool {
AsciiStr::eq(self.as_ascii_str(), other.as_ascii_str())
}
}
impl<const N: usize> PartialOrd for AsciiStrArr<N> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
AsciiStr::partial_cmp(self.as_ascii_str(), other.as_ascii_str())
}
}
impl<const N: usize> Ord for AsciiStrArr<N> {
fn cmp(&self, other: &Self) -> Ordering {
AsciiStr::cmp(self.as_ascii_str(), other.as_ascii_str())
}
}
impl<const N: usize> Hash for AsciiStrArr<N> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
AsciiStr::hash(self.as_ascii_str(), state)
}
}
impl<const N: usize> fmt::Debug for AsciiStrArr<N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
AsciiStr::fmt(self.as_ascii_str(), f)
}
}
impl<const N: usize> fmt::Display for AsciiStrArr<N> {
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<N> {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
match deserializer.deserialize_str(DeserializerVisitor) {
Ok(string) => Ok(string),
Err(err) => Err(err),
}
}
}
impl<const N: usize> serde::Serialize for AsciiStrArr<N> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_ascii_str().as_str())
}
}
// TODO: Generalize this to `impl<const N: usize, const M: usize> From<&[AsciiChar; M]> for AsciiStrArr<N> where M <= N`
impl<const N: usize> From<&[AsciiChar; N]> for AsciiStrArr<N> {
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<const N: usize, const M: usize> From<[AsciiChar; M]> for AsciiStrArr<N> where M <= N`
impl<const N: usize> From<[AsciiChar; N]> for AsciiStrArr<N> {
fn from(chars: [AsciiChar; N]) -> Self {
Self { chars, len: N }
}
}
// TODO: Generalize this to `impl<const N: usize, const M: usize> TryFrom<&[u8; M]> for AsciiStrArr<N> where M <= N`
impl<const N: usize> TryFrom<&[u8; N]> for AsciiStrArr<N> {
type Error = NotAsciiError;
fn try_from(byte_str: &[u8; N]) -> Result<Self, Self::Error> {
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<const N: usize> TryFrom<&AsciiStr> for AsciiStrArr<N> {
type Error = TooLongError<N>;
fn try_from(ascii_str: &AsciiStr) -> Result<Self, Self::Error> {
// Note: No space for null, this isn't null terminated
let len = ascii_str.len();
if len > N {
return Err(TooLongError::<N>);
}
let mut chars = [AsciiChar::Null; N];
chars[0..len].copy_from_slice(ascii_str.as_ref());
Ok(Self { chars, len })
}
}
impl<const N: usize> TryFrom<&[u8]> for AsciiStrArr<N> {
type Error = FromByteStringError<N>;
fn try_from(byte_str: &[u8]) -> Result<Self, Self::Error> {
let ascii_str = AsciiStr::from_ascii(byte_str).map_err(FromByteStringError::NotAscii)?;
Self::try_from(ascii_str).map_err(FromByteStringError::TooLong)
}
}

View File

@@ -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<const LEN: usize>;
/// 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<N>`]
#[derive(Debug, thiserror::Error)]
pub enum FromByteStringError<const LEN: usize> {
/// Too long
#[error("String was too long")]
TooLong(TooLongError<LEN>),
/// Not ascii
#[error("String contained non-ascii characters")]
NotAscii(ascii::AsAsciiStrError),
}

View File

@@ -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<const N: usize>;
impl<'de, const N: usize> serde::de::Visitor<'de> for DeserializerVisitor<N> {
type Value = AsciiStrArr<N>;
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<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
// 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)
}
}

View File

@@ -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::<ArrowColor>::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::<ArrowColor>::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);

View File

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

View File

@@ -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::<ArrowColor>::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::<ArrowColor>::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);

View File

@@ -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<Self, Self::FromError> {
// 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.

View File

@@ -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, <Move as Bytes>::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,
},

View File

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

View File

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

View File

@@ -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<Self, Self::FromError> {
// 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::<City>::from_bytes(bytes.city).map_err(FromBytesError::City)?,
armor_evo: Option::<ArmorEvo>::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() {

View File

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

View File

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

View File

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

View File

@@ -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<const N: usize> {
/// 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<AsciiStrArr<N>, 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<N>);
}
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<AsciiStrArr<$N>, 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<N>`")
)
}
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,
);

View File

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