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 ctx.entities.clear_updated();
822 }
823 last_tick = header.tick;
824 ctx.tick = header.tick;
825
826 if header.cmd == dem::STOP {
827 if last_tick >= 0 {
829 on_tick(&ctx);
830 }
831 break;
832 }
833
834 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
835
836 match header.cmd {
837 dem::FULL_PACKET => {
838 let cmd = CDemoFullPacket::decode(&body_buf[..])?;
839
840 if let Some(st) = cmd.string_table {
841 ctx.string_tables.do_full_update(st);
842 ctx.string_tables.update_instance_baselines(&ctx.class_info);
843 }
844
845 if let Some(packet) = cmd.packet {
846 let pkt_data = packet.data.unwrap_or_default();
847 Self::process_packet_entities(
848 &pkt_data,
849 &mut ctx,
850 &mut field_decode_ctx,
851 &mut packet_buf,
852 &mut fp_buf,
853 )?;
854 }
855 }
856 dem::PACKET | dem::SIGNON_PACKET => {
857 let cmd = CDemoPacket::decode(&body_buf[..])?;
858 let pkt_data = cmd.data.unwrap_or_default();
859 Self::process_packet_entities(
860 &pkt_data,
861 &mut ctx,
862 &mut field_decode_ctx,
863 &mut packet_buf,
864 &mut fp_buf,
865 )?;
866 }
867 _ => {}
868 }
869 }
870
871 Ok(())
872 }
873
874 pub fn run_to_end_filtered<F>(
878 &self,
879 class_filter: &std::collections::HashSet<&str>,
880 mut on_tick: F,
881 ) -> Result<()>
882 where
883 F: FnMut(&Context),
884 {
885 let mut ctx = self.parse_init()?;
886 let data = &self.data()[HEADER_SIZE..];
887 let mut reader = ByteReader::new(data);
888
889 let mut packet_buf = vec![0u8; BUF_SIZE];
890 let mut body_buf = Vec::with_capacity(BUF_SIZE);
891 let mut fp_buf = Vec::with_capacity(256);
892 let mut field_decode_ctx = FieldDecodeContext::new(ctx.tick_interval);
893
894 while reader.remaining() > 0 {
896 let header = Self::read_cmd_header(&mut reader)?;
897 if header.cmd == dem::SYNC_TICK {
898 reader.skip(header.body_size as usize)?;
899 break;
900 }
901 if header.cmd == dem::STOP {
902 return Ok(());
903 }
904 reader.skip(header.body_size as usize)?;
905 }
906
907 let mut last_tick: i32 = -1;
908
909 while reader.remaining() > 0 {
910 let header = Self::read_cmd_header(&mut reader)?;
911
912 if header.tick != last_tick && last_tick >= 0 {
914 on_tick(&ctx);
915 ctx.string_tables.clear_dirty();
916 ctx.entities.clear_updated();
917 }
918 last_tick = header.tick;
919 ctx.tick = header.tick;
920
921 if header.cmd == dem::STOP {
922 if last_tick >= 0 {
924 on_tick(&ctx);
925 }
926 break;
927 }
928
929 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
930
931 match header.cmd {
932 dem::FULL_PACKET => {
933 let cmd = CDemoFullPacket::decode(&body_buf[..])?;
934
935 if let Some(st) = cmd.string_table {
936 ctx.string_tables.do_full_update(st);
937 ctx.string_tables.update_instance_baselines(&ctx.class_info);
938 }
939
940 if let Some(packet) = cmd.packet {
941 let pkt_data = packet.data.unwrap_or_default();
942 Self::process_packet_entities_filtered(
943 &pkt_data,
944 &mut ctx,
945 &mut field_decode_ctx,
946 &mut packet_buf,
947 class_filter,
948 &mut fp_buf,
949 )?;
950 }
951 }
952 dem::PACKET | dem::SIGNON_PACKET => {
953 let cmd = CDemoPacket::decode(&body_buf[..])?;
954 let pkt_data = cmd.data.unwrap_or_default();
955 Self::process_packet_entities_filtered(
956 &pkt_data,
957 &mut ctx,
958 &mut field_decode_ctx,
959 &mut packet_buf,
960 class_filter,
961 &mut fp_buf,
962 )?;
963 }
964 _ => {}
965 }
966 }
967
968 Ok(())
969 }
970
971 pub fn run_to_end_with_events_filtered<F>(
975 &self,
976 class_filter: &std::collections::HashSet<&str>,
977 mut on_tick: F,
978 ) -> Result<()>
979 where
980 F: FnMut(&Context, &[GameEvent]),
981 {
982 let mut ctx = self.parse_init()?;
983 let data = &self.data()[HEADER_SIZE..];
984 let mut reader = ByteReader::new(data);
985
986 let mut packet_buf = vec![0u8; BUF_SIZE];
987 let mut event_packet_buf = vec![0u8; BUF_SIZE];
988 let mut body_buf = Vec::with_capacity(BUF_SIZE);
989 let mut fp_buf = Vec::with_capacity(256);
990 let mut field_decode_ctx = FieldDecodeContext::new(ctx.tick_interval);
991
992 let mut descriptors: HashMap<i32, EventDescriptor> = HashMap::new();
993 let mut tick_events: Vec<GameEvent> = Vec::new();
994
995 while reader.remaining() > 0 {
997 let header = Self::read_cmd_header(&mut reader)?;
998 if header.cmd == dem::SYNC_TICK {
999 reader.skip(header.body_size as usize)?;
1000 break;
1001 }
1002 if header.cmd == dem::STOP {
1003 return Ok(());
1004 }
1005 reader.skip(header.body_size as usize)?;
1006 }
1007
1008 let mut last_tick: i32 = -1;
1009
1010 while reader.remaining() > 0 {
1011 let header = Self::read_cmd_header(&mut reader)?;
1012
1013 if header.tick != last_tick && last_tick >= 0 {
1015 on_tick(&ctx, &tick_events);
1016 tick_events.clear();
1017 ctx.string_tables.clear_dirty();
1018 ctx.entities.clear_updated();
1019 }
1020 last_tick = header.tick;
1021 ctx.tick = header.tick;
1022
1023 if header.cmd == dem::STOP {
1024 if last_tick >= 0 {
1026 on_tick(&ctx, &tick_events);
1027 }
1028 break;
1029 }
1030
1031 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
1032
1033 match header.cmd {
1034 dem::FULL_PACKET => {
1035 let cmd = CDemoFullPacket::decode(&body_buf[..])?;
1036
1037 if let Some(st) = cmd.string_table {
1038 ctx.string_tables.do_full_update(st);
1039 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1040 }
1041
1042 if let Some(packet) = cmd.packet {
1043 let pkt_data = packet.data.unwrap_or_default();
1044 Self::process_packet_entities_filtered(
1045 &pkt_data,
1046 &mut ctx,
1047 &mut field_decode_ctx,
1048 &mut packet_buf,
1049 class_filter,
1050 &mut fp_buf,
1051 )?;
1052 Self::process_packet_events(
1053 &pkt_data,
1054 header.tick,
1055 &mut descriptors,
1056 &mut tick_events,
1057 &mut event_packet_buf,
1058 )?;
1059 }
1060 }
1061 dem::PACKET | dem::SIGNON_PACKET => {
1062 let cmd = CDemoPacket::decode(&body_buf[..])?;
1063 let pkt_data = cmd.data.unwrap_or_default();
1064 Self::process_packet_entities_filtered(
1065 &pkt_data,
1066 &mut ctx,
1067 &mut field_decode_ctx,
1068 &mut packet_buf,
1069 class_filter,
1070 &mut fp_buf,
1071 )?;
1072 Self::process_packet_events(
1073 &pkt_data,
1074 header.tick,
1075 &mut descriptors,
1076 &mut tick_events,
1077 &mut event_packet_buf,
1078 )?;
1079 }
1080 _ => {}
1081 }
1082 }
1083
1084 Ok(())
1085 }
1086
1087 fn process_packet_entities(
1089 pkt_data: &[u8],
1090 ctx: &mut Context,
1091 field_decode_ctx: &mut FieldDecodeContext,
1092 packet_buf: &mut Vec<u8>,
1093 fp_buf: &mut Vec<crate::entity::field_path::FieldPath>,
1094 ) -> Result<()> {
1095 let mut br = BitReader::new(pkt_data);
1096
1097 while br.bits_remaining() > 8 {
1098 let msg_type = br.read_ubitvar()?;
1099 let size = br.read_uvarint32()? as usize;
1100
1101 if !matches!(
1104 msg_type,
1105 svc::CREATE_STRING_TABLE
1106 | svc::UPDATE_STRING_TABLE
1107 | svc::SERVER_INFO
1108 | svc::PACKET_ENTITIES
1109 ) {
1110 br.skip_bits(size * 8)?;
1111 continue;
1112 }
1113
1114 if size > packet_buf.len() {
1115 packet_buf.resize(size, 0);
1116 }
1117 br.read_bytes(&mut packet_buf[..size])?;
1118 let msg_data = &packet_buf[..size];
1119
1120 match msg_type {
1121 svc::CREATE_STRING_TABLE => {
1122 let msg = CsvcMsgCreateStringTable::decode(msg_data)?;
1123 if ctx.string_tables.handle_create(msg)? {
1124 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1125 }
1126 }
1127 svc::UPDATE_STRING_TABLE => {
1128 let msg = CsvcMsgUpdateStringTable::decode(msg_data)?;
1129 if ctx.string_tables.handle_update(msg)? {
1130 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1131 }
1132 }
1133 svc::SERVER_INFO => {
1134 let msg = CsvcMsgServerInfo::decode(msg_data)?;
1135 if let Some(ti) = msg.tick_interval {
1136 ctx.tick_interval = ti;
1137 field_decode_ctx.tick_interval = ti;
1138 let ratio = DEFAULT_TICK_INTERVAL / ti;
1139 ctx.full_packet_interval = DEFAULT_FULL_PACKET_INTERVAL * ratio as i32;
1140 }
1141 }
1142 svc::PACKET_ENTITIES => {
1143 let msg = CsvcMsgPacketEntities::decode(msg_data)?;
1144 ctx.entities.handle_packet_entities(
1145 msg,
1146 &ctx.class_info,
1147 &ctx.serializers,
1148 &ctx.string_tables,
1149 field_decode_ctx,
1150 fp_buf,
1151 )?;
1152 }
1153 _ => {}
1154 }
1155 }
1156
1157 Ok(())
1158 }
1159
1160 fn process_packet_entities_filtered(
1162 pkt_data: &[u8],
1163 ctx: &mut Context,
1164 field_decode_ctx: &mut FieldDecodeContext,
1165 packet_buf: &mut Vec<u8>,
1166 class_filter: &std::collections::HashSet<&str>,
1167 fp_buf: &mut Vec<crate::entity::field_path::FieldPath>,
1168 ) -> Result<()> {
1169 let mut br = BitReader::new(pkt_data);
1170
1171 while br.bits_remaining() > 8 {
1172 let msg_type = br.read_ubitvar()?;
1173 let size = br.read_uvarint32()? as usize;
1174
1175 if !matches!(
1178 msg_type,
1179 svc::CREATE_STRING_TABLE
1180 | svc::UPDATE_STRING_TABLE
1181 | svc::SERVER_INFO
1182 | svc::PACKET_ENTITIES
1183 ) {
1184 br.skip_bits(size * 8)?;
1185 continue;
1186 }
1187
1188 if size > packet_buf.len() {
1189 packet_buf.resize(size, 0);
1190 }
1191 br.read_bytes(&mut packet_buf[..size])?;
1192 let msg_data = &packet_buf[..size];
1193
1194 match msg_type {
1195 svc::CREATE_STRING_TABLE => {
1196 let msg = CsvcMsgCreateStringTable::decode(msg_data)?;
1197 if ctx.string_tables.handle_create(msg)? {
1198 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1199 }
1200 }
1201 svc::UPDATE_STRING_TABLE => {
1202 let msg = CsvcMsgUpdateStringTable::decode(msg_data)?;
1203 if ctx.string_tables.handle_update(msg)? {
1204 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1205 }
1206 }
1207 svc::SERVER_INFO => {
1208 let msg = CsvcMsgServerInfo::decode(msg_data)?;
1209 if let Some(ti) = msg.tick_interval {
1210 ctx.tick_interval = ti;
1211 field_decode_ctx.tick_interval = ti;
1212 let ratio = DEFAULT_TICK_INTERVAL / ti;
1213 ctx.full_packet_interval = DEFAULT_FULL_PACKET_INTERVAL * ratio as i32;
1214 }
1215 }
1216 svc::PACKET_ENTITIES => {
1217 let msg = CsvcMsgPacketEntities::decode(msg_data)?;
1218 ctx.entities.handle_packet_entities_filtered(
1219 msg,
1220 &ctx.class_info,
1221 &ctx.serializers,
1222 &ctx.string_tables,
1223 field_decode_ctx,
1224 class_filter,
1225 fp_buf,
1226 )?;
1227 }
1228 _ => {}
1229 }
1230 }
1231
1232 Ok(())
1233 }
1234}