msg_tool\scripts\qlie\archive\pack/
mod.rs1mod 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 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
130pub 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 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 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}