Skip to main content

boon/entity/
serializers.rs

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