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 full_packet_offsets(&self) -> Result<Vec<(usize, i32)>> {
977 let data = &self.data()[HEADER_SIZE..];
978 let mut scan = ByteReader::new(data);
979 let mut offsets = Vec::new();
980 while scan.remaining() > 0 {
981 let pos = scan.position();
982 let header = match Self::read_cmd_header(&mut scan) {
983 Ok(h) => h,
984 Err(_) => break,
985 };
986 if header.cmd == dem::STOP {
987 break;
988 }
989 if header.cmd == dem::FULL_PACKET {
990 offsets.push((pos, header.tick));
991 }
992 scan.skip(header.body_size as usize)?;
993 }
994 Ok(offsets)
995 }
996
997 pub fn decode_segment<F>(
1015 &self,
1016 start: Option<usize>,
1017 end_tick: i32,
1018 class_filter: &std::collections::HashSet<&str>,
1019 mut on_tick: F,
1020 ) -> Result<()>
1021 where
1022 F: FnMut(&Context),
1023 {
1024 let mut ctx = self.parse_init()?;
1025 let data = &self.data()[HEADER_SIZE..];
1026 let mut reader = match start {
1027 Some(off) => ByteReader::new(&data[off..]),
1028 None => {
1029 let mut r = ByteReader::new(data);
1031 loop {
1032 if r.remaining() == 0 {
1033 return Ok(());
1034 }
1035 let header = Self::read_cmd_header(&mut r)?;
1036 if header.cmd == dem::SYNC_TICK {
1037 r.skip(header.body_size as usize)?;
1038 break;
1039 }
1040 if header.cmd == dem::STOP {
1041 return Ok(());
1042 }
1043 r.skip(header.body_size as usize)?;
1044 }
1045 r
1046 }
1047 };
1048
1049 let mut packet_buf = vec![0u8; BUF_SIZE];
1050 let mut body_buf = Vec::with_capacity(BUF_SIZE);
1051 let mut fp_buf = Vec::with_capacity(256);
1052 let mut field_decode_ctx = FieldDecodeContext::new(ctx.tick_interval);
1053
1054 let mut last_tick: i32 = -1;
1055 while reader.remaining() > 0 {
1056 let header = Self::read_cmd_header(&mut reader)?;
1057
1058 if header.cmd != dem::STOP && header.tick >= end_tick {
1061 if last_tick >= 0 {
1062 on_tick(&ctx);
1063 }
1064 return Ok(());
1065 }
1066
1067 if header.tick != last_tick && last_tick >= 0 {
1068 on_tick(&ctx);
1069 ctx.string_tables.clear_dirty();
1070 ctx.entities.clear_updated();
1071 }
1072 last_tick = header.tick;
1073 ctx.tick = header.tick;
1074
1075 if header.cmd == dem::STOP {
1076 if last_tick >= 0 {
1077 on_tick(&ctx);
1078 }
1079 break;
1080 }
1081
1082 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
1083
1084 match header.cmd {
1085 dem::FULL_PACKET => {
1086 let cmd = CDemoFullPacket::decode(&body_buf[..])?;
1087 if let Some(st) = cmd.string_table {
1088 ctx.string_tables.do_full_update(st);
1089 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1090 }
1091 if let Some(packet) = cmd.packet {
1092 let pkt_data = packet.data.unwrap_or_default();
1093 Self::process_packet_entities_filtered(
1094 &pkt_data,
1095 &mut ctx,
1096 &mut field_decode_ctx,
1097 &mut packet_buf,
1098 class_filter,
1099 &mut fp_buf,
1100 )?;
1101 }
1102 }
1103 dem::PACKET | dem::SIGNON_PACKET => {
1104 let cmd = CDemoPacket::decode(&body_buf[..])?;
1105 let pkt_data = cmd.data.unwrap_or_default();
1106 Self::process_packet_entities_filtered(
1107 &pkt_data,
1108 &mut ctx,
1109 &mut field_decode_ctx,
1110 &mut packet_buf,
1111 class_filter,
1112 &mut fp_buf,
1113 )?;
1114 }
1115 _ => {}
1116 }
1117 }
1118
1119 Ok(())
1120 }
1121
1122 pub fn distinct_ticks(&self) -> Result<Vec<i32>> {
1128 let data = &self.data()[HEADER_SIZE..];
1129 let mut scan = ByteReader::new(data);
1130
1131 let mut past_sync = false;
1133 while scan.remaining() > 0 {
1134 let header = Self::read_cmd_header(&mut scan)?;
1135 if header.cmd == dem::STOP {
1136 return Ok(Vec::new());
1137 }
1138 scan.skip(header.body_size as usize)?;
1139 if header.cmd == dem::SYNC_TICK {
1140 past_sync = true;
1141 break;
1142 }
1143 }
1144 if !past_sync {
1145 return Ok(Vec::new());
1146 }
1147
1148 let mut ticks = Vec::new();
1149 let mut last = i32::MIN;
1150 while scan.remaining() > 0 {
1151 let header = match Self::read_cmd_header(&mut scan) {
1152 Ok(h) => h,
1153 Err(_) => break,
1154 };
1155 if header.cmd == dem::STOP {
1156 break;
1157 }
1158 if header.tick != last && header.tick >= 0 {
1159 ticks.push(header.tick);
1160 last = header.tick;
1161 }
1162 scan.skip(header.body_size as usize)?;
1163 }
1164 Ok(ticks)
1165 }
1166
1167 pub fn run_to_end_with_events_filtered<F>(
1171 &self,
1172 class_filter: &std::collections::HashSet<&str>,
1173 mut on_tick: F,
1174 ) -> Result<()>
1175 where
1176 F: FnMut(&Context, &[GameEvent]),
1177 {
1178 let mut ctx = self.parse_init()?;
1179 let data = &self.data()[HEADER_SIZE..];
1180 let mut reader = ByteReader::new(data);
1181
1182 let mut packet_buf = vec![0u8; BUF_SIZE];
1183 let mut event_packet_buf = vec![0u8; BUF_SIZE];
1184 let mut body_buf = Vec::with_capacity(BUF_SIZE);
1185 let mut fp_buf = Vec::with_capacity(256);
1186 let mut field_decode_ctx = FieldDecodeContext::new(ctx.tick_interval);
1187
1188 let mut descriptors: HashMap<i32, EventDescriptor> = HashMap::new();
1189 let mut tick_events: Vec<GameEvent> = Vec::new();
1190
1191 while reader.remaining() > 0 {
1193 let header = Self::read_cmd_header(&mut reader)?;
1194 if header.cmd == dem::SYNC_TICK {
1195 reader.skip(header.body_size as usize)?;
1196 break;
1197 }
1198 if header.cmd == dem::STOP {
1199 return Ok(());
1200 }
1201 reader.skip(header.body_size as usize)?;
1202 }
1203
1204 let mut last_tick: i32 = -1;
1205
1206 while reader.remaining() > 0 {
1207 let header = Self::read_cmd_header(&mut reader)?;
1208
1209 if header.tick != last_tick && last_tick >= 0 {
1211 on_tick(&ctx, &tick_events);
1212 tick_events.clear();
1213 ctx.string_tables.clear_dirty();
1214 ctx.entities.clear_updated();
1215 }
1216 last_tick = header.tick;
1217 ctx.tick = header.tick;
1218
1219 if header.cmd == dem::STOP {
1220 if last_tick >= 0 {
1222 on_tick(&ctx, &tick_events);
1223 }
1224 break;
1225 }
1226
1227 Self::read_cmd_body(&mut reader, &header, &mut body_buf)?;
1228
1229 match header.cmd {
1230 dem::FULL_PACKET => {
1231 let cmd = CDemoFullPacket::decode(&body_buf[..])?;
1232
1233 if let Some(st) = cmd.string_table {
1234 ctx.string_tables.do_full_update(st);
1235 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1236 }
1237
1238 if let Some(packet) = cmd.packet {
1239 let pkt_data = packet.data.unwrap_or_default();
1240 Self::process_packet_entities_filtered(
1241 &pkt_data,
1242 &mut ctx,
1243 &mut field_decode_ctx,
1244 &mut packet_buf,
1245 class_filter,
1246 &mut fp_buf,
1247 )?;
1248 Self::process_packet_events(
1249 &pkt_data,
1250 header.tick,
1251 &mut descriptors,
1252 &mut tick_events,
1253 &mut event_packet_buf,
1254 )?;
1255 }
1256 }
1257 dem::PACKET | dem::SIGNON_PACKET => {
1258 let cmd = CDemoPacket::decode(&body_buf[..])?;
1259 let pkt_data = cmd.data.unwrap_or_default();
1260 Self::process_packet_entities_filtered(
1261 &pkt_data,
1262 &mut ctx,
1263 &mut field_decode_ctx,
1264 &mut packet_buf,
1265 class_filter,
1266 &mut fp_buf,
1267 )?;
1268 Self::process_packet_events(
1269 &pkt_data,
1270 header.tick,
1271 &mut descriptors,
1272 &mut tick_events,
1273 &mut event_packet_buf,
1274 )?;
1275 }
1276 _ => {}
1277 }
1278 }
1279
1280 Ok(())
1281 }
1282
1283 fn process_packet_entities(
1285 pkt_data: &[u8],
1286 ctx: &mut Context,
1287 field_decode_ctx: &mut FieldDecodeContext,
1288 packet_buf: &mut Vec<u8>,
1289 fp_buf: &mut Vec<crate::entity::field_path::FieldPath>,
1290 ) -> Result<()> {
1291 let mut br = BitReader::new(pkt_data);
1292
1293 while br.bits_remaining() > 8 {
1294 let msg_type = br.read_ubitvar()?;
1295 let size = br.read_uvarint32()? as usize;
1296
1297 if !matches!(
1300 msg_type,
1301 svc::CREATE_STRING_TABLE
1302 | svc::UPDATE_STRING_TABLE
1303 | svc::SERVER_INFO
1304 | svc::PACKET_ENTITIES
1305 ) {
1306 br.skip_bits(size * 8)?;
1307 continue;
1308 }
1309
1310 if size > packet_buf.len() {
1311 packet_buf.resize(size, 0);
1312 }
1313 br.read_bytes(&mut packet_buf[..size])?;
1314 let msg_data = &packet_buf[..size];
1315
1316 match msg_type {
1317 svc::CREATE_STRING_TABLE => {
1318 let msg = CsvcMsgCreateStringTable::decode(msg_data)?;
1319 if ctx.string_tables.handle_create(msg)? {
1320 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1321 }
1322 }
1323 svc::UPDATE_STRING_TABLE => {
1324 let msg = CsvcMsgUpdateStringTable::decode(msg_data)?;
1325 if ctx.string_tables.handle_update(msg)? {
1326 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1327 }
1328 }
1329 svc::SERVER_INFO => {
1330 let msg = CsvcMsgServerInfo::decode(msg_data)?;
1331 if let Some(ti) = msg.tick_interval {
1332 ctx.tick_interval = ti;
1333 field_decode_ctx.tick_interval = ti;
1334 let ratio = DEFAULT_TICK_INTERVAL / ti;
1335 ctx.full_packet_interval = DEFAULT_FULL_PACKET_INTERVAL * ratio as i32;
1336 }
1337 }
1338 svc::PACKET_ENTITIES => {
1339 let msg = CsvcMsgPacketEntities::decode(msg_data)?;
1340 ctx.entities.handle_packet_entities(
1341 msg,
1342 &ctx.class_info,
1343 &ctx.serializers,
1344 &ctx.string_tables,
1345 field_decode_ctx,
1346 fp_buf,
1347 )?;
1348 }
1349 _ => {}
1350 }
1351 }
1352
1353 Ok(())
1354 }
1355
1356 fn process_packet_entities_filtered(
1358 pkt_data: &[u8],
1359 ctx: &mut Context,
1360 field_decode_ctx: &mut FieldDecodeContext,
1361 packet_buf: &mut Vec<u8>,
1362 class_filter: &std::collections::HashSet<&str>,
1363 fp_buf: &mut Vec<crate::entity::field_path::FieldPath>,
1364 ) -> Result<()> {
1365 let mut br = BitReader::new(pkt_data);
1366
1367 while br.bits_remaining() > 8 {
1368 let msg_type = br.read_ubitvar()?;
1369 let size = br.read_uvarint32()? as usize;
1370
1371 if !matches!(
1374 msg_type,
1375 svc::CREATE_STRING_TABLE
1376 | svc::UPDATE_STRING_TABLE
1377 | svc::SERVER_INFO
1378 | svc::PACKET_ENTITIES
1379 ) {
1380 br.skip_bits(size * 8)?;
1381 continue;
1382 }
1383
1384 if size > packet_buf.len() {
1385 packet_buf.resize(size, 0);
1386 }
1387 br.read_bytes(&mut packet_buf[..size])?;
1388 let msg_data = &packet_buf[..size];
1389
1390 match msg_type {
1391 svc::CREATE_STRING_TABLE => {
1392 let msg = CsvcMsgCreateStringTable::decode(msg_data)?;
1393 if ctx.string_tables.handle_create(msg)? {
1394 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1395 }
1396 }
1397 svc::UPDATE_STRING_TABLE => {
1398 let msg = CsvcMsgUpdateStringTable::decode(msg_data)?;
1399 if ctx.string_tables.handle_update(msg)? {
1400 ctx.string_tables.update_instance_baselines(&ctx.class_info);
1401 }
1402 }
1403 svc::SERVER_INFO => {
1404 let msg = CsvcMsgServerInfo::decode(msg_data)?;
1405 if let Some(ti) = msg.tick_interval {
1406 ctx.tick_interval = ti;
1407 field_decode_ctx.tick_interval = ti;
1408 let ratio = DEFAULT_TICK_INTERVAL / ti;
1409 ctx.full_packet_interval = DEFAULT_FULL_PACKET_INTERVAL * ratio as i32;
1410 }
1411 }
1412 svc::PACKET_ENTITIES => {
1413 let msg = CsvcMsgPacketEntities::decode(msg_data)?;
1414 ctx.entities.handle_packet_entities_filtered(
1415 msg,
1416 &ctx.class_info,
1417 &ctx.serializers,
1418 &ctx.string_tables,
1419 field_decode_ctx,
1420 class_filter,
1421 fp_buf,
1422 )?;
1423 }
1424 _ => {}
1425 }
1426 }
1427
1428 Ok(())
1429 }
1430}