Skip to main content

wz_reader/
directory.rs

1use crate::{
2    property::WzStringMeta,
3    reader,
4    util::{offset::WzOffsetMeta, profile::WzProfile, version::PKGVersion},
5    wz_image, Reader, WzImage, WzNode, WzNodeArc, WzNodeArcVec, WzNodeName, WzObjectType, WzReader,
6    WzSliceReader,
7};
8use std::sync::Arc;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, thiserror::Error)]
14pub enum Error {
15    #[error("Lua parse error")]
16    LuaParseError,
17    #[error("parse as wz image failed, pos at {0}")]
18    ParseError(usize),
19    #[error("New Wz image header found. b = {0}, offset = {1}")]
20    UnknownWzDirectoryType(u8, usize),
21    #[error("Invalid wz version used for decryption, try parsing other version numbers.")]
22    InvalidWzVersion,
23    #[error("Entry count overflow, Invalid wz version used for decryption, try parsing other version numbers.")]
24    InvalidEntryCount,
25    #[error("Unknown pkg version, can't resolve children")]
26    UnknownPkgVersion,
27    #[error("Binary reading error")]
28    ReaderError(#[from] reader::Error),
29}
30
31#[derive(Debug, Default, Clone, PartialEq, Eq)]
32#[repr(u8)]
33pub(crate) enum WzDirectoryType {
34    #[default]
35    UnknownType = 1,
36    /// directory type and name maybe at some where alse, but usually is WzDirectory
37    MetaAtOffset = 2,
38    WzDirectory = 3,
39    WzImage = 4,
40    NewUnknownType(u8),
41}
42
43impl From<u8> for WzDirectoryType {
44    fn from(value: u8) -> Self {
45        match value {
46            1 => WzDirectoryType::UnknownType,
47            2 => WzDirectoryType::MetaAtOffset,
48            3 => WzDirectoryType::WzDirectory,
49            4 => WzDirectoryType::WzImage,
50            _ => WzDirectoryType::NewUnknownType(value),
51        }
52    }
53}
54
55#[derive(Debug, Default, Clone, PartialEq, Eq)]
56pub(crate) enum WzDirectoryVerifyStatus {
57    #[default]
58    Init,
59    EntryCreated,
60    Verified,
61}
62
63#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
64#[derive(Debug, Clone, Default)]
65pub struct WzDirectory {
66    #[cfg_attr(feature = "serde", serde(skip))]
67    pub reader: Arc<WzReader>,
68    #[cfg_attr(feature = "serde", serde(skip))]
69    pub offset: usize,
70    #[cfg_attr(feature = "serde", serde(skip))]
71    pub block_size: usize,
72    #[cfg_attr(feature = "serde", serde(skip))]
73    pub hash: u64,
74    #[cfg_attr(feature = "serde", serde(skip))]
75    pub is_parsed: bool,
76    #[cfg_attr(feature = "serde", serde(skip))]
77    pub profile: WzProfile,
78    #[cfg_attr(feature = "serde", serde(skip))]
79    verify_status: WzDirectoryVerifyStatus,
80    #[cfg_attr(feature = "serde", serde(skip))]
81    entries: Vec<WzDirectoryEntry>,
82}
83
84impl WzDirectory {
85    #[inline]
86    pub fn hash_u32(&self) -> u32 {
87        self.hash as u32
88    }
89
90    pub fn new(offset: usize, block_size: usize, reader: &Arc<WzReader>, is_parsed: bool) -> Self {
91        Self {
92            reader: reader.clone(),
93            offset,
94            block_size,
95            hash: 0,
96            is_parsed,
97            verify_status: WzDirectoryVerifyStatus::Init,
98            profile: WzProfile::default(),
99            entries: Vec::new(),
100        }
101    }
102
103    pub fn with_hash(mut self, hash: u64) -> Self {
104        self.hash = hash;
105        self
106    }
107
108    pub fn reset_entry_parse(&mut self) {
109        self.verify_status = WzDirectoryVerifyStatus::Init;
110        self.entries.clear();
111    }
112
113    pub fn resolve_children(&mut self, parent: &WzNodeArc) -> Result<WzNodeArcVec, Error> {
114        if self.block_size == 0 {
115            return Ok(vec![]);
116        }
117
118        self.prepare_entries()?;
119
120        if self.calculate_offset_and_verify().is_err() {
121            return Err(Error::InvalidWzVersion);
122        }
123
124        let nodes: WzNodeArcVec = self
125            .entries
126            .iter()
127            .map(|entry| {
128                entry.into_wz_node_tuple(parent, self.hash, &self.profile, Arc::clone(&self.reader))
129            })
130            .collect();
131
132        // if there has any directory, parse it since it's probably cheep
133        for (_, node) in nodes.iter() {
134            let mut write = node.write().unwrap();
135            if let WzObjectType::Directory(dir) = &mut write.object_type {
136                dir.profile = self.profile.clone();
137                dir.hash = self.hash;
138                let children = dir.resolve_children(node)?;
139
140                for (name, child) in children {
141                    write.children.insert(name, child);
142                }
143            }
144        }
145
146        self.verify_status = WzDirectoryVerifyStatus::Verified;
147        self.entries.clear();
148
149        Ok(nodes)
150    }
151
152    fn resolve_entry_pkg1(&self, reader: &WzSliceReader) -> Result<Vec<WzDirectoryEntry>, Error> {
153        let entry_count = reader.read_wz_int()?;
154
155        if !(0..=1000000).contains(&entry_count) {
156            return Err(Error::InvalidEntryCount);
157        }
158
159        let mut entries: Vec<WzDirectoryEntry> = Vec::with_capacity(entry_count as usize);
160
161        for _ in 0..entry_count {
162            let entry = WzDirectoryEntry::read_pkg1_entry(reader)?;
163
164            match entry.dir_type {
165                WzDirectoryType::UnknownType => {
166                    /* unknown, just skip this chunk, probably checksum(2), file size(4) and hash(4)*/
167                    reader.skip(4 + 4 + 2);
168                    continue;
169                }
170                WzDirectoryType::NewUnknownType(dir_byte) => {
171                    return Err(Error::UnknownWzDirectoryType(dir_byte, reader.pos.get()));
172                }
173                _ => {
174                    // do nothing
175                }
176            }
177
178            entries.push(entry);
179        }
180
181        Ok(entries)
182    }
183
184    fn resolve_entry_pkg2(&self, reader: &WzSliceReader) -> Result<Vec<WzDirectoryEntry>, Error> {
185        let encrypted_entry_count = reader.read_wz_int()?;
186
187        let mut wz_dir_entries: Vec<WzDirectoryEntry> = vec![];
188
189        // currently we don't know how to decrypt the entry_count, so we will just keep reading until get the encrypted_offset_count
190        loop {
191            let entry = WzDirectoryEntry::read_pkg2_entry(
192                reader,
193                encrypted_entry_count,
194                wz_dir_entries.is_empty(),
195            )?;
196
197            match entry.dir_type {
198                // im using the UnknownType to indicate the end of the entries
199                WzDirectoryType::UnknownType => {
200                    break;
201                }
202                WzDirectoryType::NewUnknownType(dir_byte) => {
203                    return Err(Error::UnknownWzDirectoryType(dir_byte, reader.pos.get()));
204                }
205                _ => {
206                    wz_dir_entries.push(entry);
207                }
208            }
209        }
210
211        let encrypted_offset_count = reader.read_wz_int()?;
212
213        if encrypted_offset_count != encrypted_entry_count || wz_dir_entries.is_empty() {
214            return Err(Error::InvalidWzVersion);
215        }
216
217        for entry in wz_dir_entries.iter_mut() {
218            entry.calculation_offset = reader.pos.get();
219            entry.encrypted_offset = reader.read_u32()?;
220        }
221
222        Ok(wz_dir_entries)
223    }
224
225    fn resolve_entry_pkg2_v64(
226        &self,
227        reader: &WzSliceReader,
228    ) -> Result<Vec<WzDirectoryEntry>, Error> {
229        let entry_count_calculator = self.profile.offset_version.get_entry_count_calculator();
230
231        let encrypted_entry_count = reader.read_wz_int64()?;
232        let entry_count: usize =
233            entry_count_calculator(&reader.header, self.hash, encrypted_entry_count)?;
234
235        if !(0..=1_000_000).contains(&entry_count) {
236            return Err(Error::InvalidEntryCount);
237        }
238
239        let mut wz_dir_entries: Vec<WzDirectoryEntry> = Vec::with_capacity(entry_count);
240
241        for i in 0..entry_count {
242            let entry =
243                WzDirectoryEntry::read_pkg2_entry(reader, encrypted_entry_count as i32, i == 0)?;
244
245            match entry.dir_type {
246                WzDirectoryType::WzDirectory | WzDirectoryType::WzImage => {
247                    wz_dir_entries.push(entry);
248                }
249                WzDirectoryType::NewUnknownType(dir_byte) => {
250                    return Err(Error::UnknownWzDirectoryType(dir_byte, reader.pos.get()));
251                }
252                _ => return Err(Error::InvalidWzVersion),
253            }
254        }
255
256        for entry in wz_dir_entries.iter_mut() {
257            entry.calculation_offset = reader.pos.get();
258            entry.encrypted_offset = reader.read_u32()?;
259        }
260
261        Ok(wz_dir_entries)
262    }
263
264    pub fn prepare_entries(&mut self) -> Result<(), Error> {
265        if self.verify_status != WzDirectoryVerifyStatus::Init {
266            return Ok(());
267        }
268
269        let reader = self.reader.create_slice_reader();
270
271        reader.seek(self.offset);
272
273        if reader.header.ident == PKGVersion::V1 {
274            self.entries = self.resolve_entry_pkg1(&reader)?;
275        } else if reader.header.ident == PKGVersion::V2 {
276            if reader.header.is_pkg2_64() {
277                self.entries = self.resolve_entry_pkg2_v64(&reader)?;
278            } else {
279                self.entries = self.resolve_entry_pkg2(&reader)?;
280            }
281        } else {
282            return Err(Error::UnknownPkgVersion);
283        }
284
285        self.verify_status = WzDirectoryVerifyStatus::EntryCreated;
286
287        Ok(())
288    }
289
290    pub fn calculate_offset_and_verify(&mut self) -> Result<(), Error> {
291        let reader = self.reader.create_slice_reader();
292
293        let offset_calculator = self.profile.offset_version.get_calculator();
294
295        for entry in self.entries.iter_mut() {
296            let meta = WzOffsetMeta {
297                hash: self.hash,
298                encrypted_offset: entry.encrypted_offset,
299                offset: entry.calculation_offset,
300            };
301
302            entry.offset = offset_calculator(&reader.header, &meta)?;
303
304            if entry.verify(&reader).is_err() {
305                return Err(Error::InvalidWzVersion);
306            }
307        }
308
309        Ok(())
310    }
311
312    pub fn verify_string_decryptor(&mut self) -> bool {
313        if self.verify_status != WzDirectoryVerifyStatus::EntryCreated {
314            return false;
315        }
316        if self.entries.is_empty() {
317            return true;
318        }
319        self.entries[0].resolve_name(&self.reader).is_ok()
320    }
321}
322
323#[derive(Debug, Default, Clone)]
324struct WzDirectoryEntry {
325    name: WzStringMeta,
326    dir_type: WzDirectoryType,
327    size: usize,
328    calculation_offset: usize,
329    encrypted_offset: u32,
330    offset: usize,
331    _checksum: i32,
332}
333
334impl WzDirectoryEntry {
335    pub fn read_pkg1_entry(reader: &WzSliceReader) -> Result<Self, Error> {
336        let mut entry = WzDirectoryEntry {
337            dir_type: reader.read_u8()?.into(),
338            ..Default::default()
339        };
340
341        match entry.dir_type {
342            WzDirectoryType::UnknownType => {
343                return Ok(entry);
344            }
345            WzDirectoryType::MetaAtOffset => {
346                let str_offset = reader.read_i32()?;
347
348                let offset = reader.header.fstart + str_offset as usize;
349
350                entry.dir_type = reader.read_u8_at(offset)?.into();
351                entry.name = reader.read_wz_string_meta_at(offset + 1)?;
352            }
353            WzDirectoryType::WzDirectory | WzDirectoryType::WzImage => {
354                entry.name = reader.read_wz_string_meta()?;
355            }
356            WzDirectoryType::NewUnknownType(_) => {
357                return Ok(entry);
358            }
359        }
360
361        entry.size = reader.read_wz_int()? as usize;
362        entry._checksum = reader.read_wz_int()?;
363        entry.calculation_offset = reader.pos.get();
364        entry.encrypted_offset = reader.read_u32()?;
365
366        Ok(entry)
367    }
368
369    pub fn read_pkg2_entry(
370        reader: &WzSliceReader,
371        encrypted_entry_count: i32,
372        use_pkg2_dir_read: bool,
373    ) -> Result<Self, Error> {
374        let mut entry = WzDirectoryEntry {
375            dir_type: reader.read_u8()?.into(),
376            ..Default::default()
377        };
378
379        match entry.dir_type {
380            WzDirectoryType::WzDirectory | WzDirectoryType::WzImage => {
381                if use_pkg2_dir_read && reader.pkg2_keys.read().unwrap().is_pkg2() {
382                    if reader.header.is_pkg2_64() {
383                        entry.name = reader.read_wz_string_pkg2_u64_dir_meta()?;
384                    } else {
385                        entry.name = reader.read_wz_string_pkg2_dir_meta()?;
386                    }
387                } else {
388                    entry.name = reader.read_wz_string_meta()?;
389                }
390            }
391            WzDirectoryType::NewUnknownType(_) => {
392                let current_pos = reader.pos.get();
393                reader.pos.set(current_pos - 1);
394                let test_wz_int = reader.read_wz_int()?;
395
396                // if reach the value is same as encrypted_entry_count, mean we reach the end of the entries
397                if test_wz_int == encrypted_entry_count {
398                    reader.pos.set(current_pos - 1);
399                    entry.dir_type = WzDirectoryType::UnknownType;
400                }
401                return Ok(entry);
402            }
403            _ => {
404                return Err(Error::InvalidWzVersion);
405            }
406        }
407
408        entry.size = reader.read_wz_int()? as usize;
409        entry._checksum = reader.read_wz_int()?;
410
411        Ok(entry)
412    }
413
414    pub fn verify(&self, reader: &WzSliceReader) -> Result<(), Error> {
415        if !reader.is_valid_pos(self.offset + self.size) {
416            return Err(Error::InvalidWzVersion);
417        }
418
419        if self.dir_type == WzDirectoryType::WzImage {
420            let header_byte = reader.read_u8_at(self.offset)?;
421            if !wz_image::is_valid_image_header(header_byte) {
422                return Err(Error::InvalidWzVersion);
423            }
424            // should we try to parse the wz_image?
425        } else if self.dir_type == WzDirectoryType::WzDirectory {
426            reader.seek(self.offset);
427
428            let entry_count = if reader.header.is_pkg2_64() {
429                reader.read_wz_int64()?
430            } else {
431                reader.read_wz_int()? as i64
432            };
433
434            if entry_count < 0 {
435                return Err(Error::InvalidWzVersion);
436            }
437        }
438        Ok(())
439    }
440
441    pub fn resolve_name(&self, reader: &Arc<WzReader>) -> Result<WzNodeName, Error> {
442        Ok(reader
443            .resolve_wz_string_meta(
444                &self.name.string_type,
445                self.name.offset,
446                self.name.length as usize,
447            )?
448            .into())
449    }
450
451    pub fn try_into_wz_node_tuple(
452        &self,
453        parent: &WzNodeArc,
454        hash: u64,
455        profile: &WzProfile,
456        reader: Arc<WzReader>,
457    ) -> Result<(WzNodeName, WzNodeArc), Error> {
458        let node: WzNode;
459        let name: WzNodeName = self.resolve_name(&reader)?;
460
461        match self.dir_type {
462            WzDirectoryType::WzDirectory => {
463                let mut wz_dir =
464                    WzDirectory::new(self.offset, self.size, &reader, false).with_hash(hash);
465                wz_dir.profile = profile.clone();
466                node = WzNode::new(&name, wz_dir, Some(parent));
467            }
468            WzDirectoryType::WzImage => {
469                let wz_image = WzImage::new(&name, self.offset, self.size, &reader);
470                node = WzNode::new(&name, wz_image, Some(parent));
471            }
472            _ => {
473                node = WzNode::empty();
474            }
475        }
476
477        Ok((name.clone(), node.into_lock()))
478    }
479
480    pub fn into_wz_node_tuple(
481        &self,
482        parent: &WzNodeArc,
483        hash: u64,
484        profile: &WzProfile,
485        reader: Arc<WzReader>,
486    ) -> (WzNodeName, WzNodeArc) {
487        self.try_into_wz_node_tuple(parent, hash, profile, reader)
488            .unwrap()
489    }
490}