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}