mirror of
https://github.com/Zenithsiz/dcb.git
synced 2026-02-05 08:43:46 +00:00
Improved implementation of Drv filesystem.
This commit is contained in:
parent
42b694ced0
commit
15c2b5ac46
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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),
|
||||
}
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()))?;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user