mirror of
https://github.com/Zenithsiz/dcb.git
synced 2026-02-06 09:27:41 +00:00
Improved .PAK reading.
This commit is contained in:
parent
529263d5d3
commit
4f2415b4a5
@ -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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -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),
|
||||
}
|
||||
@ -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),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
11
dcb-io/src/pak/header/error.rs
Normal file
11
dcb-io/src/pak/header/error.rs
Normal 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
49
dcb-io/src/pak/reader.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
17
dcb-io/src/pak/reader/error.rs
Normal file
17
dcb-io/src/pak/reader/error.rs
Normal 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),
|
||||
}
|
||||
@ -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(())
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user