Removed ByteArray::SIZE.

Added several filesystem entities.
Revised alphabet strings.
This commit is contained in:
Filipe Rodrigues 2021-01-17 21:26:21 +00:00
parent 8b650c5aad
commit b0921aa93c
14 changed files with 411 additions and 92 deletions

View File

@ -9,7 +9,7 @@ where
Self: Sized,
{
/// The type of array required by this structure
type ByteArray: ByteArray;
type ByteArray: ?Sized + ByteArray;
/// The error type used for the operation
type FromError: Error;
@ -25,15 +25,10 @@ where
}
/// A trait for restricting `Bytes::ByteArray`
pub trait ByteArray {
/// Size of this array
const SIZE: usize;
}
pub trait ByteArray {}
impl<const N: usize> ByteArray for [u8; N] {
const SIZE: usize = N;
}
impl<const N: usize> ByteArray for [u8; N] {}
impl ByteArray for u8 {
const SIZE: usize = 1;
}
impl ByteArray for [u8] {}
impl ByteArray for u8 {}

View File

@ -1,10 +1,11 @@
//! Game file filesystem
//!
//! The filesystem used is ISO 9960, which is mostly implemented
//! in this module, abstracted from the CD-ROM/XA sectors.
//! The filesystem is composed of an outer layer of ISO 9960 with
//! a custom file system that may be mounted on some files.
// Modules
pub mod date_time;
pub mod dir_record;
pub mod error;
pub mod string;
pub mod volume_descriptor;
@ -15,12 +16,13 @@ pub use string::{StrArrA, StrArrD};
pub use volume_descriptor::VolumeDescriptor;
// Imports
use self::volume_descriptor::TypeCode;
use crate::GameFile;
use dcb_bytes::Bytes;
use std::io;
/// The filesystem
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct Filesystem {
/// Primary volume descriptor
primary_volume_descriptor: VolumeDescriptor,
@ -30,8 +32,12 @@ impl Filesystem {
/// Reads the filesystem from a game file
pub fn new<R: io::Read + io::Seek>(file: &mut GameFile<R>) -> Result<Self, NewError> {
// Read the primary volume descriptor from sector `0x10`
// Note: First `32 kiB` (= 16 sectors) are reserved for arbitrary data.
let sector = file.sector(0x10).map_err(NewError::ReadPrimaryVolumeSector)?;
let primary_volume_descriptor = VolumeDescriptor::from_bytes(&sector.data).map_err(NewError::ParsePrimaryVolume)?;
if primary_volume_descriptor.type_code() != TypeCode::Primary {
return Err(NewError::FirstVolumeNotPrimary(primary_volume_descriptor.type_code()));
}
Ok(Self { primary_volume_descriptor })
}

View File

@ -0,0 +1,87 @@
//! A directory entry
// Modules
pub mod error;
// Exports
pub use error::FromBytesError;
// Imports
use super::string::FileString;
use byteorder::{ByteOrder, LittleEndian};
use dcb_bytes::Bytes;
use dcb_util::array_split;
use std::convert::TryInto;
/// A directory record
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct DirRecord {
/// Record total size
record_size: u8,
/// File's name
name: FileString,
/// File's location
location: u32,
/// File's size
size: u32,
}
impl DirRecord {
/// Returns this record's size
#[must_use]
pub const fn size(&self) -> u8 {
self.record_size
}
}
impl Bytes for DirRecord {
type ByteArray = [u8];
type FromError = FromBytesError;
type ToError = !;
fn from_bytes(bytes: &Self::ByteArray) -> Result<Self, Self::FromError> {
// Get the header
let header_bytes: &[u8; 0x21] = match bytes.get(..0x21).and_then(|bytes| bytes.try_into().ok()) {
Some(header_bytes) => header_bytes,
None => return Err(FromBytesError::TooSmallHeader),
};
let header_bytes = array_split!(header_bytes,
record_size : 0x1,
extended_attribute_record_len: 0x1,
extent_location_lsb : [0x4],
extent_location_msb : [0x4],
extent_size_lsb : [0x4],
extent_size_msb : [0x4],
recording_date_time : [0x7],
file_flags : 0x1,
file_unit_size : 0x1,
interleave_gap_size : 0x1,
volume_sequence_number_lsb : [0x2],
volume_sequence_number_msb : [0x2],
name_len : 0x1,
);
dbg!(bytes);
// Then read the name
let name = bytes
.get(0x21..0x21 + usize::from(*header_bytes.name_len))
.ok_or(FromBytesError::TooSmallName(*header_bytes.name_len))?;
let name = FileString::from_bytes(name).map_err(FromBytesError::Name)?;
Ok(Self {
record_size: *header_bytes.record_size,
name,
location: LittleEndian::read_u32(header_bytes.extent_location_lsb),
size: LittleEndian::read_u32(header_bytes.extent_size_lsb),
})
}
fn to_bytes(&self, _bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> {
todo!()
}
}

View File

@ -0,0 +1,20 @@
//! Errors
// Imports
use crate::fs::string;
/// Error type for [`Bytes::from_bytes`](dcb_bytes::Bytes::from_bytes)
#[derive(Debug, thiserror::Error)]
pub enum FromBytesError {
/// Too small
#[error("Buffer was too small for header")]
TooSmallHeader,
/// Too small
#[error("Buffer was too small for name (expected {_0} for name)")]
TooSmallName(u8),
/// Unable to read name
#[error("Unable to read name")]
Name(#[source] string::ValidateFileAlphabetError),
}

View File

@ -1,7 +1,7 @@
//! Errors
// Imports
use super::volume_descriptor;
use super::volume_descriptor::{self, TypeCode};
use crate::game_file::SectorError;
/// Error type for [`Filesystem::new`](super::Filesystem::new)
@ -14,4 +14,8 @@ pub enum NewError {
/// Unable to parse primary volume
#[error("Unable to parse primary volume")]
ParsePrimaryVolume(#[source] volume_descriptor::FromBytesError),
/// First volume was not the primary volume
#[error("First volume was not the primary volume, was {_0:?}")]
FirstVolumeNotPrimary(TypeCode),
}

View File

@ -1,109 +1,141 @@
//! Filesystem strings
/// Modules
pub mod arr;
pub mod error;
pub mod owned;
pub mod slice;
// Exports
pub use error::InvalidCharError;
pub use arr::StrArrAlphabet;
pub use error::{InvalidCharError, ValidateFileAlphabetError};
pub use owned::StringAlphabet;
pub use slice::StrAlphabet;
// Imports
use std::{fmt, marker::PhantomData};
/// An alphabet
/// An alphabet for a string
pub trait Alphabet {
/// The alphabet
fn alphabet() -> &'static [u8];
/// Error type
type Error;
/// Returns if `bytes` are valid for this alphabet
fn validate(bytes: &[u8]) -> Result<(), Self::Error>;
}
/// A alphabetic specific string
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct StrArrAlphabet<A: Alphabet, const N: usize>([u8; N], PhantomData<A>);
impl<A: Alphabet, const N: usize> StrArrAlphabet<A, N> {
/// Parses a string from bytes
pub fn from_bytes(bytes: &[u8; N]) -> Result<Self, InvalidCharError> {
/// Implements the [`Alphabet`] trait from an alphabet
pub trait ImplFromAlphabet {
/// The alphabet
fn alphabet() -> &'static [u8];
/// String terminator
fn terminator() -> u8;
}
impl<A: ImplFromAlphabet> Alphabet for A {
type Error = InvalidCharError;
fn validate(bytes: &[u8]) -> Result<(), Self::Error> {
// If any are invalid, return Err
let alphabet = A::alphabet();
for (pos, &byte) in bytes.iter().enumerate() {
// If we found a space, as long as everything after this position is also a space, it's a valid string
if byte == b' ' {
match bytes[pos..].iter().all(|&b| b == b' ') {
true => break,
false => return Err(InvalidCharError { byte, pos }),
};
// If we found the terminator, terminate
// TODO: Maybe make sure everything after the `;` is valid too
if byte == Self::terminator() {
break;
}
if !alphabet.contains(&byte) {
// Else make sure it contains this byte
if !Self::alphabet().contains(&byte) {
return Err(InvalidCharError { byte, pos });
}
}
Ok(Self(*bytes, PhantomData))
}
/// Returns the bytes from this string
#[must_use]
pub fn as_bytes(&self) -> &[u8; N] {
&self.0
Ok(())
}
}
impl<A: Alphabet, const N: usize> fmt::Debug for StrArrAlphabet<A, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Try to get self as a string to debug it
// TODO: Not allocate here
let s = String::from_utf8_lossy(self.as_bytes());
// Then trim any spaces we might have
let s = s.trim();
write!(f, "{s:?}")
}
}
impl<A: Alphabet, const N: usize> fmt::Display for StrArrAlphabet<A, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Try to get self as a string to debug it
// TODO: Not allocate here
let s = String::from_utf8_lossy(self.as_bytes());
// Then trim any spaces we might have
let s = s.trim();
write!(f, "{s}")
}
}
/// A-type alphabet
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct AlphabetA;
impl Alphabet for AlphabetA {
impl ImplFromAlphabet for AlphabetA {
fn alphabet() -> &'static [u8] {
&[
b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W',
b'X', b'Y', b'Z', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'_', b' ', b'!', b'"', b'%', b'&', b'\'', b'(', b')',
b'*', b'+', b',', b'-', b'.', b'/', b':', b';', b'<', b'=', b'>', b'?',
b'X', b'Y', b'Z', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'_', b'!', b'"', b'%', b'&', b'\'', b'(', b')', b'*',
b'+', b',', b'-', b'.', b'/', b':', b';', b'<', b'=', b'>', b'?',
]
}
fn terminator() -> u8 {
b' '
}
}
/// D-type alphabet
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct AlphabetD;
impl Alphabet for AlphabetD {
impl ImplFromAlphabet for AlphabetD {
fn alphabet() -> &'static [u8] {
&[
b'A', b'B', b'C', b'D', b'E', b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W',
b'X', b'Y', b'Z', b'0', b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'_',
]
}
fn terminator() -> u8 {
b' '
}
}
/// A-type string
/// File alphabet
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct FileAlphabet;
impl Alphabet for FileAlphabet {
type Error = ValidateFileAlphabetError;
fn validate(bytes: &[u8]) -> Result<(), Self::Error> {
// Separate into `<name>.<extension>;<version>`
let dot_idx = bytes.iter().position(|&b| b == b'.').ok_or(ValidateFileAlphabetError::MissingExtension)?;
let version_idx = bytes.iter().position(|&b| b == b';').ok_or(ValidateFileAlphabetError::MissingVersion)?;
let (name, bytes) = bytes.split_at(dot_idx);
let (extension, version) = bytes.split_at(version_idx);
// Validate all separately
AlphabetD::validate(name).map_err(ValidateFileAlphabetError::InvalidNameChar)?;
AlphabetD::validate(extension).map_err(ValidateFileAlphabetError::InvalidExtensionChar)?;
match version {
[b'0'..=b'9'] => Ok(()),
_ => Err(ValidateFileAlphabetError::InvalidVersion),
}
}
}
/// A-type string array
pub type StrArrA<const N: usize> = StrArrAlphabet<AlphabetA, N>;
/// D-type string
/// A-type string
pub type StringA = StringAlphabet<AlphabetA>;
/// A-type string slice
pub type StrA = StrAlphabet<AlphabetA>;
/// D-type string array
pub type StrArrD<const N: usize> = StrArrAlphabet<AlphabetD, N>;
/// D-type string
pub type StringD = StringAlphabet<AlphabetD>;
/// D-type string slice
pub type StrD = StrAlphabet<AlphabetD>;
/// File string array
pub type FileStrArr<const N: usize> = StrArrAlphabet<FileAlphabet, N>;
/// File string
pub type FileString = StringAlphabet<FileAlphabet>;
/// File string slice
pub type FileStr = StrAlphabet<FileAlphabet>;

View File

@ -0,0 +1,37 @@
//! String array
// Imports
use super::{Alphabet, StrAlphabet};
use std::{fmt, marker::PhantomData, ops::Deref};
/// A alphabetic specific string array
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
pub struct StrArrAlphabet<A: Alphabet, const N: usize>(PhantomData<A>, [u8; N]);
impl<A: Alphabet, const N: usize> StrArrAlphabet<A, N> {
/// Parses a string from bytes
pub fn from_bytes(bytes: &[u8; N]) -> Result<Self, A::Error> {
A::validate(bytes).map(|()| Self(PhantomData, *bytes))
}
}
impl<A: Alphabet, const N: usize> Deref for StrArrAlphabet<A, N> {
type Target = StrAlphabet<A>;
fn deref(&self) -> &Self::Target {
ref_cast::RefCast::ref_cast(self.1.as_slice())
}
}
impl<A: Alphabet, const N: usize> fmt::Debug for StrArrAlphabet<A, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", &*self)
}
}
impl<A: Alphabet, const N: usize> fmt::Display for StrArrAlphabet<A, N> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", &*self)
}
}

View File

@ -1,6 +1,6 @@
//! Errors
/// Error for [`StringArrA::from_bytes`] and [`StringArrD::from_bytes`]
/// Error for [`Alphabet::validate`](super::Alphabet::validate)'s impl of [`AlphabetA`] and [`AlphabetB`]
#[derive(Debug, thiserror::Error)]
#[error("Invalid character '{byte:#x}' at index {pos}")]
pub struct InvalidCharError {
@ -10,3 +10,27 @@ pub struct InvalidCharError {
/// Position
pub pos: usize,
}
/// Error for [`Alphabet::validate`](super::Alphabet::validate)'s impl of [`AlphabetA`] and [`AlphabetB`]
#[derive(Debug, thiserror::Error)]
pub enum ValidateFileAlphabetError {
/// Invalid name character
#[error("Invalid name character")]
InvalidNameChar(#[source] InvalidCharError),
/// Invalid extension character
#[error("Invalid extension character")]
InvalidExtensionChar(#[source] InvalidCharError),
/// Missing file name extension
#[error("Missing file name extension")]
MissingExtension,
/// Missing file name version
#[error("Missing file name version")]
MissingVersion,
/// Invalid version
#[error("Invalid version")]
InvalidVersion,
}

View File

@ -0,0 +1,36 @@
//! String
// Imports
use super::{Alphabet, StrAlphabet};
use std::{fmt, marker::PhantomData, ops::Deref};
/// A alphabetic specific string
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone)]
pub struct StringAlphabet<A: Alphabet>(PhantomData<A>, Vec<u8>);
impl<A: Alphabet> StringAlphabet<A> {
/// Parses a string from bytes
pub fn from_bytes(bytes: &[u8]) -> Result<Self, A::Error> {
A::validate(bytes).map(|()| Self(PhantomData, bytes.to_vec()))
}
}
impl<A: Alphabet> Deref for StringAlphabet<A> {
type Target = StrAlphabet<A>;
fn deref(&self) -> &Self::Target {
ref_cast::RefCast::ref_cast(self.1.as_slice())
}
}
impl<A: Alphabet> fmt::Debug for StringAlphabet<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", &*self)
}
}
impl<A: Alphabet> fmt::Display for StringAlphabet<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", &*self)
}
}

