popcap_pak/
pak.rs

1use crate::FILEFLAGS_DEFAULT;
2use crate::FILEFLAGS_END;
3use crate::MAGIC;
4use crate::PakError;
5use crate::VERSION;
6use crate::entry::Entry;
7use crate::reader::PakReader;
8use crate::writer::PakWriter;
9use std::io::Cursor;
10use std::io::Read;
11use std::io::Write;
12
13/// An in-memory PAK file.
14///
15/// It may reference borrowed data to avoid decrypting the entire file in memory all at once.
16#[derive(Debug, PartialEq)]
17pub struct Pak<'a> {
18    /// All entries in this PAK file.
19    ///
20    /// This will likely become private in the future and replaced by safer ways to interact with entries.
21    pub entries: Vec<Entry<'a>>,
22}
23
24impl<'a> Pak<'a> {
25    /// Make an empty PAK file.
26    pub fn new() -> Self {
27        Self {
28            entries: Vec::new(),
29        }
30    }
31
32    /// Read a PAK file from a reader.
33    ///
34    /// [`Pak::from_bytes`] should probably be preferred, as it is faster to load upfront, though reading takes slightly longer
35    /// and manipulations will incur memory allocations and expensisve copying/decryption.
36    ///
37    /// # Returns
38    /// Returns a PAK file with ownership over is data, decrypting it all upfront.
39    pub fn from_read<R>(reader: R) -> Result<Pak<'static>, PakError>
40    where
41        R: Read,
42    {
43        let mut reader = PakReader::new(reader);
44        reader.read_magic()?;
45        reader.read_version()?;
46
47        let records = reader.read_records()?;
48
49        let mut entries = Vec::with_capacity(records.len());
50        for record in records {
51            let mut data = vec![
52                0;
53                usize::try_from(record.file_size).map_err(|error| {
54                    PakError::InvalidRecordFileSize {
55                        file_size: record.file_size,
56                        error,
57                    }
58                })?
59            ];
60            reader.read_exact(&mut data)?;
61
62            entries.push(Entry {
63                path: record.name.into(),
64                file_time: record.file_time,
65                data: Cursor::new(data.into()),
66            });
67        }
68
69        Ok(Pak { entries })
70    }
71
72    /// Read a PAK file from a byte slice.
73    ///
74    /// # Returns
75    /// Returns a PAK file that borrows sections of data from the slice.
76    ///
77    /// Compared to [`Pak::from_read`],
78    /// this takes less time to load,
79    /// but reading takes slightly longer and manipulations require copying and decrypting the data.
80    pub fn from_bytes(bytes: &'a [u8]) -> Result<Pak<'a>, PakError> {
81        let mut reader = PakReader::new(bytes);
82        reader.read_magic()?;
83        reader.read_version()?;
84
85        let records = reader.read_records()?;
86
87        let mut bytes = reader.into_reader();
88
89        let mut entries = Vec::with_capacity(records.len());
90        for record in records {
91            let len = usize::try_from(record.file_size).map_err(|error| {
92                PakError::InvalidRecordFileSize {
93                    file_size: record.file_size,
94                    error,
95                }
96            })?;
97            let data = &bytes[..len];
98            bytes = &bytes[len..];
99
100            entries.push(Entry {
101                path: record.name.into(),
102                file_time: record.file_time,
103                data: Cursor::new(data.into()),
104            });
105        }
106
107        Ok(Self { entries })
108    }
109
110    /// Takes ownership of all data,
111    /// decrypting it and returing a [`Pak`] that is guaranteed to not reference external data.
112    pub fn into_owned(self) -> Pak<'static> {
113        let entries = self
114            .entries
115            .into_iter()
116            .map(|entry| entry.into_owned())
117            .collect();
118
119        Pak { entries }
120    }
121
122    /// Writes data to a writeable destination.
123    ///
124    /// This takes `&mut self` because at the end of this function,
125    /// all files' cursors will be at the end of the stream.
126    pub fn write_to<W>(&mut self, writer: W) -> Result<(), PakError>
127    where
128        W: Write,
129    {
130        let mut writer = PakWriter::new(writer);
131        writer.write_all(MAGIC)?;
132        writer.write_all(VERSION)?;
133
134        for entry in self.entries.iter() {
135            writer.write_u8(FILEFLAGS_DEFAULT)?;
136            writer.write_filename(&entry.path)?;
137            writer.write_u32(entry.size().try_into().map_err(|error| {
138                PakError::InvalidFileDataLength {
139                    length: entry.size(),
140                    error,
141                }
142            })?)?;
143            writer.write_u64(entry.file_time.into_raw())?;
144        }
145        writer.write_u8(FILEFLAGS_END)?;
146
147        for entry in self.entries.iter_mut() {
148            std::io::copy(entry, &mut writer)?;
149        }
150
151        Ok(())
152    }
153}
154
155impl<'a> Default for Pak<'a> {
156    fn default() -> Self {
157        Self::new()
158    }
159}