serde_hkx/bytes/de/parser/
classnames.rs

1use std::collections::HashMap;
2
3use winnow::{
4    Parser,
5    binary::{self, Endianness},
6    error::{ContextError, StrContext, StrContextValue::*},
7    seq,
8    token::take_while,
9};
10
11use crate::{bytes::de::parser::type_kind::string, tri};
12
13/// - key: class name start offset
14/// - value: class name
15pub type ClassNames<'a> = HashMap<u32, &'a str>;
16
17const FIXUP_VALUE_FOR_ALIGN: u32 = u32::MAX;
18
19/// Create `local_fixups` from bytes.
20///
21/// # Returns
22/// <class name start position, class name>
23///
24/// # Note
25/// And take align mark(0xff) bytes.
26pub fn classnames_section<'a>(
27    endian: Endianness,
28    base_offset: usize,
29) -> impl Parser<&'a [u8], ClassNames<'a>, ContextError> {
30    move |bytes: &mut &'a [u8]| {
31        let mut class_map = HashMap::new();
32        let mut offset = base_offset; // Necessary to determine the starting position of class_name.
33
34        while let Ok(_signature) = binary::u32::<&[u8], ContextError>(endian)
35            .verify(|src| *src != FIXUP_VALUE_FOR_ALIGN)
36            .context(StrContext::Expected(Description("local_fixup.src(u32)")))
37            .parse_next(bytes)
38        {
39            #[cfg(feature = "tracing")]
40            tracing::trace!("signature: {_signature:#x}");
41
42            let (class_name,) =tri!(seq! {
43                _: binary::u8::<&[u8], ContextError>
44                    .verify(|byte| *byte == 0x9)
45                    .context(StrContext::Expected(Description("class name separator(0x9)"))),
46                string // Parse until `\0`
47                    .verify(|s: &str| s.is_ascii())
48                    .context(StrContext::Expected(Description("ASCII class name(e.g. `hkReferencedObject`")))
49            }.parse_next(bytes));
50
51            offset += 5; // signature(4bytes) + separator(1byte)
52            #[cfg(feature = "tracing")]
53            tracing::trace!("class_name: {class_name}, offset: {offset}");
54            class_map.insert(offset as u32, class_name);
55
56            // Safety: as long as ASCII
57            offset += class_name.len() + 1; // name + `\0` size
58        }
59        tri!(take_while(0.., 0xff).parse_next(bytes)); // take align mark bytes.
60        Ok(class_map)
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::classnames_section;
67    use pretty_assertions::assert_eq;
68    use winnow::{Parser, binary::Endianness};
69
70    #[test]
71    fn should_parse_classnames() {
72        #[rustfmt::skip]
73        let bytes= &[
74         0xf6, 0x5e, 0x58, 0x75, 0x09, 0x68, 0x6b, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x00, 0xc2, 0xa4, 0x7e, // 000000d0:  .^Xu.hkClass...~
75         0x5c, 0x09, 0x68, 0x6b, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x00, // 000000e0:  \.hkClassMember.
76         0xcf, 0x09, 0x36, 0x8a, 0x09, 0x68, 0x6b, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x75, 0x6d, // 000000f0:  ..6..hkClassEnum
77         0x00, 0x6c, 0x8a, 0x6f, 0xce, 0x09, 0x68, 0x6b, 0x43, 0x6c, 0x61, 0x73, 0x73, 0x45, 0x6e, 0x75, // 00000100:  .l.o..hkClassEnu
78         0x6d, 0x49, 0x74, 0x65, 0x6d, 0x00, 0x1e, 0xc1, 0x72, 0x27, 0x09, 0x68, 0x6b, 0x52, 0x6f, 0x6f, // 00000110:  mItem...r'.hkRoo
79         0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x00, // 00000120:  tLevelContainer.
80         0xa7, 0x9b, 0xa3, 0x13, 0x09, 0x68, 0x6b, 0x62, 0x50, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x44, // 00000130:  .....hkbProjectD
81         0x61, 0x74, 0x61, 0x00, 0x0a, 0xd6, 0x6a, 0x07, 0x09, 0x68, 0x6b, 0x62, 0x50, 0x72, 0x6f, 0x6a, // 00000140:  ata...j..hkbProj
82         0x65, 0x63, 0x74, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x44, 0x61, 0x74, 0x61, 0x00, 0xff, 0xff, // 00000150:  ectStringData...
83        ];
84
85        match classnames_section(Endianness::Little, 0).parse(bytes) {
86            Ok(class_map) => {
87                let expected_class_map = [
88                    (5, "hkClass"),
89                    (18, "hkClassMember"),
90                    (37, "hkClassEnum"),
91                    (54, "hkClassEnumItem"),
92                    (75, "hkRootLevelContainer"),
93                    (101, "hkbProjectData"),
94                    (121, "hkbProjectStringData"),
95                ]
96                .into();
97                assert_eq!(class_map, expected_class_map);
98            }
99            Err(err) => panic!("{err}"),
100        }
101    }
102}