Improved implementation of Drv filesystem.

This commit is contained in:
Filipe Rodrigues 2021-01-24 12:31:20 +00:00
parent 42b694ced0
commit 15c2b5ac46
7 changed files with 173 additions and 104 deletions

View File

@ -8,30 +8,16 @@ pub mod file;
// Exports
pub use dir::{DirEntry, DirReader};
pub use error::FromReaderError;
pub use file::File;
// Imports
use std::io;
pub use file::FileReader;
/// Filesystem reader
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct DrvFsReader {
/// Root directory
root: DirReader,
}
pub struct DrvFsReader;
impl DrvFsReader {
/// Reads a filesystem from a reader
pub fn from_reader<R: io::Read>(reader: &mut R) -> Result<Self, FromReaderError> {
// Read the root directory
let root = DirReader::from_reader(reader).map_err(FromReaderError::RootDir)?;
Ok(Self { root })
}
/// Returns the root directory of this filesystem
#[must_use]
pub const fn root(&self) -> &DirReader {
&self.root
pub const fn root() -> DirReader {
DirReader::new(0)
}
}

View File

@ -1,4 +1,4 @@
//! Directories
//! Directory
// Modules
pub mod entry;
@ -6,43 +6,61 @@ pub mod error;
// Exports
pub use entry::DirEntry;
pub use error::FromReaderError;
pub use error::{EntriesError, ReadEntryError};
// Imports
use dcb_bytes::Bytes;
use std::io;
use std::io::{self, SeekFrom};
/// Directory reader
#[derive(PartialEq, Eq, Clone, Debug)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct DirReader {
/// All directory entries
entries: Vec<DirEntry>,
/// Sector position
sector_pos: u32,
}
impl DirReader {
/// Parses a directory from a reader
pub fn from_reader<R: io::Read>(reader: &mut R) -> Result<Self, FromReaderError> {
let entries = std::iter::from_fn(move || {
/// Creates a directory reader from it's sector
#[must_use]
pub const fn new(sector_pos: u32) -> Self {
Self { sector_pos }
}
/// Returns this directory's sector position
#[must_use]
pub const fn sector_pos(self) -> u32 {
self.sector_pos
}
/// Seeks to this directory on a reader
pub fn seek_to<R: io::Seek>(self, reader: &mut R) -> Result<u64, io::Error> {
reader.seek(SeekFrom::Start(u64::from(self.sector_pos) * 2048))
}
/// Returns an iterator over all entries in this directory
pub fn entries<R: io::Read + io::Seek>(
self, reader: &mut R,
) -> Result<impl Iterator<Item = Result<DirEntry, ReadEntryError>> + '_, EntriesError> {
// Seek to the sector
reader
.seek(SeekFrom::Start(u64::from(self.sector_pos) * 2048))
.map_err(EntriesError::Seek)?;
// Then create the iterator
let iter = std::iter::from_fn(move || {
// Read the bytes
let mut entry_bytes = [0; 0x20];
if let Err(err) = reader.read_exact(&mut entry_bytes) {
return Some(Err(FromReaderError::ReadEntry(err)));
return Some(Err(ReadEntryError::ReadEntry(err)));
}
// And parse it
match DirEntry::from_bytes(&entry_bytes) {
Err(entry::FromBytesError::InvalidKind(0)) => None,
res => Some(res.map_err(FromReaderError::ParseEntry)),
res => Some(res.map_err(ReadEntryError::ParseEntry)),
}
})
.collect::<Result<Vec<_>, _>>()?;
});
Ok(Self { entries })
}
/// Returns all the entries in this directory
#[must_use]
pub fn entries(&self) -> &[DirEntry] {
&self.entries
Ok(iter)
}
}

View File

