nom_mpq/parser/
mpq_user_data.rs

1//! Nom Parsing The MPQ User Data Section
2//! NOTES:
3//! - MPyQ uses struct_format: '<4s3I'
4//!   - The devklog.net website doesn't have an entry for UserDataHeaderSize
5//!     and claims the userdata starts at offset 0x0c.
6//!     In this implementation the MPyQ version is honored.
7
8use super::LITTLE_ENDIAN;
9use crate::dbg_dmp;
10use nom::bytes::complete::take;
11use nom::number::complete::u32;
12use nom::*;
13
14/// The MPQ User Data
15#[derive(Debug, Default)]
16pub struct MPQUserData {
17    /// The number of bytes that have been allocated for user data.
18    pub user_data_size: u32, // This variable is unused
19    /// The offset in the file to continue reading the  archive header.
20    pub archive_header_offset: u32,
21    /// The block to store user data in.
22    pub user_data_header_size: u32,
23    /// The contents of the user data, in Starcraft 2 replay files contains
24    /// the build information of the game version that created the replay.
25    pub content: Vec<u8>,
26}
27
28impl MPQUserData {
29    /// Parses all the fields in the expected order
30    pub fn parse(input: &[u8]) -> IResult<&[u8], Self> {
31        let (input, user_data_size) = Self::parse_user_data_size(input)?;
32        let (input, archive_header_offset) = Self::parse_archive_header_offset(input)?;
33        let (input, user_data_header_size) = Self::parse_user_data_header_size(input)?;
34        let (input, content) = Self::parse_content(input, user_data_header_size)?;
35        let (input, _) =
36            Self::consume_until_header_offset(input, user_data_header_size, archive_header_offset)?;
37        Ok((
38            input,
39            MPQUserData {
40                user_data_size,
41                archive_header_offset,
42                user_data_header_size,
43                content,
44            },
45        ))
46    }
47
48    /// `Offset 0x04`: int32 UserDataSize
49    ///
50    /// The number of bytes that have been allocated in this archive for user
51    /// data. This does not need to be the exact size of the data itself, but
52    /// merely the maximum amount of data which may be stored in this archive.
53    pub fn parse_user_data_size(input: &[u8]) -> IResult<&[u8], u32> {
54        dbg_dmp(u32(LITTLE_ENDIAN), "user_data_size")(input)
55    }
56
57    /// `Offset 0x08`: int32 ArchiveHeaderOffset
58    ///
59    /// The offset in the file at which to continue the search for the archive
60    /// header.
61    pub fn parse_archive_header_offset(input: &[u8]) -> IResult<&[u8], u32> {
62        dbg_dmp(u32(LITTLE_ENDIAN), "archive_header_offset")(input)
63    }
64
65    /// `Offset 0x0c`: int32 UserDataHeaderSize
66    /// The block to store user data in.
67    pub fn parse_user_data_header_size(input: &[u8]) -> IResult<&[u8], u32> {
68        dbg_dmp(u32(LITTLE_ENDIAN), "user_data_size")(input)
69    }
70
71    /// `Offset 0x10`: byte(UserDataSize) UserData
72    ///
73    /// The block to store user data in.
74    pub fn parse_content(input: &[u8], user_data_header_size: u32) -> IResult<&[u8], Vec<u8>> {
75        let (input, content) = dbg_dmp(take(user_data_header_size as usize), "content")(input)?;
76        Ok((input, content.to_vec()))
77    }
78
79    /// Offset Varies: padded data
80    ///
81    /// Consumes until the header_offset, in MPyQ this is done through file.seek
82    pub fn consume_until_header_offset(
83        input: &[u8],
84        user_data_header_size: u32,
85        archive_header_offset: u32,
86    ) -> IResult<&[u8], ()> {
87        // Thus far we have read 16 bytes + the user_data_header_size
88        // - 4 bytes for the magic
89        // - 4 bytes for the user_data_size
90        // - 4 bytes for the archive_header_offset
91        // - 4 bytes for the user_data_header_size
92        // - user_data_header_size bytes
93        let curr_read_byte_count = 16;
94        if archive_header_offset < user_data_header_size + curr_read_byte_count {
95            tracing::error!(
96                "Invalid archive_header_offset: {}, should be bigger than {}",
97                archive_header_offset,
98                user_data_header_size + curr_read_byte_count
99            );
100            return Err(nom::Err::Incomplete(nom::Needed::new(
101                (user_data_header_size + curr_read_byte_count) as usize,
102            )));
103        }
104        let (input, _) = dbg_dmp(
105            take((archive_header_offset - (user_data_header_size + curr_read_byte_count)) as usize),
106            "content",
107        )(input)?;
108        Ok((input, ()))
109    }
110}
111
112#[cfg(test)]
113/// User Data Tests
114pub mod tests {
115    use super::*;
116    use crate::parser::*;
117
118    /// Generates a basic user data header
119    pub fn basic_user_header() -> Vec<u8> {
120        // - struct_format: '<4s3I'
121        vec![
122            b'M',
123            b'P',
124            b'Q', // Magic
125            MPQ_USER_DATA_HEADER_TYPE,
126            0x00,
127            0x00,
128            0x00,
129            0x00, // user_data_size (unused)
130            0x18,
131            0x00,
132            0x00,
133            0x00, // archive_header_offset
134            0x04,
135            0x00,
136            0x00,
137            0x00, // user_data_header_size
138            0xbe,
139            0xef,
140            0xca,
141            0x4e, // content
142            0x00,
143            0x00,
144            0x00,
145            0x00, // Some padded data, MPQFileHeader should continue
146        ]
147    }
148
149    #[test]
150    fn it_parses_header() {
151        // The user data header by itself
152        let user_data_header_input = basic_user_header();
153        let (input, header_type) = get_header_type(&user_data_header_input).unwrap();
154        assert_eq!(header_type, MPQSectionType::UserData);
155        let (input, user_data) = MPQUserData::parse(input).unwrap();
156        assert_eq!(user_data.archive_header_offset, 0x18);
157        assert_eq!(user_data.user_data_header_size, 0x04);
158        assert_eq!(user_data.content, vec![0xbe, 0xef, 0xca, 0x4e]);
159        assert_eq!(input, &b""[..]);
160    }
161}