source2_demo/parser/
mod.rs1mod context;
2mod demo;
3mod observer;
4
5pub use context::*;
6pub use demo::runner::*;
7pub use observer::*;
8
9use crate::error::*;
10use crate::proto::*;
11use crate::reader::*;
12use std::cell::RefCell;
13use std::rc::Rc;
14
15use crate::parser::demo::DemoCommands;
16use crate::try_observers;
17#[cfg(feature = "dota")]
18use std::collections::VecDeque;
19
20pub struct Parser<'a, R = SliceReader<'a>>
21where
22 R: BitsReader + MessageReader,
23{
24 pub(crate) reader: R,
25 pub(crate) field_reader: FieldReader,
26
27 pub(crate) observers: Vec<Rc<RefCell<dyn Observer + 'a>>>,
28 pub(crate) observer_masks: Vec<Interests>,
29 pub(crate) global_mask: Interests,
30
31 #[cfg(feature = "dota")]
32 pub(crate) combat_log: VecDeque<CMsgDotaCombatLogEntry>,
33
34 pub(crate) prologue_completed: bool,
35 pub(crate) skip_deltas: bool,
36
37 pub(crate) replay_info: CDemoFileInfo,
38 pub(crate) last_tick: u32,
39 pub(crate) context: Context,
40
41 _phantom: std::marker::PhantomData<&'a ()>,
42}
43
44impl<'a> Parser<'a, SliceReader<'a>> {
45 pub fn new(replay: &'a [u8]) -> Result<Self, ParserError> {
46 let mut reader = SliceReader::new(replay);
47
48 if replay.len() < 16 || reader.read_bytes(8) != b"PBDEMS2\0" {
49 return Err(ParserError::WrongMagic);
50 };
51
52 reader.read_bytes(8);
53
54 let replay_info = reader.read_replay_info()?;
55 let last_tick = replay_info.playback_ticks() as u32;
56
57 reader.seek(16);
58
59 Ok(Parser {
60 reader,
61 field_reader: FieldReader::default(),
62
63 observers: Vec::default(),
64 observer_masks: Vec::default(),
65 global_mask: Interests::empty(),
66
67 #[cfg(feature = "dota")]
68 combat_log: VecDeque::default(),
69
70 prologue_completed: false,
71 skip_deltas: false,
72
73 context: Context::new(replay_info.clone()),
74
75 replay_info,
76 last_tick,
77 _phantom: std::marker::PhantomData,
78 })
79 }
80
81 #[inline]
82 pub fn from_slice(replay: &'a [u8]) -> Result<Self, ParserError> {
83 Self::new(replay)
84 }
85}
86
87impl<S> Parser<'static, SeekableReader<S>>
88where
89 S: std::io::Read + std::io::Seek,
90{
91 pub fn from_reader(reader: S) -> Result<Self, ParserError> {
92 let mut reader = SeekableReader::new(reader)
93 .map_err(|e| ParserError::IoError(e.to_string()))?;
94
95 let magic = reader.read_bytes(8);
96 if magic != b"PBDEMS2\0" {
97 return Err(ParserError::WrongMagic);
98 }
99
100 reader.read_bytes(8);
101
102 let replay_info = Self::read_file_info_from_reader(&mut reader)?;
103 let last_tick = replay_info.playback_ticks() as u32;
104
105 reader.seek(16);
106
107 Ok(Parser {
108 reader,
109 field_reader: FieldReader::default(),
110 observers: Vec::default(),
111 observer_masks: Vec::default(),
112 global_mask: Interests::empty(),
113
114 #[cfg(feature = "dota")]
115 combat_log: VecDeque::default(),
116
117 prologue_completed: false,
118 skip_deltas: false,
119
120 context: Context::new(replay_info.clone()),
121
122 replay_info,
123 last_tick,
124 _phantom: std::marker::PhantomData,
125 })
126 }
127
128 fn read_file_info_from_reader(reader: &mut SeekableReader<S>) -> Result<CDemoFileInfo, ParserError> {
129 reader.seek(8);
130 let offset_bytes = reader.read_bytes(4);
131 let offset = u32::from_le_bytes([offset_bytes[0], offset_bytes[1], offset_bytes[2], offset_bytes[3]]) as usize;
132
133 reader.seek(offset);
134
135 if let Some(msg) = reader.read_next_message()? {
136 Ok(CDemoFileInfo::decode(msg.buf.as_slice())?)
137 } else {
138 Err(ParserError::ReplayEncodingError)
139 }
140 }
141}
142
143impl<'a, R> Parser<'a, R>
144where
145 R: BitsReader + MessageReader,
146{
147 pub fn context(&self) -> &Context {
148 &self.context
149 }
150
151 pub fn replay_info(&self) -> &CDemoFileInfo {
152 &self.replay_info
153 }
154
155 pub fn register_observer<T>(&mut self) -> Rc<RefCell<T>>
158 where
159 T: Observer + Default + 'a,
160 {
161 let rc = Rc::new(RefCell::new(T::default()));
162 let mask = rc.borrow().interests();
163 self.global_mask |= mask;
164 self.observer_masks.push(mask);
165 self.observers.push(rc.clone());
166 rc.clone()
167 }
168
169 #[inline]
170 fn anyone_interested(&self, flag: Interests) -> bool {
171 self.global_mask.intersects(flag)
172 }
173
174 pub(crate) fn prologue(&mut self) -> Result<(), ParserError> {
175 if self.prologue_completed && self.context.tick != u32::MAX {
176 return Ok(());
177 }
178
179 while let Some(message) = self.reader.read_next_message()? {
180 if self.prologue_completed
181 && (message.msg_type == EDemoCommands::DemSendTables
182 || message.msg_type == EDemoCommands::DemClassInfo)
183 {
184 continue;
185 }
186
187 self.on_demo_command(message.msg_type, message.buf.as_slice())?;
188
189 if message.msg_type == EDemoCommands::DemSyncTick {
190 self.prologue_completed = true;
191 break;
192 }
193 }
194
195 Ok(())
196 }
197
198 pub(crate) fn on_demo_command(
199 &mut self,
200 msg_type: EDemoCommands,
201 msg: &[u8],
202 ) -> Result<(), ParserError> {
203 match msg_type {
204 EDemoCommands::DemSendTables => {
205 self.dem_send_tables(CDemoSendTables::decode(msg)?)?;
206 }
207 EDemoCommands::DemClassInfo => {
208 self.dem_class_info(CDemoClassInfo::decode(msg)?)?;
209 }
210 EDemoCommands::DemPacket | EDemoCommands::DemSignonPacket => {
211 self.dem_packet(CDemoPacket::decode(msg)?)?;
212 }
213 EDemoCommands::DemFullPacket => self.dem_full_packet(CDemoFullPacket::decode(msg)?)?,
214 EDemoCommands::DemStringTables => {
215 self.dem_string_tables(CDemoStringTables::decode(msg)?)?
216 }
217 EDemoCommands::DemStop => {
218 self.dem_stop()?;
219 }
220 _ => {}
221 };
222
223 try_observers!(self, DEMO, on_demo_command(&self.context, msg_type, msg))?;
224 Ok(())
225 }
226}
227
228impl<S> Parser<'static, SeekableReader<S>>
229where
230 S: std::io::Read + std::io::Seek,
231{
232 #[cfg(feature = "deadlock")]
233 pub fn deadlock_match_details(&mut self) -> Result<CMsgMatchMetaDataContents, ParserError> {
234 self.reader.read_deadlock_match_details()
235 }
236}
237
238impl<'a> Parser<'a, SliceReader<'a>> {
239 #[cfg(feature = "deadlock")]
240 pub fn deadlock_match_details(&mut self) -> Result<CMsgMatchMetaDataContents, ParserError> {
241 self.reader.read_deadlock_match_details()
242 }
243}