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        let resolve_sym = |i: i32| -> &str { &symbols[i as usize] };
252
253        // Build fields and serializers
254        let mut field_cache: HashMap<i32, Arc<SerializerField>> = HashMap::new();
255        let mut serializer_map: FxHashMap<String, Arc<Serializer>> = FxHashMap::default();
256
257        for serializer_proto in &msg.serializers {
258            let ser_name = resolve_sym(serializer_proto.serializer_name_sym.unwrap_or(0));
259            let mut serializer = Serializer {
260                name: ser_name.to_string(),
261                fields: Vec::with_capacity(serializer_proto.fields_index.len()),
262            };
263
264            for &field_index in &serializer_proto.fields_index {
265                if let Some(cached) = field_cache.get(&field_index) {
266                    serializer.fields.push(cached.clone());
267                    continue;
268                }
269
270                let field_proto = &msg.fields[field_index as usize];
271
272                let var_type = field_proto
273                    .var_type_sym
274                    .map(resolve_sym)
275                    .unwrap_or("")
276                    .to_string();
277                let var_name = field_proto
278                    .var_name_sym
279                    .map(resolve_sym)
280                    .unwrap_or("")
281                    .to_string();
282                let send_node = field_proto.send_node_sym.map(resolve_sym).map(String::from);
283                let var_encoder = field_proto
284                    .var_encoder_sym
285                    .map(resolve_sym)
286                    .map(String::from);
287                let field_serializer_name = field_proto
288                    .field_serializer_name_sym
289                    .map(resolve_sym)
290                    .map(String::from);
291
292                let field_type = parse_type(&var_type);
293                let metadata = field_decoder::get_field_metadata(
294                    &var_type,
295                    &var_name,
296                    field_proto.bit_count,
297                    field_proto.low_value,
298                    field_proto.high_value,
299                    field_proto.encode_flags,
300                    var_encoder.as_deref(),
301                    field_serializer_name.is_some(),
302                );
303
304                // Resolve field serializer
305                let field_serializer = match &metadata {
306                    fm if fm.is_fixed_array() => {
307                        let length = fm.fixed_array_length().unwrap_or(0);
308                        // Build a pseudo-serializer containing `length` copies of the inner field
309                        let inner_ser = field_serializer_name
310                            .as_deref()
311                            .and_then(|n| serializer_map.get(n).cloned());
312
313                        let inner_field = SerializerField {
314                            var_type: var_type.clone(),
315                            var_name: var_name.clone(),
316                            bit_count: field_proto.bit_count,
317                            low_value: field_proto.low_value,
318                            high_value: field_proto.high_value,
319                            encode_flags: field_proto.encode_flags,
320                            field_serializer_name: field_serializer_name.clone(),
321                            send_node: send_node.clone(),
322                            var_encoder: var_encoder.clone(),
323                            field_serializer: inner_ser,
324                            field_type: field_type.clone(),
325                            metadata: metadata.clone(),
326                        };
327                        let inner_rc = Arc::new(inner_field);
328                        let mut fields = Vec::with_capacity(length);
329                        fields.resize(length, inner_rc);
330                        Some(Arc::new(Serializer {
331                            name: String::new(),
332                            fields,
333                        }))
334                    }
335                    fm if fm.is_dynamic_array() => {
336                        // For dynamic arrays of serializers, build a single-element serializer
337                        if fm.is_dynamic_serializer_array() {
338                            let inner_ser = field_serializer_name
339                                .as_deref()
340                                .and_then(|n| serializer_map.get(n).cloned());
341                            let inner = Arc::new(SerializerField {
342                                var_type: String::new(),
343                                var_name: String::new(),
344                                bit_count: None,
345                                low_value: None,
346                                high_value: None,
347                                encode_flags: None,
348                                field_serializer_name: None,
349                                send_node: None,
350                                var_encoder: None,
351                                field_serializer: inner_ser,
352                                field_type: parse_type(""),
353                                metadata: FieldMetadata::default(),
354                            });
355                            Some(Arc::new(Serializer {
356                                name: String::new(),
357                                fields: vec![inner],
358                            }))
359                        } else {
360                            // Dynamic array of primitives: single-element serializer with inner decoder
361                            let inner_metadata = fm.dynamic_array_inner_metadata();
362                            let inner = Arc::new(SerializerField {
363                                var_type: String::new(),
364                                var_name: String::new(),
365                                bit_count: None,
366                                low_value: None,
367                                high_value: None,
368                                encode_flags: None,
369                                field_serializer_name: None,
370                                send_node: None,
371                                var_encoder: None,
372                                field_serializer: None,
373                                field_type: parse_type(""),
374                                metadata: inner_metadata,
375                            });
376                            Some(Arc::new(Serializer {
377                                name: String::new(),
378                                fields: vec![inner],
379                            }))
380                        }
381                    }
382                    fm if fm.is_pointer() => field_serializer_name
383                        .as_deref()
384                        .and_then(|n| serializer_map.get(n).cloned()),
385                    _ => field_serializer_name
386                        .as_deref()
387                        .and_then(|n| serializer_map.get(n).cloned()),
388                };
389
390                let field = Arc::new(SerializerField {
391                    var_type,
392                    var_name,
393                    bit_count: field_proto.bit_count,
394                    low_value: field_proto.low_value,
395                    high_value: field_proto.high_value,
396                    encode_flags: field_proto.encode_flags,
397                    field_serializer_name,
398                    send_node,
399                    var_encoder,
400                    field_serializer,
401                    field_type,
402                    metadata,
403                });
404
405                field_cache.insert(field_index, field.clone());
406                serializer.fields.push(field);
407            }
408
409            serializer_map.insert(serializer.name.clone(), Arc::new(serializer));
410        }
411
412        Ok(Self {
413            serializers: serializer_map,
414        })
415    }
416
417    /// Look up a serializer by class network name.
418    pub fn get(&self, name: &str) -> Option<&Serializer> {
419        self.serializers.get(name).map(|arc| arc.as_ref())
420    }
421}
422
423/// Parse a var_type string into a FieldType.
424pub fn parse_type(s: &str) -> FieldType {
425    let s = s.trim();
426
427    // Check for pointer
428    if let Some(stripped) = s.strip_suffix('*') {
429        return FieldType {
430            base_type: stripped.trim().to_string(),
431            pointer: true,
432            generic_type: None,
433            array_length: None,
434            count: None,
435        };
436    }
437
438    // Check for array: type[length]
439    if let Some(bracket_pos) = s.find('[')
440        && s.ends_with(']')
441    {
442        let base = s[..bracket_pos].trim();
443        let len_str = s[bracket_pos + 1..s.len() - 1].trim();
444        let array_length = len_str.parse::<usize>().ok();
445        let count = if array_length.is_none() {
446            Some(len_str.to_string())
447        } else {
448            None
449        };
450        return FieldType {
451            base_type: base.to_string(),
452            pointer: false,
453            generic_type: None,
454            array_length,
455            count,
456        };
457    }
458
459    // Check for generic: Type< InnerType >
460    if let Some(angle_pos) = s.find('<')
461        && let Some(close_pos) = s.rfind('>')
462    {
463        let base = s[..angle_pos].trim();
464        let inner = s[angle_pos + 1..close_pos].trim();
465        return FieldType {
466            base_type: base.to_string(),
467            pointer: false,
468            generic_type: Some(Box::new(parse_type(inner))),
469            array_length: None,
470            count: None,
471        };
472    }
473
474    // Simple type
475    FieldType {
476        base_type: s.to_string(),
477        pointer: false,
478        generic_type: None,
479        array_length: None,
480        count: None,
481    }
482}
483
484#[cfg(test)]
485mod tests {
486    use super::super::field_decoder::{Decoder, FieldMetadata};
487    use super::*;
488
489    // ── parse_type ──
490
491    #[test]
492    fn parse_type_simple() {
493        let ft = parse_type("int32");
494        assert_eq!(ft.base_type, "int32");
495        assert!(!ft.pointer);
496        assert!(ft.generic_type.is_none());
497        assert!(ft.array_length.is_none());
498        assert!(ft.count.is_none());
499    }
500
501    #[test]
502    fn parse_type_pointer() {
503        let ft = parse_type("CBaseEntity*");
504        assert_eq!(ft.base_type, "CBaseEntity");
505        assert!(ft.pointer);
506    }
507
508    #[test]
509    fn parse_type_array_numeric() {
510        let ft = parse_type("int32[4]");
511        assert_eq!(ft.base_type, "int32");
512        assert_eq!(ft.array_length, Some(4));
513        assert!(ft.count.is_none());
514    }
515
516    #[test]
517    fn parse_type_array_symbolic() {
518        let ft = parse_type("int32[MAX_ABILITIES]");
519        assert_eq!(ft.base_type, "int32");
520        assert!(ft.array_length.is_none());
521        assert_eq!(ft.count.as_deref(), Some("MAX_ABILITIES"));
522    }
523
524    #[test]
525    fn parse_type_generic() {
526        let ft = parse_type("CNetworkUtlVectorBase< int32 >");
527        assert_eq!(ft.base_type, "CNetworkUtlVectorBase");
528        let inner = ft.generic_type.as_ref().unwrap();
529        assert_eq!(inner.base_type, "int32");
530    }
531
532    #[test]
533    fn parse_type_generic_nested() {
534        let ft = parse_type("CHandle< CBaseEntity >");
535        assert_eq!(ft.base_type, "CHandle");
536        assert_eq!(ft.generic_type.as_ref().unwrap().base_type, "CBaseEntity");
537    }
538
539    #[test]
540    fn parse_type_whitespace_trimming() {
541        let ft = parse_type("  float32  ");
542        assert_eq!(ft.base_type, "float32");
543    }
544
545    #[test]
546    fn parse_type_empty_string() {
547        let ft = parse_type("");
548        assert_eq!(ft.base_type, "");
549        assert!(!ft.pointer);
550    }
551
552    #[test]
553    fn parse_type_complex_all_fields() {
554        let ft = parse_type("uint32[16]");
555        assert_eq!(ft.base_type, "uint32");
556        assert!(!ft.pointer);
557        assert!(ft.generic_type.is_none());
558        assert_eq!(ft.array_length, Some(16));
559        assert!(ft.count.is_none());
560    }
561
562    // ── Serializer key resolution ──
563
564    fn make_field(name: &str, send_node: Option<&str>) -> Arc<SerializerField> {
565        Arc::new(SerializerField {
566            var_type: String::new(),
567            var_name: name.to_string(),
568            bit_count: None,
569            low_value: None,
570            high_value: None,
571            encode_flags: None,
572            field_serializer_name: None,
573            send_node: send_node.map(String::from),
574            var_encoder: None,
575            field_serializer: None,
576            field_type: parse_type(""),
577            metadata: FieldMetadata {
578                decoder: Decoder::U64,
579                special: None,
580            },
581        })
582    }
583
584    #[test]
585    fn resolve_field_key_found() {
586        let ser = Serializer {
587            name: "test".to_string(),
588            fields: vec![make_field("m_iHealth", None)],
589        };
590        assert!(ser.resolve_field_key("m_iHealth").is_some());
591    }
592
593    #[test]
594    fn resolve_field_key_not_found() {
595        let ser = Serializer {
596            name: "test".to_string(),
597            fields: vec![make_field("m_iHealth", None)],
598        };
599        assert!(ser.resolve_field_key("m_iMana").is_none());
600    }
601
602    #[test]
603    fn field_name_roundtrip() {
604        let ser = Serializer {
605            name: "test".to_string(),
606            fields: vec![make_field("m_iHealth", None), make_field("m_iMana", None)],
607        };
608        let key = ser.resolve_field_key("m_iMana").unwrap();
609        let name = ser.field_name_for_key(key).unwrap();
610        assert_eq!(name, "m_iMana");
611    }
612
613    #[test]
614    fn resolve_with_send_node() {
615        let ser = Serializer {
616            name: "test".to_string(),
617            fields: vec![make_field("m_bPaused", Some("m_pGameRules"))],
618        };
619        let key = ser.resolve_field_key("m_pGameRules.m_bPaused");
620        assert!(key.is_some());
621        let name = ser.field_name_for_key(key.unwrap()).unwrap();
622        assert_eq!(name, "m_pGameRules.m_bPaused");
623    }
624}