Skip to main content

source2_demo/parser/demo/writer/
mod.rs

1mod baseline;
2mod entity;
3mod input;
4mod output;
5mod packet;
6mod packet_state;
7mod rewriter;
8mod run;
9mod string_table;
10
11use crate::entity::field::FieldValue;
12use crate::entity::{Entity, EntityEvents};
13use crate::error::ParserError;
14use crate::parser::Parser;
15use crate::reader::{BitsReader, MessageReader, SeekableReader, SliceReader};
16use crate::string_table::PackedStringTableState;
17use std::cell::RefCell;
18use std::io::{Seek, Write};
19use std::rc::Rc;
20
21use input::RawDemoMessage;
22pub use rewriter::{
23    rewrite_protobuf_message, DemoRewriter, MessageRewrite, PacketMessage, RewriteInterests,
24};
25
26const INSTANCE_BASELINE_TABLE: &str = "instancebaseline";
27
28/// Demo writer that reads demo messages and writes a rewritten stream.
29///
30/// The writer maintains the parser metadata needed for the registered
31/// rewrites, such as serializers, classes, string tables, baselines, and
32/// entity state. Output targets must be seekable so the demo header can be
33/// patched after writing.
34pub struct DemoWriter<'a, R, W>
35where
36    R: BitsReader + MessageReader,
37    W: Write + Seek,
38{
39    parser: Parser<'a, R>,
40    writer: W,
41    string_table_rewrite_states: Vec<Option<PackedStringTableState>>,
42    rewriters: Vec<Box<dyn DemoRewriter + 'a>>,
43    rewriter_interests: RewriteInterests,
44    bytes_written: u64,
45    file_info_offset: Option<u64>,
46}
47
48impl<'a, R, W> DemoWriter<'a, R, W>
49where
50    R: BitsReader + MessageReader,
51    W: Write + Seek,
52{
53    /// Creates a new demo writer from an existing parser and output target.
54    pub fn new(parser: Parser<'a, R>, writer: W) -> Self {
55        Self {
56            parser,
57            writer,
58            string_table_rewrite_states: Vec::new(),
59            rewriters: Vec::new(),
60            rewriter_interests: RewriteInterests::empty(),
61            bytes_written: 0,
62            file_info_offset: None,
63        }
64    }
65
66    /// Adds an already constructed demo rewriter and returns a handle to it.
67    ///
68    /// Use this when the rewriter needs custom constructor state. Rewriters run
69    /// in registration order; message callbacks see the output of earlier
70    /// rewriters.
71    pub fn add_rewriter<T>(&mut self, rewriter: T) -> Rc<RefCell<T>>
72    where
73        T: DemoRewriter + 'a,
74    {
75        let rewriter = Rc::new(RefCell::new(rewriter));
76        self.rewriter_interests |= rewriter.borrow().interests();
77        self.rewriters.push(Box::new(rewriter.clone()));
78        rewriter
79    }
80
81    /// Registers a default demo rewriter and returns a handle to it.
82    ///
83    /// This mirrors
84    /// [`Parser::register_observer`](crate::Parser::register_observer): the
85    /// writer constructs `T::default()`, registers it, and returns an
86    /// `Rc<RefCell<T>>` so callers can inspect accumulated state after
87    /// writing.
88    ///
89    /// # Examples
90    ///
91    /// ```ignore
92    /// # use source2_demo::prelude::*;
93    /// # use source2_demo::proto::CDotaUserMsgChatMessage;
94    /// # use source2_demo::writer::*;
95    /// # use std::fs::File;
96    /// #[derive(Default)]
97    /// struct RemoveChat;
98    ///
99    /// #[rewriter]
100    /// impl RemoveChat {
101    ///     #[rewrite_packet_message]
102    ///     fn remove_chat(
103    ///         &mut self,
104    ///         _message: CDotaUserMsgChatMessage,
105    ///     ) -> Result<MessageRewrite, ParserError> {
106    ///         Ok(MessageRewrite::Drop)
107    ///     }
108    /// }
109    ///
110    /// # fn main() -> anyhow::Result<()> {
111    /// # let input = File::open("input.dem")?;
112    /// # let output = File::create("output.dem")?;
113    /// let mut writer = DemoWriter::from_reader(input, output)?;
114    /// writer.register_rewriter::<RemoveChat>();
115    /// writer.run()?;
116    /// # Ok(())
117    /// # }
118    /// ```
119    pub fn register_rewriter<T>(&mut self) -> Rc<RefCell<T>>
120    where
121        T: DemoRewriter + Default + 'a,
122    {
123        self.add_rewriter(T::default())
124    }
125
126    pub(crate) fn should_rewrite_entity(&mut self, event: EntityEvents, entity: &Entity) -> bool {
127        let ctx = &self.parser.context;
128        self.rewriters
129            .iter_mut()
130            .filter(|rewriter| {
131                rewriter
132                    .interests()
133                    .contains(RewriteInterests::ENTITY_FIELDS)
134            })
135            .all(|rewriter| rewriter.should_rewrite_entity(ctx, event, entity))
136    }
137
138    pub(crate) fn should_track_entity(&mut self, event: EntityEvents, entity: &Entity) -> bool {
139        let ctx = &self.parser.context;
140        self.rewriters
141            .iter_mut()
142            .filter(|rewriter| {
143                rewriter
144                    .interests()
145                    .contains(RewriteInterests::ENTITY_FIELDS)
146            })
147            .all(|rewriter| rewriter.should_track_entity(ctx, event, entity))
148    }
149
150    pub(crate) fn replace_entity_field(
151        &mut self,
152        event: EntityEvents,
153        entity: &Entity,
154        field_name: &str,
155        value: &FieldValue,
156    ) -> Option<FieldValue> {
157        let ctx = &self.parser.context;
158        self.rewriters
159            .iter_mut()
160            .filter(|rewriter| {
161                rewriter
162                    .interests()
163                    .contains(RewriteInterests::ENTITY_FIELDS)
164            })
165            .find_map(|rewriter| {
166                rewriter.replace_entity_field(ctx, event, entity, field_name, value)
167            })
168    }
169
170    fn has_rewriters(&self, interests: RewriteInterests) -> bool {
171        self.rewriter_interests.intersects(interests)
172    }
173
174    fn rewrites_entity_fields(&self) -> bool {
175        self.has_rewriters(RewriteInterests::ENTITY_FIELDS)
176    }
177
178    fn rewrites_string_table_entries(&self) -> bool {
179        self.has_rewriters(RewriteInterests::STRING_TABLE_ENTRIES)
180    }
181
182    fn needs_string_table_context(&self) -> bool {
183        self.rewrites_entity_fields() || self.rewrites_string_table_entries()
184    }
185
186    fn needs_packet_scan(&self) -> bool {
187        self.has_rewriters(RewriteInterests::PACKET_MESSAGE | RewriteInterests::PACKET_MESSAGES)
188            || self.rewrites_entity_fields()
189            || self.rewrites_string_table_entries()
190            || self.has_rewriters(
191                RewriteInterests::SVC_CREATE_STRING_TABLE
192                    | RewriteInterests::SVC_UPDATE_STRING_TABLE,
193            )
194    }
195
196    fn needs_svc_packet_scan(&self) -> bool {
197        self.needs_string_table_context()
198            || self.has_rewriters(
199                RewriteInterests::SVC_CREATE_STRING_TABLE
200                    | RewriteInterests::SVC_UPDATE_STRING_TABLE,
201            )
202    }
203
204    fn needs_packet_state(&self) -> bool {
205        self.needs_string_table_context()
206    }
207
208    fn needs_class_metadata(&self) -> bool {
209        self.rewrites_entity_fields()
210    }
211
212    fn needs_demo_string_table_scan(&self) -> bool {
213        self.needs_string_table_context()
214            || self.has_rewriters(RewriteInterests::DEMO_STRING_TABLES)
215    }
216
217    fn needs_demo_string_table_state(&self) -> bool {
218        self.rewrites_string_table_entries()
219    }
220
221    /// Returns the wrapped parser and output target.
222    pub fn into_parts(self) -> (Parser<'a, R>, W) {
223        (self.parser, self.writer)
224    }
225}
226
227impl<'a, W> DemoWriter<'a, SliceReader<'a>, W>
228where
229    W: Write + Seek,
230{
231    /// Creates a demo writer from replay bytes and an output target.
232    ///
233    /// This is a convenience wrapper around [`Parser::from_slice`] and
234    /// [`DemoWriter::new`].
235    pub fn from_slice(replay: &'a [u8], writer: W) -> Result<Self, ParserError> {
236        Ok(Self::new(Parser::from_slice(replay)?, writer))
237    }
238}
239
240impl<S, W> DemoWriter<'static, SeekableReader<S>, W>
241where
242    S: std::io::Read + std::io::Seek,
243    W: Write + Seek,
244{
245    /// Creates a demo writer from a seekable reader and an output target.
246    ///
247    /// This is a convenience wrapper around [`Parser::from_reader`] and
248    /// [`DemoWriter::new`].
249    pub fn from_reader(reader: S, writer: W) -> Result<Self, ParserError> {
250        Ok(Self::new(Parser::from_reader(reader)?, writer))
251    }
252}