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""[..]);
}
}