Added try_to_data and to_real methods to the addresses.

Started using the new address methods instead of `From` / `TryFrom`.
Moved definition of `TryFrom` and `From` for addresses into their respective modules.
Revised implementation of `Seek` for `GameFile`.
This commit is contained in:
2020-09-10 14:35:07 +01:00
parent 3786c89f18
commit 68916aa93d
6 changed files with 108 additions and 79 deletions

View File

@@ -80,7 +80,7 @@ impl Table {
/// Deserializes the card table from a game file
pub fn deserialize<R: Read + Write + Seek>(file: &mut GameFile<R>) -> Result<Self, DeserializeError> {
// Seek to the table
file.seek(std::io::SeekFrom::Start(u64::from(Self::START_ADDRESS)))
file.seek(std::io::SeekFrom::Start(Self::START_ADDRESS.as_u64()))
.map_err(DeserializeError::Seek)?;
// Read header
@@ -196,7 +196,7 @@ impl Table {
}
// Seek to the beginning of the card table
file.seek(std::io::SeekFrom::Start(u64::from(Self::START_ADDRESS)))
file.seek(std::io::SeekFrom::Start(Self::START_ADDRESS.as_u64()))
.map_err(SerializeError::Seek)?;
// Write header
@@ -267,6 +267,7 @@ 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 }

View File

@@ -49,7 +49,7 @@ impl Table {
R: Read + Write + Seek,
{
// Seek to the beginning of the deck table
file.seek(std::io::SeekFrom::Start(u64::from(Self::START_ADDRESS)))
file.seek(std::io::SeekFrom::Start(Self::START_ADDRESS.as_u64()))
.map_err(DeserializeError::Seek)?;
// Read header
@@ -105,7 +105,7 @@ impl Table {
}
// Seek to the beginning of the deck table
file.seek(std::io::SeekFrom::Start(u64::from(Self::START_ADDRESS)))
file.seek(std::io::SeekFrom::Start(Self::START_ADDRESS.as_u64()))
.map_err(SerializeError::Seek)?;
// Write header

View File

@@ -12,54 +12,3 @@ pub mod real;
// Exports
pub use data::Data;
pub use real::Real;
/// Error type for `TryFrom<Real> for Data`
#[derive(PartialEq, Eq, Clone, Copy, Debug, thiserror::Error)]
pub enum RealToDataError {
/// Occurs when the Real is outside of the data section of the sector
#[error("The real address {} could not be converted to a data address as it is not in the data section", .0)]
OutsideDataSection(Real),
}
// Real -> Data
impl std::convert::TryFrom<Real> for Data {
type Error = RealToDataError;
fn try_from(real_address: Real) -> Result<Self, Self::Error> {
// If the real address isn't in the data section, then return err
if !real_address.in_data_section() {
return Err(Self::Error::OutsideDataSection(real_address));
}
// Else get the sector and offset
let real_sector = real_address.sector();
let real_sector_offset = real_address.offset();
// The data address is just converting the real_sector
// to a data_sector and subtracting the header from the
// real offset to get the data offset
#[rustfmt::skip]
Ok(Self::from(
Real::SECTOR_BYTE_SIZE * real_sector + // Base of data sector
real_sector_offset - Real::HEADER_BYTE_SIZE, // Data offset (skipping header)
))
}
}
// Data -> Real
impl From<Data> for Real {
fn from(data_address: Data) -> Self {
// Get the sector and offset
let data_sector = data_address.sector();
let data_sector_offset = data_address.offset();
// Then the real address is just converting the data_sector
// to a real_sector and adding the header plus the offset
#[rustfmt::skip]
Self::from(
Self::SECTOR_BYTE_SIZE * data_sector + // Base of real sector
Self::HEADER_BYTE_SIZE + // Skip header
data_sector_offset, // Offset inside data sector
)
}
}

View File

@@ -27,6 +27,23 @@ impl Data {
self.0
}
/// Converts this data offset to a real offset
#[must_use]
pub const fn to_real(self) -> Real {
// Get the sector and offset
let data_sector = self.sector();
let data_sector_offset = self.offset();
// Then the real address is just converting the data_sector
// to a real_sector and adding the header plus the offset
#[rustfmt::skip]
Real::from_u64(
Real::SECTOR_BYTE_SIZE * data_sector + // Base of real sector
Real::HEADER_BYTE_SIZE + // Skip header
data_sector_offset, // Offset inside data sector
)
}
/// Returns the sector associated with this address
#[must_use]
pub const fn sector(self) -> u64 {
@@ -115,3 +132,9 @@ impl std::fmt::Display for Data {
write!(f, "{:x}", u64::from(*self))
}
}
impl From<Data> for Real {
fn from(data_address: Data) -> Self {
data_address.to_real()
}
}

View File

@@ -1,7 +1,10 @@
//! File real addresses
// Imports
use crate::util::{abs_diff, signed_offset};
use crate::{
io::address::Data,
util::{abs_diff, signed_offset},
};
/// A type for defining addresses on the `.bin` file.
///
@@ -11,6 +14,14 @@ use crate::util::{abs_diff, signed_offset};
#[derive(derive_more::From, derive_more::Into)]
pub struct Real(u64);
/// Error type for [`Real::to_data`]
#[derive(PartialEq, Eq, Clone, Copy, Debug, thiserror::Error)]
pub enum ToDataError {
/// Occurs when the Real is outside of the data section of the sector
#[error("Unable to convert real address {} to a data address, as it was not in the data section", .0)]
OutsideDataSection(Real),
}
// Constants
impl Real {
/// The number of bytes the data section takes up in the sector
@@ -42,6 +53,27 @@ impl Real {
self.0
}
/// Converts this real sector into a data sector
pub const fn try_to_data(self) -> Result<Data, ToDataError> {
// If the real address isn't in the data section, then return err
if !self.in_data_section() {
return Err(ToDataError::OutsideDataSection(self));
}
// Else get the sector and offset
let real_sector = self.sector();
let real_sector_offset = self.offset();
// The data address is just converting the real_sector
// to a data_sector and subtracting the header from the
// real offset to get the data offset
#[rustfmt::skip]
Ok(Data::from_u64(
Self::SECTOR_BYTE_SIZE * real_sector + // Base of data sector
real_sector_offset - Self::HEADER_BYTE_SIZE, // Data offset (skipping header)
))
}
/// Returns the real sector associated with this address
#[must_use]
pub const fn sector(self) -> u64 {
@@ -119,13 +151,22 @@ impl std::ops::Sub<Real> for Real {
type Output = i64;
fn sub(self, address: Self) -> i64 {
abs_diff(u64::from(self), u64::from(address))
abs_diff(self.as_u64(), address.as_u64())
}
}
// Display
impl std::fmt::Display for Real {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:x}", u64::from(*self))
write!(f, "{:x}", self.as_u64())
}
}
// Real -> Data
impl std::convert::TryFrom<Real> for Data {
type Error = ToDataError;
fn try_from(real_address: Real) -> Result<Self, Self::Error> {
real_address.try_to_data()
}
}

View File

@@ -3,10 +3,10 @@
//! See [`GameFile`] for details
// Imports
use crate::io::address::{Data as DataAddress, Real as RealAddress, RealToDataError};
use crate::io::address::{real, Data as DataAddress, Real as RealAddress};
use std::{
convert::TryInto,
io::{Read, Seek, Write},
io::{Read, Seek, SeekFrom, Write},
};
/// A type that abstracts over a the game reader.
@@ -28,6 +28,14 @@ use std::{
/// `GameFile` is generic over `R`, this being any type that implements
/// `Read`, `Write` and `Seek`, thus being able to read from either a
/// reader, a buffer in memory or even some remote network location.
///
/// # Read/Write Strategy
/// The strategy this employs for reading and writing currently is to
/// get the current 2048 byte block and work on it until it is exhausted,
/// then to get a new 2048 byte block until the operation is complete.
/// This will require an `io` call for every single 2048 byte block instead
/// of an unique call for all of the block, but due to the invariants required,
/// this is the strategy employed.
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default, Hash, Debug)]
pub struct GameFile<R: Read + Write + Seek> {
/// The type to read and write from
@@ -48,7 +56,7 @@ impl<R: Read + Write + Seek> GameFile<R> {
pub fn from_reader(mut reader: R) -> Result<Self, NewGameFileError> {
// Seek the reader to the beginning of the data section
reader
.seek(std::io::SeekFrom::Start(RealAddress::DATA_START))
.seek(SeekFrom::Start(DataAddress::from_u64(0).to_real().as_u64()))
.map_err(NewGameFileError::SeekData)?;
Ok(Self { reader })
@@ -78,7 +86,7 @@ impl<R: Read + Write + Seek> Read for GameFile<R> {
// If we're at the end of the data section, seek to the next data section
if cur_real_address == data_section_end {
// Seek ahead by skipping the footer and next header
self.reader.seek(std::io::SeekFrom::Current(
self.reader.seek(SeekFrom::Current(
(RealAddress::FOOTER_BYTE_SIZE + RealAddress::HEADER_BYTE_SIZE)
.try_into()
.expect("Sector offset didn't fit into `u64`"),
@@ -151,7 +159,7 @@ impl<R: Read + Write + Seek> Write for GameFile<R> {
// If we're at the end of the data section, seek to the next data section
if cur_real_address == data_section_end {
// Seek ahead by skipping the footer and next header
self.reader.seek(std::io::SeekFrom::Current(
self.reader.seek(SeekFrom::Current(
(RealAddress::FOOTER_BYTE_SIZE + RealAddress::HEADER_BYTE_SIZE)
.try_into()
.expect("Sector offset didn't fit into `u64`"),
@@ -208,35 +216,42 @@ impl<R: Read + Write + Seek> Write for GameFile<R> {
/// Returned when, after seeking, we ended up in a non-data section
#[derive(PartialEq, Eq, Clone, Copy, Debug, thiserror::Error)]
#[error("Reader seeked into a non-data section")]
pub struct SeekNonDataError(#[source] RealToDataError);
pub struct SeekNonDataError(#[source] real::ToDataError);
impl<R: Read + Write + Seek> Seek for GameFile<R> {
fn seek(&mut self, data_pos: std::io::SeekFrom) -> std::io::Result<u64> {
use std::{convert::TryFrom, io::SeekFrom};
fn seek(&mut self, data_pos: SeekFrom) -> std::io::Result<u64> {
// Imports
use std::ops::Add;
// Calculate the real position
let real_pos = match data_pos {
SeekFrom::Start(data_address) => SeekFrom::Start(u64::from(RealAddress::from(DataAddress::from(data_address)))),
SeekFrom::Current(data_offset) => SeekFrom::Start(u64::from(RealAddress::from(
DataAddress::try_from(RealAddress::from(self.reader.stream_position()?))
.map_err(SeekNonDataError)
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))? +
data_offset,
))),
SeekFrom::Start(data_address) => SeekFrom::Start(
// Parse the address as data, then convert it to real
DataAddress::from(data_address).to_real().as_u64(),
),
SeekFrom::Current(data_offset) => SeekFrom::Start(
// Get the real address, convert it to data, add the offset in data units, then convert it back into real
RealAddress::from(self.reader.stream_position()?)
.try_to_data()
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, SeekNonDataError(err)))?
.add(data_offset)
.to_real()
.as_u64(),
),
SeekFrom::End(_) => {
todo!("SeekFrom::End isn't currently implemented");
todo!("`SeekFrom::End` seeking isn't currently implemented");
},
};
// Seek to the real position and get where we are right now
let cur_real_address = self.reader.seek(real_pos)?;
let cur_real_address = RealAddress::from(self.reader.seek(real_pos)?);
// Get the data address
let data_address = DataAddress::try_from(RealAddress::from(cur_real_address))
.map_err(SeekNonDataError)
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
let data_address = cur_real_address
.try_to_data()
.map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, SeekNonDataError(err)))?;
// And return the new data address
Ok(u64::from(data_address))
Ok(data_address.as_u64())
}
}