1use bitbuffer::{
2 BitError, BitReadBuffer, BitReadStream, BitWrite, BitWriteSized, BitWriteStream, LittleEndian,
3};
4use num_traits::{PrimInt, Unsigned};
5use serde::{Deserialize, Serialize};
6use snap::raw::{decompress_len, Decoder};
7
8use crate::demo::lzss::decompress;
9use crate::demo::packet::stringtable::{
10 ExtraData, FixedUserDataSize, StringTable, StringTableEntry,
11};
12use crate::demo::parser::{Encode, ParseBitSkip};
13use crate::{Parse, ParseError, ParserState, ReadResult, Result, Stream};
14use std::borrow::Cow;
15use std::cmp::min;
16
17#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
18#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
19#[serde(bound(deserialize = "'a: 'static"))]
20pub struct CreateStringTableMessage<'a> {
21 pub table: StringTable<'a>,
22}
23
24#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct StringTableMeta {
27 pub max_entries: u16,
28 pub fixed_userdata_size: Option<FixedUserDataSize>,
29}
30
31impl From<&StringTable<'_>> for StringTableMeta {
32 fn from(table: &StringTable) -> Self {
33 StringTableMeta {
34 max_entries: table.max_entries,
35 fixed_userdata_size: table.fixed_user_data_size,
36 }
37 }
38}
39
40impl<'a> Parse<'a> for CreateStringTableMessage<'a> {
41 fn parse(stream: &mut Stream<'a>, state: &ParserState) -> Result<Self> {
42 let name = stream.read()?;
43 let max_entries: u16 = stream.read()?;
44 let encode_bits = log_base2(max_entries);
45 let entity_count: u16 = stream.read_sized(encode_bits as usize + 1)?;
46 let length = if state.protocol_version > 23 {
47 read_var_int(stream)?
48 } else {
49 stream.read_sized(20)?
50 };
51
52 let fixed_userdata_size = stream.read()?;
53
54 let compressed = stream.read()?;
55
56 let mut table_data = stream.read_bits(length as usize)?;
57
58 if compressed {
59 let decompressed_size: u32 = table_data.read()?;
60 let compressed_size: u32 = table_data.read()?;
61
62 if !(4..=10 * 1024 * 1024).contains(&compressed_size) {
63 return Err(ParseError::InvalidDemo(
64 "Invalid compressed string table size",
65 ));
66 }
67
68 if decompressed_size > 100 * 1024 * 1024 {
69 return Err(ParseError::InvalidDemo(
70 "Invalid decompressed string table size",
71 ));
72 }
73
74 let magic = table_data.read_string(Some(4))?;
75
76 match magic.as_ref() {
77 "SNAP" => {
78 let compressed_data = table_data.read_bytes(compressed_size as usize - 4)?;
79
80 let mut decoder = Decoder::new();
81
82 let decompressed_size_from_header = decompress_len(&compressed_data)?;
83
84 if decompressed_size_from_header != decompressed_size as usize {
85 return Err(ParseError::UnexpectedDecompressedSize {
86 expected: decompressed_size,
87 size: decompressed_size_from_header as u32,
88 });
89 }
90
91 let mut decompressed_data = vec![0; decompressed_size_from_header];
92 decoder
93 .decompress(&compressed_data, &mut decompressed_data)
94 .map_err(ParseError::from)?;
95
96 let buffer = BitReadBuffer::new_owned(decompressed_data, LittleEndian);
97 table_data = BitReadStream::new(buffer);
98 }
99 "LZSS" => {
100 let compressed_data = table_data.read_bytes(compressed_size as usize - 4)?;
101 let mut decompressed_data = Vec::with_capacity(decompressed_size as usize);
102 decompress(&compressed_data, &mut decompressed_data);
103
104 if decompressed_data.len() != decompressed_size as usize {
105 return Err(ParseError::UnexpectedDecompressedSize {
106 expected: decompressed_size,
107 size: decompressed_data.len() as u32,
108 });
109 }
110
111 let buffer = BitReadBuffer::new_owned(decompressed_data, LittleEndian);
112 table_data = BitReadStream::new(buffer);
113 }
114 _ => {
115 return Err(ParseError::UnexpectedCompressionType(magic.into_owned()));
116 }
117 }
118 }
119
120 let table_meta = StringTableMeta {
121 max_entries,
122 fixed_userdata_size,
123 };
124
125 let entries = parse_string_table_update(&mut table_data, &table_meta, entity_count)?;
126
127 let table = StringTable {
128 entries,
129 max_entries,
130 fixed_user_data_size: fixed_userdata_size,
131 client_entries: None,
132 compressed,
133 name,
134 };
135 Ok(CreateStringTableMessage { table })
136 }
137}
138
139impl<'a> ParseBitSkip<'a> for CreateStringTableMessage<'a> {
140 fn parse_skip(stream: &mut Stream<'a>, state: &ParserState) -> Result<()> {
141 let _: String = stream.read()?;
142 let max_entries: u16 = stream.read()?;
143 let encode_bits = log_base2(max_entries);
144 let _: u16 = stream.read_sized(encode_bits as usize + 1)?;
145 let length = if state.protocol_version > 23 {
146 read_var_int(stream)?
147 } else {
148 stream.read_sized(20)?
149 };
150
151 let _: Option<FixedUserDataSize> = stream.read()?;
152
153 let _: bool = stream.read()?;
154
155 stream.skip_bits(length as usize).map_err(ParseError::from)
156 }
157}
158
159impl Encode for CreateStringTableMessage<'_> {
160 fn encode(
161 &self,
162 stream: &mut BitWriteStream<LittleEndian>,
163 _state: &ParserState,
164 ) -> Result<()> {
165 let table = &self.table;
166 table.name.write(stream)?;
167 table.max_entries.write(stream)?;
168 let encode_bits = log_base2(table.max_entries) as usize;
169 (table.entries.len() as u16).write_sized(stream, encode_bits + 1)?;
170
171 stream.reserve_int::<ParseError, _>(40, |stream| {
172 table.fixed_user_data_size.is_some().write(stream)?;
173 if let Some(fixed_size) = table.fixed_user_data_size {
174 fixed_size.write(stream)?;
175 }
176
177 false.write(stream)?;
179
180 let start = stream.bit_len();
181
182 let table_meta = table.get_table_meta();
183
184 write_string_table_update(&table.entries, stream, &table_meta)?;
185
186 let end = stream.bit_len();
187 Ok(encode_var_int_fixed((end - start) as u32))
188 })?;
189
190 Ok(())
191 }
192}
193
194#[test]
195fn test_create_string_table_roundtrip() {
196 let state = ParserState::new(24, |_| false, false);
197 crate::test_roundtrip_encode(
198 CreateStringTableMessage {
199 table: StringTable {
200 name: "table1".into(),
201 entries: vec![],
202 max_entries: 16,
203 fixed_user_data_size: None,
204 client_entries: None,
205 compressed: false,
206 },
207 },
208 &state,
209 );
210 crate::test_roundtrip_encode(
211 CreateStringTableMessage {
212 table: StringTable {
213 name: "table1".into(),
214 entries: vec![
215 (
216 0,
217 StringTableEntry {
218 text: Some("foo".into()),
219 extra_data: None,
220 },
221 ),
222 (
223 1,
224 StringTableEntry {
225 text: Some("bar".into()),
226 extra_data: None,
227 },
228 ),
229 ],
230 max_entries: 16,
231 fixed_user_data_size: Some(FixedUserDataSize { size: 12, bits: 4 }),
232 client_entries: None,
233 compressed: false,
234 },
235 },
236 &state,
237 );
238}
239
240#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
241#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
242#[serde(bound(deserialize = "'a: 'static"))]
243pub struct UpdateStringTableMessage<'a> {
244 pub entries: Vec<(u16, StringTableEntry<'a>)>,
245 pub table_id: u8,
246}
247
248impl<'a> Parse<'a> for UpdateStringTableMessage<'a> {
249 fn parse(stream: &mut Stream<'a>, state: &ParserState) -> Result<Self> {
250 let table_id = stream.read_sized(5)?;
251
252 let changed: u16 = if stream.read()? { stream.read()? } else { 1 };
253 let length: u32 = stream.read_int(20)?;
254
255 let mut data = stream.read_bits(length as usize)?;
256
257 let entries = match state.string_tables.get(table_id as usize) {
258 Some(table) => parse_string_table_update(&mut data, table, changed),
259 None => return Err(ParseError::StringTableNotFound(table_id)),
260 }?;
261
262 Ok(UpdateStringTableMessage { entries, table_id })
263 }
264}
265
266impl<'a> ParseBitSkip<'a> for UpdateStringTableMessage<'a> {
267 fn parse_skip(stream: &mut Stream<'a>, _state: &ParserState) -> Result<()> {
268 let _: u8 = stream.read_sized(5)?;
269
270 let _: u16 = if stream.read()? { stream.read()? } else { 1 };
271 let length: u32 = stream.read_int(20)?;
272 stream.skip_bits(length as usize).map_err(ParseError::from)
273 }
274}
275
276impl Encode for UpdateStringTableMessage<'_> {
277 fn encode(&self, stream: &mut BitWriteStream<LittleEndian>, state: &ParserState) -> Result<()> {
278 self.table_id.write_sized(stream, 5)?;
279 if self.entries.len() == 1 {
280 false.write(stream)?;
281 } else {
282 true.write(stream)?;
283 (self.entries.len() as u16).write(stream)?;
284 }
285
286 match state.string_tables.get(self.table_id as usize) {
287 Some(table) => Ok(stream.reserve_length(20, |stream| {
288 write_string_table_update(&self.entries, stream, table)
289 })?),
290 None => Err(ParseError::StringTableNotFound(self.table_id)),
291 }
292 }
293}
294
295#[test]
296fn test_update_string_table_roundtrip() {
297 let mut state = ParserState::new(24, |_| false, false);
298 state.string_tables = vec![StringTableMeta {
299 max_entries: 16,
300 fixed_userdata_size: None,
301 }];
302 crate::test_roundtrip_encode(
303 UpdateStringTableMessage {
304 entries: vec![],
305 table_id: 0,
306 },
307 &state,
308 );
309 crate::test_roundtrip_encode(
310 UpdateStringTableMessage {
311 entries: vec![(
312 2,
313 StringTableEntry {
314 text: Some("foo".into()),
315 extra_data: None,
316 },
317 )],
318 table_id: 0,
319 },
320 &state,
321 );
322 crate::test_roundtrip_encode(
323 UpdateStringTableMessage {
324 entries: vec![
325 (
326 2,
327 StringTableEntry {
328 text: Some("foo".into()),
329 extra_data: None,
330 },
331 ),
332 (
333 3,
334 StringTableEntry {
335 text: Some("bar".into()),
336 extra_data: None,
337 },
338 ),
339 ],
340 table_id: 0,
341 },
342 &state,
343 );
344}
345
346struct TableEntries<'a> {
347 entries: Vec<(u16, StringTableEntry<'a>)>,
348 history: Vec<u16>,
349}
350
351impl<'a> TableEntries<'a> {
352 pub fn new(count: usize) -> Self {
353 TableEntries {
354 entries: Vec::with_capacity(min(count, 128)),
355 history: Vec::with_capacity(32),
356 }
357 }
358
359 pub fn push(&mut self, entry: (u16, StringTableEntry<'a>)) {
360 if self.history.len() > 31 {
361 self.history.remove(0);
362 }
363 let entry_index = self.entries.len();
364 self.entries.push(entry);
365 self.history.push(entry_index as u16);
366 }
367
368 pub fn get_history(&self, index: usize) -> Option<&StringTableEntry<'a>> {
369 self.history
370 .get(index)
371 .and_then(|entry_index| self.entries.get(*entry_index as usize))
372 .map(|entry| &entry.1)
373 }
374
375 pub fn into_entries(self) -> Vec<(u16, StringTableEntry<'a>)> {
376 self.entries
377 }
378
379 pub fn find_best_history(&self, text: &str) -> Option<(usize, usize)> {
380 let mut best_index = None;
381 let mut best_count = 0;
382 for (history_index, entry_index) in self.history.iter().enumerate() {
383 if let Some((_, entry)) = self.entries.get(*entry_index as usize) {
384 let similar = min(31, count_similar_characters(entry.text(), text));
385 if similar >= 3 && similar > best_count {
386 best_index = Some(history_index);
387 best_count = similar;
388 }
389 }
390 }
391
392 best_index.map(|index| (index, best_count))
393 }
394}
395
396fn count_similar_characters(a: &str, b: &str) -> usize {
397 for (i, (a, b)) in a.bytes().zip(b.bytes()).enumerate() {
398 if a != b {
399 return i;
400 }
401 }
402 min(a.len(), b.len())
403}
404
405pub fn parse_string_table_update<'a>(
406 stream: &mut Stream<'a>,
407 table_meta: &StringTableMeta,
408 entry_count: u16,
409) -> ReadResult<Vec<(u16, StringTableEntry<'a>)>> {
410 let entry_bits = log_base2(table_meta.max_entries);
411 let mut entries = TableEntries::new(entry_count as usize);
412
413 let mut last_entry: i16 = -1;
414
415 for _ in 0..entry_count {
416 let index = if stream.read()? {
417 last_entry.saturating_add(1) as u16
418 } else {
419 stream.read_sized(entry_bits as usize)?
420 };
421
422 last_entry = index as i16;
423
424 let entry = read_table_entry(stream, table_meta, &entries)?;
425 entries.push((index, entry));
426 }
427
428 Ok(entries.into_entries())
429}
430
431pub fn write_string_table_update(
432 entries: &[(u16, StringTableEntry)],
433 stream: &mut BitWriteStream<LittleEndian>,
434 table_meta: &StringTableMeta,
435) -> ReadResult<()> {
436 let entry_bits = log_base2(table_meta.max_entries);
437
438 let mut last_entry: i16 = -1;
439 let mut history = TableEntries::new(entries.len());
440
441 for (index, entry) in entries.iter() {
442 let index = *index as i16;
443 if index == (last_entry + 1) {
444 true.write(stream)?;
445 } else {
446 false.write(stream)?;
447 index.write_sized(stream, entry_bits as usize)?;
448 }
449 last_entry = index;
450
451 write_table_entry(entry, stream, table_meta, &history)?;
452 history.push((index as u16, entry.clone()));
453 }
454
455 Ok(())
456}
457
458#[test]
459fn test_table_update_roundtrip() {
460 fn entry_roundtrip(
461 entries: Vec<(u16, StringTableEntry)>,
462 max_entries: u16,
463 fixed_bits: Option<u8>,
464 ) {
465 let table_meta = StringTableMeta {
466 max_entries,
467 fixed_userdata_size: fixed_bits.map(|bits| FixedUserDataSize { size: 0, bits }),
468 };
469 let mut data = Vec::new();
470 let pos = {
471 let mut write = BitWriteStream::new(&mut data, LittleEndian);
472 write_string_table_update(&entries, &mut write, &table_meta).unwrap();
473 write.bit_len()
474 };
475 let mut read = BitReadStream::new(BitReadBuffer::new(&data, LittleEndian));
476 assert_eq!(
477 entries,
478 parse_string_table_update(&mut read, &table_meta, entries.len() as u16).unwrap()
479 );
480 assert_eq!(pos, read.pos());
481 }
482 entry_roundtrip(
483 vec![(
484 3,
485 StringTableEntry {
486 text: None,
487 extra_data: None,
488 },
489 )],
490 8,
491 None,
492 );
493 entry_roundtrip(
494 vec![
495 (
496 0,
497 StringTableEntry {
498 text: Some("bar".into()),
499 extra_data: None,
500 },
501 ),
502 (
503 1,
504 StringTableEntry {
505 text: Some("foo".into()),
506 extra_data: None,
507 },
508 ),
509 (
510 5,
511 StringTableEntry {
512 text: Some("asd".into()),
513 extra_data: None,
514 },
515 ),
516 ],
517 16,
518 None,
519 );
520 entry_roundtrip(
521 vec![
522 (
523 1,
524 StringTableEntry {
525 text: Some("foo".into()),
526 extra_data: None,
527 },
528 ),
529 (
530 2,
531 StringTableEntry {
532 text: Some("asd".into()),
533 extra_data: None,
534 },
535 ),
536 ],
537 16,
538 None,
539 );
540 entry_roundtrip(
541 vec![(
542 1,
543 StringTableEntry {
544 text: Some("foo".into()),
545 extra_data: None,
546 },
547 )],
548 16,
549 None,
550 );
551}
552
553fn read_table_entry<'a>(
554 stream: &mut Stream<'a>,
555 table_meta: &StringTableMeta,
556 history: &TableEntries,
557) -> ReadResult<StringTableEntry<'a>> {
558 let text = if stream.read()? {
559 if stream.read()? {
561 let index: u16 = stream.read_sized(5)?;
563 let bytes_to_copy: u32 = stream.read_sized(5)?;
564 let rest_of_string: Cow<str> = stream.read()?;
565
566 Some(
567 match history
568 .get_history(index as usize)
569 .and_then(|entry| entry.text.as_ref())
570 {
571 Some(text) => Cow::Owned(String::from_utf8({
572 text.bytes()
573 .take(bytes_to_copy as usize)
574 .chain(rest_of_string.bytes())
575 .collect()
576 })?),
577 None => rest_of_string, },
579 )
580 } else {
581 Some(stream.read()?)
582 }
583 } else {
584 None
585 };
586
587 let extra_data = if stream.read()? {
588 Some(match table_meta.fixed_userdata_size {
589 Some(size) => stream.read_bits(size.bits as usize)?,
590 None => {
591 let bytes: u16 = stream.read_sized(14)?;
592 stream.read_bits(bytes as usize * 8)?
593 }
594 })
595 } else {
596 None
597 }
598 .map(ExtraData::new);
599
600 Ok(StringTableEntry { text, extra_data })
601}
602
603fn write_table_entry(
604 entry: &StringTableEntry,
605 stream: &mut BitWriteStream<LittleEndian>,
606 table_meta: &StringTableMeta,
607 history: &TableEntries,
608) -> ReadResult<()> {
609 entry.text.is_some().write(stream)?;
610 if let Some(text) = entry.text.as_deref() {
611 let history_item = history.find_best_history(text);
612 history_item.is_some().write(stream)?;
613 if let Some((history_index, history_count)) = history_item {
614 history_index.write_sized(stream, 5)?;
615 history_count.write_sized(stream, 5)?;
616 let diff_bytes =
617 text.as_bytes()
618 .get(history_count..)
619 .ok_or(BitError::IndexOutOfBounds {
620 pos: history_count,
621 size: text.len(),
622 })?;
623 stream.write_bytes(diff_bytes)?;
624 0u8.write(stream)?; } else {
626 text.write(stream)?;
627 }
628 }
629
630 entry.extra_data.is_some().write(stream)?;
631 if let Some(extra_data) = entry.extra_data.as_ref() {
632 match table_meta.fixed_userdata_size {
633 Some(size) => {
634 extra_data.data.write_sized(stream, size.bits as usize)?;
635 }
636 None => {
637 extra_data.byte_len.write_sized(stream, 14)?;
638 extra_data
639 .data
640 .write_sized(stream, extra_data.byte_len as usize * 8)?;
641 }
642 }
643 }
644
645 Ok(())
646}
647
648#[test]
649fn test_table_entry_roundtrip() {
650 fn entry_roundtrip(entry: StringTableEntry, fixed_bits: Option<u8>) {
651 let table_meta = StringTableMeta {
652 max_entries: 0,
653 fixed_userdata_size: fixed_bits.map(|bits| FixedUserDataSize { size: 0, bits }),
654 };
655 let mut data = Vec::new();
656 let pos = {
657 let history = TableEntries::new(1);
658 let mut write = BitWriteStream::new(&mut data, LittleEndian);
659 write_table_entry(&entry, &mut write, &table_meta, &history).unwrap();
660 write.bit_len()
661 };
662 let mut read = BitReadStream::new(BitReadBuffer::new(&data, LittleEndian));
663 assert_eq!(
664 entry,
665 read_table_entry(&mut read, &table_meta, &TableEntries::new(0)).unwrap()
666 );
667 assert_eq!(pos, read.pos());
668 }
669 entry_roundtrip(
670 StringTableEntry {
671 text: None,
672 extra_data: None,
673 },
674 None,
675 );
676 entry_roundtrip(
677 StringTableEntry {
678 text: Some("foo".into()),
679 extra_data: None,
680 },
681 None,
682 );
683 entry_roundtrip(
684 StringTableEntry {
685 text: None,
686 extra_data: Some(ExtraData::new(BitReadStream::new(
687 BitReadBuffer::new_owned(vec![0x55], LittleEndian),
688 ))),
689 },
690 None,
691 );
692 entry_roundtrip(
693 StringTableEntry {
694 text: None,
695 extra_data: Some(ExtraData::new(BitReadStream::new(
696 BitReadBuffer::new_owned(vec![0x55; 128], LittleEndian),
697 ))),
698 },
699 None,
700 );
701 entry_roundtrip(
702 StringTableEntry {
703 text: None,
704 extra_data: Some(ExtraData::new(BitReadStream::new(
705 BitReadBuffer::new_owned(vec![0x55; 4], LittleEndian),
706 ))),
707 },
708 Some(4 * 8),
709 );
710}
711
712pub fn read_var_int(stream: &mut Stream) -> ReadResult<u32> {
713 let mut result: u32 = 0;
714 for i in (0..35u32).step_by(7) {
715 let byte: u8 = stream.read()?;
716 result |= (byte as u32 & 0x7F) << i;
717
718 if (byte >> 7) == 0 {
719 break;
720 }
721 }
722 Ok(result)
723}
724
725pub fn write_var_int(mut int: u32, stream: &mut BitWriteStream<LittleEndian>) -> ReadResult<()> {
726 while int > 0x7F {
727 let byte: u8 = int as u8 & 0x7F;
728 (byte | 0x80).write(stream)?;
729 int >>= 7;
730 }
731 (int as u8).write(stream)
732}
733
734pub fn encode_var_int_fixed(mut int: u32) -> u64 {
737 let mut out = 0;
738 for i in 0..4 {
739 let byte: u8 = int as u8 & 0x7F;
740 out |= ((byte | 0x80) as u64) << (i * 8);
741 int >>= 7;
742 }
743 out |= (int as u64) << 32;
744 out
745}
746
747#[test]
748fn test_var_int_roundtrip() {
749 fn var_int_roundtrip(int: u32) {
750 let mut data = Vec::new();
751 let pos = {
752 let mut write = BitWriteStream::new(&mut data, LittleEndian);
753 write_var_int(int, &mut write).unwrap();
754 write.bit_len()
755 };
756 let mut read = BitReadStream::new(BitReadBuffer::new(&data, LittleEndian));
757 assert_eq!(int, read_var_int(&mut read).unwrap());
758 assert_eq!(pos, read.pos());
759 }
760 var_int_roundtrip(0);
761 var_int_roundtrip(1);
762 var_int_roundtrip(10);
763 var_int_roundtrip(55);
764 var_int_roundtrip(355);
765 var_int_roundtrip(12354);
766 var_int_roundtrip(123125412);
767}
768
769#[test]
770fn test_var_int_fixed_roundtrip() {
771 fn var_int_roundtrip(int: u32) {
772 let mut data = Vec::new();
773 let pos = {
774 let mut write = BitWriteStream::new(&mut data, LittleEndian);
775 let encoded = encode_var_int_fixed(int);
776 encoded.write_sized(&mut write, 40).unwrap();
777 write.bit_len()
778 };
779 assert_eq!(40, pos);
780 let mut read = BitReadStream::new(BitReadBuffer::new(&data, LittleEndian));
781 assert_eq!(int, read_var_int(&mut read).unwrap());
782 assert_eq!(pos, read.pos());
783 }
784 var_int_roundtrip(0);
785 var_int_roundtrip(1);
786 var_int_roundtrip(10);
787 var_int_roundtrip(55);
788 var_int_roundtrip(355);
789 var_int_roundtrip(12354);
790 var_int_roundtrip(123125412);
791}
792
793pub fn log_base2<T: PrimInt + Unsigned>(num: T) -> u32 {
794 (std::mem::size_of::<T>() as u32 * 8 - 1).saturating_sub(num.leading_zeros())
797}