mirror of
https://github.com/Zenithsiz/dcb.git
synced 2026-02-09 03:40:23 +00:00
Added AsciiStrArr and modified all modules to use it.
This commit is contained in:
212
src/ascii_str_arr.rs
Normal file
212
src/ascii_str_arr.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
26
src/ascii_str_arr/error.rs
Normal file
26
src/ascii_str_arr/error.rs
Normal 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),
|
||||
}
|
||||
26
src/ascii_str_arr/visitor.rs
Normal file
26
src/ascii_str_arr/visitor.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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 })?;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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 },
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user