Skip to main content

dem/
lib.rs

1//! GoldSrc demo parser and writer
2//!
3//! # Example
4//!
5//! ```ignore
6//! let mut demo = open_demo("./src/tests/demotest.dem").unwrap();
7//!
8//! for entry in &mut demo.directory.entries {
9//!     for frame in &mut entry.frames {
10//!         if let FrameData::NetworkMessage(ref mut box_type) = &mut frame.frame_data {
11//!             let data = &mut box_type.as_mut().1;
12//!             
13//!             if let MessageData::Parsed(messages) = &mut data.messages {
14//!                 messages.push(NetMessage::EngineMessage(Box::new(EngineMessage::SvcBad)));
15//!             };
16//!         }
17//!     }
18//! }
19//!
20//! demo.write_to_file("./src/tests/demo2test.dem").unwrap();
21//! ```
22use std::{ffi::OsStr, fs::OpenOptions, io::Read, path::Path};
23
24use types::{DeltaDecoderTable, Demo};
25
26mod byte_writer;
27mod delta;
28mod nom_helper;
29mod utils;
30
31pub mod bit;
32pub mod demo_parser;
33pub mod demo_writer;
34pub mod error;
35pub mod netmsg_doer;
36pub mod types;
37
38// need this to have the conversion function
39pub use crate::bit::BitSliceCast;
40
41use crate::{demo_parser::parse_demo, error::DemoError, types::MessageDataParseMode};
42pub use utils::bitslice_to_string;
43
44// /// Re-exporting hldemo to have latest changes than 0.3.0 hldemo
45// pub extern crate hldemo;
46
47/// Re-exporting bitvec to avoid clogging the main project
48pub extern crate bitvec;
49
50impl Demo {
51    /// It is faster to parse netmessage because it just is for some reasons
52    pub fn parse_from_file(
53        path: impl AsRef<OsStr> + AsRef<Path>,
54        netmsg_parse_mode: MessageDataParseMode,
55    ) -> Result<Self, DemoError> {
56        let mut file = OpenOptions::new().read(true).open(path)?;
57        let mut bytes: Vec<u8> = vec![];
58
59        file.read_to_end(&mut bytes)?;
60
61        Self::parse_from_bytes(bytes.as_slice(), netmsg_parse_mode)
62    }
63
64    pub fn parse_from_bytes(
65        demo_bytes: &[u8],
66        netmsg_parse_mode: MessageDataParseMode,
67    ) -> Result<Self, DemoError> {
68        parse_demo(demo_bytes, netmsg_parse_mode)
69            // discard errors because they aren't very helpful anyway
70            .map_err(|_| DemoError::ParseError)
71            .map(|(_, x)| x)
72    }
73}
74
75/// Opens a demo
76///
77/// # Example
78/// ```ignore
79/// let demo = open_demo("./tests/demotest.dem").unwrap();
80/// ```
81pub fn open_demo(demo_path: impl AsRef<Path> + AsRef<OsStr>) -> Result<Demo, DemoError> {
82    Demo::parse_from_file(demo_path, types::MessageDataParseMode::Parse)
83}
84
85pub fn open_demo_from_bytes(demo_bytes: &[u8]) -> Result<Demo, DemoError> {
86    Demo::parse_from_bytes(demo_bytes, types::MessageDataParseMode::Parse)
87}
88
89/// Writes a [`u32`] into [`types::BitVec`]
90///
91/// (number: u32, bit_count)
92#[macro_export]
93macro_rules! nbit_num {
94    ($num:expr, $bit:expr) => {{
95        use $crate::bit::BitWriter;
96
97        let mut writer = BitWriter::new();
98        writer.append_u32_range($num as u32, $bit);
99        writer.data
100    }};
101}
102
103/// Writes a string into [`types::BitVec`]
104#[macro_export]
105macro_rules! nbit_str {
106    ($name:expr) => {{
107        use $crate::bit::BitWriter;
108
109        let mut writer = BitWriter::new();
110        $name.as_bytes().iter().for_each(|s| writer.append_u8(*s));
111        writer.data
112    }};
113}
114
115#[cfg(test)]
116mod test {
117    use super::*;
118
119    #[test]
120    fn open() {
121        open_demo("./src/tests/demotest.dem").unwrap();
122    }
123
124    #[test]
125    fn open_without_netmessage() {
126        Demo::parse_from_file(
127            "./src/tests/demotest.dem",
128            types::MessageDataParseMode::Parse,
129        )
130        .unwrap();
131    }
132
133    #[test]
134    fn write_read() {
135        let dem = open_demo("./src/tests/demotest.dem").unwrap();
136        let a = dem.write_to_bytes();
137        let _dem = open_demo_from_bytes(&a).unwrap();
138    }
139
140    #[test]
141    fn read_a_lot() {
142        let folder = "./src/tests/";
143
144        std::fs::read_dir(folder)
145            .map(|res| {
146                res.filter_map(|entry| entry.ok()).for_each(|entry| {
147                    let path = entry.path();
148
149                    if path.is_dir() {
150                        return;
151                    }
152
153                    if path.extension().map(|ext| ext != "dem").unwrap_or(false) {
154                        return;
155                    }
156
157                    // let a = open_demo(path.as_path()).unwrap();
158
159                    assert!(
160                        open_demo(path.as_path()).is_ok(),
161                        "error opening {}",
162                        path.display()
163                    )
164                })
165            })
166            .unwrap_or_else(|_| assert!(false));
167    }
168
169    #[test]
170    fn read_write_read_a_lot() {
171        let folder = "./src/tests/";
172
173        if let Ok(read_dir_res) = std::fs::read_dir(folder) {
174            read_dir_res
175                .filter_map(|entry| entry.ok())
176                .for_each(|entry| {
177                    let path = entry.path();
178
179                    if path.is_dir() {
180                        return;
181                    }
182
183                    if path.extension().map(|ext| ext != "dem").unwrap_or(false) {
184                        return;
185                    }
186
187                    let a = open_demo(path.as_path());
188
189                    assert!(a.is_ok(), "error opening {}", path.display());
190
191                    let a = a.unwrap();
192
193                    let a_bytes = a.write_to_bytes();
194
195                    let a_again = open_demo_from_bytes(&a_bytes);
196
197                    assert!(
198                        a_again.is_ok(),
199                        "error parse write parse {}",
200                        path.display()
201                    )
202                })
203        } else {
204            panic!("cannot read dir for read_write_read_a_lot");
205        }
206    }
207}