1use std::path::Path;
2
3use memmap2::Mmap;
4use prost::Message;
5
6use crate::entity::{
7 ClassInfo, EntityContainer, FieldDecodeContext, SerializerContainer, StringTableContainer,
8};
9use crate::error::{Error, Result};
10use crate::io::{BitReader, ByteReader};
11
12use std::collections::HashMap;
13
14use super::command::{self, CmdHeader, dem, ge, svc};
15
16use boon_proto::proto::{
17 CDemoClassInfo, CDemoFileHeader, CDemoFileInfo, CDemoFullPacket, CDemoPacket, CDemoSendTables,
18 CMsgSource1LegacyGameEvent, CMsgSource1LegacyGameEventList, CitadelUserMessageIds,
19 CsvcMsgCreateStringTable, CsvcMsgPacketEntities, CsvcMsgServerInfo, CsvcMsgUpdateStringTable,
20 CsvcMsgUserMessage, EBaseUserMessages, ECitadelGameEvents,
21};
22
23const MAGIC: &[u8; 8] = b"PBDEMS2\0";
25const HEADER_SIZE: usize = 16;
27
28const DEFAULT_TICK_INTERVAL: f32 = 1.0 / 30.0;
31const DEFAULT_FULL_PACKET_INTERVAL: i32 = 1800;
33
34const BUF_SIZE: usize = 2 * 1024 * 1024;
36
37#[derive(Debug, Clone, serde::Serialize)]
39pub struct MessageInfo {
40 pub index: usize,
42 pub cmd: i32,
44 pub cmd_name: String,
46 pub tick: i32,
48 pub compressed: bool,
50 pub body_size: u32,
52 pub offset: usize,
54}
55
56pub struct Context {
63 pub serializers: SerializerContainer,
65 pub class_info: ClassInfo,
67 pub string_tables: StringTableContainer,
69 pub entities: EntityContainer,
71 pub tick_interval: f32,
73 pub full_packet_interval: i32,
75 pub tick: i32,
77}
78
79#[derive(Debug, Clone, serde::Serialize)]
81pub struct GameEvent {
82 pub tick: i32,
84 pub name: String,
86 pub msg_type: u32,
88 pub keys: Vec<(String, String)>,
90 #[serde(skip)]
92 pub payload: Vec<u8>,
93}
94
95struct EventDescriptor {
96 name: String,
97 field_names: Vec<String>,
98}
99
100fn format_event_key(key: &boon_proto::proto::c_msg_source1_legacy_game_event::KeyT) -> String {
101 if let Some(ref s) = key.val_string {
102 return s.clone();
103 }
104 if let Some(f) = key.val_float {
105 return f.to_string();
106 }
107 if let Some(l) = key.val_long {
108 return l.to_string();
109 }
110 if let Some(s) = key.val_short {
111 return s.to_string();
112 }
113 if let Some(b) = key.val_byte {
114 return b.to_string();
115 }
116 if let Some(b) = key.val_bool {
117 return b.to_string();
118 }
119 if let Some(u) = key.val_uint64 {
120 return u.to_string();
121 }
122 String::new()
123}
124
125enum Storage {
127 Mmap(Mmap),
128 Bytes(Vec<u8>),
129}
130
131impl AsRef<[u8]> for Storage {
132 fn as_ref(&self) -> &[u8] {
133 match self {
134 Storage::Mmap(m) => m,
135 Storage::Bytes(b) => b,
136 }
137 }
138}
139
140pub struct Parser {
142 storage: Storage,
143}
144
145impl Parser {
146 pub fn from_file(path: &Path) -> Result<Self> {
148 let file = std::fs::File::open(path)?;
149 let mmap = unsafe { Mmap::map(&file)? };
154 Ok(Self {
155 storage: Storage::Mmap(mmap),
156 })
157 }
158
159 pub fn from_bytes(bytes: Vec<u8>) -> Self {
164 Self {
165 storage: Storage::Bytes(bytes),
166 }
167 }
168
169 fn data(&self) -> &[u8] {
171 self.storage.as_ref()
172 }
173
174 pub fn verify(&self) -> Result<()> {
177 if self.data().len() < HEADER_SIZE {
178 return Err(Error::Parse {
179 context: "file too small for demo header".into(),
180 });
181 }
182
183 let mut magic = [0u8; 8];
184 magic.copy_from_slice(&self.data()[0..8]);
185 if &magic != MAGIC {
186 return Err(Error::InvalidMagic { got: magic });
187 }
188
189 Ok(())
190 }
191
192 fn read_cmd_header(reader: &mut ByteReader) -> Result<CmdHeader> {
193 let raw_cmd = reader.read_uvarint32()?;
194 let compress_flag = dem::IS_COMPRESSED;
195 let compressed = (raw_cmd & compress_flag) != 0;
196 let cmd = (raw_cmd & !compress_flag) as i32;
197 let tick_raw = reader.read_uvarint32()?;
198 let tick = tick_raw as i32;
199 let body_size = reader.read_uvarint32()?;
200 Ok(CmdHeader {
201 cmd,
202 tick,
203 compressed,
204 body_size,
205 })
206 }
207
208 fn read_cmd_body(reader: &mut ByteReader, header: &CmdHeader, buf: &mut Vec<u8>) -> Result<()> {
211 let raw = reader.read_bytes(header.body_size as usize)?;
212 if header.compressed {
213 let decompressed_len =
214 snap::raw::decompress_len(raw).map_err(|e| Error::Decompress(e.to_string()))?;
215 buf.clear();
216 buf.resize(decompressed_len, 0);
217 snap::raw::Decoder::new()
218 .decompress(raw, buf)
219 .map_err(|e| Error::Decompress(e.to_string()))?;
220 } else {
221 buf.clear();
222 buf.extend_from_slice(raw);
223 }
224 Ok(())
225 }
226
227 pub fn messages(&self) -> Result<Vec<MessageInfo>> {
230 self.verify()?;
231 let data = &self.data()[HEADER_SIZE..];
232 let mut reader = ByteReader::new(data);
233 let mut messages = Vec::new();
234 let mut index = 0;
235
236 while reader.remaining() > 0 {
237 let offset = reader.position() + HEADER_SIZE;
238 let header = match Self::read_cmd_header(&mut reader) {
239 Ok(h) => h,
240 Err(_) => break,
241 };
242
243 messages.push(MessageInfo {
244 index,
245 cmd: header.cmd,
246 cmd_name: command::command_name(header.cmd).to_string(),
247 tick: header.tick,
248 compressed: header.compressed,
249 body_size: header.body_size,
250 offset,
251 });
252
253 if header.cmd == dem::STOP {
255 index += 1;
256 continue;
257 }
258
259 if header.cmd == dem::FILE_INFO {
261 reader.skip(header.body_size as usize).ok();
262 break;
263 }
264
265 if reader.skip(header.body_size as usize).is_err() {
266 break;
267 }
268
269 index += 1;
270 }
271
272 Ok(messages)
273 }
274
275 pub fn file_header(&self) -> Result<CDemoFileHeader> {
277 self.verify()?;
278 let data = &self.data()[HEADER_SIZE..];
279 let mut reader = ByteReader::new(data);
280 let mut body_buf = Vec::with_capacity(BUF_SIZE);
281
282 while reader.remaining() > 0 {
283 let header = Self::read_cmd_header(&mut reader)?;
284
285 if header.cmd == dem::FILE_HEADER {
286 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
287 return CDemoFileHeader::decode(&body_buf[..]).map_err(Error::from);
288 }
289
290 if header.cmd == dem::STOP {
291 break;
292 }
293
294 reader.skip(header.body_size as usize)?;
295 }
296
297 Err(Error::Parse {
298 context: "DEM_FileHeader not found".into(),
299 })
300 }
301
302 pub fn file_info(&self) -> Result<CDemoFileInfo> {
304 self.verify()?;
305
306 let fileinfo_offset = u32::from_le_bytes([
308 self.data()[8],
309 self.data()[9],
310 self.data()[10],
311 self.data()[11],
312 ]) as usize;
313
314 let data = &self.data()[HEADER_SIZE..];
315 let mut reader = ByteReader::new(data);
316 reader.seek(fileinfo_offset.saturating_sub(HEADER_SIZE))?;
318
319 let header = Self::read_cmd_header(&mut reader)?;
320 if header.cmd != dem::FILE_INFO {
321 return Err(Error::Parse {
322 context: format!(
323 "expected DEM_FileInfo at offset {}, found command {}",
324 fileinfo_offset, header.cmd
325 ),
326 });
327 }
328
329 let mut body_buf = Vec::new();
330 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
331 CDemoFileInfo::decode(&body_buf[..]).map_err(Error::from)
332 }
333
334 pub fn events(&self, max_tick: Option<i32>) -> Result<Vec<GameEvent>> {
340 self.verify()?;
341 let data = &self.data()[HEADER_SIZE..];
342 let mut reader = ByteReader::new(data);
343 let mut body_buf = Vec::with_capacity(BUF_SIZE);
344 let mut packet_buf = vec![0u8; BUF_SIZE];
345 let mut events = Vec::new();
346 let mut descriptors: HashMap<i32, EventDescriptor> = HashMap::new();
347
348 while reader.remaining() > 0 {
349 let header = match Self::read_cmd_header(&mut reader) {
350 Ok(h) => h,
351 Err(_) => break,
352 };
353
354 if header.cmd == dem::STOP {
355 break;
356 }
357
358 if let Some(max) = max_tick
359 && header.tick > max
360 {
361 break;
362 }
363
364 match header.cmd {
365 dem::PACKET | dem::SIGNON_PACKET => {
366 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
367 let cmd = CDemoPacket::decode(&body_buf[..])?;
368 let pkt_data = cmd.data.unwrap_or_default();
369 Self::process_packet_events(
370 &pkt_data,
371 header.tick,
372 &mut descriptors,
373 &mut events,
374 &mut packet_buf,
375 )?;
376 }
377 dem::FULL_PACKET => {
378 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
379 let cmd = CDemoFullPacket::decode(&body_buf[..])?;
380 if let Some(packet) = cmd.packet {
381 let pkt_data = packet.data.unwrap_or_default();
382 Self::process_packet_events(
383 &pkt_data,
384 header.tick,
385 &mut descriptors,
386 &mut events,
387 &mut packet_buf,
388 )?;
389 }
390 }
391 _ => {
392 reader.skip(header.body_size as usize)?;
393 }
394 }
395 }
396
397 Ok(events)
398 }
399
400 fn process_packet_events(
402 pkt_data: &[u8],
403 tick: i32,
404 descriptors: &mut HashMap<i32, EventDescriptor>,
405 events: &mut Vec<GameEvent>,
406 packet_buf: &mut Vec<u8>,
407 ) -> Result<()> {
408 let mut br = BitReader::new(pkt_data);
409
410 while br.bits_remaining() > 8 {
411 let msg_type = br.read_ubitvar()?;
412 let size = br.read_uvarint32()? as usize;
413
414 if size > packet_buf.len() {
415 packet_buf.resize(size, 0);
416 }
417 br.read_bytes(&mut packet_buf[..size])?;
418 let msg_data = &packet_buf[..size];
419
420 match msg_type {
421 ge::SOURCE1_LEGACY_GAME_EVENT_LIST => {
422 let msg = CMsgSource1LegacyGameEventList::decode(msg_data)?;
423 for desc in msg.descriptors {
424 let eventid = desc.eventid.unwrap_or_default();
425 let name = desc.name.unwrap_or_default();
426 let field_names = desc
427 .keys
428 .iter()
429 .map(|k| k.name.clone().unwrap_or_default())
430 .collect();
431 descriptors.insert(eventid, EventDescriptor { name, field_names });
432 }
433 }
434 ge::SOURCE1_LEGACY_GAME_EVENT => {
435 let msg = CMsgSource1LegacyGameEvent::decode(msg_data)?;
436 let eventid = msg.eventid.unwrap_or_default();
437 let (name, keys) = if let Some(desc) = descriptors.get(&eventid) {
438 let keys: Vec<(String, String)> = desc
439 .field_names
440 .iter()
441 .zip(msg.keys.iter())
442 .map(|(fname, key)| (fname.clone(), format_event_key(key)))
443 .collect();
444 (desc.name.clone(), keys)
445 } else {
446 let name = msg
447 .event_name
448 .unwrap_or_else(|| format!("event_{}", eventid));
449 (name, Vec::new())
450 };
451 events.push(GameEvent {
452 tick,
453 name,
454 msg_type,
455 keys,
456 payload: msg_data.to_vec(),
457 });
458 }
459 svc::USER_MESSAGE => {
460 let msg = CsvcMsgUserMessage::decode(msg_data)?;
461 let inner_type = msg.msg_type.unwrap_or_default();
462 let name = command::user_message_name(inner_type);
463 let inner_payload = msg.msg_data.unwrap_or_default();
464 events.push(GameEvent {
465 tick,
466 name,
467 msg_type: inner_type as u32,
468 keys: Vec::new(),
469 payload: inner_payload,
470 });
471 }
472 _ => {
473 let t = msg_type as i32;
476 let name = if let Ok(e) = CitadelUserMessageIds::try_from(t) {
477 Some(e.as_str_name().to_string())
478 } else if let Ok(e) = ECitadelGameEvents::try_from(t) {
479 Some(e.as_str_name().to_string())
480 } else if let Ok(e) = EBaseUserMessages::try_from(t) {
481 Some(e.as_str_name().to_string())
482 } else {
483 None
484 };
485 if let Some(name) = name {
486 events.push(GameEvent {
487 tick,
488 name,
489 msg_type,
490 keys: Vec::new(),
491 payload: msg_data.to_vec(),
492 });
493 }
494 }
495 }
496 }
497
498 Ok(())
499 }
500
501 pub fn parse_send_tables(&self) -> Result<SerializerContainer> {
503 self.verify()?;
504 let data = &self.data()[HEADER_SIZE..];
505 let mut reader = ByteReader::new(data);
506 let mut body_buf = Vec::with_capacity(BUF_SIZE);
507
508 while reader.remaining() > 0 {
509 let header = Self::read_cmd_header(&mut reader)?;
510
511 if header.cmd == dem::SEND_TABLES {
512 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
513 let cmd = CDemoSendTables::decode(&body_buf[..])?;
514 return SerializerContainer::parse(cmd);
515 }
516
517 if header.cmd == dem::STOP || header.cmd == dem::SYNC_TICK {
518 break;
519 }
520
521 reader.skip(header.body_size as usize)?;
522 }
523
524 Err(Error::Parse {
525 context: "DEM_SendTables not found".into(),
526 })
527 }
528
529 pub fn parse_class_info(&self) -> Result<ClassInfo> {
531 self.verify()?;
532 let data = &self.data()[HEADER_SIZE..];
533 let mut reader = ByteReader::new(data);
534 let mut body_buf = Vec::with_capacity(BUF_SIZE);
535
536 while reader.remaining() > 0 {
537 let header = Self::read_cmd_header(&mut reader)?;
538
539 if header.cmd == dem::CLASS_INFO {
540 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
541 let cmd = CDemoClassInfo::decode(&body_buf[..])?;
542 return Ok(ClassInfo::parse(cmd));
543 }
544
545 if header.cmd == dem::STOP || header.cmd == dem::SYNC_TICK {
546 break;
547 }
548
549 reader.skip(header.body_size as usize)?;
550 }
551
552 Err(Error::Parse {
553 context: "DEM_ClassInfo not found".into(),
554 })
555 }
556
557 pub fn parse_init(&self) -> Result<Context> {
559 self.verify()?;
560 let data = &self.data()[HEADER_SIZE..];
561 let mut reader = ByteReader::new(data);
562
563 let mut packet_buf = vec![0u8; BUF_SIZE];
564 let mut body_buf = Vec::with_capacity(BUF_SIZE);
565
566 let mut serializers: Option<SerializerContainer> = None;
567 let mut class_info: Option<ClassInfo> = None;
568 let mut string_tables = StringTableContainer::new();
569 let mut tick_interval: f32 = 0.0;
570 let mut full_packet_interval: i32 = DEFAULT_FULL_PACKET_INTERVAL;
571
572 while reader.remaining() > 0 {
573 let header = Self::read_cmd_header(&mut reader)?;
574
575 if header.cmd == dem::SYNC_TICK {
576 reader.skip(header.body_size as usize)?;
577 break;
578 }
579
580 if header.cmd == dem::STOP {
581 break;
582 }
583
584 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
585
586 match header.cmd {
587 dem::SEND_TABLES => {
588 let cmd = CDemoSendTables::decode(&body_buf[..])?;
589 serializers = Some(SerializerContainer::parse(cmd)?);
590 }
591 dem::CLASS_INFO => {
592 let cmd = CDemoClassInfo::decode(&body_buf[..])?;
593 class_info = Some(ClassInfo::parse(cmd));
594 }
595 dem::PACKET | dem::SIGNON_PACKET => {
596 let cmd = CDemoPacket::decode(&body_buf[..])?;
597 let pkt_data = cmd.data.unwrap_or_default();
598 Self::process_packet_for_init(
599 &pkt_data,
600 &mut string_tables,
601 &mut tick_interval,
602 &mut full_packet_interval,
603 &mut packet_buf,
604 )?;
605 }
606 _ => {}
607 }
608 }
609
610 let serializers = serializers.ok_or_else(|| Error::Parse {
611 context: "DEM_SendTables not found during init".into(),
612 })?;
613 let class_info = class_info.ok_or_else(|| Error::Parse {
614 context: "DEM_ClassInfo not found during init".into(),
615 })?;
616
617 string_tables.update_instance_baselines(&class_info);
619
620 Ok(Context {
621 serializers,
622 class_info,
623 string_tables,
624 entities: EntityContainer::new(),
625 tick_interval,
626 full_packet_interval,
627 tick: -1,
628 })
629 }
630
631 fn process_packet_for_init(
633 pkt_data: &[u8],
634 string_tables: &mut StringTableContainer,
635 tick_interval: &mut f32,
636 full_packet_interval: &mut i32,
637 packet_buf: &mut Vec<u8>,
638 ) -> Result<()> {
639 let mut br = BitReader::new(pkt_data);
640
641 while br.bits_remaining() > 8 {
642 let msg_type = br.read_ubitvar()?;
643 let size = br.read_uvarint32()? as usize;
644
645 if size > packet_buf.len() {
647 packet_buf.resize(size, 0);
648 }
649 br.read_bytes(&mut packet_buf[..size])?;
650 let msg_data = &packet_buf[..size];
651
652 match msg_type {
653 svc::CREATE_STRING_TABLE => {
654 let msg = CsvcMsgCreateStringTable::decode(msg_data)?;
655 string_tables.handle_create(msg)?;
656 }
657 svc::UPDATE_STRING_TABLE => {
658 let msg = CsvcMsgUpdateStringTable::decode(msg_data)?;
659 string_tables.handle_update(msg)?;
660 }
661 svc::SERVER_INFO => {
662 let msg = CsvcMsgServerInfo::decode(msg_data)?;
663 if let Some(ti) = msg.tick_interval {
664 *tick_interval = ti;
665 let ratio = DEFAULT_TICK_INTERVAL / ti;
666 *full_packet_interval = DEFAULT_FULL_PACKET_INTERVAL * ratio as i32;
667 }
668 }
669 _ => {}
670 }
671 }
672
673 Ok(())
674 }
675
676 pub fn parse_to_tick(&self, target_tick: i32) -> Result<Context> {
682 let mut ctx = self.parse_init()?;
683 let data = &self.data()[HEADER_SIZE..];
684 let mut reader = ByteReader::new(data);
685
686 let mut packet_buf = vec![0u8; BUF_SIZE];
687 let mut body_buf = Vec::with_capacity(BUF_SIZE);
688 let mut fp_buf = Vec::with_capacity(256);
689 let mut field_decode_ctx = FieldDecodeContext::new(ctx.tick_interval);
690
691 let mut past_sync = false;
693 while reader.remaining() > 0 {
694 let header = Self::read_cmd_header(&mut reader)?;
695 if header.cmd == dem::SYNC_TICK {
696 reader.skip(header.body_size as usize)?;
697 past_sync = true;
698 break;
699 }
700 if header.cmd == dem::STOP {
701 return Ok(ctx);
702 }
703 reader.skip(header.body_size as usize)?;
704 }
705
706 if !past_sync {
707 return Ok(ctx);
708 }
709
710 let mut did_handle_last_full_packet = false;
712
713 while reader.remaining() > 0 {
714 let header = Self::read_cmd_header(&mut reader)?;
715
716 if header.tick > target_tick && header.cmd != dem::STOP {
717 break;
718 }
719
720 ctx.tick = header.tick;
721
722 if header.cmd == dem::STOP {
723 break;
724 }
725
726 let is_full_packet = header.cmd == dem::FULL_PACKET;
727 let distance = target_tick - header.tick;
728 let has_full_packet_ahead = distance > ctx.full_packet_interval + 100;
729
730 if is_full_packet {
731 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
732 let cmd = CDemoFullPacket::decode(&body_buf[..])?;
733
734 if let Some(st) = cmd.string_table {
736 ctx.string_tables.do_full_update(st);
737 ctx.string_tables.update_instance_baselines(&ctx.class_info);
738 }
739
740 if !has_full_packet_ahead {
742 if let Some(packet) = cmd.packet {
743 let pkt_data = packet.data.unwrap_or_default();
744 Self::process_packet_entities(
745 &pkt_data,
746 &mut ctx,
747 &mut field_decode_ctx,
748 &mut packet_buf,
749 &mut fp_buf,
750 )?;
751 }
752 did_handle_last_full_packet = true;
753 }
754
755 continue;
756 }
757
758 if !did_handle_last_full_packet {
759 reader.skip(header.body_size as usize)?;
760 continue;
761 }
762
763 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
764
765 match header.cmd {
766 dem::PACKET | dem::SIGNON_PACKET => {
767 let cmd = CDemoPacket::decode(&body_buf[..])?;
768 let pkt_data = cmd.data.unwrap_or_default();
769 Self::process_packet_entities(
770 &pkt_data,
771 &mut ctx,
772 &mut field_decode_ctx,
773 &mut packet_buf,
774 &mut fp_buf,
775 )?;
776 }
777 _ => {}
778 }
779 }
780
781 Ok(ctx)
782 }
783
784 pub fn run_to_end<F>(&self, mut on_tick: F) -> Result<()>
787 where
788 F: FnMut(&Context),
789 {
790 let mut ctx = self.parse_init()?;
791 let data = &self.data()[HEADER_SIZE..];
792 let mut reader = ByteReader::new(data);
793
794 let mut packet_buf = vec![0u8; BUF_SIZE];
795 let mut body_buf = Vec::with_capacity(BUF_SIZE);
796 let mut fp_buf = Vec::with_capacity(256);
797 let mut field_decode_ctx = FieldDecodeContext::new(ctx.tick_interval);
798
799 while reader.remaining() > 0 {
801 let header = Self::read_cmd_header(&mut reader)?;
802 if header.cmd == dem::SYNC_TICK {
803 reader.skip(header.body_size as usize)?;
804 break;
805 }
806 if header.cmd == dem::STOP {
807 return Ok(());
808 }
809 reader.skip(header.body_size as usize)?;
810 }
811
812 let mut last_tick: i32 = -1;
813
814 while reader.remaining() > 0 {
815 let header = Self::read_cmd_header(&mut reader)?;
816
817 if header.tick != last_tick && last_tick >= 0 {
819 on_tick(&ctx);
820 ctx.string_tables.clear_dirty();
821 }
822 last_tick = header.tick;
823 ctx.tick = header.tick;
824
825 if header.cmd == dem::STOP {
826 if last_tick >= 0 {
828 on_tick(&ctx);
829 }
830 break;
831 }
832
833 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
834
835 match header.cmd {
836 dem::FULL_PACKET => {
837 let cmd = CDemoFullPacket::decode(&body_buf[..])?;
838
839 if let Some(st) = cmd.string_table {
840 ctx.string_tables.do_full_update(st);
841 ctx.string_tables.update_instance_baselines(&ctx.class_info);
842 }
843
844 if let Some(packet) = cmd.packet {
845 let pkt_data = packet.data.unwrap_or_default();
846 Self::process_packet_entities(
847 &pkt_data,
848 &mut ctx,
849 &mut field_decode_ctx,
850 &mut packet_buf,
851 &mut fp_buf,
852 )?;
853 }
854 }
855 dem::PACKET | dem::SIGNON_PACKET => {
856 let cmd = CDemoPacket::decode(&body_buf[..])?;
857 let pkt_data = cmd.data.unwrap_or_default();
858 Self::process_packet_entities(
859 &pkt_data,
860 &mut ctx,
861 &mut field_decode_ctx,
862 &mut packet_buf,
863 &mut fp_buf,
864 )?;
865 }
866 _ => {}
867 }
868 }
869
870 Ok(())
871 }
872
873 pub fn run_to_end_filtered<F>(
877 &self,
878 class_filter: &std::collections::HashSet<&str>,
879 mut on_tick: F,
880 ) -> Result<()>
881 where
882 F: FnMut(&Context),
883 {
884 let mut ctx = self.parse_init()?;
885 let data = &self.data()[HEADER_SIZE..];
886 let mut reader = ByteReader::new(data);
887
888 let mut packet_buf = vec![0u8; BUF_SIZE];
889 let mut body_buf = Vec::with_capacity(BUF_SIZE);
890 let mut fp_buf = Vec::with_capacity(256);
891 let mut field_decode_ctx = FieldDecodeContext::new(ctx.tick_interval);
892
893 while reader.remaining() > 0 {
895 let header = Self::read_cmd_header(&mut reader)?;
896 if header.cmd == dem::SYNC_TICK {
897 reader.skip(header.body_size as usize)?;
898 break;
899 }
900 if header.cmd == dem::STOP {
901 return Ok(());
902 }
903 reader.skip(header.body_size as usize)?;
904 }
905
906 let mut last_tick: i32 = -1;
907
908 while reader.remaining() > 0 {
909 let header = Self::read_cmd_header(&mut reader)?;
910
911 if header.tick != last_tick && last_tick >= 0 {
913 on_tick(&ctx);
914 ctx.string_tables.clear_dirty();
915 }
916 last_tick = header.tick;
917 ctx.tick = header.tick;
918
919 if header.cmd == dem::STOP {
920 if last_tick >= 0 {
922 on_tick(&ctx);
923 }
924 break;
925 }
926
927 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
928
929 match header.cmd {
930 dem::FULL_PACKET => {
931 let cmd = CDemoFullPacket::decode(&body_buf[..])?;
932
933 if let Some(st) = cmd.string_table {
934 ctx.string_tables.do_full_update(st);
935 ctx.string_tables.update_instance_baselines(&ctx.class_info);
936 }
937
938 if let Some(packet) = cmd.packet {
939 let pkt_data = packet.data.unwrap_or_default();
940 Self::process_packet_entities_filtered(
941 &pkt_data,
942 &mut ctx,
943 &mut field_decode_ctx,
944 &mut packet_buf,
945 class_filter,
946 &mut fp_buf,
947 )?;
948 }
949 }
950 dem::PACKET | dem::SIGNON_PACKET => {
951 let cmd = CDemoPacket::decode(&body_buf[..])?;
952 let pkt_data = cmd.data.unwrap_or_default();
953 Self::process_packet_entities_filtered(
954 &pkt_data,
955 &mut ctx,
956 &mut field_decode_ctx,
957 &mut packet_buf,
958 class_filter,
959 &mut fp_buf,
960 )?;
961 }
962 _ => {}
963 }
964 }
965
966 Ok(())
967 }
968
969 pub fn run_to_end_with_events_filtered<F>(
973 &self,
974 class_filter: &std::collections::HashSet<&str>,
975 mut on_tick: F,
976 ) -> Result<()>
977 where
978 F: FnMut(&Context, &[GameEvent]),
979 {
980 let mut ctx = self.parse_init()?;
981 let data = &self.data()[HEADER_SIZE..];
982 let mut reader = ByteReader::new(data);
983
984 let mut packet_buf = vec![0u8; BUF_SIZE];
985 let mut event_packet_buf = vec![0u8; BUF_SIZE];
986 let mut body_buf = Vec::with_capacity(BUF_SIZE);
987 let mut fp_buf = Vec::with_capacity(256);
988 let mut field_decode_ctx = FieldDecodeContext::new(ctx.tick_interval);
989
990 let mut descriptors: HashMap<i32, EventDescriptor> = HashMap::new();
991 let mut tick_events: Vec<GameEvent> = Vec::new();
992
993 while reader.remaining() > 0 {
995 let header = Self::read_cmd_header(&mut reader)?;
996 if header.cmd == dem::SYNC_TICK {
997 reader.skip(header.body_size as usize)?;
998 break;
999 }
1000 if header.cmd == dem::STOP {
1001 return Ok(());
1002 }
1003 reader.skip(header.body_size as usize)?;
1004 }
1005
1006 let mut last_tick: i32 = -1;
1007
1008 while reader.remaining() > 0 {
1009 let header = Self::read_cmd_header(&mut reader)?;
1010
1011 if header.tick != last_tick && last_tick >= 0 {
1013 on_tick(&ctx, &tick_events);
1014 tick_events.clear();
1015 ctx.string_tables.clear_dirty();
1016 }
1017 last_tick = header.tick;
1018 ctx.tick = header.tick;
1019
1020 if header.cmd == dem::STOP {
1021 if last_tick >= 0 {
1023 on_tick(&ctx, &tick_events);
1024 }
1025 break;
1026 }
1027
1028 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
1029
1030 match header.cmd {
1031 dem::FULL_PACKET => {
1032 let cmd = CDemoFullPacket::decode(&body_buf[..])?;
1033
1034 if let Some(st) = cmd.string_table {
1035 ctx.string_tables.do_full_update(st);
1036 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1037 }
1038
1039 if let Some(packet) = cmd.packet {
1040 let pkt_data = packet.data.unwrap_or_default();
1041 Self::process_packet_entities_filtered(
1042 &pkt_data,
1043 &mut ctx,
1044 &mut field_decode_ctx,
1045 &mut packet_buf,
1046 class_filter,
1047 &mut fp_buf,
1048 )?;
1049 Self::process_packet_events(
1050 &pkt_data,
1051 header.tick,
1052 &mut descriptors,
1053 &mut tick_events,
1054 &mut event_packet_buf,
1055 )?;
1056 }
1057 }
1058 dem::PACKET | dem::SIGNON_PACKET => {
1059 let cmd = CDemoPacket::decode(&body_buf[..])?;
1060 let pkt_data = cmd.data.unwrap_or_default();
1061 Self::process_packet_entities_filtered(
1062 &pkt_data,
1063 &mut ctx,
1064 &mut field_decode_ctx,
1065 &mut packet_buf,
1066 class_filter,
1067 &mut fp_buf,
1068 )?;
1069 Self::process_packet_events(
1070 &pkt_data,
1071 header.tick,
1072 &mut descriptors,
1073 &mut tick_events,
1074 &mut event_packet_buf,
1075 )?;
1076 }
1077 _ => {}
1078 }
1079 }
1080
1081 Ok(())
1082 }
1083
1084 fn process_packet_entities(
1086 pkt_data: &[u8],
1087 ctx: &mut Context,
1088 field_decode_ctx: &mut FieldDecodeContext,
1089 packet_buf: &mut Vec<u8>,
1090 fp_buf: &mut Vec<crate::entity::field_path::FieldPath>,
1091 ) -> Result<()> {
1092 let mut br = BitReader::new(pkt_data);
1093
1094 while br.bits_remaining() > 8 {
1095 let msg_type = br.read_ubitvar()?;
1096 let size = br.read_uvarint32()? as usize;
1097
1098 if !matches!(
1101 msg_type,
1102 svc::CREATE_STRING_TABLE
1103 | svc::UPDATE_STRING_TABLE
1104 | svc::SERVER_INFO
1105 | svc::PACKET_ENTITIES
1106 ) {
1107 br.skip_bits(size * 8)?;
1108 continue;
1109 }
1110
1111 if size > packet_buf.len() {
1112 packet_buf.resize(size, 0);
1113 }
1114 br.read_bytes(&mut packet_buf[..size])?;
1115 let msg_data = &packet_buf[..size];
1116
1117 match msg_type {
1118 svc::CREATE_STRING_TABLE => {
1119 let msg = CsvcMsgCreateStringTable::decode(msg_data)?;
1120 if ctx.string_tables.handle_create(msg)? {
1121 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1122 }
1123 }
1124 svc::UPDATE_STRING_TABLE => {
1125 let msg = CsvcMsgUpdateStringTable::decode(msg_data)?;
1126 if ctx.string_tables.handle_update(msg)? {
1127 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1128 }
1129 }
1130 svc::SERVER_INFO => {
1131 let msg = CsvcMsgServerInfo::decode(msg_data)?;
1132 if let Some(ti) = msg.tick_interval {
1133 ctx.tick_interval = ti;
1134 field_decode_ctx.tick_interval = ti;
1135 let ratio = DEFAULT_TICK_INTERVAL / ti;
1136 ctx.full_packet_interval = DEFAULT_FULL_PACKET_INTERVAL * ratio as i32;
1137 }
1138 }
1139 svc::PACKET_ENTITIES => {
1140 let msg = CsvcMsgPacketEntities::decode(msg_data)?;
1141 ctx.entities.handle_packet_entities(
1142 msg,
1143 &ctx.class_info,
1144 &ctx.serializers,
1145 &ctx.string_tables,
1146 field_decode_ctx,
1147 fp_buf,
1148 )?;
1149 }
1150 _ => {}
1151 }
1152 }
1153
1154 Ok(())
1155 }
1156
1157 fn process_packet_entities_filtered(
1159 pkt_data: &[u8],
1160 ctx: &mut Context,
1161 field_decode_ctx: &mut FieldDecodeContext,
1162 packet_buf: &mut Vec<u8>,
1163 class_filter: &std::collections::HashSet<&str>,
1164 fp_buf: &mut Vec<crate::entity::field_path::FieldPath>,
1165 ) -> Result<()> {
1166 let mut br = BitReader::new(pkt_data);
1167
1168 while br.bits_remaining() > 8 {
1169 let msg_type = br.read_ubitvar()?;
1170 let size = br.read_uvarint32()? as usize;
1171
1172 if !matches!(
1175 msg_type,
1176 svc::CREATE_STRING_TABLE
1177 | svc::UPDATE_STRING_TABLE
1178 | svc::SERVER_INFO
1179 | svc::PACKET_ENTITIES
1180 ) {
1181 br.skip_bits(size * 8)?;
1182 continue;
1183 }
1184
1185 if size > packet_buf.len() {
1186 packet_buf.resize(size, 0);
1187 }
1188 br.read_bytes(&mut packet_buf[..size])?;
1189 let msg_data = &packet_buf[..size];
1190
1191 match msg_type {
1192 svc::CREATE_STRING_TABLE => {
1193 let msg = CsvcMsgCreateStringTable::decode(msg_data)?;
1194 if ctx.string_tables.handle_create(msg)? {
1195 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1196 }
1197 }
1198 svc::UPDATE_STRING_TABLE => {
1199 let msg = CsvcMsgUpdateStringTable::decode(msg_data)?;
1200 if ctx.string_tables.handle_update(msg)? {
1201 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1202 }
1203 }
1204 svc::SERVER_INFO => {
1205 let msg = CsvcMsgServerInfo::decode(msg_data)?;
1206 if let Some(ti) = msg.tick_interval {
1207 ctx.tick_interval = ti;
1208 field_decode_ctx.tick_interval = ti;
1209 let ratio = DEFAULT_TICK_INTERVAL / ti;
1210 ctx.full_packet_interval = DEFAULT_FULL_PACKET_INTERVAL * ratio as i32;
1211 }
1212 }
1213 svc::PACKET_ENTITIES => {
1214 let msg = CsvcMsgPacketEntities::decode(msg_data)?;
1215 ctx.entities.handle_packet_entities_filtered(
1216 msg,
1217 &ctx.class_info,
1218 &ctx.serializers,
1219 &ctx.string_tables,
1220 field_decode_ctx,
1221 class_filter,
1222 fp_buf,
1223 )?;
1224 }
1225 _ => {}
1226 }
1227 }
1228
1229 Ok(())
1230 }
1231}