View File

@ -0,0 +1,57 @@
//! String slice
// Imports
use super::Alphabet;
use std::{fmt, marker::PhantomData};
/// A alphabetic specific string slice
#[derive(PartialEq, Eq, PartialOrd, Ord)]
#[derive(ref_cast::RefCast)]
#[repr(transparent)]
pub struct StrAlphabet<A: Alphabet>(PhantomData<A>, [u8]);
impl<A: Alphabet> StrAlphabet<A> {
/// Returns the bytes from this string
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
&self.1
}
/// Returns the length of this string
#[must_use]
pub fn len(&self) -> usize {
self.as_bytes().len()
}
/// Returns if this string is empty
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl<A: Alphabet> fmt::Debug for StrAlphabet<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Try to get self as a string to debug it
// TODO: Not allocate here
let s = String::from_utf8_lossy(self.as_bytes());
// Then trim any spaces we might have
let s = s.trim();
write!(f, "{s:?}")
}
}
impl<A: Alphabet> fmt::Display for StrAlphabet<A> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// Try to get self as a string to debug it
// TODO: Not allocate here
let s = String::from_utf8_lossy(self.as_bytes());
// Then trim any spaces we might have
let s = s.trim();
write!(f, "{s}")
}
}

View File

@ -9,13 +9,13 @@ pub use error::{FromBytesError, ParseBootRecordError, ParsePrimaryError};
pub use type_code::TypeCode;
// Imports
use super::{date_time::DecDateTime, StrArrA, StrArrD};
use super::{date_time::DecDateTime, dir_record::DirRecord, StrArrA, StrArrD};
use byteorder::{ByteOrder, LittleEndian};
use dcb_bytes::Bytes;
use dcb_util::array_split;
/// A volume descriptor
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum VolumeDescriptor {
/// Boot record
BootRecord {
@ -56,7 +56,7 @@ pub enum VolumeDescriptor {
path_table_opt_location: u32,
/// Root directory entry
root_dir_entry: [u8; 0x22],
root_dir_entry: DirRecord,
/// Volume set identifier
volume_set_id: StrArrD<0x80>,
@ -91,6 +91,21 @@ pub enum VolumeDescriptor {
/// Volume effective date time
volume_effective_date_time: DecDateTime,
},
/// Set terminator
SetTerminator,
}
impl VolumeDescriptor {
/// Returns the type code for this descriptor
#[must_use]
pub const fn type_code(&self) -> TypeCode {
match self {
Self::BootRecord { .. } => TypeCode::BootRecord,
Self::Primary { .. } => TypeCode::Primary,
Self::SetTerminator => TypeCode::SetTerminator,
}
}
}
impl VolumeDescriptor {
@ -138,10 +153,10 @@ impl VolumeDescriptor {
logical_block_size_msb : [0x2 ],
path_table_size_lsb : [0x4 ],
path_table_size_msb : [0x4 ],
path_table_location_lsb : [0x4 ],
path_table_opt_location_lsb : [0x4 ],
path_table_location_msb : [0x4 ],
path_table_opt_location_msb : [0x4 ],
path_table_lsb_location : [0x4 ],
path_table_lsb_opt_location : [0x4 ],
path_table_msb_location : [0x4 ],
path_table_msb_opt_location : [0x4 ],
root_dir_entry : [0x22],
volume_set_id : [0x80],
publisher_id : [0x80],
@ -167,9 +182,9 @@ impl VolumeDescriptor {
volume_sequence_number: LittleEndian::read_u16(bytes.volume_sequence_number_lsb),
logical_block_size: LittleEndian::read_u16(bytes.logical_block_size_lsb),
path_table_size: LittleEndian::read_u32(bytes.path_table_size_lsb),
path_table_location: LittleEndian::read_u32(bytes.path_table_location_lsb),
path_table_opt_location: LittleEndian::read_u32(bytes.path_table_opt_location_lsb),
root_dir_entry: *bytes.root_dir_entry,
path_table_location: LittleEndian::read_u32(bytes.path_table_lsb_location),
path_table_opt_location: LittleEndian::read_u32(bytes.path_table_lsb_opt_location),
root_dir_entry: DirRecord::from_bytes(bytes.root_dir_entry).map_err(ParsePrimaryError::RootDirEntry)?,
volume_set_id: StrArrD::from_bytes(bytes.volume_set_id).map_err(ParsePrimaryError::VolumeSetId)?,
publisher_id: StrArrA::from_bytes(bytes.publisher_id).map_err(ParsePrimaryError::PublisherId)?,
data_preparer_id: StrArrA::from_bytes(bytes.data_preparer_id).map_err(ParsePrimaryError::DataPreparerId)?,
@ -219,9 +234,8 @@ impl Bytes for VolumeDescriptor {
match type_code {
TypeCode::BootRecord => Self::parse_boot_record(bytes.descriptor).map_err(FromBytesError::ParseBootRecord),
TypeCode::Primary => Self::parse_primary(bytes.descriptor).map_err(FromBytesError::ParsePrimary),
TypeCode::Supplementary | TypeCode::VolumePartition | TypeCode::SetTerminator | TypeCode::Reserved(_) => {
Err(FromBytesError::TypeCode(type_code))
},
TypeCode::SetTerminator => Ok(Self::SetTerminator),
TypeCode::Supplementary | TypeCode::VolumePartition | TypeCode::Reserved(_) => Err(FromBytesError::TypeCode(type_code)),
}
}

View File

@ -2,7 +2,7 @@
// Imports
use super::TypeCode;
use crate::fs::{date_time, string};
use crate::fs::{date_time, dir_record, string};
/// Error type for [`Bytes::from_bytes`](dcb_bytes::Bytes::from_bytes)
#[derive(Debug, thiserror::Error)]
@ -27,6 +27,7 @@ pub enum FromBytesError {
#[error("Unable to parse primary")]
ParsePrimary(#[source] ParsePrimaryError),
}
/// Error type for [`VolumeDescriptor::parse_boot_record`](super::VolumeDescriptor::parse_boot_record)
#[derive(Debug, thiserror::Error)]
pub enum ParseBootRecordError {
@ -50,6 +51,10 @@ pub enum ParsePrimaryError {
#[error("Unable to parse volume id")]
VolumeId(#[source] string::InvalidCharError),
/// Unable to parse the root dir entry
#[error("Unable to parse the root dir entry")]
RootDirEntry(#[source] dir_record::FromBytesError),
/// Unable to parse volume set id
#[error("Unable to parse volume set id")]
VolumeSetId(#[source] string::InvalidCharError),

View File

@ -42,7 +42,7 @@ impl<R: Read + Seek> GameFile<R> {
self.reader.seek(SeekFrom::Start(Self::SECTOR_SIZE * n)).map_err(SectorError::Seek)?;
// Read it
let mut bytes = [0u8; <<Sector as Bytes>::ByteArray as dcb_bytes::ByteArray>::SIZE];
let mut bytes = [0; 2352];
self.reader.read_exact(&mut bytes).map_err(SectorError::Read)?;
// And parse it

View File

@ -14,7 +14,9 @@
unsafe_block_in_unsafe_fn,
never_type,
unwrap_infallible,
min_const_generics
min_const_generics,
array_methods,
slice_strip
)]
// Lints
#![warn(clippy::restriction, clippy::pedantic, clippy::nursery)]