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#[derive(Debug)]
42pub struct RenpyArchive<R: Seek + BufRead> {
43 pub reader: R,
45
46 pub key: Option<u64>,
48
49 pub offset: u64,
51
52 pub version: RpaVersion,
54
55 pub content: ContentMap,
57}
58
59impl RenpyArchive<Cursor<Vec<u8>>> {
60 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 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 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 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 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 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 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 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 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 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 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 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 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 trace!("Rebuilding indexes from content");
252 let mut indexes = HashMap::new();
253
254 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 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 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 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 let mut cursor = Cursor::new(compressed);
296 io::copy(&mut cursor, writer)?;
297 debug!("Done writing indexes");
298 }
299
300 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 writer.flush()?;
321 debug!("Done writing archive");
322
323 Ok(())
324 }
325}