Improved .PAK reading.

This commit is contained in:
Filipe Rodrigues 2021-01-27 10:37:35 +00:00
parent 529263d5d3
commit 4f2415b4a5
9 changed files with 131 additions and 101 deletions

View File

@ -76,4 +76,4 @@ pub mod tim;
// Exports
pub use drv::DrvFsReader;
pub use game_file::GameFile;
pub use pak::PakFile;
pub use pak::PakFileReader;

View File

@ -1,65 +1,11 @@
//! `.PAK` file parser
//! `.PAK` files
// Modules
pub mod entry;
pub mod error;
pub mod header;
pub mod reader;
// Exports
pub use entry::PakEntry;
pub use error::FromReaderError;
pub use header::Header;
// Imports
use dcb_bytes::Bytes;
use std::io;
/// A `.PAK` file
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct PakFile {
/// All entries
entries: Vec<PakEntry>,
}
impl PakFile {
/// Deserializes a `.PAK` file from a reader
pub fn from_reader<R: io::Read + io::Seek>(reader: &mut R) -> Result<Self, FromReaderError> {
// Keep reading headers until we find the final header.
// TODO: Rewrite with scan + collect
let mut entries = vec![];
let mut cur_pos = 0;
loop {
// Read the header
// Note: We do a two-part read so we can quit early if we find `0xffff`
let mut header_bytes = [0u8; 0x8];
reader.read_exact(&mut header_bytes[..0x4]).map_err(FromReaderError::ReadHeader)?;
// If we found `0xFFFF`, this is the final header, return
if header_bytes[..0x4] == [0xff, 0xff, 0xff, 0xff] {
break;
}
// Then read the rest and parse the header
reader.read_exact(&mut header_bytes[0x4..]).map_err(FromReaderError::ReadHeader)?;
let header = Header::from_bytes(&header_bytes).map_err(FromReaderError::ParseHeader)?;
cur_pos += 8;
// Parse the entry
let entry = PakEntry::new(header, cur_pos);
entries.push(entry);
// Then update our position and seek past
let size = u64::from(header.size);
cur_pos += size;
reader.seek(io::SeekFrom::Start(cur_pos)).map_err(FromReaderError::SeekPastEntry)?;
}
Ok(Self { entries })
}
/// Returns all entries from this file
#[must_use]
pub fn entries(&self) -> &[PakEntry] {
&self.entries
}
}
pub use reader::PakFileReader;

View File

