msg_tool\scripts\qlie\archive\pack/
mod.rs

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