Skip to main content

boon/entity/
serializers.rs

1use std::collections::HashMap;
2// Arc (not Rc) so that SerializerContainer is Send + Sync and can be
3// shared across threads.  The serializer graph is immutable after
4// construction, so atomic refcounting adds negligible overhead.
5use std::sync::Arc;
6
7use prost::Message;
8
9use crate::error::Result;
10use crate::io::ByteReader;
11
12use super::field_decoder::{self, FieldMetadata};
13use super::field_path::FieldPath;
14
15use boon_proto::proto::{CDemoSendTables, CsvcMsgFlattenedSerializer};
16
17/// Parsed type information from a `var_type` string (e.g. `"CNetworkUtlVectorBase< int32 >"`).
18#[derive(Debug, Clone)]
19pub struct FieldType {
20    /// The core type name (e.g. `"int32"`, `"CNetworkUtlVectorBase"`).
21    pub base_type: String,
22    /// `true` if the type ends with `*` (pointer / handle).
23    pub pointer: bool,
24    /// Inner type parameter for generics (e.g. `int32` inside `CNetworkUtlVectorBase<int32>`).
25    pub generic_type: Option<Box<FieldType>>,
26    /// Numeric array length for `Type[N]` syntax.
27    pub array_length: Option<usize>,
28    /// Symbolic array length for `Type[SYMBOL]` syntax (e.g. `MAX_ABILITY_DRAFT_ABILITIES`).
29    pub count: Option<String>,
30}
31
32/// A single field within a serializer, describing one network property.
33#[derive(Debug, Clone)]
34pub struct SerializerField {
35    /// Source 2 type string (e.g. `"float32"`, `"CNetworkUtlVectorBase< int32 >"`).
36    pub var_type: String,
37    /// Field name (e.g. `"m_iHealth"`).
38    pub var_name: String,
39    /// Bit width hint for quantized floats and QAngle.
40    pub bit_count: Option<i32>,
41    /// Low end of quantized float range.
42    pub low_value: Option<f32>,
43    /// High end of quantized float range.
44    pub high_value: Option<f32>,
45    /// Quantized float encoding flags (see `QFE_*` constants).
46    pub encode_flags: Option<i32>,
47    /// Name of a nested serializer (for composite / array fields).
48    pub field_serializer_name: Option<String>,
49    /// Dotted path prefix used by the entity field name resolution.
50    pub send_node: Option<String>,
51    /// Optional encoder hint (e.g. `"coord"`, `"normal"`, `"fixed64"`).
52    pub var_encoder: Option<String>,
53    /// Resolved nested serializer (populated during [`SerializerContainer::parse`]).
54    pub field_serializer: Option<Arc<Serializer>>,
55    /// Parsed type information.
56    pub field_type: FieldType,
57    /// Resolved decoder and special descriptor.
58    pub metadata: FieldMetadata,
59}
60
61impl SerializerField {
62    /// Get a field from the nested serializer at `index`, if present.
63    pub fn get_child(&self, index: usize) -> Option<&SerializerField> {
64        self.field_serializer
65            .as_ref()
66            .and_then(|s| s.fields.get(index).map(|f| f.as_ref()))
67    }
68
69    /// Returns `true` if this field represents a variable-length array.
70    pub fn is_dynamic_array(&self) -> bool {
71        self.metadata.is_dynamic_array()
72    }
73}
74
75/// A serializer: a named collection of fields describing an entity class.
76#[derive(Debug, Clone)]
77pub struct Serializer {
78    pub name: String,
79    pub fields: Vec<Arc<SerializerField>>,
80}
81
82impl Serializer {
83    /// Resolve a dotted field name (e.g. "m_pGameRules.m_bGamePaused") to a packed u64 key.
84    /// Walks the serializer hierarchy matching send_node + var_name against path components.
85    pub fn resolve_field_key(&self, path: &str) -> Option<u64> {
86        let parts: Vec<&str> = path.split('.').collect();
87        self.resolve_parts(&parts, 0)
88    }
89
90    fn resolve_parts(&self, parts: &[&str], depth: usize) -> Option<u64> {
91        if depth >= parts.len() {
92            return None;
93        }
94
95        for (field_idx, field) in self.fields.iter().enumerate() {
96            // Build the name parts this field contributes (send_node + var_name)
97            let mut field_parts: Vec<&str> = Vec::new();
98            if let Some(ref sn) = field.send_node
99                && !sn.is_empty()
100            {
101                for part in sn.split('.') {
102                    field_parts.push(part);
103                }
104            }
105            if !field.var_name.is_empty() {
106                field_parts.push(&field.var_name);
107            }
108
109            // Check if the remaining path starts with these field parts
110            let remaining = &parts[depth..];
111            if remaining.len() < field_parts.len() {
112                continue;
113            }
114            if field_parts != remaining[..field_parts.len()] {
115                continue;
116            }
117
118            let consumed = depth + field_parts.len();
119
120            // If we've consumed all parts, this is the field
121            if consumed == parts.len() {
122                let mut fp = FieldPath::default();
123                fp.data[0] = field_idx as u8;
124                // last stays 0
125                return Some(fp.pack());
126            }
127
128            // More parts remain — we need to recurse into a sub-serializer
129            let next_part = parts[consumed];
130
131            // Dynamic array: next part is a numeric index
132            if field.is_dynamic_array() {
133                if let Ok(array_idx) = next_part.parse::<usize>()
134                    && let Some(ref fs) = field.field_serializer
135                {
136                    let inner_field = &fs.fields[0];
137                    let after_idx = consumed + 1;
138
139                    if after_idx == parts.len() {
140                        // The array element itself is the value
141                        let mut fp = FieldPath::default();
142                        fp.data[0] = field_idx as u8;
143                        fp.data[1] = array_idx as u8;
144                        fp.last = 1;
145                        return Some(fp.pack());
146                    }
147
148                    // Recurse into the inner field's serializer
149                    if let Some(ref inner_fs) = inner_field.field_serializer
150                        && let Some(key) = inner_fs.resolve_parts(parts, after_idx)
151                    {
152                        let inner_fp = FieldPath::unpack(key);
153                        let mut fp = FieldPath::default();
154                        fp.data[0] = field_idx as u8;
155                        fp.data[1] = array_idx as u8;
156                        fp.last = 2 + inner_fp.last;
157                        for i in 0..=inner_fp.last {
158                            fp.data[2 + i] = inner_fp.data[i];
159                        }
160                        return Some(fp.pack());
161                    }
162                }
163                continue;
164            }
165
166            // Non-dynamic: recurse into field_serializer
167            if let Some(ref fs) = field.field_serializer
168                && let Some(key) = fs.resolve_parts(parts, consumed)
169            {
170                let inner_fp = FieldPath::unpack(key);
171                let mut fp = FieldPath::default();
172                fp.data[0] = field_idx as u8;
173                fp.last = 1 + inner_fp.last;
174                for i in 0..=inner_fp.last {
175                    fp.data[1 + i] = inner_fp.data[i];
176                }
177                return Some(fp.pack());
178            }
179        }
180
181        None
182    }
183
184    /// Convert a packed u64 key back to a dotted field name string.
185    /// Walks the serializer hierarchy using the unpacked FieldPath.
186    pub fn field_name_for_key(&self, key: u64) -> Option<String> {
187        let fp = FieldPath::unpack(key);
188        let mut parts: Vec<String> = Vec::new();
189        let mut field = self.fields.get(fp.get(0))?;
190
191        if let Some(ref sn) = field.send_node
192            && !sn.is_empty()
193        {
194            for part in sn.split('.') {
195                parts.push(part.to_string());
196            }
197        }
198        parts.push(field.var_name.clone());
199
200        for i in 1..=fp.last {
201            let idx = fp.get(i);
202            if field.is_dynamic_array() {
203                parts.push(idx.to_string());
204                if let Some(ref fs) = field.field_serializer {
205                    field = &fs.fields[0];
206                } else {
207                    break;
208                }
209            } else if let Some(ref fs) = field.field_serializer {
210                field = fs.fields.get(idx)?;
211                if let Some(ref sn) = field.send_node
212                    && !sn.is_empty()
213                {
214                    for part in sn.split('.') {
215                        parts.push(part.to_string());
216                    }
217                }
218                parts.push(field.var_name.clone());
219            } else {
220                break;
221            }
222        }
223
224        Some(parts.join("."))
225    }
226}
227
228/// Container holding all parsed serializers, indexed by name.
229pub struct SerializerContainer {
230    pub serializers: HashMap<String, Arc<Serializer>>,
231}
232
233impl SerializerContainer {
234    /// Parse a CDemoSendTables message into a SerializerContainer.
235    pub fn parse(cmd: CDemoSendTables) -> Result<Self> {
236        let data = cmd.data.unwrap_or_default();
237        let mut data_reader = ByteReader::new(&data);
238
239        // Read varint size prefix, then decode the flattened serializer message
240        let _size = data_reader.read_uvarint64()?;
241        let remaining = data_reader.read_bytes(data_reader.remaining())?;
242        let msg = CsvcMsgFlattenedSerializer::decode(remaining)?;
243
244        let symbols = &msg.symbols;
245
246        let resolve_sym = |i: i32| -> &str { &symbols[i as usize] };
247
248        // Build fields and serializers
249        let mut field_cache: HashMap<i32, Arc<SerializerField>> = HashMap::new();
250        let mut serializer_map: HashMap<String, Arc<Serializer>> = HashMap::new();
251
252        for serializer_proto in &msg.serializers {
253            let ser_name = resolve_sym(serializer_proto.serializer_name_sym.unwrap_or(0));
254            let mut serializer = Serializer {
255                name: ser_name.to_string(),
256                fields: Vec::with_capacity(serializer_proto.fields_index.len()),
257            };
258
259            for &field_index in &serializer_proto.fields_index {
260                if let Some(cached) = field_cache.get(&field_index) {
261                    serializer.fields.push(cached.clone());
262                    continue;
263                }
264
265                let field_proto = &msg.fields[field_index as usize];
266
267                let var_type = field_proto
268                    .var_type_sym
269                    .map(resolve_sym)
270                    .unwrap_or("")
271                    .to_string();
272                let var_name = field_proto
273                    .var_name_sym
274                    .map(resolve_sym)
275                    .unwrap_or("")
276                    .to_string();
277                let send_node = field_proto.send_node_sym.map(resolve_sym).map(String::from);
278                let var_encoder = field_proto
279                    .var_encoder_sym
280                    .map(resolve_sym)
281                    .map(String::from);
282                let field_serializer_name = field_proto
283                    .field_serializer_name_sym
284                    .map(resolve_sym)
285                    .map(String::from);
286
287                let field_type = parse_type(&var_type);
288                let metadata = field_decoder::get_field_metadata(
289                    &var_type,
290                    &var_name,
291                    field_proto.bit_count,
292                    field_proto.low_value,
293                    field_proto.high_value,
294                    field_proto.encode_flags,
295                    var_encoder.as_deref(),
296                    field_serializer_name.is_some(),
297                );
298
299                // Resolve field serializer
300                let field_serializer = match &metadata {
301                    fm if fm.is_fixed_array() => {
302                        let length = fm.fixed_array_length().unwrap_or(0);
303                        // Build a pseudo-serializer containing `length` copies of the inner field
304                        let inner_ser = field_serializer_name
305                            .as_deref()
306                            .and_then(|n| serializer_map.get(n).cloned());
307
308                        let inner_field = SerializerField {
309                            var_type: var_type.clone(),
310                            var_name: var_name.clone(),
311                            bit_count: field_proto.bit_count,
312                            low_value: field_proto.low_value,
313                            high_value: field_proto.high_value,
314                            encode_flags: field_proto.encode_flags,
315                            field_serializer_name: field_serializer_name.clone(),
316                            send_node: send_node.clone(),
317                            var_encoder: var_encoder.clone(),
318                            field_serializer: inner_ser,
319                            field_type: field_type.clone(),
320                            metadata: metadata.clone(),
321                        };
322                        let inner_rc = Arc::new(inner_field);
323                        let mut fields = Vec::with_capacity(length);
324                        fields.resize(length, inner_rc);
325                        Some(Arc::new(Serializer {
326                            name: String::new(),
327                            fields,
328                        }))
329                    }
330                    fm if fm.is_dynamic_array() => {
331                        // For dynamic arrays of serializers, build a single-element serializer
332                        if fm.is_dynamic_serializer_array() {
333                            let inner_ser = field_serializer_name
334                                .as_deref()
335                                .and_then(|n| serializer_map.get(n).cloned());
336                            let inner = Arc::new(SerializerField {
337                                var_type: String::new(),
338                                var_name: String::new(),
339                                bit_count: None,
340                                low_value: None,
341                                high_value: None,
342                                encode_flags: None,
343                                field_serializer_name: None,
344                                send_node: None,
345                                var_encoder: None,
346                                field_serializer: inner_ser,
347                                field_type: parse_type(""),
348                                metadata: FieldMetadata::default(),
349                            });
350                            Some(Arc::new(Serializer {
351                                name: String::new(),
352                                fields: vec![inner],
353                            }))
354                        } else {
355                            // Dynamic array of primitives: single-element serializer with inner decoder
356                            let inner_metadata = fm.dynamic_array_inner_metadata();
357                            let inner = Arc::new(SerializerField {
358                                var_type: String::new(),
359                                var_name: String::new(),
360                                bit_count: None,
361                                low_value: None,
362                                high_value: None,
363                                encode_flags: None,
364                                field_serializer_name: None,
365                                send_node: None,
366                                var_encoder: None,
367                                field_serializer: None,
368                                field_type: parse_type(""),
369                                metadata: inner_metadata,
370                            });
371                            Some(Arc::new(Serializer {
372                                name: String::new(),
373                                fields: vec![inner],
374                            }))
375                        }
376                    }
377                    fm if fm.is_pointer() => field_serializer_name
378                        .as_deref()
379                        .and_then(|n| serializer_map.get(n).cloned()),
380                    _ => field_serializer_name
381                        .as_deref()
382                        .and_then(|n| serializer_map.get(n).cloned()),
383                };
384
385                let field = Arc::new(SerializerField {
386                    var_type,
387                    var_name,
388                    bit_count: field_proto.bit_count,
389                    low_value: field_proto.low_value,
390                    high_value: field_proto.high_value,
391                    encode_flags: field_proto.encode_flags,
392                    field_serializer_name,
393                    send_node,
394                    var_encoder,
395                    field_serializer,
396                    field_type,
397                    metadata,
398                });
399
400                field_cache.insert(field_index, field.clone());
401                serializer.fields.push(field);
402            }
403
404            serializer_map.insert(serializer.name.clone(), Arc::new(serializer));
405        }
406
407        Ok(Self {
408            serializers: serializer_map,
409        })
410    }
411
412    /// Look up a serializer by class network name.
413    pub fn get(&self, name: &str) -> Option<&Serializer> {
414        self.serializers.get(name).map(|arc| arc.as_ref())
415    }
416}
417
418/// Parse a var_type string into a FieldType.
419pub fn parse_type(s: &str) -> FieldType {
420    let s = s.trim();
421
422    // Check for pointer
423    if let Some(stripped) = s.strip_suffix('*') {
424        return FieldType {
425            base_type: stripped.trim().to_string(),
426            pointer: true,
427            generic_type: None,
428            array_length: None,
429            count: None,
430        };
431    }
432
433    // Check for array: type[length]
434    if let Some(bracket_pos) = s.find('[')
435        && s.ends_with(']')
436    {
437        let base = s[..bracket_pos].trim();
438        let len_str = s[bracket_pos + 1..s.len() - 1].trim();
439        let array_length = len_str.parse::<usize>().ok();
440        let count = if array_length.is_none() {
441            Some(len_str.to_string())
442        } else {
443            None
444        };
445        return FieldType {
446            base_type: base.to_string(),
447            pointer: false,
448            generic_type: None,
449            array_length,
450            count,
451        };
452    }
453
454    // Check for generic: Type< InnerType >
455    if let Some(angle_pos) = s.find('<')
456        && let Some(close_pos) = s.rfind('>')
457    {
458        let base = s[..angle_pos].trim();
459        let inner = s[angle_pos + 1..close_pos].trim();
460        return FieldType {
461            base_type: base.to_string(),
462            pointer: false,
463            generic_type: Some(Box::new(parse_type(inner))),
464            array_length: None,
465            count: None,
466        };
467    }
468
469    // Simple type
470    FieldType {
471        base_type: s.to_string(),
472        pointer: false,
473        generic_type: None,
474        array_length: None,
475        count: None,
476    }
477}
478
479#[cfg(test)]
480mod tests {
481    use super::super::field_decoder::{Decoder, FieldMetadata};
482    use super::*;
483
484    // ── parse_type ──
485
486    #[test]
487    fn parse_type_simple() {
488        let ft = parse_type("int32");
489        assert_eq!(ft.base_type, "int32");
490        assert!(!ft.pointer);
491        assert!(ft.generic_type.is_none());
492        assert!(ft.array_length.is_none());
493        assert!(ft.count.is_none());
494    }
495
496    #[test]
497    fn parse_type_pointer() {
498        let ft = parse_type("CBaseEntity*");
499        assert_eq!(ft.base_type, "CBaseEntity");
500        assert!(ft.pointer);
501    }
502
503    #[test]
504    fn parse_type_array_numeric() {
505        let ft = parse_type("int32[4]");
506        assert_eq!(ft.base_type, "int32");
507        assert_eq!(ft.array_length, Some(4));
508        assert!(ft.count.is_none());
509    }
510
511    #[test]
512    fn parse_type_array_symbolic() {
513        let ft = parse_type("int32[MAX_ABILITIES]");
514        assert_eq!(ft.base_type, "int32");
515        assert!(ft.array_length.is_none());
516        assert_eq!(ft.count.as_deref(), Some("MAX_ABILITIES"));
517    }
518
519    #[test]
520    fn parse_type_generic() {
521        let ft = parse_type("CNetworkUtlVectorBase< int32 >");
522        assert_eq!(ft.base_type, "CNetworkUtlVectorBase");
523        let inner = ft.generic_type.as_ref().unwrap();
524        assert_eq!(inner.base_type, "int32");
525    }
526
527    #[test]
528    fn parse_type_generic_nested() {
529        let ft = parse_type("CHandle< CBaseEntity >");
530        assert_eq!(ft.base_type, "CHandle");
531        assert_eq!(ft.generic_type.as_ref().unwrap().base_type, "CBaseEntity");
532    }
533
534    #[test]
535    fn parse_type_whitespace_trimming() {
536        let ft = parse_type("  float32  ");
537        assert_eq!(ft.base_type, "float32");
538    }
539
540    #[test]
541    fn parse_type_empty_string() {
542        let ft = parse_type("");
543        assert_eq!(ft.base_type, "");
544        assert!(!ft.pointer);
545    }
546
547    #[test]
548    fn parse_type_complex_all_fields() {
549        let ft = parse_type("uint32[16]");
550        assert_eq!(ft.base_type, "uint32");
551        assert!(!ft.pointer);
552        assert!(ft.generic_type.is_none());
553        assert_eq!(ft.array_length, Some(16));
554        assert!(ft.count.is_none());
555    }
556
557    // ── Serializer key resolution ──
558
559    fn make_field(name: &str, send_node: Option<&str>) -> Arc<SerializerField> {
560        Arc::new(SerializerField {
561            var_type: String::new(),
562            var_name: name.to_string(),
563            bit_count: None,
564            low_value: None,
565            high_value: None,
566            encode_flags: None,
567            field_serializer_name: None,
568            send_node: send_node.map(String::from),
569            var_encoder: None,
570            field_serializer: None,
571            field_type: parse_type(""),
572            metadata: FieldMetadata {
573                decoder: Decoder::U64,
574                special: None,
575            },
576        })
577    }
578
579    #[test]
580    fn resolve_field_key_found() {
581        let ser = Serializer {
582            name: "test".to_string(),
583            fields: vec![make_field("m_iHealth", None)],
584        };
585        assert!(ser.resolve_field_key("m_iHealth").is_some());
586    }
587
588    #[test]
589    fn resolve_field_key_not_found() {
590        let ser = Serializer {
591            name: "test".to_string(),
592            fields: vec![make_field("m_iHealth", None)],
593        };
594        assert!(ser.resolve_field_key("m_iMana").is_none());
595    }
596
597    #[test]
598    fn field_name_roundtrip() {
599        let ser = Serializer {
600            name: "test".to_string(),
601            fields: vec![make_field("m_iHealth", None), make_field("m_iMana", None)],
602        };
603        let key = ser.resolve_field_key("m_iMana").unwrap();
604        let name = ser.field_name_for_key(key).unwrap();
605        assert_eq!(name, "m_iMana");
606    }
607
608    #[test]
609    fn resolve_with_send_node() {
610        let ser = Serializer {
611            name: "test".to_string(),
612            fields: vec![make_field("m_bPaused", Some("m_pGameRules"))],
613        };
614        let key = ser.resolve_field_key("m_pGameRules.m_bPaused");
615        assert!(key.is_some());
616        let name = ser.field_name_for_key(key.unwrap()).unwrap();
617        assert_eq!(name, "m_pGameRules.m_bPaused");
618    }
619}