1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! Nom Parsing The MPQ User Data Section
//! NOTES:
//! - MPyQ uses struct_format: '<4s3I'
//!   - The devklog.net website doesn't have an entry for UserDataHeaderSize
//!     and claims the userdata starts at offset 0x0c.
//!     In this implementation the MPyQ version is honored.

use super::LITTLE_ENDIAN;
use nom::bytes::complete::take;
use nom::error::dbg_dmp;
use nom::number::complete::u32;
use nom::*;

/// The MPQ User Data
#[derive(Debug, Default)]
pub struct MPQUserData {
    /// The number of bytes that have been allocated for user data.
    pub user_data_size: u32, // This variable is unused
    /// The offset in the file to continue reading the  archive header.
    pub archive_header_offset: u32,
    /// The block to store user data in.
    pub user_data_header_size: u32,
    /// The contents of the user data, in Starcraft 2 replay files contains
    /// the build information of the game version that created the replay.
    pub content: Vec<u8>,
}

impl MPQUserData {
    /// Parses all the fields in the expected order
    pub fn parse(input: &[u8]) -> IResult<&[u8], Self> {
        let (input, user_data_size) = Self::parse_user_data_size(input)?;
        let (input, archive_header_offset) = Self::parse_archive_header_offset(input)?;
        let (input, user_data_header_size) = Self::parse_user_data_header_size(input)?;
        let (input, content) = Self::parse_content(input, user_data_header_size)?;
        let (input, _) =
            Self::consume_until_header_offset(input, user_data_header_size, archive_header_offset)?;
        Ok((
            input,
            MPQUserData {
                user_data_size,
                archive_header_offset,
                user_data_header_size,
                content,
            },
        ))
    }

    /// `Offset 0x04`: int32 UserDataSize
    ///
    /// The number of bytes that have been allocated in this archive for user
    /// data. This does not need to be the exact size of the data itself, but
    /// merely the maximum amount of data which may be stored in this archive.
    pub fn parse_user_data_size(input: &[u8]) -> IResult<&[u8], u32> {
        dbg_dmp(u32(LITTLE_ENDIAN), "user_data_size")(input)
    }

    /// `Offset 0x08`: int32 ArchiveHeaderOffset
    ///
    /// The offset in the file at which to continue the search for the archive
    /// header.
    pub fn parse_archive_header_offset(input: &[u8]) -> IResult<&[u8], u32> {
        dbg_dmp(u32(LITTLE_ENDIAN), "archive_header_offset")(input)
    }

    /// `Offset 0x0c`: int32 UserDataHeaderSize
    /// The block to store user data in.
    pub fn parse_user_data_header_size(input: &[u8]) -> IResult<&[u8], u32> {
        dbg_dmp(u32(LITTLE_ENDIAN), "user_data_size")(input)
    }

    /// `Offset 0x10`: byte(UserDataSize) UserData
    ///
    /// The block to store user data in.
    pub fn parse_content(input: &[u8], user_data_header_size: u32) -> IResult<&[u8], Vec<u8>> {
        let (input, content) = dbg_dmp(take(user_data_header_size as usize), "content")(input)?;
        Ok((input, content.to_vec()))
    }

    /// Offset Varies: padded data
    ///
    /// Consumes until the header_offset, in MPyQ this is done through file.seek
    pub fn consume_until_header_offset(
        input: &[u8],
        user_data_header_size: u32,
        archive_header_offset: u32,
    ) -> IResult<&[u8], ()> {
        // Thus far we have read 16 bytes + the user_data_header_size
        // - 4 bytes for the magic
        // - 4 bytes for the user_data_size
        // - 4 bytes for the archive_header_offset
        // - 4 bytes for the user_data_header_size
        // - user_data_header_size bytes
        let curr_read_byte_count = 16;
        if archive_header_offset < user_data_header_size + curr_read_byte_count {
            panic!(
                "Invalid archive_header_offset: {}, should be bigger than {}",
                archive_header_offset,
                user_data_header_size + curr_read_byte_count
            );
        }
        let (input, _) = dbg_dmp(
            take((archive_header_offset - (user_data_header_size + curr_read_byte_count)) as usize),
            "content",
        )(input)?;
        Ok((input, ()))
    }
}

#[cfg(test)]
pub mod tests {
    use super::*;
    use crate::parser::*;

    pub fn basic_user_header() -> Vec<u8> {
        // - struct_format: '<4s3I'
        vec![
            b'M',
            b'P',
            b'Q', // Magic
            MPQ_USER_DATA_HEADER_TYPE,
            0x00,
            0x00,
            0x00,
            0x00, // user_data_size (unused)
            0x18,
            0x00,
            0x00,
            0x00, // archive_header_offset
            0x04,
            0x00,
            0x00,
            0x00, // user_data_header_size
            0xbe,
            0xef,
            0xca,
            0x4e, // content
            0x00,
            0x00,
            0x00,
            0x00, // Some padded data, MPQFileHeader should continue
        ]
    }

    #[test]
    fn it_parses_header() {
        // The user data header by itself
        let user_data_header_input = basic_user_header();
        let (input, header_type) = get_header_type(&user_data_header_input).unwrap();
        assert_eq!(header_type, MPQSectionType::UserData);
        let (input, user_data) = MPQUserData::parse(input).unwrap();
        assert_eq!(user_data.archive_header_offset, 0x18);
        assert_eq!(user_data.user_data_header_size, 0x04);
        assert_eq!(user_data.content, vec![0xbe, 0xef, 0xca, 0x4e]);
        assert_eq!(input, &b""[..]);
    }
}