source_demo_tool/
lib.rs

1pub mod demo_file;
2pub mod engine_types;
3pub mod event_data;
4
5mod parse_tools;
6pub mod protobuf_message;
7
8pub extern crate source_demo_tool_impl_proc_macros;
9
10/*
11#[cfg(test)]
12mod tests {
13    use std::{ fs::File, io::Write, collections::BTreeMap, };
14
15    use crate::demo_file::{
16        frame::Command,
17        packet::netmessage::NetMessage,
18    };
19    use super::*;
20
21    fn full_file_open_routine(filepath: &str, tag: &str) -> Result<(), String> {
22        let demo_file = match demo_file::DemoFile::open(filepath) {
23            Ok(df) => df,
24            Err(e) => return Err(format!("couldn't open test file: {e}"))
25        };
26
27        let output_first_100_frames = format!("{:#?}", &demo_file.frames[0..100]);
28        let mut out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-first_100_frames.txt").unwrap();
29        out_file.write_all(output_first_100_frames.as_bytes()).unwrap();
30
31        let out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-summary.txt").unwrap();
32        summarize_demo_file(&demo_file, out_file);
33
34        let header = format!("{:#?}", &demo_file.header);
35        let mut out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-header.txt").unwrap();
36        out_file.write_all(header.as_bytes()).unwrap();
37
38        let mut user_messages = Vec::new();
39        let mut game_events = Vec::new();
40
41        let mut first_1000_entity_data = Vec::new();
42        for frame in &demo_file.frames {
43            if let Command::Packet(packet) = &frame.command {
44                for netmsg in &packet.network_messages {
45                    let msg = netmsg.message.as_ref().unwrap();
46                    match msg {
47                        NetMessage::PacketEntity(pe) => {
48                            if first_1000_entity_data.len() == 1000 {
49                                continue
50                            }
51                            first_1000_entity_data.push(pe);
52                        },
53                        NetMessage::UserMessage(um) => {
54                            user_messages.push(um);
55                        },
56                        NetMessage::GameEvent(ge) => {
57                            game_events.push(ge);
58                        },
59                        _ => {}
60                    }
61                }
62            }
63        }
64
65        // raw entity data
66        let mut output_raw_entity_data = Vec::new();
67        for ed in first_1000_entity_data {
68            output_raw_entity_data.append(&mut ed.entity_data.clone().unwrap());
69            output_raw_entity_data.append(&mut [0xDE, 0xAD, 0xFF, 0xDE, 0xAD, 0xFF].to_vec());
70        }
71        let mut out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-first_1000_entity_data.raw").unwrap();
72        out_file.write_all(output_raw_entity_data.as_slice()).unwrap();
73
74        // sign on frames
75        let mut out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-signon_frames.txt").unwrap();
76        out_file.write_all(format!{"{:#?}", &demo_file.sign_on_frames}.as_bytes()).unwrap();
77
78        // user messages
79        let mut output_user_messages = Vec::new();
80        for um in user_messages {
81            output_user_messages.append(&mut um.msg_type.unwrap().to_le_bytes().to_vec());
82            output_user_messages.append(&mut um.msg_data.as_ref().unwrap().clone());
83            output_user_messages.append(&mut [0xDE, 0xAD, 0xFF, 0xDE, 0xAD, 0xFF].to_vec());
84        }
85        let mut out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-user_messages.raw").unwrap();
86        out_file.write_all(output_user_messages.as_slice()).unwrap();
87
88        // data tables
89        let output_data_tables = format!("{:#?}", demo_file.get_data_tables());
90        let mut out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-data_tables.txt").unwrap();
91        out_file.write_all(output_data_tables.as_bytes()).unwrap();
92
93        // game events
94        let output_game_events = format!("{:#?}", game_events);
95        let mut out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-game_events.txt").unwrap();
96        out_file.write_all(output_game_events.as_bytes()).unwrap();
97
98        // server info
99        let output_server_info = format!("{:#?}", demo_file.get_server_info().unwrap());
100        let mut out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-server_info.txt").unwrap();
101        out_file.write_all(output_server_info.as_bytes()).unwrap();
102
103        // sign on data summary
104        {
105            let mut command_counter = BTreeMap::new();
106            let mut message_counter = BTreeMap::new();
107            let mut class_info = None;
108            let mut server_info = None;
109            let mut game_event_list = None;
110            let mut set_con_var = None;
111            let mut set_view = None;
112            let mut sign_on_states = Vec::new();
113            let mut tick = None;
114            let mut voice_init = None;
115            let mut create_string_table_data_messages = Vec::new();
116            for f in &demo_file.sign_on_frames {
117                match &f.command {
118                    Command::DataTables(_dt) => {
119                        command_counter.entry("DataTables").and_modify(|e| {*e+=1}).or_insert(1);
120                    },
121                    Command::Packet(_pd) => {
122                        command_counter.entry("Packet").and_modify(|e| {*e+=1}).or_insert(1);
123                    },
124                    Command::SignOn(pd) => {
125                        command_counter.entry("SignOn").and_modify(|e| {*e+=1}).or_insert(1);
126
127                        for msg in &pd.network_messages {
128                            if let Some(nmsg) = &msg.message {
129                                message_counter.entry(nmsg.to_string()).and_modify(|e| {*e+=1}).or_insert(1);
130                                match &nmsg {
131                                    NetMessage::ClassInfo(ci) => class_info = Some(ci),
132                                    NetMessage::ServerInfo(si) => server_info = Some(si),
133                                    NetMessage::GameEventList(gel) => game_event_list = Some(gel),
134                                    NetMessage::SetConVar(scv) => set_con_var = Some(scv),
135                                    NetMessage::SetView(sv) => set_view = Some(sv),
136                                    NetMessage::SignOnState(sos) => sign_on_states.push(sos),
137                                    NetMessage::Tick(td) => tick = Some(td),
138                                    NetMessage::VoiceInit(vid) => voice_init = Some(vid),
139                                    NetMessage::CreateStringTable(cstd) => create_string_table_data_messages.push(cstd),
140                                    _ => {}
141                                }
142                                
143                            }
144                        }
145                    },
146                    Command::Stop => {
147                        command_counter.entry("Stop").and_modify(|e| {*e+=1}).or_insert(1);
148                    },
149                    Command::SyncTick => {
150                        command_counter.entry("SyncTick").and_modify(|e| {*e+=1}).or_insert(1);
151                    }
152                }
153            }
154
155            let output_sod_summary = format! {
156                "\
157                command_counter \
158                    {:#?}\n\
159                message_counter \
160                    {:#?}\n\
161                class_info \
162                    {:#?}\n\
163                server_info \
164                    {:#?}\n\
165                set_con_var \
166                    {:#?}\n\
167                set_view \
168                    {:#?}\n\
169                sign_on_state \
170                    {:#?}\n\
171                tick \
172                    {:#?}\n\
173                voice_init \
174                    {:#?}\n\
175                "
176                , command_counter, message_counter, class_info, server_info, set_con_var, set_view,
177                sign_on_states, tick, voice_init
178            };
179            let mut out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-sign_on_data_summary.txt").unwrap();
180            out_file.write_all(output_sod_summary.as_bytes()).unwrap();
181
182            // game event list
183            let mut out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-game_event_list.txt").unwrap();
184            out_file.write_all(format!{"{:#?}", game_event_list}.as_bytes()).unwrap();
185
186            // create string table data
187            let mut out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-create_string_table.txt").unwrap();
188            out_file.write_all(format!{"{:#?}", create_string_table_data_messages}.as_bytes()).unwrap();
189        }
190
191        // dump full game events
192        let full_game_events = demo_file.get_full_game_events();
193        let mut out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-full_game_events.txt").unwrap();
194            out_file.write_all(format!{"{:#?}", &full_game_events}.as_bytes()).unwrap();
195
196        let mut game_event_counter = BTreeMap::new();
197        let mut game_event_id_name_map = BTreeMap::new();
198        for ev in &full_game_events {
199            game_event_counter.entry(&ev.event_id).and_modify(|e|{*e+=1}).or_insert(1);
200            game_event_id_name_map.insert(&ev.event_id, &ev.event_name);
201        }
202
203        let mut out_file = File::create("test_output/file_full_open-".to_owned() + tag + "-game_events_summary.txt").unwrap();
204        out_file.write_all(format!("{:>6}{:>42}{:>10}\n", "id", "name", "count").as_bytes()).unwrap();
205        for id in game_event_id_name_map {
206            out_file.write_all(format!("{:>6}{:>42}{:>10}\n", id.0, id.1, game_event_counter[id.0]).as_bytes()).unwrap();
207        }
208
209        Ok(())
210    }
211
212    #[test]
213    fn file_full_gotv_open1_full_gotv() -> Result<(), String> {
214        full_file_open_routine("assets/test_demos/full_gotv.dem", "full_gotv")
215    }
216
217    #[test]
218    fn file_full_gotv_open2_glitched() -> Result<(), String> {
219        full_file_open_routine("assets/test_demos/glitched_texture_r14_acor.dem", "glitched")
220    }
221
222    #[test]
223    fn file_full_gotv_open3_full_gotv() -> Result<(), String> {
224        full_file_open_routine("assets/test_demos/gamerlegion-vs-masonic-m1-vertigo.dem", "full_gotv3")
225    }
226
227    #[test]
228    fn file_full_gotv_open4_full_gotv() -> Result<(), String> {
229        full_file_open_routine("assets/test_demos/gamerlegion-vs-masonic-m3-ancient.dem", "full_gotv4")
230    }
231
232    fn summarize_demo_file(demo_file: &demo_file::DemoFile, mut out_file: File) {
233        let mut unknown_message_counter = BTreeMap::new();
234        let mut unknown_field_counter = BTreeMap::new();
235        let mut missing_field_counter = BTreeMap::new();
236        let mut invalid_or_corrupt_events = BTreeMap::new();
237        let mut frame_command_counter = BTreeMap::new();
238        let mut frame_player_slot_counter = BTreeMap::new();
239        let mut message_counter = BTreeMap::new();
240        let mut first_10_sub_warnings = Vec::new();
241        let mut user_message_counter = BTreeMap::new();
242
243        for frame in &demo_file.frames {
244            frame_command_counter.entry(frame.command.as_u8())
245                .and_modify(|e| {*e += 1 })
246                .or_insert(1);
247
248            frame_player_slot_counter.entry(&frame.player_slot)
249                .and_modify(|e| { *e += 1 })
250                .or_insert(1);
251
252            if let Command::Packet(p) = &frame.command {
253                for netmsg in &p.network_messages {
254                    if let Some(msg) = &netmsg.message {
255                        message_counter.entry(msg.to_string())
256                            .and_modify(|e| { *e += 1 })
257                            .or_insert(1);
258                        if let NetMessage::UserMessage(umd) = &netmsg.message.as_ref().unwrap() {
259                            user_message_counter.entry(umd.msg_type.unwrap())
260                            .and_modify(|e|{*e+=1}).or_insert(1);
261                        }
262                    }
263
264                    if let Some(msg_warns) = &netmsg.warnings {
265                        let message_name = netmsg.message.as_ref().unwrap().to_string();
266                        for field in &msg_warns.missing_fields {
267                            missing_field_counter.entry((message_name.clone(), field.0, field.1))
268                                .and_modify(|e| { *e += 1 })
269                                .or_insert(1);
270                        }
271                        for field in &msg_warns.unknown_fields {
272                            unknown_field_counter.entry((message_name.clone(), field.field_number))
273                                .and_modify(|(_, counter)| { *counter += 1})
274                                .or_insert((field.clone(), 1));
275                        }
276                        for field in &msg_warns.sub_warnings {
277                            if first_10_sub_warnings.len() < 10 {
278                                if !(field.missing_fields.is_empty() &&
279                                    field.unknown_fields.is_empty() &&
280                                    field.sub_warnings.is_empty()) {
281                                    first_10_sub_warnings.push((message_name.clone(), field));
282                                }
283                            }
284                        }
285                    }
286
287                    if let Some(msg) = &netmsg.err {
288                        type ErrT = demo_file::packet::ParseMessageErr;
289                        match msg {
290                            ErrT::InvalidOrCorrupt(ev) => { invalid_or_corrupt_events.entry(ev)
291                                .and_modify(|e| {*e += 1 })
292                                .or_insert(1); },
293                            ErrT::UnknownCommand(n) => {
294                                unknown_message_counter.entry(n)
295                                    .and_modify(|e| { *e += 1 })
296                                    .or_insert(1);
297                            }
298                        }
299                    }
300                }
301            }
302        }
303
304        out_file.write_all(b"Unknown Messages -- msg_num: Count\n").unwrap();
305        out_file.write_all(format!("{:#?}", unknown_message_counter).as_bytes()).unwrap();
306        out_file.write_all(b"\n").unwrap();
307
308        out_file.write_all(b"Missing Fields -- (message_name, field_num, field_name): Count\n").unwrap();
309        out_file.write_all(format!("{:#?}", missing_field_counter).as_bytes()).unwrap();
310        out_file.write_all(b"\n").unwrap();
311
312        out_file.write_all(b"Unknown Fields -- (message_name, field_num): Count\n").unwrap();
313        out_file.write_all(format!("{:#?}", unknown_field_counter).as_bytes()).unwrap();
314        out_file.write_all(b"\n").unwrap();
315
316        out_file.write_all(b"Invalid or corrupt frame -- Event: Count\n").unwrap();
317        out_file.write_all(format!("{:#?}", invalid_or_corrupt_events).as_bytes()).unwrap();
318        out_file.write_all(b"\n").unwrap();
319
320        out_file.write_all(b"Frame Commands -- Command: Count\n").unwrap();
321        out_file.write_all(format!("{:#?}", frame_command_counter).as_bytes()).unwrap();
322        out_file.write_all(b"\n").unwrap();
323
324        out_file.write_all(b"Player Slot -- player_slot: Count\n").unwrap();
325        out_file.write_all(format!("{:#?}", frame_player_slot_counter).as_bytes()).unwrap();
326        out_file.write_all(b"\n").unwrap();
327
328        out_file.write_all(b"Messages -- message_name: Count\n").unwrap();
329        out_file.write_all(format!("{:#?}", message_counter).as_bytes()).unwrap();
330        out_file.write_all(b"\n").unwrap();
331
332        out_file.write_all(b"First 10 sub-warnings\n").unwrap();
333        out_file.write_all(format!("{:#?}", first_10_sub_warnings).as_bytes()).unwrap();
334        out_file.write_all(b"\n").unwrap();
335
336        out_file.write_all(b"UserMessages -- msg_type: Count\n").unwrap();
337        out_file.write_all(format!("{:#?}", user_message_counter).as_bytes()).unwrap();
338        out_file.write_all(b"\n").unwrap();
339
340        out_file.write_all(b"UserMessage warnings & errors\n").unwrap();
341        let user_messages = demo_file.get_user_messages();
342        let mut user_messages_warnings = Vec::new();
343        let mut user_messages_errors = Vec::new();
344        let mut first_10_sub_warnings = Vec::new();
345        for um in &user_messages {
346            if let Some(warns) = &um.warnings {
347                for sub_warn in &warns.sub_warnings {
348                    if !(sub_warn.missing_fields.is_empty() && sub_warn.unknown_fields.is_empty()) {
349                        first_10_sub_warnings.push(sub_warn);
350                    }
351                }
352
353                if !(warns.missing_fields.is_empty() && warns.unknown_fields.is_empty()) {
354                    user_messages_warnings.push(warns);
355                }
356
357            }
358
359            if let Some(err) = &um.err {
360                user_messages_errors.push(err);
361            }
362        }
363        out_file.write_all(format!("\
364        Warnings \
365          {:#?}\n\
366        Errors \
367          {:#?}\n\
368        First 10 sub warnings \
369          {:#?}\
370        ", user_messages_warnings, user_messages_errors, first_10_sub_warnings).as_bytes()).unwrap();
371    }
372}
373*/