PSArc_lib/archive/
archive_file.rs

1use std::io::Read;
2
3use super::{PSArchiveCompression, PSArchiveTOC, PSArchiveTableItem, PSArchiveVersion};
4use crate::{
5    prelude::ParsableContext,
6    traits::{ConvertAsBytes, Parsable},
7};
8use anyhow::anyhow;
9
10const PSARC_HEADER: &str = "PSAR";
11
12/// **PSArchive** contains all the information about a complete singular Playstation Archive file
13#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
14#[derive(Debug, Clone, Default, PartialEq, Eq)]
15pub struct PSArchive {
16    /// **version** is the Playstation Archive file version
17    pub version: PSArchiveVersion,
18    /// **compression** is the Playstation Archive file compression
19    pub compression: PSArchiveCompression,
20    /// **table_of_contents** is the table of contents for the Playstation Archive file
21    pub table_of_contents: PSArchiveTOC,
22    /// **manifest** is the manifest file for the Playstation Archive file
23    pub manifest: PSArchiveTableItem,
24    pub files: Vec<String>,
25    pub contents: Vec<PSArchiveTableItem>,
26    pub file_size: usize,
27}
28
29impl PSArchive {
30    pub fn get_manifest_size_offset(&self) -> (usize, usize) {
31        return (
32            self.contents[0].file_offset as usize - self.manifest.file_offset as usize,
33            self.manifest.file_offset as usize,
34        );
35    }
36
37    pub fn get_size_offset(&self, item: usize) -> (usize, usize) {
38        if item + 1 == self.contents.len() {
39            return (
40                self.file_size - self.contents[item].file_offset as usize,
41                self.contents[item].file_offset as usize,
42            );
43        } else {
44            return (
45                self.contents[item + 1].file_offset as usize
46                    - self.contents[item].file_offset as usize
47                    - 1,
48                self.contents[item].file_offset as usize,
49            );
50        }
51    }
52
53    pub fn parse_manifest(&mut self, bytes: impl ConvertAsBytes) -> Vec<String> {
54        let bytes = bytes.convert_as_bytes();
55        let (size, _) = self.get_manifest_size_offset();
56        if size > 100 {
57            let mut z = flate2::read::ZlibDecoder::new(&bytes[..]);
58            let mut s = String::new();
59            z.read_to_string(&mut s).unwrap();
60            let strings = s
61                .split("\n")
62                .collect::<Vec<&str>>()
63                .iter()
64                .map(|f| f.to_string())
65                .collect::<Vec<String>>();
66            self.files = strings.clone();
67            return strings;
68        } else {
69            let s = String::from_utf8(bytes).unwrap();
70            let strings = s
71                .split("\n")
72                .collect::<Vec<&str>>()
73                .iter()
74                .map(|f| f.to_string())
75                .collect::<Vec<String>>();
76            self.files = strings.clone();
77            return strings;
78        }
79    }
80
81    pub fn parse_file(&self, item: usize, bytes: impl ConvertAsBytes) -> Vec<u8> {
82        let bytes = bytes.convert_as_bytes();
83        let item = &self.contents[item];
84        if item.uncompressed_size > 100 {
85            let mut z = flate2::read::ZlibDecoder::new(&bytes[..]);
86            let mut s = Vec::new();
87            z.read_to_end(&mut s).unwrap();
88            return s;
89        } else {
90            return bytes;
91        }
92    }
93}
94
95impl Parsable for PSArchive {
96    type Error = anyhow::Error;
97    fn parse(bytes: impl ConvertAsBytes) -> Result<Self, Self::Error> {
98        let bytes = bytes.convert_as_bytes();
99        let header = (&bytes[0..4]).convert_as_bytes();
100        if header == PSARC_HEADER.convert_as_bytes() {
101            let version = PSArchiveVersion::parse(&bytes[0x4..0x8])?;
102            let compression = PSArchiveCompression::parse(&bytes[0x8..0xC])?;
103            let table_of_contents = PSArchiveTOC::parse(&bytes[0xC..0x20])?;
104            let mut manifest = PSArchiveTableItem::new(PSArchiveCompression::ZLIB);
105            let manifest = manifest.parse(&bytes[0x20..0x3E])?;
106            let mut contents = Vec::new();
107            for i in 0..table_of_contents.entry_count as usize {
108                let item_bytes = &bytes[0x3E + i * table_of_contents.entry_size as usize
109                    ..0x3E
110                        + i * table_of_contents.entry_size as usize
111                        + table_of_contents.entry_size as usize];
112                let mut item = PSArchiveTableItem::new(PSArchiveCompression::ZLIB);
113                let item = item.parse(item_bytes).unwrap();
114                contents.push(item);
115            }
116            return Ok(Self {
117                version,
118                compression,
119                table_of_contents,
120                manifest,
121                files: Vec::new(),
122                contents,
123                file_size: 0,
124            });
125        } else {
126            return Err(anyhow!("Invalid header for PSArc format"));
127        }
128    }
129}
130
131#[cfg(test)]
132#[doc(hidden)]
133mod test {
134    use super::{
135        super::PSArchiveFlags, PSArchiveCompression, PSArchiveTOC, PSArchiveTableItem,
136        PSArchiveVersion,
137    };
138    use crate::prelude::*;
139
140    #[test]
141    fn test_archive_parsing() {
142        let bytes = include_bytes!("../../res/test.pak").to_vec();
143        let result = PSArchive::parse(&*bytes);
144        assert_eq!(result.is_ok(), true);
145        let mut result = result.unwrap();
146        result.file_size = bytes.len();
147        let (size, offset) = result.get_manifest_size_offset();
148        result.parse_manifest(&bytes[offset..offset + size]);
149        assert_eq!(
150            result,
151            PSArchive {
152                version: PSArchiveVersion { major: 1, minor: 4 },
153                compression: PSArchiveCompression::ZLIB,
154                table_of_contents: PSArchiveTOC {
155                    length: 64,
156                    entry_size: 30,
157                    entry_count: 1,
158                    flags: PSArchiveFlags::ABSOLUTE,
159                },
160                manifest: PSArchiveTableItem {
161                    compression_type: PSArchiveCompression::ZLIB,
162                    block_offset: 0,
163                    uncompressed_size: 17,
164                    file_offset: 96,
165                },
166                files: vec!["/data/example.xml".to_string()],
167                contents: vec![PSArchiveTableItem {
168                    compression_type: PSArchiveCompression::ZLIB,
169                    block_offset: 1,
170                    uncompressed_size: 115,
171                    file_offset: 113,
172                }],
173                file_size: 196,
174            }
175        );
176        let (size, offset) = result.get_size_offset(0);
177        let contents = String::from_utf8(result.parse_file(0, &bytes[offset..offset + size]));
178        assert_eq!(contents.is_ok(), true);
179        assert_eq!(contents.unwrap(), "<TextData>\r\n	<Property name=\"color\" value=\"#ff0000\" />\r\n	<Property name=\"text\" value=\"Hello there!\" />\r\n</TextData>");
180    }
181
182    #[test]
183    pub fn temp() {
184        let bytes = include_bytes!("C:\\Users\\Jacob\\Downloads\\NMSARC.AnimMBIN.pak").to_vec();
185        let result = PSArchive::parse(&*bytes).unwrap();
186    }
187}