aoe2_probe/
scenario.rs

1use miniz_oxide::{deflate::compress_to_vec, inflate::decompress_to_vec};
2use serde_repr::{Deserialize_repr, Serialize_repr};
3
4use crate::{
5    io::Source,
6    parse::{Encode, Token, TokenBuilder},
7    prebuilt::{ver1_46, ver1_47, ver1_48, ver1_49, ver1_51, ver1_53},
8};
9use std::{
10    fs::{self, File, OpenOptions},
11    io::{Read, Write},
12};
13
14pub struct Scenario {
15    pub versio: Token,
16    version: String,
17}
18
19impl Scenario {
20    /// Read scenario data from the given file.
21    /// Please check the github homepage for the version support status.
22    /// Generally, ver.1.46 and above are well supported.
23    /// # Examples
24    ///
25    /// ```
26    /// use aoe2_probe::Scenario;
27    /// let scenario = Scenario::from_file("./resources/chapter_1.aoe2scenario");
28    /// ```
29    pub fn from_file(filename: &str) -> Result<Self, String> {
30        let mut file = File::open(&filename).expect("File not found");
31        let metadata = fs::metadata(&filename).expect("Unable to read metadata");
32        let mut buffer = vec![0; metadata.len() as usize];
33        file.read_exact(&mut buffer).expect("buffer overflow");
34
35        Self::from_le_vec(buffer)
36    }
37
38    /// Read scenario data from the given little endian buffer.
39    /// # Examples
40    ///
41    /// ```
42    /// use aoe2_probe::Scenario;
43    /// //Encode a scenario to a little endian vector of uint8
44    /// let source_scenario = Scenario::from_file("./resources/chapter_1.aoe2scenario").unwrap();
45    /// let buffer = source_scenario.to_le_export_vec();
46    ///
47    /// //Decode a scenario from a little endian vector of uint8
48    /// let scenario = Scenario::from_le_vec(buffer);
49    /// ```
50    pub fn from_le_vec(buffer: Vec<u8>) -> Result<Self, String> {
51        let version = Self::get_scenario_version(&buffer);
52        let mut source = Source::new(buffer);
53        match version.as_str() {
54            "1.46" => {
55                let header = TokenBuilder::create_from_template(
56                    &ver1_46::FileHeader::template(),
57                    &mut source,
58                )?;
59
60                let mut uncompressed = header.to_le_vec();
61                let content = decompress_to_vec(&source.get_rest_vec()).unwrap();
62                uncompressed.extend(content);
63
64                let mut source = Source::new(uncompressed);
65                Ok(Scenario {
66                    versio: TokenBuilder::create_from_template(
67                        &ver1_46::Versio::template(),
68                        &mut source,
69                    )?,
70                    version,
71                })
72            }
73            "1.47" => {
74                let header = TokenBuilder::create_from_template(
75                    &ver1_47::FileHeader::template(),
76                    &mut source,
77                )?;
78
79                let mut uncompressed = header.to_le_vec();
80                let content = decompress_to_vec(&source.get_rest_vec()).unwrap();
81                uncompressed.extend(content);
82
83                let mut source = Source::new(uncompressed);
84                Ok(Scenario {
85                    versio: TokenBuilder::create_from_template(
86                        &ver1_47::Versio::template(),
87                        &mut source,
88                    )?,
89                    version,
90                })
91            }
92            "1.48" => {
93                let header = TokenBuilder::create_from_template(
94                    &ver1_48::FileHeader::template(),
95                    &mut source,
96                )?;
97
98                let mut uncompressed = header.to_le_vec();
99                let content = decompress_to_vec(&source.get_rest_vec()).unwrap();
100                uncompressed.extend(content);
101
102                let mut source = Source::new(uncompressed);
103                Ok(Scenario {
104                    versio: TokenBuilder::create_from_template(
105                        &ver1_48::Versio::template(),
106                        &mut source,
107                    )?,
108                    version,
109                })
110            }
111            "1.49" => {
112                let header = TokenBuilder::create_from_template(
113                    &ver1_49::FileHeader::template(),
114                    &mut source,
115                )?;
116
117                let mut uncompressed = header.to_le_vec();
118                let content = decompress_to_vec(&source.get_rest_vec()).unwrap();
119                uncompressed.extend(content);
120
121                let mut source = Source::new(uncompressed);
122                Ok(Scenario {
123                    versio: TokenBuilder::create_from_template(
124                        &ver1_49::Versio::template(),
125                        &mut source,
126                    )?,
127                    version,
128                })
129            }
130            "1.51" => {
131                let header = TokenBuilder::create_from_template(
132                    &ver1_51::FileHeader::template(),
133                    &mut source,
134                )?;
135
136                let mut uncompressed = header.to_le_vec();
137                let content = decompress_to_vec(&source.get_rest_vec()).unwrap();
138                uncompressed.extend(content);
139
140                let mut source = Source::new(uncompressed);
141                Ok(Scenario {
142                    versio: TokenBuilder::create_from_template(
143                        &ver1_49::Versio::template(),
144                        &mut source,
145                    )?,
146                    version,
147                })
148            }
149            "1.53" => {
150                let header = TokenBuilder::create_from_template(
151                    &ver1_53::FileHeader::template(),
152                    &mut source,
153                )?;
154
155                let mut uncompressed = header.to_le_vec();
156                let content = decompress_to_vec(&source.get_rest_vec()).unwrap();
157                uncompressed.extend(content);
158
159                let mut source = Source::new(uncompressed);
160                Ok(Scenario {
161                    versio: TokenBuilder::create_from_template(
162                        &ver1_49::Versio::template(),
163                        &mut source,
164                    )?,
165                    version,
166                })
167            }
168            _ => Err("Unsupported version!".to_string()),
169        }
170    }
171
172    pub fn from_versio(versio: &Token) -> Result<Self, String> {
173        let version = versio
174            .get_by_path("/file_header/version")
175            .try_c4()
176            .content()
177            .clone();
178
179        return Ok(Scenario {
180            version,
181            versio: versio.clone(),
182        });
183    }
184
185    /// Encode a scenario struct to little endian vector of uint8
186    /// If you want to generate an .aoe2scenario file, then this function are not recommended.
187    /// The whole .aoe2scenario except file header part are compressed.
188    /// The bytes generated by this function are raw uncompressed.
189    /// See function to_export_le_vec for compressed bytes.  
190    /// # Examples
191    ///
192    /// ```
193    /// use aoe2_probe::Scenario;
194    /// //Encode a scenario to a little endian vector of uint8
195    /// let source_scenario = Scenario::from_file("./resources/chapter_1.aoe2scenario").unwrap();
196    /// let buffer = source_scenario.to_le_vec();
197    /// ```
198    pub fn to_le_vec(self) -> Vec<u8> {
199        self.versio.to_le_vec()
200    }
201
202    /// Encode a scenario struct to little endian vector of uint8 that can be read by AoE2 DE
203    /// # Examples
204    ///
205    /// ```
206    /// use aoe2_probe::Scenario;
207    /// //Encode a scenario to a little endian vector of uint8
208    /// let source_scenario = Scenario::from_file("./resources/chapter_1.aoe2scenario").unwrap();
209    /// let buffer = source_scenario.to_le_export_vec();
210    /// ```
211    pub fn to_le_export_vec(&self) -> Vec<u8> {
212        let file_header = &self.versio.try_map()["file_header"];
213        let mut content = file_header.to_le_vec();
214        let header_size = content.len();
215
216        let uncompressed = &self.versio.to_le_vec()[header_size..];
217        let mut compressed = compress_to_vec(uncompressed, 6);
218
219        content.append(&mut compressed);
220
221        content
222    }
223
224    /// Save the given scenario to the given path.
225    /// The path will be created if not existed.
226    /// # Examples
227    ///
228    /// ```
229    /// use aoe2_probe::{Scenario, ExportFormat};
230    /// let source_scenario = Scenario::from_file("./resources/chapter_1.aoe2scenario").unwrap();
231    /// let buffer = source_scenario.to_file("./resources/temp.aoe2scenario", ExportFormat::AoE2Scenario);
232    /// ```
233    pub fn to_file(&self, file_path: &str, format: ExportFormat) -> Result<(), String> {
234        match format {
235            ExportFormat::AoE2Scenario => self.to_aoe2scenario(file_path),
236            ExportFormat::JSON => self.to_json_file(file_path),
237        }
238    }
239
240    pub fn to_aoe2scenario(&self, file_path: &str) -> Result<(), String> {
241        let buffer = self.to_le_export_vec();
242
243        let path = std::path::Path::new(file_path);
244        let prefix = path.parent().expect("Fail to get parent path!");
245        std::fs::create_dir_all(prefix).expect("Fail to create directory!");
246
247        let mut file = OpenOptions::new()
248            .create(true)
249            .write(true)
250            .open(file_path)
251            .unwrap();
252
253        file.write_all(&buffer).expect("Fail to write content!");
254        Ok(())
255    }
256
257    pub fn to_json_file(&self, file_path: &str) -> Result<(), String> {
258        let buffer = serde_json::to_string(&self.versio).expect("Fail to convert to json format");
259
260        let path = std::path::Path::new(file_path);
261        let prefix = path.parent().expect("Fail to get parent path!");
262        std::fs::create_dir_all(prefix).expect("Fail to create directory!");
263
264        let mut file = OpenOptions::new()
265            .create(true)
266            .write(true)
267            .open(file_path)
268            .unwrap();
269
270        file.write_all(buffer.as_bytes())
271            .expect("Fail to write content!");
272        Ok(())
273    }
274
275    pub fn version(&self) -> &str {
276        &self.version
277    }
278
279    fn get_scenario_version(buffer: &[u8]) -> String {
280        std::str::from_utf8(&buffer[0..4]).unwrap().to_string()
281    }
282}
283
284#[derive(Serialize_repr, Deserialize_repr, PartialEq, Clone, Debug)]
285#[repr(u8)]
286pub enum ExportFormat {
287    AoE2Scenario = 0,
288    JSON = 1,
289}