nom_mpq/parser/
mpq_hash_table_entry.rs

1//! The Hash Table Parsing
2//!
3//! Instead of storing file names, for quick access MoPaQs use a fixed,
4//! power of two-size hash table of files in the archive. A file is uniquely
5//! identified by its file path, its language, and its platform.
6//! The home entry for a file in the hash table is computed as a hash of the
7//! file path. In the event of a collision (the home entry is occupied by
8//! another file), progressive overflow is used, and the file is placed in the
9//! next available hash table entry. Searches for a desired file in the hash
10//! table proceed from the home entry for the file until either the file is
11//! found, the entire hash table is searched, or an empty hash table entry
12//! (FileBlockIndex of 0xffffffff) is encountered.
13//! The hash table is always encrypted, using the hash of "(hash table)" as the
14//! key.
15//! Prior to Starcraft 2, the hash table is stored uncompressed.
16//! In Starcraft 2, however, the table may optionally be compressed.
17//! If the offset of the block table is not equal to the offset of the
18//! hash table plus the uncompressed size, Starcraft 2 interprets the
19//! hash table as being compressed (not imploded).
20//! This calculation assumes that the block table immediately follows the
21//! hash table, and will fail or crash otherwise.
22//! NOTES:
23//! - MPyQ uses struct_format: '2I2HI'
24//!   - The format above claims the [`MPQHashTableEntry.platform`] is a u16.
25//!   - The devklog.net website claims the [`MPQHashTableEntry.platform`] field is u8
26//!   - In this implementation the u16 MPyQ version is honored.
27
28use super::LITTLE_ENDIAN;
29use crate::dbg_dmp;
30use nom::number::complete::{u16, u32};
31use nom::*;
32
33/// The hash table entry definition
34#[derive(Debug, PartialEq, Default, Clone)]
35pub struct MPQHashTableEntry {
36    /// The hash of the file path, using method A.
37    pub hash_a: u32,
38    /// The hash of the file path, using method B.
39    pub hash_b: u32,
40    /// The language of the file.
41    /// See [`MPQHashTableEntry::parse_locale`] for more information.
42    pub locale: u16,
43    /// The platform the file is used for.
44    /// See [`MPQHashTableEntry::parse_platform`] for more information.
45    pub platform: u16,
46    /// Index into the block table of the file.
47    /// See [`MPQHashTableEntry::parse_block_table_index`] for more information.
48    pub block_table_index: u32,
49}
50
51impl MPQHashTableEntry {
52    /// This method is not related to parsing but for testing, maybe we should consider further
53    /// splitting this into a MPQHashTableEntryParser, maybe overkill.
54    pub fn new(
55        hash_a: u32,
56        hash_b: u32,
57        locale: u16,
58        platform: u16,
59        block_table_index: u32,
60    ) -> Self {
61        Self {
62            hash_a,
63            hash_b,
64            locale,
65            platform,
66            block_table_index,
67        }
68    }
69
70    /// Parses all the fields in the expected order
71    pub fn parse(input: &[u8]) -> IResult<&[u8], Self> {
72        let (tail, hash_a) = Self::parse_hash_a(input)?;
73        let (tail, hash_b) = Self::parse_hash_b(tail)?;
74        let (tail, locale) = Self::parse_locale(tail)?;
75        let (tail, platform) = Self::parse_platform(tail)?;
76        let (tail, block_table_index) = Self::parse_block_table_index(tail)?;
77        Ok((
78            tail,
79            MPQHashTableEntry {
80                hash_a,
81                hash_b,
82                locale,
83                platform,
84                block_table_index,
85            },
86        ))
87    }
88
89    /// `Offset 0x00`: int32 FilePathHashA
90    ///
91    /// The hash of the file path, using method A.
92    pub fn parse_hash_a(input: &[u8]) -> IResult<&[u8], u32> {
93        dbg_dmp(u32(LITTLE_ENDIAN), "hash_a")(input)
94    }
95
96    /// `Offset 0x04`: int32 FilePathHashB
97    ///
98    /// The hash of the file path, using method B.
99    pub fn parse_hash_b(input: &[u8]) -> IResult<&[u8], u32> {
100        dbg_dmp(u32(LITTLE_ENDIAN), "hash_b")(input)
101    }
102
103    /// `Offset 0x08`: int16 Language
104    ///
105    /// The language of the file. This is a Windows LANGID data type, and uses
106    /// the same values.
107    /// 0 indicates the default language (American English), or that the file
108    /// is language-neutral.
109    pub fn parse_locale(input: &[u8]) -> IResult<&[u8], u16> {
110        dbg_dmp(u16(LITTLE_ENDIAN), "locale")(input)
111    }
112
113    /// `Offset 0x0a`: int16 Platform
114    ///
115    /// The platform the file is used for. 0 indicates the default platform.
116    /// No other values have been observed.
117    pub fn parse_platform(input: &[u8]) -> IResult<&[u8], u16> {
118        dbg_dmp(u16(LITTLE_ENDIAN), "platform")(input)
119    }
120
121    /// `Offset 0x0c`: int32 FileBlockIndex
122    ///
123    /// If the hash table entry is valid, this is the index into the
124    /// block table of the file.
125    /// Otherwise, one of the following two values:
126    ///
127    /// * `0xffffffff` -  Hash table entry is empty, and has always been empty.
128    ///   Terminates searches for a given file.
129    /// * `0xfffffffe` - Hash table entry is empty, but was valid at some point
130    ///   (in other words, the file was deleted).  Does not terminate searches
131    ///   for a given file.
132    pub fn parse_block_table_index(input: &[u8]) -> IResult<&[u8], u32> {
133        dbg_dmp(u32(LITTLE_ENDIAN), "block_table_index")(input)
134    }
135}