use crate::{S2ProtoResult, S2ProtocolError, dbg_peek_hex};
use nom::bytes::complete::*;
use nom::number::complete::*;
use nom_mpq::MPQ;
use nom_mpq::parser::peek_hex;
use tracing::instrument;
#[derive(Default, Debug, Clone)]
pub struct DocumentHeader {
pub maybe_dimension_x1: i32,
pub maybe_dimension_y1: i32,
pub some_epoch_1: i32,
pub some_epoch_2: i32,
pub mod_info: String,
pub name: String,
pub description_long: String,
pub description_short: String,
}
impl DocumentHeader {
pub fn set_field_by_name(&mut self, name: &str, value: String) {
match name {
"DocInfo/Name" => self.name = value,
"DocInfo/DescLong" => self.description_long = value,
"DocInfo/DescShort" => self.description_short = value,
"MapInfo/Player00/Name"
| "MapInfo/Player01/Name"
| "MapInfo/Player02/Name"
| "MapInfo/Player03/Name"
| "DocInfo/Screenshot01"
| "DocInfo/Screenshot02"
| "DocInfo/Screenshot03"
| "DocInfo/HowToPlayBasic00"
| "DocInfo/HowToPlayBasic01"
| "DocInfo/HowToPlayBasic02"
| "DocInfo/HowToPlayAdvanced00"
| "DocInfo/HowToPlayAdvanced01"
| "DocInfo/HowToPlayAdvanced02" => tracing::debug!("Ignoring {name}"),
_ => {
tracing::warn!("Unknown field name {name}")
}
}
}
#[instrument(skip(mpq, file_contents))]
pub fn from_mpq(mpq: &MPQ, file_contents: &[u8]) -> Result<Self, S2ProtocolError> {
let (_, document_header_sector) =
mpq.read_mpq_file_sector("DocumentHeader", false, file_contents)?;
let (_, document_header) = Self::parse(&document_header_sector)?;
Ok(document_header)
}
#[tracing::instrument(level = "debug", skip(input), fields(input = peek_hex(input)))]
pub fn parse(input: &[u8]) -> S2ProtoResult<&[u8], Self> {
let mut res = Self::default();
let (tail, _) = dbg_peek_hex(tag(&b"H2CS"[..]), "read file magic, H2CS bytes")(input)?;
let (tail, maybe_file_version_bytes) =
dbg_peek_hex(take(4usize), "read maybe_file_version, 4 bytes")(tail)?;
let (_, _maybe_file_version) =
i32(nom::number::Endianness::Little)(maybe_file_version_bytes)?;
let (tail, maybe_map_compatibility_bytes) =
dbg_peek_hex(take(4usize), "read maybe_map_compatibility, 4 bytes")(tail)?;
let _maybe_compatibility =
String::from_utf8_lossy(maybe_map_compatibility_bytes).to_string();
let (tail, _unknown_bytes) =
dbg_peek_hex(take(4usize), "read _unknown_bytes after S2.., 4 bytes")(tail)?;
let (tail, maybe_i32_bytes) =
dbg_peek_hex(take(4usize), "read maybe_i32_bytes, 4 bytes")(tail)?;
let (_, maybe_dimension_x1) = i32(nom::number::Endianness::Little)(maybe_i32_bytes)?;
res.maybe_dimension_x1 = maybe_dimension_x1;
let (tail, maybe_i32_bytes) =
dbg_peek_hex(take(4usize), "read maybe_i32_bytes, 4 bytes")(tail)?;
let (_, maybe_dimension_y1) = i32(nom::number::Endianness::Little)(maybe_i32_bytes)?;
res.maybe_dimension_y1 = maybe_dimension_y1;
let (tail, _padding_bytes) = dbg_peek_hex(
take(4usize),
"read _padding_bytes after maybe dimensions1, 4 bytes",
)(tail)?;
let (tail, maybe_i32_bytes) =
dbg_peek_hex(take(4usize), "read maybe_i32_bytes, 4 bytes")(tail)?;
let (_, some_epoch_1) = i32(nom::number::Endianness::Little)(maybe_i32_bytes)?;
res.some_epoch_1 = some_epoch_1;
let (tail, maybe_i32_bytes) =
dbg_peek_hex(take(4usize), "read maybe_i32_bytes, 4 bytes")(tail)?;
let (_, some_epoch_2) = i32(nom::number::Endianness::Little)(maybe_i32_bytes)?;
res.some_epoch_2 = some_epoch_2;
let (tail, _padding_bytes) = dbg_peek_hex(
take(12usize),
"read _padding_bytes after maybe dimensions2, 12 bytes: ",
)(tail)?;
let (tail, string_bytes) = dbg_peek_hex(take_while(|x| x != 0u8), "read mod string")(tail)?;
res.mod_info = String::from_utf8_lossy(string_bytes).to_string();
if res.mod_info != "bnet:Void (Mod)/0.0/999,file:Mods/Void.SC2Mod" {
return Err(S2ProtocolError::Map(crate::cache_handles::MapError::Other(
"Only parsed Void typed.".to_string(),
)));
}
tracing::info!("Got mod_info: {}", res.mod_info);
let (tail, _past_the_zero_delim) = dbg_peek_hex(
take(1usize),
"read _past_the_zero_delim on mod_info, 1 bytes",
)(tail)?;
let (tail, docinfo_field_count_bytes) =
dbg_peek_hex(take(2usize), "read doc_info field count, 2 bytes")(tail)?;
let (_, docinfo_field_count) =
u16(nom::number::Endianness::Little)(docinfo_field_count_bytes)?;
tracing::info!(
"after read filed count filed count bytes: {:?} tail is {}",
docinfo_field_count_bytes,
peek_hex(tail)
);
let (mut new_tail, _) =
dbg_peek_hex(tag(&[0, 0][..]), "doc_info element count delimiter.")(tail)?;
tracing::info!("Expect to read {} fields", docinfo_field_count);
for _ in 0..docinfo_field_count {
tracing::info!("after read string size, tail is {}", peek_hex(new_tail));
let (tail, next_str_size_bytes) =
dbg_peek_hex(take(2usize), "read string size, 2 bytes")(new_tail)?;
let (_, next_str_size) = u16(nom::number::Endianness::Little)(next_str_size_bytes)?;
let (tail, docinfo_field_name_bytes) =
dbg_peek_hex(take(next_str_size), "read docinfo_field_name, n bytes")(tail)?;
let docinfo_field_name = String::from_utf8_lossy(docinfo_field_name_bytes).to_string();
tracing::info!(
"after field name: {} {}",
docinfo_field_name,
peek_hex(tail)
);
let (tail, _) =
dbg_peek_hex(tag(&b"SUne"[..]), "doc_info file type, SUne bytes")(tail)?;
let (tail, next_str_size_bytes) =
dbg_peek_hex(take(2usize), "read string size, 2 bytes")(tail)?;
let (_, next_str_size) = u16(nom::number::Endianness::Little)(next_str_size_bytes)?;
let (tail, docinfo_field_value_bytes) =
dbg_peek_hex(take(next_str_size), "read docinfo_field_value, n bytes")(tail)?;
let docinfo_field_value =
String::from_utf8_lossy(docinfo_field_value_bytes).to_string();
res.set_field_by_name(&docinfo_field_name, docinfo_field_value);
new_tail = tail;
}
Ok((tail, res))
}
}
#[cfg(test)]
pub mod document_header_tests {
use super::*;
pub fn document_header_cache_content() -> Vec<u8> {
vec![
0x48, 0x32, 0x43, 0x53, 0x08, 0x00, 0x00, 0x00, 0x32, 0x53, 0x00, 0x00, 0x01, 0x05, 0x00, 0x0e, 0x95, 0x6c, 0x01, 0x00, 0x95, 0x6c, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe7, 0x9c, 0xc5, 0x58, 0xe7, 0x9c, 0xc5, 0x58, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x62, 0x6e, 0x65, 0x74, 0x3a, 0x56, 0x6f, 0x69, 0x64, 0x20, 0x28, 0x4d, 0x6f, 0x64,
0x29, 0x2f, 0x30, 0x2e, 0x30, 0x2f, 0x39, 0x39, 0x39, 0x2c, 0x66, 0x69, 0x6c, 0x65,
0x3a, 0x4d, 0x6f, 0x64, 0x73, 0x2f, 0x56, 0x6f, 0x69, 0x64, 0x2e, 0x53, 0x43, 0x32,
0x4d, 0x6f, 0x64, 0x00, 0x07, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x44, 0x6f, 0x63, 0x49, 0x6e, 0x66, 0x6f, 0x2f, 0x4e, 0x61, 0x6d,
0x65, 0x53, 0x55, 0x6e, 0x65, 0x0a, 0x00, 0x54, 0x6f, 0x6b, 0x61, 0x6d, 0x61, 0x6b, 0x20, 0x4c, 0x45,
0x15, 0x00, 0x4d, 0x61, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x2f, 0x50, 0x6c, 0x61, 0x79,
0x65, 0x72, 0x30, 0x33, 0x2f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x55, 0x6e, 0x65, 0x07,
0x00, 0x48, 0x6f, 0x73, 0x74, 0x69, 0x6c, 0x65, 0x10, 0x00, 0x44, 0x6f, 0x63, 0x49,
0x6e, 0x66, 0x6f, 0x2f, 0x44, 0x65, 0x73, 0x63, 0x4c, 0x6f, 0x6e, 0x67, 0x53, 0x55,
0x6e, 0x65, 0xd1, 0x00, 0x57, 0x69, 0x64, 0x65, 0x20, 0x6d, 0x61, 0x63, 0x72, 0x6f,
0x20, 0x6d, 0x61, 0x70, 0x20, 0x77, 0x69, 0x74, 0x63, 0x68, 0x20, 0x31, 0x36, 0x20,
0x62, 0x61, 0x73, 0x65, 0x73, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x72, 0x65, 0x20, 0x61,
0x72, 0x65, 0x20, 0x74, 0x77, 0x6f, 0x20, 0x68, 0x79, 0x62, 0x72, 0x69, 0x64, 0x20,
0x62, 0x61, 0x73, 0x65, 0x73, 0x20, 0x77, 0x69, 0x74, 0x68, 0x20, 0x6d, 0x69, 0x6e,
0x65, 0x72, 0x61, 0x6c, 0x73, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x69, 0x6e, 0x67,
0x20, 0x61, 0x20, 0x70, 0x61, 0x74, 0x68, 0x2e, 0x20, 0x54, 0x68, 0x65, 0x20, 0x70,
0x61, 0x74, 0x68, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x62, 0x65, 0x20, 0x6f, 0x70, 0x65,
0x6e, 0x20, 0x62, 0x79, 0x20, 0x6d, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x20, 0x6f, 0x72,
0x20, 0x62, 0x79, 0x20, 0x64, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x69, 0x6e, 0x67,
0x20, 0x74, 0x68, 0x65, 0x20, 0x63, 0x6f, 0x6f, 0x6c, 0x69, 0x6e, 0x67, 0x20, 0x74,
0x6f, 0x77, 0x65, 0x72, 0x73, 0x20, 0x62, 0x65, 0x68, 0x69, 0x6e, 0x64, 0x20, 0x74,
0x68, 0x65, 0x20, 0x62, 0x61, 0x73, 0x65, 0x20, 0x77, 0x68, 0x69, 0x63, 0x68, 0x20,
0x63, 0x72, 0x75, 0x73, 0x68, 0x65, 0x73, 0x20, 0x61, 0x6c, 0x6c, 0x20, 0x62, 0x6c,
0x75, 0x65, 0x20, 0x6d, 0x69, 0x6e, 0x65, 0x72, 0x61, 0x6c, 0x20, 0x6e, 0x6f, 0x64,
0x65, 0x73, 0x2e, 0x15, 0x00, 0x4d, 0x61, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x2f, 0x50,
0x6c, 0x61, 0x79, 0x65, 0x72, 0x30, 0x30, 0x2f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x55,
0x6e, 0x65, 0x07, 0x00, 0x4e, 0x65, 0x75, 0x74, 0x72, 0x61, 0x6c, 0x15, 0x00, 0x4d,
0x61, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x2f, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x30,
0x32, 0x2f, 0x4e, 0x61, 0x6d, 0x65, 0x53, 0x55, 0x6e, 0x65, 0x08, 0x00, 0x50, 0x6c,
0x61, 0x79, 0x65, 0x72, 0x20, 0x32, 0x11, 0x00, 0x44, 0x6f, 0x63, 0x49, 0x6e, 0x66,
0x6f, 0x2f, 0x44, 0x65, 0x73, 0x63, 0x53, 0x68, 0x6f, 0x72, 0x74, 0x53, 0x55, 0x6e,
0x65, 0x01, 0x00, 0x32, 0x15, 0x00, 0x4d, 0x61, 0x70, 0x49, 0x6e, 0x66, 0x6f, 0x2f,
0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x30, 0x31, 0x2f, 0x4e, 0x61, 0x6d, 0x65, 0x53,
0x55, 0x6e, 0x65, 0x08, 0x00, 0x50, 0x6c, 0x61, 0x79, 0x65, 0x72, 0x20, 0x31, 0x00,
]
}
#[test_log::test]
fn test_parse_document_header() {
let cache_contents: Vec<u8> = document_header_cache_content();
let (_, document_header) = DocumentHeader::parse(&cache_contents).unwrap();
assert_eq!(document_header.name, "Tokamak LE");
assert_eq!(
document_header.mod_info,
"bnet:Void (Mod)/0.0/999,file:Mods/Void.SC2Mod"
);
}
}