Skip to main content

tf_demo_parser/demo/packet/
stringtable.rs

1use bitbuffer::{BitRead, LittleEndian};
2#[cfg(feature = "write")]
3use bitbuffer::{BitWrite, BitWriteStream};
4use serde::{Deserialize, Serialize};
5use std::fmt;
6
7use crate::demo::data::DemoTick;
8use crate::demo::message::stringtable::StringTableMeta;
9#[cfg(feature = "write")]
10use crate::demo::parser::Encode;
11use crate::{Parse, ParseError, ParserState, ReadResult, Result, Stream};
12use std::borrow::{Borrow, Cow};
13use std::cmp::min;
14
15#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
16#[derive(BitRead, Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
17#[cfg_attr(feature = "write", derive(BitWrite))]
18pub struct FixedUserDataSize {
19    #[size = 12]
20    pub size: u16,
21    #[size = 4]
22    pub bits: u8,
23}
24
25#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
26#[derive(Debug, Serialize, Deserialize, Clone)]
27#[serde(bound(deserialize = "'a: 'static"))]
28pub struct StringTable<'a> {
29    pub name: Cow<'a, str>,
30    pub entries: Vec<(u16, StringTableEntry<'a>)>,
31    pub max_entries: u16,
32    pub fixed_user_data_size: Option<FixedUserDataSize>,
33    pub client_entries: Option<Vec<StringTableEntry<'a>>>,
34    pub compressed: bool,
35}
36
37impl PartialEq for StringTable<'_> {
38    fn eq(&self, other: &Self) -> bool {
39        // ignore `compresses` until we encode compressed
40        self.name.eq(&other.name)
41            && (self.entries.eq(&other.entries))
42            && (self.max_entries.eq(&other.max_entries))
43            && (self.fixed_user_data_size.eq(&other.fixed_user_data_size))
44            && (self.client_entries.eq(&other.client_entries))
45    }
46}
47
48impl StringTable<'_> {
49    pub fn get_table_meta(&self) -> StringTableMeta {
50        StringTableMeta {
51            fixed_userdata_size: self.fixed_user_data_size,
52            max_entries: self.max_entries,
53        }
54    }
55}
56
57impl<'a> BitRead<'a, LittleEndian> for StringTable<'a> {
58    fn read(stream: &mut Stream<'a>) -> ReadResult<Self> {
59        let name = stream.read()?;
60        let entry_count = stream.read_int(16)?;
61        let mut entries = Vec::with_capacity(min(entry_count, 128) as usize);
62
63        for index in 0..entry_count {
64            entries.push((index, stream.read()?))
65        }
66
67        let client_entries = if stream.read_bool()? {
68            let count = stream.read_int(16)?;
69            Some(stream.read_sized(count)?)
70        } else {
71            None
72        };
73
74        Ok(StringTable {
75            name,
76            entries,
77            max_entries: entry_count,
78            fixed_user_data_size: None,
79            client_entries,
80            compressed: false,
81        })
82    }
83}
84
85#[cfg(feature = "write")]
86impl BitWrite<LittleEndian> for StringTable<'_> {
87    fn write(&self, stream: &mut BitWriteStream<LittleEndian>) -> ReadResult<()> {
88        self.name.as_ref().write(stream)?;
89        (self.entries.len() as u16).write(stream)?;
90        for (_, entry) in self.entries.iter() {
91            entry.write(stream)?;
92        }
93
94        self.client_entries.is_some().write(stream)?;
95        if let Some(client_entries) = self.client_entries.as_ref() {
96            (client_entries.len() as u16).write(stream)?;
97            client_entries.write(stream)?;
98        }
99
100        Ok(())
101    }
102}
103
104#[test]
105#[cfg(feature = "write")]
106fn test_string_table_roundtrip() {
107    crate::test_roundtrip_write(StringTable {
108        name: "foo".into(),
109        entries: vec![],
110        max_entries: 0,
111        fixed_user_data_size: None,
112        client_entries: None,
113        compressed: false,
114    });
115    crate::test_roundtrip_write(StringTable {
116        name: "foo".into(),
117        entries: vec![(
118            0,
119            StringTableEntry {
120                text: Some("bar".into()),
121                extra_data: None,
122            },
123        )],
124        max_entries: 1,
125        fixed_user_data_size: None,
126        client_entries: None,
127        compressed: false,
128    });
129    crate::test_roundtrip_write(StringTable {
130        name: "foo".into(),
131        entries: vec![
132            (
133                0,
134                StringTableEntry {
135                    text: Some("bar".into()),
136                    extra_data: None,
137                },
138            ),
139            (
140                1,
141                StringTableEntry {
142                    text: Some("asd".into()),
143                    extra_data: None,
144                },
145            ),
146        ],
147        max_entries: 2,
148        fixed_user_data_size: None,
149        client_entries: Some(vec![StringTableEntry {
150            text: Some("client".into()),
151            extra_data: None,
152        }]),
153        compressed: false,
154    });
155}
156
157#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
158#[derive(BitRead, Clone, Debug, PartialEq, Serialize, Deserialize)]
159#[cfg_attr(feature = "write", derive(BitWrite))]
160#[endianness = "LittleEndian"]
161#[serde(bound(deserialize = "'a: 'static"))]
162pub struct ExtraData<'a> {
163    pub byte_len: u16,
164    #[size = "(byte_len as usize).saturating_mul(8)"]
165    pub data: Stream<'a>,
166}
167
168impl<'a> ExtraData<'a> {
169    pub fn new(data: Stream<'a>) -> Self {
170        let byte_len = (data.bit_len() / 8) as u16;
171        ExtraData { byte_len, data }
172    }
173
174    pub fn to_owned(&self) -> ExtraData<'static> {
175        ExtraData {
176            byte_len: self.byte_len,
177            data: self.data.to_owned(),
178        }
179    }
180}
181
182#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
183#[derive(Clone, Default, PartialEq, Serialize, Deserialize)]
184#[serde(bound(deserialize = "'a: 'static"))]
185pub struct StringTableEntry<'a> {
186    pub text: Option<Cow<'a, str>>,
187    pub extra_data: Option<ExtraData<'a>>,
188}
189
190impl StringTableEntry<'_> {
191    pub fn text(&self) -> &str {
192        self.text
193            .as_ref()
194            .map(|text| text.borrow())
195            .unwrap_or_default()
196    }
197
198    pub fn to_owned(&self) -> StringTableEntry<'static> {
199        StringTableEntry {
200            text: self.text.as_deref().map(|text| Cow::Owned(text.into())),
201            extra_data: self.extra_data.as_ref().map(|data| data.to_owned()),
202        }
203    }
204}
205
206impl<'a> BitRead<'a, LittleEndian> for StringTableEntry<'a> {
207    fn read(stream: &mut Stream<'a>) -> ReadResult<Self> {
208        Ok(StringTableEntry {
209            text: Some(stream.read()?),
210            extra_data: stream.read()?,
211        })
212    }
213}
214
215#[cfg(feature = "write")]
216impl BitWrite<LittleEndian> for StringTableEntry<'_> {
217    fn write(&self, stream: &mut BitWriteStream<LittleEndian>) -> ReadResult<()> {
218        self.text.as_deref().unwrap_or_default().write(stream)?;
219        self.extra_data.is_some().write(stream)?;
220        if let Some(extra_data) = self.extra_data.as_ref() {
221            extra_data.write(stream)?;
222        }
223        Ok(())
224    }
225}
226
227impl fmt::Debug for StringTableEntry<'_> {
228    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229        match &self.extra_data {
230            None => write!(f, "StringTableEntry {{ text: \"{}\" }}", self.text()),
231            Some(extra_data) => write!(
232                f,
233                "StringTableEntry{{ text: \"{}\" extra_data: {} bytes }}",
234                self.text(),
235                extra_data.byte_len
236            ),
237        }
238    }
239}
240
241#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
242#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
243#[serde(bound(deserialize = "'a: 'static"))]
244pub struct StringTablePacket<'a> {
245    pub tick: DemoTick,
246    pub tables: Vec<StringTable<'a>>,
247}
248
249impl<'a> Parse<'a> for StringTablePacket<'a> {
250    fn parse(stream: &mut Stream<'a>, _state: &ParserState) -> Result<Self> {
251        let tick = stream.read()?;
252        let length: usize = stream.read_int(32)?;
253        let mut packet_data = stream.read_bits(length.saturating_mul(8))?;
254        let count: usize = packet_data.read_int(8)?;
255        let tables = packet_data.read_sized(count)?;
256
257        if packet_data.bits_left() > 7 {
258            Err(ParseError::DataRemaining(packet_data.bits_left()))
259        } else {
260            Ok(StringTablePacket { tick, tables })
261        }
262    }
263}
264
265#[cfg(feature = "write")]
266impl Encode for StringTablePacket<'_> {
267    fn encode(
268        &self,
269        stream: &mut BitWriteStream<LittleEndian>,
270        _state: &ParserState,
271    ) -> Result<()> {
272        self.tick.write(stream)?;
273        stream.reserve_byte_length(32, |stream| {
274            (self.tables.len() as u8).write(stream)?;
275            self.tables.write(stream)?;
276
277            Ok(())
278        })
279    }
280}
281
282#[test]
283#[cfg(feature = "write")]
284fn test_string_table_packet_roundtrip() {
285    let state = ParserState::new(24, |_| false, false);
286    crate::test_roundtrip_encode(
287        StringTablePacket {
288            tick: 1.into(),
289            tables: vec![],
290        },
291        &state,
292    );
293    crate::test_roundtrip_encode(
294        StringTablePacket {
295            tick: 1.into(),
296            tables: vec![StringTable {
297                name: "table1".into(),
298                entries: vec![],
299                max_entries: 0,
300                fixed_user_data_size: None,
301                client_entries: None,
302                compressed: false,
303            }],
304        },
305        &state,
306    );
307    crate::test_roundtrip_encode(
308        StringTablePacket {
309            tick: 1.into(),
310            tables: vec![
311                StringTable {
312                    name: "table1".into(),
313                    entries: vec![(
314                        0,
315                        StringTableEntry {
316                            text: Some("bar".into()),
317                            extra_data: None,
318                        },
319                    )],
320                    max_entries: 1,
321                    fixed_user_data_size: None,
322                    client_entries: None,
323                    compressed: false,
324                },
325                StringTable {
326                    name: "table2".into(),
327                    entries: vec![
328                        (
329                            0,
330                            StringTableEntry {
331                                text: Some("bar".into()),
332                                extra_data: None,
333                            },
334                        ),
335                        (
336                            1,
337                            StringTableEntry {
338                                text: Some("asd".into()),
339                                extra_data: None,
340                            },
341                        ),
342                    ],
343                    max_entries: 2,
344                    fixed_user_data_size: None,
345                    client_entries: Some(vec![StringTableEntry {
346                        text: Some("client".into()),
347                        extra_data: None,
348                    }]),
349                    compressed: false,
350                },
351            ],
352        },
353        &state,
354    );
355}