@ -2,41 +2,62 @@
// Modules
pub mod animation2d;
pub mod error;
pub mod model3d_set;
// Exports
pub use animation2d::Animation2d;
pub use error::FromReaderError;
pub use model3d_set::Model3dSet;
// Imports
use super::Header;
use dcb_bytes::Bytes;
use std::io;
/// A `.PAK` entry
#[derive(PartialEq, Eq, Clone, Debug)]
pub struct PakEntry {
/// A `.PAK` entry reader
#[derive(PartialEq, Eq, Debug)]
pub struct PakEntry<'a, R> {
/// Header
header: Header,
/// Position
pos: u64,
/// Entry reader
reader: &'a mut R,
}
impl PakEntry {
/// Creates a pak entry from it's header and position
#[must_use]
pub const fn new(header: Header, pos: u64) -> Self {
Self { header, pos }
}
impl<'a, R> PakEntry<'a, R> {
/// Returns this entry's header
#[must_use]
pub const fn header(&self) -> &Header {
&self.header
}
}
/// Returns this entry's position
impl<'a, R: io::Read> PakEntry<'a, R> {
/// Deserializes an entry from a reader
pub fn from_reader(reader: &'a mut R) -> Result<Option<Self>, FromReaderError> {
// Read the header
// Note: We do a two-part read so we can quit early if we find `0xffff`
let mut header_bytes = [0u8; 0x8];
reader.read_exact(&mut header_bytes[..0x4]).map_err(FromReaderError::ReadHeader)?;
// If we found `0xFFFF`, this is the final header, return
if header_bytes[..0x4] == [0xff, 0xff, 0xff, 0xff] {
return Ok(None);
}
// Then read the rest and parse the header
reader.read_exact(&mut header_bytes[0x4..]).map_err(FromReaderError::ReadHeader)?;
let header = Header::from_bytes(&header_bytes).map_err(FromReaderError::ParseHeader)?;
Ok(Some(Self { header, reader }))
}
/// Returns the contents of this entry
#[must_use]
pub const fn pos(&self) -> u64 {
self.pos
pub fn contents(self) -> impl io::Read + 'a {
// Note: We left the reader at the start of the read, so
// this will be correct.
<&mut R as io::Read>::take(self.reader, u64::from(self.header.size))
}
}

View File

@ -1,10 +1,10 @@
//! Errors
// Imports
use super::header;
use crate::pak::header;
use std::io;
/// Error for [`PakFile::deserialize`](super::PakFile::deserialize)
/// Error for [`PakEntry::from_reader`](super::PakEntry::from_reader)
#[derive(Debug, thiserror::Error)]
pub enum FromReaderError {
/// Unable to read header
@ -14,8 +14,4 @@ pub enum FromReaderError {
/// Unable to parse header
#[error("Unable to parse header")]
ParseHeader(#[source] header::FromBytesError),
/// Unable to seek past data
#[error("Unable to seek past entry")]
SeekPastEntry(#[source] io::Error),
}

View File

@ -1,9 +1,11 @@
//! Header
// Modules
pub mod error;
pub mod kind;
// Export
pub use error::FromBytesError;
pub use kind::Kind;
// Imports
@ -24,14 +26,6 @@ pub struct Header {
pub size: u32,
}
/// Error type for [`Bytes::from_bytes`]
#[derive(Debug, thiserror::Error)]
pub enum FromBytesError {
/// Unable to parse file kind
#[error("Unable to parse file kind")]
Kind(#[source] kind::FromBytesError),
}
impl Bytes for Header {
type ByteArray = [u8; 0x8];
type FromError = FromBytesError;
@ -47,7 +41,7 @@ impl Bytes for Header {
Ok(Self {
kind: Kind::from_bytes(bytes.file_kind).map_err(FromBytesError::Kind)?,
id: LittleEndian::read_u16(bytes.file_id),
size: LittleEndian::read_u32(bytes.size),
size: LittleEndian::read_u32(bytes.size),
})
}

View File

@ -0,0 +1,11 @@
//! Errors
// Imports
use super::kind;
/// Error type for [`Bytes::from_bytes`](dcb_bytes::Bytes::from_bytes)
#[derive(Debug, thiserror::Error)]
pub enum FromBytesError {
/// Unable to parse file kind
#[error("Unable to parse file kind")]
Kind(#[source] kind::FromBytesError),
}

49
dcb-io/src/pak/reader.rs Normal file
View File

@ -0,0 +1,49 @@
//! `.PAK` file reader
// Modules
pub mod error;
// Exports
pub use error::FromReaderError;
// Imports
use super::PakEntry;
use std::io::{self, SeekFrom};
/// A `.PAK` file reader
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub struct PakFileReader<R> {
/// Reader
reader: R,
/// Current position
cur_pos: u64,
}
impl<R> PakFileReader<R> {
/// Creates a new reader
pub const fn new(reader: R) -> Self {
Self { reader, cur_pos: 0 }
}
}
// Constructor
impl<R: io::Read + io::Seek> PakFileReader<R> {
/// Returns the next entry
pub fn next_entry(&mut self) -> Result<Option<PakEntry<R>>, FromReaderError> {
// Seek to our current position
self.reader.seek(SeekFrom::Start(self.cur_pos)).map_err(FromReaderError::SeekNextEntry)?;
// Try to read an entry
let entry = match PakEntry::from_reader(&mut self.reader).map_err(FromReaderError::ReadEntry)? {
Some(entry) => {
self.cur_pos += u64::from(entry.header().size);
Some(entry)
},
None => None,
};
Ok(entry)
}
}

View File

@ -0,0 +1,17 @@
//! Errors
// Imports
use crate::pak::entry;
use std::io;
/// Error for [`PakFile::deserialize`](super::PakFile::deserialize)
#[derive(Debug, thiserror::Error)]
pub enum FromReaderError {
/// Unable to seek to next entry
#[error("Unable to seek to next entry")]
SeekNextEntry(#[source] io::Error),
/// Unable to read entry
#[error("Unable to read entry")]
ReadEntry(#[source] entry::FromReaderError),
}

View File

@ -9,11 +9,8 @@ mod logger;
// Imports
use anyhow::Context;
use dcb_io::{pak, PakFile};
use std::{
io::{Read, Seek, SeekFrom},
path::Path,
};
use dcb_io::{pak, PakFileReader};
use std::path::Path;
fn main() -> Result<(), anyhow::Error> {
@ -33,11 +30,13 @@ fn main() -> Result<(), anyhow::Error> {
fn extract_file(input_file: &Path, output_dir: &Path) -> Result<(), anyhow::Error> {
// Open the file and parse a `pak` filesystem from it.
let mut input_file = std::fs::File::open(input_file).context("Unable to open input file")?;
let mut pak_fs = PakFileReader::new(&mut input_file);
let pak_fs = PakFile::from_reader(&mut input_file).context("Unable to parse file")?;
// Try to create the output directory
self::try_create_folder(output_dir)?;
for entry in pak_fs.entries() {
// Then read all entries
while let Some(entry) = pak_fs.next_entry().context("Unable to read entry")? {
// Get the filename
let filename = entry.header().id;
@ -59,17 +58,14 @@ fn extract_file(input_file: &Path, output_dir: &Path) -> Result<(), anyhow::Erro
log::info!("{} ({} bytes)", path.display(), entry.header().size);
// Seek the file and read it's size at most
input_file
.seek(SeekFrom::Start(entry.pos()))
.with_context(|| format!("Unable to seek to file {}", path.display()))?;
let mut input_file = input_file.by_ref().take(u64::from(entry.header().size));
let mut contents = entry.contents();
// Then create the output file and copy.
if path.exists() {
log::warn!("Overriding file {}", path.display());
}
let mut output_file = std::fs::File::create(&path).with_context(|| format!("Unable to create file {}", path.display()))?;
std::io::copy(&mut input_file, &mut output_file).with_context(|| format!("Unable to write file {}", path.display()))?;
std::io::copy(&mut contents, &mut output_file).with_context(|| format!("Unable to write file {}", path.display()))?;
}
Ok(())