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 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}