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 replace_entity_field(
139        &mut self,
140        event: EntityEvents,
141        entity: &Entity,
142        field_name: &str,
143        value: &FieldValue,
144    ) -> Option<FieldValue> {
145        let ctx = &self.parser.context;
146        self.rewriters
147            .iter_mut()
148            .filter(|rewriter| {
149                rewriter
150                    .interests()
151                    .contains(RewriteInterests::ENTITY_FIELDS)
152            })
153            .find_map(|rewriter| {
154                rewriter.replace_entity_field(ctx, event, entity, field_name, value)
155            })
156    }
157
158    fn has_rewriters(&self, interests: RewriteInterests) -> bool {
159        self.rewriter_interests.intersects(interests)
160    }
161
162    fn rewrites_entity_fields(&self) -> bool {
163        self.has_rewriters(RewriteInterests::ENTITY_FIELDS)
164    }
165
166    fn rewrites_string_table_entries(&self) -> bool {
167        self.has_rewriters(RewriteInterests::STRING_TABLE_ENTRIES)
168    }
169
170    fn needs_string_table_context(&self) -> bool {
171        self.rewrites_entity_fields() || self.rewrites_string_table_entries()
172    }
173
174    fn needs_packet_scan(&self) -> bool {
175        self.has_rewriters(RewriteInterests::PACKET_MESSAGE | RewriteInterests::PACKET_MESSAGES)
176            || self.rewrites_entity_fields()
177            || self.rewrites_string_table_entries()
178            || self.has_rewriters(
179                RewriteInterests::SVC_CREATE_STRING_TABLE
180                    | RewriteInterests::SVC_UPDATE_STRING_TABLE,
181            )
182    }
183
184    fn needs_svc_packet_scan(&self) -> bool {
185        self.needs_string_table_context()
186            || self.has_rewriters(
187                RewriteInterests::SVC_CREATE_STRING_TABLE
188                    | RewriteInterests::SVC_UPDATE_STRING_TABLE,
189            )
190    }
191
192    fn needs_packet_state(&self) -> bool {
193        self.needs_string_table_context()
194    }
195
196    fn needs_class_metadata(&self) -> bool {
197        self.rewrites_entity_fields()
198    }
199
200    fn needs_demo_string_table_scan(&self) -> bool {
201        self.needs_string_table_context()
202            || self.has_rewriters(RewriteInterests::DEMO_STRING_TABLES)
203    }
204
205    fn needs_demo_string_table_state(&self) -> bool {
206        self.rewrites_string_table_entries()
207    }
208
209    /// Returns the wrapped parser and output target.
210    pub fn into_parts(self) -> (Parser<'a, R>, W) {
211        (self.parser, self.writer)
212    }
213}
214
215impl<'a, W> DemoWriter<'a, SliceReader<'a>, W>
216where
217    W: Write + Seek,
218{
219    /// Creates a demo writer from replay bytes and an output target.
220    ///
221    /// This is a convenience wrapper around [`Parser::from_slice`] and
222    /// [`DemoWriter::new`].
223    pub fn from_slice(replay: &'a [u8], writer: W) -> Result<Self, ParserError> {
224        Ok(Self::new(Parser::from_slice(replay)?, writer))
225    }
226}
227
228impl<S, W> DemoWriter<'static, SeekableReader<S>, W>
229where
230    S: std::io::Read + std::io::Seek,
231    W: Write + Seek,
232{
233    /// Creates a demo writer from a seekable reader and an output target.
234    ///
235    /// This is a convenience wrapper around [`Parser::from_reader`] and
236    /// [`DemoWriter::new`].
237    pub fn from_reader(reader: S, writer: W) -> Result<Self, ParserError> {
238        Ok(Self::new(Parser::from_reader(reader)?, writer))
239    }
240}