msg_tool\scripts\qlie\archive\pack/
mod.rs1mod delphi;
3mod encryption;
4mod twister;
5mod types;
6mod v31;
7
8use crate::ext::io::*;
9use crate::scripts::base::*;
10use crate::types::*;
11use crate::utils::struct_pack::*;
12use anyhow::Result;
13use encryption::Encryption;
14use std::io::{Read, Seek, SeekFrom};
15use std::sync::{Arc, Mutex};
16use types::*;
17
18#[derive(Debug)]
19pub struct QliePackArchiveBuilder {}
20
21impl QliePackArchiveBuilder {
22 pub fn new() -> Self {
23 Self {}
24 }
25}
26
27impl ScriptBuilder for QliePackArchiveBuilder {
28 fn default_encoding(&self) -> Encoding {
29 Encoding::Cp932
30 }
31
32 fn default_archive_encoding(&self) -> Option<Encoding> {
33 Some(Encoding::Cp932)
34 }
35
36 fn build_script(
37 &self,
38 data: Vec<u8>,
39 filename: &str,
40 _encoding: Encoding,
41 archive_encoding: Encoding,
42 config: &ExtraConfig,
43 _archive: Option<&Box<dyn Script>>,
44 ) -> Result<Box<dyn Script + Send + Sync>> {
45 Ok(Box::new(QliePackArchive::new(
46 MemReader::new(data),
47 archive_encoding,
48 config,
49 filename,
50 )?))
51 }
52
53 fn build_script_from_file(
54 &self,
55 filename: &str,
56 _encoding: Encoding,
57 archive_encoding: Encoding,
58 config: &ExtraConfig,
59 _archive: Option<&Box<dyn Script>>,
60 ) -> Result<Box<dyn Script + Send + Sync>> {
61 if filename == "-" {
62 let data = crate::utils::files::read_file(filename)?;
63 Ok(Box::new(QliePackArchive::new(
64 MemReader::new(data),
65 archive_encoding,
66 config,
67 filename,
68 )?))
69 } else {
70 let f = std::fs::File::open(filename)?;
71 let reader = std::io::BufReader::new(f);
72 Ok(Box::new(QliePackArchive::new(
73 reader,
74 archive_encoding,
75 config,
76 filename,
77 )?))
78 }
79 }
80
81 fn build_script_from_reader<'a>(
82 &self,
83 reader: Box<dyn ReadSeek + Send + Sync + 'a>,
84 filename: &str,
85 _encoding: Encoding,
86 archive_encoding: Encoding,
87 config: &ExtraConfig,
88 _archive: Option<&Box<dyn Script>>,
89 ) -> Result<Box<dyn Script + Send + Sync + 'a>> {
90 Ok(Box::new(QliePackArchive::new(
91 reader,
92 archive_encoding,
93 config,
94 filename,
95 )?))
96 }
97
98 fn extensions(&self) -> &'static [&'static str] {
99 &["pack"]
100 }
101
102 fn script_type(&self) -> &'static ScriptType {
103 &ScriptType::QliePack
104 }
105
106 fn is_this_format(&self, filename: &str, _buf: &[u8], _buf_len: usize) -> Option<u8> {
107 match is_this_format(filename) {
111 Ok(true) => Some(30),
112 _ => None,
113 }
114 }
115
116 fn is_archive(&self) -> bool {
117 true
118 }
119
120 fn create_archive(
121 &self,
122 filename: &str,
123 files: &[&str],
124 _encoding: Encoding,
125 config: &ExtraConfig,
126 ) -> Result<Box<dyn Archive>> {
127 let f = std::fs::File::create(filename)?;
128 let buf = std::io::BufWriter::new(f);
129 Ok(Box::new(v31::QliePackArchiveWriterV31::new(
130 buf, files, config,
131 )?))
132 }
133}
134
135pub fn is_this_format<P: AsRef<std::path::Path> + ?Sized>(path: &P) -> Result<bool> {
137 let path = path.as_ref();
138 if !path.exists() || !path.is_file() {
139 return Ok(false);
140 }
141 let mut file = std::fs::File::open(path)?;
142 file.seek(SeekFrom::End(-0x1C))?;
143 let header = QlieHeader::unpack(&mut file, false, Encoding::Utf8, &None)?;
144 Ok(header.is_valid())
145}
146
147#[derive(Debug)]
148pub struct QliePackArchive<'b, T: Read + Seek + std::fmt::Debug + Send + Sync + 'b> {
149 header: QlieHeader,
150 encryption: Box<dyn Encryption + Send + Sync>,
151 reader: Arc<Mutex<T>>,
152 qkey: Option<QlieKey>,
153 entries: Vec<QlieEntry>,
154 common_key: Option<Vec<u8>>,
155 _mark: std::marker::PhantomData<&'b ()>,
156}
157
158impl<'b, T: Read + Seek + std::fmt::Debug + Send + Sync + 'b> QliePackArchive<'b, T> {
159 pub fn new(
160 mut reader: T,
161 archive_encoding: Encoding,
162 _config: &ExtraConfig,
163 filename: &str,
164 ) -> Result<Self> {
165 reader.seek(SeekFrom::End(-0x1C))?;
166 let header = QlieHeader::unpack(&mut reader, false, archive_encoding, &None)?;
167 if !header.is_valid() {
168 return Err(anyhow::anyhow!("Invalid Qlie Pack Archive header"));
169 }
170 let file_size = reader.stream_position()?;
171 if header.index_offset > file_size - 0x1C {
172 return Err(anyhow::anyhow!(
173 "Invalid index offset in Qlie Pack Archive header"
174 ));
175 }
176 let major = header.major_version();
177 let minor = header.minor_version();
178 let mut game_key = None;
179 if major == 3 && minor == 0 {
180 game_key = encryption::find_key_data(filename)?;
181 }
182 let mut encryption = encryption::create_encryption(major, minor, game_key)?;
183 let mut key = 0;
185 let mut qkey = None;
186 if major >= 2 {
187 reader.seek(SeekFrom::End(-0x440))?;
188 let mut qk = QlieKey::unpack(&mut reader, false, archive_encoding, &None)?;
189 if qk.hash_size as u64 > file_size || qk.hash_size < 0x44 {
190 return Err(anyhow::anyhow!("Invalid Qlie Pack Archive key"));
191 }
192 if major >= 3 {
193 key = encryption.compute_hash(&qk.key[..0x100])? & 0xFFFFFFF;
194 }
195 encryption::decrypt(&mut qk.signature, key)?;
196 if &qk.signature != QLIE_KEY_SIGNATURE {
197 eprintln!(
198 "WARNING: Invalid Qlie Pack Archive key signature, decryption key may be incorrect"
199 );
200 crate::COUNTER.inc_warning();
201 }
202 qkey = Some(qk);
203 }
204 let mut entries = if major >= 2 {
206 Self::read_entries(&mut reader, key, &header, archive_encoding, &encryption)?
207 } else {
208 let possible_encs: [Box<dyn Encryption + Send + Sync>; 3] = [
209 Box::new(encryption::Encryption10::new()),
210 Box::new(encryption::Encryption20::new_no_hash()),
211 Box::new(encryption::Encryption20::new()),
212 ];
213 let mut t = None;
214 for enc in possible_encs {
215 match Self::read_entries(&mut reader, key, &header, archive_encoding, &enc) {
216 Ok(entries) => {
217 encryption = enc;
218 t = Some(entries);
219 break;
220 }
221 Err(_) => continue,
222 }
223 }
224 t.ok_or_else(|| {
225 anyhow::anyhow!(
226 "Failed to read Qlie Pack Archive entries with any encryption method"
227 )
228 })?
229 };
230 let mut common_key = None;
231 if major >= 3 {
232 common_key = encryption::find_game_key(filename)?;
233 if let Some(common_key_entry) = entries.iter_mut().find(|e| e.name == QLIE_KEY_FILE) {
234 reader.seek(SeekFrom::Start(common_key_entry.offset))?;
235 common_key_entry.common_key = common_key.clone();
236 let stream = StreamRegion::with_size(&mut reader, common_key_entry.size as u64)?;
237 let mut decrypted = encryption.decrypt_entry(Box::new(stream), common_key_entry)?;
238 if common_key_entry.is_packed != 0 {
239 decrypted = encryption::decompress(decrypted)?;
240 }
241 let mut key_data = Vec::new();
242 decrypted.read_to_end(&mut key_data)?;
243 if minor == 1 {
244 common_key = Some(encryption::get_common_key(&key_data)?);
245 } else {
246 common_key = Some(key_data);
247 }
248 }
249 }
250 Ok(Self {
251 header,
252 encryption,
253 reader: Arc::new(Mutex::new(reader)),
254 qkey,
255 entries,
256 common_key,
257 _mark: std::marker::PhantomData,
258 })
259 }
260
261 fn read_entries(
262 reader: &mut T,
263 key: u32,
264 header: &QlieHeader,
265 archive_encoding: Encoding,
266 encryption: &Box<dyn Encryption + Send + Sync>,
267 ) -> Result<Vec<QlieEntry>> {
268 let mut entries = Vec::new();
269 reader.seek(SeekFrom::Start(header.index_offset))?;
270 let has_hash = encryption.index_has_hash();
271 for _ in 0..header.file_count {
272 let name_length = reader.read_u16()?;
273 let raw_name_length = if encryption.is_unicode() {
274 name_length as usize * 2
275 } else {
276 name_length as usize
277 };
278 let mut raw_name = reader.read_exact_vec(raw_name_length)?;
279 let name = encryption.decrypt_name(&mut raw_name, key as i32, archive_encoding)?;
280 let offset = reader.read_u64()?;
281 let size = reader.read_u32()?;
282 let unpacked_size = reader.read_u32()?;
283 let is_packed = reader.read_u32()?;
284 let is_encrypted = reader.read_u32()?;
285 let hash = if has_hash { reader.read_u32()? } else { 0 };
286 let entry = QlieEntry {
287 raw_name,
288 name,
289 offset,
290 size,
291 unpacked_size,
292 is_packed,
293 is_encrypted,
294 hash,
295 key,
296 common_key: None,
297 };
298 entries.push(entry);
299 }
300 Ok(entries)
301 }
302}
303
304impl<'b, T: Read + Seek + std::fmt::Debug + Send + Sync + 'b> Script for QliePackArchive<'b, T> {
305 fn default_output_script_type(&self) -> OutputScriptType {
306 OutputScriptType::Json
307 }
308
309 fn default_format_type(&self) -> FormatOptions {
310 FormatOptions::None
311 }
312
313 fn is_archive(&self) -> bool {
314 true
315 }
316
317 fn iter_archive_filename<'a>(
318 &'a self,
319 ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
320 Ok(Box::new(self.entries.iter().map(|e| Ok(e.name.clone()))))
321 }
322
323 fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + Send + Sync + 'a>> {
324 let mut entry = self
325 .entries
326 .get(index)
327 .ok_or_else(|| anyhow::anyhow!("Invalid file index {} for Qlie Pack Archive", index))?
328 .clone();
329 if self.common_key.is_some() && entry.common_key.is_none() {
330 entry.common_key = self.common_key.clone();
331 }
332 let stream = StreamRegion::with_size(
333 MutexWrapper::new(self.reader.clone(), entry.offset),
334 entry.size as u64,
335 )?;
336 let stream_clone = StreamRegion::with_size(
337 MutexWrapper::new(self.reader.clone(), entry.offset),
338 entry.size as u64,
339 )?;
340 let mut stream = self.encryption.decrypt_entry(Box::new(stream), &entry)?;
341 let mut stream_clone = self
342 .encryption
343 .decrypt_entry(Box::new(stream_clone), &entry)?;
344 if entry.is_packed != 0 {
345 stream = encryption::decompress(stream)?;
346 stream_clone = encryption::decompress(stream_clone)?;
347 }
348 let mut entry = QliePackArchiveContent::new(stream, entry);
349 let mut header_buffer = [0u8; 1024];
350 let readed = stream_clone.read_most(&mut header_buffer)?;
351 entry.typ = detect_script_type(&entry.entry.name, &header_buffer, readed);
352 Ok(Box::new(entry))
353 }
354}
355
356fn detect_script_type(_name: &str, buf: &[u8], buf_len: usize) -> Option<ScriptType> {
357 if super::super::script::is_this_format(buf, buf_len) {
358 return Some(ScriptType::Qlie);
359 }
360 #[cfg(feature = "qlie-img")]
361 {
362 if buf_len >= 4 && buf.starts_with(b"DPNG") {
363 return Some(ScriptType::QlieDpng);
364 }
365 if buf_len >= 6 && buf.starts_with(b"abmp1") {
366 let ver = buf[5];
367 if ver >= b'0' && ver <= b'2' {
368 return Some(ScriptType::QlieAbmp10);
369 }
370 }
371 }
372 None
373}
374
375#[derive(Debug)]
376struct QliePackArchiveContent<T: Read + std::fmt::Debug> {
377 reader: T,
378 entry: QlieEntry,
379 typ: Option<ScriptType>,
380}
381
382impl<T: Read + std::fmt::Debug> QliePackArchiveContent<T> {
383 pub fn new(reader: T, entry: QlieEntry) -> Self {
384 Self {
385 reader,
386 entry,
387 typ: None,
388 }
389 }
390}
391
392impl<T: Read + std::fmt::Debug> Read for QliePackArchiveContent<T> {
393 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
394 self.reader.read(buf)
395 }
396}
397
398impl<T: Read + std::fmt::Debug> ArchiveContent for QliePackArchiveContent<T> {
399 fn name(&self) -> &str {
400 &self.entry.name
401 }
402
403 fn size(&self) -> Option<u64> {
404 Some(self.entry.size as u64)
405 }
406
407 fn script_type(&self) -> Option<&ScriptType> {
408 self.typ.as_ref()
409 }
410}