use std::{
fs::File,
io::{Cursor, Read, Seek},
path::Path,
};
use crate::{
PlaystationArchive,
compression::CompressionType,
error::{Error, Result},
file::{BasicFileEntry as _, FileEntry},
path::ArchiveFlags,
toc::{DecryptionKey, TableOfContent},
};
#[derive(Default)]
pub struct Config {
pub decryption_key: Option<DecryptionKey>,
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum BlockSize {
U16,
U24,
U32,
}
impl BlockSize {
pub(crate) const fn as_u32(self) -> u32 {
match self {
Self::U16 => 0x0001_0000,
Self::U24 => 0x0100_0000,
Self::U32 => u32::MAX,
}
}
pub(crate) fn read<R>(self, reader: &mut R) -> Result<u32>
where
R: Read + Seek,
{
match self {
Self::U16 => Ok(u32::from(read_u16(reader)?)),
Self::U24 => u32::try_from(read_unsigned::<3>(reader)?)
.map_err(|_| Error::Corrupt("3 bytes got parsed as more")),
Self::U32 => read_u32(reader),
}
}
}
impl TryFrom<u32> for BlockSize {
type Error = Error;
fn try_from(value: u32) -> Result<Self> {
match value {
0x0001_0000 => Ok(Self::U16),
0x0100_0000 => Ok(Self::U24),
u32::MAX => Ok(Self::U32),
_ => Err(Error::Corrupt("unregular block size")),
}
}
}
impl<R: Read + Seek> PlaystationArchive<R> {
pub fn new(reader: R) -> Result<Self> {
Self::with_config(Config::default(), reader)
}
pub fn with_config(config: Config, mut reader: R) -> Result<Self> {
let mut magic = [0_u8; 4];
reader.read_exact(&mut magic)?;
if magic != *b"PSAR" {
return Err(Error::UnrecognizedFile);
}
let major_version = read_u16(&mut reader)?;
let minor_version = read_u16(&mut reader)?;
if major_version != 1 || minor_version != 4 {
return Err(Error::UnsupportedVersion);
}
let compression_type: CompressionType = read_u32(&mut reader)?.try_into()?;
let table_of_content = TableOfContent::read(&mut reader)?;
let block_size: BlockSize = read_u32(&mut reader)?.try_into()?;
let archive_flags: ArchiveFlags = read_u32(&mut reader)?.try_into()?;
let table_of_content_entries =
table_of_content.read_entries(&mut reader, archive_flags, config.decryption_key)?;
let blocks_amount = table_of_content.blocks_amount();
let block_sizes = (0..blocks_amount)
.map(|_| block_size.read(&mut reader))
.collect::<Result<Vec<_>>>()?;
let manifest_entry = table_of_content_entries
.first()
.ok_or(Error::FileAtIndexDoesNotExist(0))?;
let mut manifest_bytes = vec![0; manifest_entry.input_size()];
let mut manifest_writer = Cursor::new(&mut manifest_bytes);
compression_type.decompress(
&mut reader,
&mut manifest_writer,
manifest_entry,
block_size,
&block_sizes,
)?;
let manifest_string = String::from_utf8(manifest_bytes)
.map_err(|_| Error::Corrupt("Manifest is not valid UTF-8"))?;
let file_entries = std::iter::once("manifest.txt")
.chain(manifest_string.lines())
.zip(table_of_content_entries)
.map(|(path, table_of_content_entry)| {
FileEntry::from_table_of_content(table_of_content_entry, path)
})
.collect();
Ok(Self {
compression_type,
file_entries,
block_size,
reader,
block_sizes,
})
}
pub fn extract<P>(&mut self, directory: P) -> Result<()>
where
P: AsRef<Path>,
{
let directory = directory.as_ref();
std::fs::create_dir_all(directory)?;
let directory = directory.canonicalize()?;
let file_entries = self
.file_entries
.iter()
.skip(1)
.cloned()
.collect::<Vec<_>>();
for file_entry in file_entries {
let file_path = directory.join(&file_entry.path);
if let Some(file_dir) = file_path.parent() {
std::fs::create_dir_all(file_dir)?;
}
let mut file = File::create_new(file_path)?;
self.write_bytes_by_entry(&mut file, &file_entry)?;
}
Ok(())
}
}
#[inline]
pub(crate) fn read_unsigned<const N: usize>(reader: &mut impl Read) -> Result<usize> {
let mut buf = [0_u8; size_of::<usize>()];
reader.read_exact(&mut buf[const { size_of::<usize>() - N }..])?;
Ok(usize::from_be_bytes(buf))
}
#[inline]
pub(crate) fn read_u32<R>(reader: &mut R) -> Result<u32>
where
R: Read,
{
let mut buf = [0_u8; size_of::<u32>()];
reader.read_exact(&mut buf)?;
Ok(u32::from_be_bytes(buf))
}
#[inline]
pub(crate) fn read_u16<R>(reader: &mut R) -> Result<u16>
where
R: Read,
{
let mut buf = [0_u8; size_of::<u16>()];
reader.read_exact(&mut buf)?;
Ok(u16::from_be_bytes(buf))
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
#[test]
fn u64_bytes() {
let test_value = 123_456_789_usize;
let be_bytes = Cursor::new(test_value.to_be_bytes());
let mut test = be_bytes.clone();
assert_eq!(
read_unsigned::<8>(&mut test).expect("Error reading"),
test_value
);
let mut test = be_bytes.clone();
test.seek_relative(1).expect("Unexpected EOB");
assert_eq!(
read_unsigned::<7>(&mut test).expect("Error reading"),
test_value
);
let mut test = be_bytes.clone();
test.seek_relative(2).expect("Unexpected EOB");
assert_eq!(
read_unsigned::<6>(&mut test).expect("Error reading"),
test_value
);
let mut test = be_bytes;
test.seek_relative(3).expect("Unexpected EOB");
assert_eq!(
read_unsigned::<5>(&mut test).expect("Error reading"),
test_value
);
}
}