warpalib/
archive.rs

1use std::{
2    collections::{BTreeMap, HashMap},
3    fs::File,
4    io::{self, BufRead, BufReader, Cursor, Read, Seek, SeekFrom, Write},
5    path::{Path, PathBuf},
6};
7
8use flate2::{read::ZlibDecoder, write::ZlibEncoder, Compression};
9use log::{debug, trace};
10use serde_pickle::{DeOptions, HashableValue, SerOptions, Value};
11
12use crate::{record::Record, version::RpaVersion, Content, ContentMap, RpaError, RpaResult};
13
14/// Represents a renpy archive.
15///
16/// This struct does not enforce in-memory or in-storage. It is left upto the
17/// use where the data is stored.
18///
19/// # Examples
20///
21/// ```rust
22/// use warpalib::RenpyArchive;
23/// use std::{
24///     io::{BufWriter, Cursor},
25///     fs::File,
26/// };
27///
28/// // Open in memory archive
29/// let mut archive = RenpyArchive::new();
30///
31/// // Insert new data into archive
32/// archive.content.insert_raw("log.txt", vec![0u8; 1024]);
33///
34/// // or, insert new file
35/// // archive.add_file(Path::new("log.txt"));
36///
37/// // Write archive to a file
38/// let mut writer = Cursor::new(vec![]);
39/// archive.flush(&mut writer).expect("Failed to write archive");
40/// ```
41#[derive(Debug)]
42pub struct RenpyArchive<R: Seek + BufRead> {
43    /// Handle to the archive data.
44    pub reader: R,
45
46    /// Key used to encode and decode index locations.
47    pub key: Option<u64>,
48
49    /// The offset where index data is stored.
50    pub offset: u64,
51
52    /// The version of this archive.
53    pub version: RpaVersion,
54
55    /// The content present in this archive.
56    pub content: ContentMap,
57}
58
59impl RenpyArchive<Cursor<Vec<u8>>> {
60    /// Create a new in-memory archive without allocating to heap.
61    pub fn new() -> Self {
62        Self::default()
63    }
64}
65
66impl Default for RenpyArchive<Cursor<Vec<u8>>> {
67    fn default() -> Self {
68        Self {
69            reader: Cursor::new(Vec::with_capacity(0)),
70            offset: 0,
71            version: RpaVersion::V3_0,
72            key: Some(0xDEADBEEF),
73            content: Default::default(),
74        }
75    }
76}
77
78impl RenpyArchive<BufReader<File>> {
79    /// Open archive from file.
80    pub fn open(path: &Path) -> RpaResult<Self> {
81        trace!("Opening archive from file: {}", path.display());
82
83        let mut reader = BufReader::new(File::open(path)?);
84
85        let version = match path.file_name() {
86            Some(name) => Self::version(&mut reader, &name.to_string_lossy())?,
87            None => Self::version(&mut reader, "")?,
88        };
89
90        let (offset, key, content) = Self::metadata(&mut reader, &version)?;
91
92        Ok(Self {
93            reader,
94            offset,
95            version,
96            key,
97            content,
98        })
99    }
100}
101
102type MetaData = (u64, Option<u64>, ContentMap);
103
104impl<R> RenpyArchive<R>
105where
106    R: Seek + BufRead,
107{
108    /// Open an archive from bytes.
109    pub fn read(mut reader: R) -> RpaResult<Self> {
110        trace!("Opening archive from reader");
111
112        let version = Self::version(&mut reader, "")?;
113        let (offset, key, content) = Self::metadata(&mut reader, &version)?;
114
115        Ok(Self {
116            reader,
117            offset,
118            version,
119            key,
120            content,
121        })
122    }
123
124    /// Identify version by reading header and provided filename
125    pub fn version(reader: &mut R, file_name: &str) -> RpaResult<RpaVersion> {
126        let mut version = String::new();
127        reader.by_ref().take(7).read_to_string(&mut version)?;
128        RpaVersion::identify(file_name, &version).ok_or(RpaError::IdentifyVersion)
129    }
130
131    /// Retrieve `offset`, `key`, and content indexes from the archive
132    pub fn metadata(reader: &mut R, version: &RpaVersion) -> RpaResult<MetaData> {
133        trace!("Parsing metadata from archive version ({version})");
134
135        let mut first_line = String::new();
136        reader.read_line(&mut first_line)?;
137        debug!("Read first line: {first_line}");
138
139        // Dont't need the newline character
140        let metadata = first_line[..(first_line.len() - 1)]
141            .split(' ')
142            .collect::<Vec<_>>();
143
144        let offset = u64::from_str_radix(metadata[1], 16).map_err(|_| RpaError::ParseOffset)?;
145
146        let key = match version {
147            RpaVersion::V3_0 => {
148                let mut key = 0;
149                for subkey in &metadata[2..] {
150                    key ^= u64::from_str_radix(subkey, 16).map_err(|_| RpaError::ParseKey)?;
151                }
152                Some(key)
153            }
154            RpaVersion::V3_2 => {
155                let mut key = 0;
156                for subkey in &metadata[3..] {
157                    key ^= u64::from_str_radix(subkey, 16).map_err(|_| RpaError::ParseKey)?;
158                }
159                Some(key)
160            }
161            _ => None,
162        };
163        debug!("Parsed the obfuscation key: {key:?}");
164
165        trace!("Commencing index retrieval");
166
167        // Retrieve indexes.
168        reader.seek(SeekFrom::Start(offset))?;
169        let mut contents = Vec::new();
170        reader.read_to_end(&mut contents)?;
171        debug!("Read raw index bytes");
172
173        // Decode indexes data.
174        let mut decoder = ZlibDecoder::new(Cursor::new(contents));
175        let mut contents = Vec::new();
176        io::copy(&mut decoder, &mut contents)?;
177        debug!("Decoded index data with zlib");
178
179        // Deserialize indexes using pickle.
180        let options = DeOptions::default();
181        let raw_indexes: HashMap<String, Value> = serde_pickle::from_slice(&contents[..], options)
182            .map_err(|_| RpaError::DeserializeRecord)?;
183        debug!("Deserialized index data using pickle");
184
185        // Map indexes to an easier format.
186        let mut content = HashMap::new();
187        for (path, value) in raw_indexes.into_iter() {
188            let value = Record::from_value(value, key)?;
189            content.insert(PathBuf::from(path), Content::Record(value));
190        }
191        debug!("Parsed index data to struct");
192
193        Ok((offset, key, content.into()))
194    }
195}
196
197impl<R> RenpyArchive<R>
198where
199    R: Seek + BufRead,
200{
201    /// Copy content from a file in the archive to the `writer`.
202    ///
203    /// # Errors
204    ///
205    /// This function returns `NotFound` error if `path` is not present in
206    /// the archive and any errors raised during the copy process.
207    pub fn copy_file<W: Write>(&mut self, path: &Path, writer: &mut W) -> RpaResult<u64> {
208        if let Some(content) = self.content.get(Path::new(path)) {
209            return content
210                .copy_to(&mut self.reader, writer)
211                .map_err(|e| e.into());
212        }
213
214        Err(RpaError::NotFound(path.to_path_buf()))
215    }
216}
217
218impl<R> RenpyArchive<R>
219where
220    R: Seek + BufRead,
221{
222    /// Consume and write the archive to the `writer`.
223    ///
224    /// The archive is consumed as this rebuilds the indexes and reorgenizes the
225    /// stored data.
226    ///
227    /// This function defers control of data flow by not enforcing that archive
228    /// or writer be in-memory. This means that both archive and writer could be
229    /// both a file and the program would use minimal memory since they wont be
230    /// loaded into memory.
231    ///
232    /// # Warnings
233    ///
234    /// Take care not to write to the same archive as being read from.
235    pub fn flush<W: Seek + Write>(mut self, writer: &mut W) -> RpaResult<()> {
236        trace!("Commencing archive flush");
237
238        let mut offset: u64 = 0;
239
240        // Write a placeholder header to be filled later.
241        // Not using seek since writer might not have any data.
242        let header_length = self.version.header_length()?;
243        let header = vec![0u8; header_length];
244        offset += writer.write(&header)? as u64;
245        debug!(
246            "Written placeholder header for version ({}) length ({} bytes)",
247            self.version, header_length,
248        );
249
250        // Build indexes while writing to the archive.
251        trace!("Rebuilding indexes from content");
252        let mut indexes = HashMap::new();
253
254        // Copy data from content.
255        for (path, content) in self.content.into_iter() {
256            let length = content.copy_to(&mut self.reader, writer)?;
257            let path = path.as_os_str().to_string_lossy().to_string();
258            debug!("Written content from path ({path}) length ({length} bytes)",);
259
260            indexes.insert(path, Record::new(offset, length, None, self.key));
261            offset += length;
262        }
263
264        {
265            trace!("Preparing to write indexes");
266
267            // Convert indexes into serializable values.
268            let values = Value::Dict(BTreeMap::from_iter(
269                indexes
270                    .into_iter()
271                    .map(|(k, v)| (HashableValue::String(k), v.into_value())),
272            ));
273
274            // Serialize indexes with picke protocol 2.
275            let mut buffer = Vec::new();
276            let options = SerOptions::new().proto_v2();
277            match serde_pickle::value_to_writer(&mut buffer, &values, options) {
278                Ok(_) => Ok(()),
279                Err(serde_pickle::Error::Io(e)) => Err(RpaError::Io(e)),
280                Err(_) => Err(RpaError::SerializeRecord),
281            }?;
282            debug!(
283                "Encoded indexes using pickle format 2: {} bytes",
284                buffer.len()
285            );
286
287            // Compress serialized data with zlib.
288            let mut input = Cursor::new(buffer);
289            let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
290            io::copy(&mut input, &mut encoder)?;
291            let compressed = encoder.finish()?;
292            debug!("Compressed indexes using zlib: {} bytes", compressed.len());
293
294            // Write compressed data to writer.
295            let mut cursor = Cursor::new(compressed);
296            io::copy(&mut cursor, writer)?;
297            debug!("Done writing indexes");
298        }
299
300        // Back to start, time to write the header.
301        trace!("Rewinding and writing archive header");
302        writer.rewind()?;
303
304        let key = self.key.unwrap_or(0);
305        let header = match self.version {
306            RpaVersion::V3_0 => format!("RPA-3.0 {:016x} {:08x}\n", offset, key),
307            RpaVersion::V2_0 => format!("RPA-2.0 {:016x}\n", offset),
308            v @ (RpaVersion::V3_2 | RpaVersion::V1_0) => {
309                return Err(RpaError::WritingNotSupported(v))
310            }
311        };
312
313        {
314            let header = header.into_bytes();
315            writer.write_all(&header)?;
316            debug!("Written header ({} bytes) key ({})", header.len(), key);
317        }
318
319        // And done.
320        writer.flush()?;
321        debug!("Done writing archive");
322
323        Ok(())
324    }
325}