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