@ -6,52 +6,56 @@ pub mod error;
// Exports
pub use error::FromBytesError;
use crate::drv::FileReader;
// Imports
use super::DirReader;
use byteorder::{ByteOrder, LittleEndian};
use chrono::NaiveDateTime;
use dcb_bytes::Bytes;
use dcb_util::{array_split, array_split_mut, ascii_str_arr::AsciiChar, AsciiStrArr};
use std::{
convert::TryFrom,
io::{self, Seek, SeekFrom},
};
use std::convert::TryFrom;
/// A directory entry kind
#[derive(PartialEq, Eq, Clone, Debug)]
pub enum DirEntryKind {
/// A file
File {
/// File extension
extension: AsciiStrArr<0x3>,
/// Size
size: u32,
},
File(FileReader),
/// Directory
Dir,
Dir(DirReader),
}
/// A directory entry
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct DirEntry {
/// Entry name
pub name: AsciiStrArr<0x10>,
name: AsciiStrArr<0x10>,
/// Entry date
pub date: NaiveDateTime,
/// Sector position
pub sector_pos: u32,
date: NaiveDateTime,
/// Entry kind
pub kind: DirEntryKind,
kind: DirEntryKind,
}
impl DirEntry {
/// Seeks to this entry's data on a reader
pub fn seek_to<R: Seek>(&self, reader: &mut R) -> Result<u64, io::Error> {
reader.seek(SeekFrom::Start(u64::from(self.sector_pos) * 2048))
/// Returns this entry's name
#[must_use]
pub const fn name(&self) -> &AsciiStrArr<0x10> {
&self.name
}
/// Returns this entry's date
#[must_use]
pub const fn date(&self) -> NaiveDateTime {
self.date
}
/// Returns this entry's kind
#[must_use]
pub const fn kind(&self) -> &DirEntryKind {
&self.kind
}
}
@ -70,6 +74,8 @@ impl Bytes for DirEntry {
name : [0x10],
);
let sector_pos = LittleEndian::read_u32(bytes.sector_pos);
// Check kind
let kind = match bytes.kind {
0x1 => {
@ -77,9 +83,9 @@ impl Bytes for DirEntry {
extension.trim_end(AsciiChar::Null);
let size = LittleEndian::read_u32(bytes.size);
DirEntryKind::File { extension, size }
DirEntryKind::File(FileReader::new(extension, sector_pos, size))
},
0x80 => DirEntryKind::Dir,
0x80 => DirEntryKind::Dir(DirReader::new(sector_pos)),
&kind => return Err(FromBytesError::InvalidKind(kind)),
};
@ -96,15 +102,9 @@ impl Bytes for DirEntry {
// Then get the name and other common metadata
let mut name = AsciiStrArr::from_bytes(bytes.name).map_err(FromBytesError::Name)?;
name.trim_end(AsciiChar::Null);
let sector_pos = LittleEndian::read_u32(bytes.sector_pos);
let date = NaiveDateTime::from_timestamp(i64::from(LittleEndian::read_u32(bytes.data)), 0);
Ok(Self {
name,
date,
sector_pos,
kind,
})
Ok(Self { name, date, kind })
}
fn to_bytes(&self, bytes: &mut Self::ByteArray) -> Result<(), Self::ToError> {
@ -118,15 +118,20 @@ impl Bytes for DirEntry {
);
match &self.kind {
DirEntryKind::File { extension, size } => {
DirEntryKind::File(file) => {
*bytes.kind = 0x1;
let extension = extension.as_bytes();
let extension = file.extension().as_bytes();
bytes.extension[..extension.len()].copy_from_slice(extension);
bytes.extension[extension.len()..].fill(0);
LittleEndian::write_u32(bytes.size, *size);
LittleEndian::write_u32(bytes.sector_pos, file.sector_pos());
LittleEndian::write_u32(bytes.size, file.size());
},
DirEntryKind::Dir => {
DirEntryKind::Dir(dir) => {
*bytes.kind = 0x80;
LittleEndian::write_u32(bytes.sector_pos, dir.sector_pos());
},
};
@ -135,8 +140,7 @@ impl Bytes for DirEntry {
bytes.name[..name.len()].copy_from_slice(name);
bytes.name[name.len()..].fill(0);
// TODO: Support dates after by either returning error or saturating.
LittleEndian::write_u32(bytes.sector_pos, self.sector_pos);
// Write the date by saturating it if it's too large or small.
let secs = self.date.timestamp();
let secs = match u32::try_from(secs) {
Ok(secs) => secs,

View File

@ -4,9 +4,17 @@
use super::entry;
use std::io;
/// Error for [`Dir::from_reader`](super::Dir::from_reader)
/// Error for [`Dir::entries`](super::Dir::entries)
#[derive(Debug, thiserror::Error)]
pub enum FromReaderError {
pub enum EntriesError {
/// Unable to seek to directory
#[error("Unable to seek to directory")]
Seek(#[source] io::Error),
}
/// Error for [`Dir::entries`](super::Dir::entries)
#[derive(Debug, thiserror::Error)]
pub enum ReadEntryError {
/// Unable to read entry bytes
#[error("Unable to read entry bytes")]
ReadEntry(#[source] io::Error),

View File

@ -8,5 +8,5 @@ use super::dir;
pub enum FromReaderError {
/// Unable to read root directory
#[error("Unable to read root directory")]
RootDir(#[source] dir::FromReaderError),
RootDir(#[source] dir::ReadEntryError),
}

View File

@ -1,6 +1,60 @@
//! File
/// File
// Imports
use dcb_util::AsciiStrArr;
use std::io::{self, SeekFrom};
/// A file reader
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct File {}
pub struct FileReader {
/// File extension
extension: AsciiStrArr<0x3>,
/// Sector position
sector_pos: u32,
/// Size
size: u32,
}
impl FileReader {
/// Creates a new file reader from it's extension, sector position and size.
#[must_use]
pub const fn new(extension: AsciiStrArr<0x3>, sector_pos: u32, size: u32) -> Self {
Self { extension, sector_pos, size }
}
/// Returns this file's extension
#[must_use]
pub const fn extension(&self) -> &AsciiStrArr<0x3> {
&self.extension
}
/// Returns this file's sector position
#[must_use]
pub const fn sector_pos(&self) -> u32 {
self.sector_pos
}
/// Returns this file's sector size
#[must_use]
pub const fn size(&self) -> u32 {
self.size
}
/// Returns a reader for this file from the filesystem reader
pub fn reader<'a, R: io::Read + io::Seek>(&self, reader: &'a mut R) -> Result<impl io::Read + 'a, io::Error> {
// Seek to our file
self.seek_to(reader)?;
// Then take at most our size
let reader = <&mut R as io::Read>::take(reader, u64::from(self.size));
Ok(reader)
}
/// Seeks to this file on a reader
pub fn seek_to<R: io::Seek>(&self, reader: &mut R) -> Result<u64, io::Error> {
reader.seek(SeekFrom::Start(u64::from(self.sector_pos) * 2048))
}
}

View File

@ -34,40 +34,42 @@ fn extract_file(input_file: &Path, output_dir: &Path) -> Result<(), anyhow::Erro
// Open the file and parse a `drv` filesystem from it.
let mut input_file = std::fs::File::open(input_file).context("Unable to open input file")?;
let drv_fs = DrvFsReader::from_reader(&mut input_file).context("Unable to parse filesystem")?;
self::extract_tree(&mut input_file, drv_fs.root(), output_dir).context("Unable to extract files from root")
// Create output directory if it doesn't exist
self::try_create_folder(output_dir)?;
// Then extract the tree
self::extract_tree(&mut input_file, &DrvFsReader::root(), output_dir).context("Unable to extract files from root")
}
/// Extracts a `.drv` file from a reader and starting directory
fn extract_tree<R: io::Read + io::Seek>(reader: &mut R, dir: &DirReader, path: &Path) -> Result<(), anyhow::Error> {
// Create path if it doesn't exist
self::try_create_folder(path)?;
// Then for each entry create it
for entry in dir.entries() {
let entries: Vec<_> = dir
.entries(reader)
.with_context(|| format!("Unable to get directory entries of {}", path.display()))?
.collect();
for entry in entries {
// If we can't read it, return Err
let entry = entry.with_context(|| format!("Unable to read directory entry of {}", path.display()))?;
// Get the filename and new path
let name = match &entry.kind {
DirEntryKind::File { extension, .. } => format!("{}.{}", entry.name, extension),
DirEntryKind::Dir => entry.name.to_string(),
let name = match entry.kind() {
DirEntryKind::File(file) => format!("{}.{}", entry.name(), file.extension()),
DirEntryKind::Dir(_) => entry.name().to_string(),
};
let path = path.join(name);
// Create the date
// Note: `.DRV` only supports second precision.
let time = filetime::FileTime::from_unix_time(entry.date.timestamp(), 0);
// Seek to the entry's data
entry
.seek_to(reader)
.with_context(|| format!("Unable to seek to directory entry {}", path.display()))?;
let time = filetime::FileTime::from_unix_time(entry.date().timestamp(), 0);
// Then check what we need to do with it
match entry.kind {
DirEntryKind::File { size, .. } => {
log::info!("{} ({} bytes)", path.display(), size);
match entry.kind() {
DirEntryKind::File(file) => {
log::info!("{} ({} bytes)", path.display(), file.size());
// Limit the input file to it's size
let mut reader = <&mut R as io::Read>::take(reader, u64::from(size));
let mut reader = file.reader(reader).with_context(|| format!("Unable to read file {}", path.display()))?;
// Then create the output file and copy.
let mut output_file = std::fs::File::create(&path).with_context(|| format!("Unable to create file {}", path.display()))?;
@ -77,18 +79,15 @@ fn extract_tree<R: io::Read + io::Seek>(reader: &mut R, dir: &DirReader, path: &
filetime::set_file_handle_times(&output_file, None, Some(time))
.with_context(|| format!("Unable to write date for file {}", path.display()))?;
},
DirEntryKind::Dir => {
// Read the directory
let dir = DirReader::from_reader(reader).with_context(|| format!("Unable to parse directory {}", path.display()))?;
log::info!("{} ({} entries)", path.display(), dir.entries().len());
DirEntryKind::Dir(dir) => {
log::info!("{}", path.display());
// Create the directory and set it's modification date
self::try_create_folder(&path)?;
filetime::set_file_mtime(&path, time).with_context(|| format!("Unable to write date for directory {}", path.display()))?;
// Then recurse over it
self::extract_tree(reader, &dir, &path).with_context(|| format!("Unable to extract directory {}", path.display()))?;
self::extract_tree(reader, dir, &path).with_context(|| format!("Unable to extract directory {}", path.display()))?;
},
}
}