use std::io::Read;
use super::{PSArchiveCompression, PSArchiveTOC, PSArchiveTableItem, PSArchiveVersion};
use crate::{
prelude::ParsableContext,
traits::{ConvertAsBytes, Parsable},
};
use anyhow::anyhow;
const PSARC_HEADER: &str = "PSAR";
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PSArchive {
pub version: PSArchiveVersion,
pub compression: PSArchiveCompression,
pub table_of_contents: PSArchiveTOC,
pub manifest: PSArchiveTableItem,
pub files: Vec<String>,
pub contents: Vec<PSArchiveTableItem>,
pub file_size: usize,
}
impl PSArchive {
pub fn get_manifest_size_offset(&self) -> (usize, usize) {
return (
self.contents[0].file_offset as usize - self.manifest.file_offset as usize,
self.manifest.file_offset as usize,
);
}
pub fn get_size_offset(&self, item: usize) -> (usize, usize) {
if item + 1 == self.contents.len() {
return (
self.file_size - self.contents[item].file_offset as usize,
self.contents[item].file_offset as usize,
);
} else {
return (
self.contents[item + 1].file_offset as usize
- self.contents[item].file_offset as usize
- 1,
self.contents[item].file_offset as usize,
);
}
}
pub fn parse_manifest(&mut self, bytes: impl ConvertAsBytes) -> Vec<String> {
let bytes = bytes.convert_as_bytes();
let (size, _) = self.get_manifest_size_offset();
if size > 100 {
let mut z = flate2::read::ZlibDecoder::new(&bytes[..]);
let mut s = String::new();
z.read_to_string(&mut s).unwrap();
let strings = s
.split("\n")
.collect::<Vec<&str>>()
.iter()
.map(|f| f.to_string())
.collect::<Vec<String>>();
self.files = strings.clone();
return strings;
} else {
let s = String::from_utf8(bytes).unwrap();
let strings = s
.split("\n")
.collect::<Vec<&str>>()
.iter()
.map(|f| f.to_string())
.collect::<Vec<String>>();
self.files = strings.clone();
return strings;
}
}
pub fn parse_file(&self, item: usize, bytes: impl ConvertAsBytes) -> Vec<u8> {
let bytes = bytes.convert_as_bytes();
let item = &self.contents[item];
if item.uncompressed_size > 100 {
let mut z = flate2::read::ZlibDecoder::new(&bytes[..]);
let mut s = Vec::new();
z.read_to_end(&mut s).unwrap();
return s;
} else {
return bytes;
}
}
}
impl Parsable for PSArchive {
type Error = anyhow::Error;
fn parse(bytes: impl ConvertAsBytes) -> Result<Self, Self::Error> {
let bytes = bytes.convert_as_bytes();
let header = (&bytes[0..4]).convert_as_bytes();
if header == PSARC_HEADER.convert_as_bytes() {
let version = PSArchiveVersion::parse(&bytes[0x4..0x8])?;
let compression = PSArchiveCompression::parse(&bytes[0x8..0xC])?;
let table_of_contents = PSArchiveTOC::parse(&bytes[0xC..0x20])?;
let mut manifest = PSArchiveTableItem::new(PSArchiveCompression::ZLIB);
let manifest = manifest.parse(&bytes[0x20..0x3E])?;
let mut contents = Vec::new();
for i in 0..table_of_contents.entry_count as usize {
let item_bytes = &bytes[0x3E + i * table_of_contents.entry_size as usize
..0x3E
+ i * table_of_contents.entry_size as usize
+ table_of_contents.entry_size as usize];
let mut item = PSArchiveTableItem::new(PSArchiveCompression::ZLIB);
let item = item.parse(item_bytes).unwrap();
contents.push(item);
}
return Ok(Self {
version,
compression,
table_of_contents,
manifest,
files: Vec::new(),
contents,
file_size: 0,
});
} else {
return Err(anyhow!("Invalid header for PSArc format"));
}
}
}
#[cfg(test)]
#[doc(hidden)]
mod test {
use super::{
super::PSArchiveFlags, PSArchiveCompression, PSArchiveTOC, PSArchiveTableItem,
PSArchiveVersion,
};
use crate::prelude::*;
#[test]
fn test_archive_parsing() {
let bytes = include_bytes!("../../res/test.pak").to_vec();
let result = PSArchive::parse(&*bytes);
assert_eq!(result.is_ok(), true);
let mut result = result.unwrap();
result.file_size = bytes.len();
let (size, offset) = result.get_manifest_size_offset();
result.parse_manifest(&bytes[offset..offset + size]);
assert_eq!(
result,
PSArchive {
version: PSArchiveVersion { major: 1, minor: 4 },
compression: PSArchiveCompression::ZLIB,
table_of_contents: PSArchiveTOC {
length: 64,
entry_size: 30,
entry_count: 1,
flags: PSArchiveFlags::ABSOLUTE,
},
manifest: PSArchiveTableItem {
compression_type: PSArchiveCompression::ZLIB,
block_offset: 0,
uncompressed_size: 17,
file_offset: 96,
},
files: vec!["/data/example.xml".to_string()],
contents: vec![PSArchiveTableItem {
compression_type: PSArchiveCompression::ZLIB,
block_offset: 1,
uncompressed_size: 115,
file_offset: 113,
}],
file_size: 196,
}
);
let (size, offset) = result.get_size_offset(0);
let contents = String::from_utf8(result.parse_file(0, &bytes[offset..offset + size]));
assert_eq!(contents.is_ok(), true);
assert_eq!(contents.unwrap(), "<TextData>\r\n <Property name=\"color\" value=\"#ff0000\" />\r\n <Property name=\"text\" value=\"Hello there!\" />\r\n</TextData>");
}
#[test]
pub fn temp() {
let bytes = include_bytes!("C:\\Users\\Jacob\\Downloads\\NMSARC.AnimMBIN.pak").to_vec();
let result = PSArchive::parse(&*bytes).unwrap();
}
}