1pub mod parser;
2
3use fmt::{Debug, Display};
4use serde::{Deserialize, Serialize};
5use std::{convert::TryFrom, fmt};
6
7use enum_repr::EnumRepr;
8
9#[derive(Serialize, Deserialize)]
11pub struct JPEGData(pub Vec<u8>);
12
13impl fmt::Display for JPEGData {
14 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15 write!(f, "JPEG({}B)", self.0.len())
16 }
17}
18
19impl fmt::Debug for JPEGData {
20 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21 fmt::Display::fmt(self, f)
22 }
23}
24
25#[derive(Debug, Serialize, Deserialize)]
30pub struct GBX {
31 pub origin: GBXOrigin,
32 pub filesize: usize,
33 header_start: usize,
34 header_length: usize,
35 thumbnail_start: Option<usize>,
36 thumbnail_length: Option<usize>,
37 pub thumbnail: Option<JPEGData>,
38 pub bin_header: GBXBinaryHeader,
39 pub challenge_header: Option<ChallengeXMLHeader>,
40 pub replay_header: Option<ReplayXMLHeader>,
41 pub header_xml: String,
42}
43
44impl Display for GBX {
45 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46 fn unoption<T: Display>(o: &Option<&T>) -> String {
47 o.map(|x| format!("{}", x))
48 .unwrap_or_else(|| "Not present".to_owned())
49 }
50 write!(
51 f,
52 "GBX Info Dump (Size={}B)\nFrom file={}\n\nMagic\n=====\n{}\n\nChallenge\n=========\n{}\n\nReplay\n======\n{}",
53 self.filesize, self.origin, self.bin_header, unoption(&self.challenge_header.as_ref()), unoption(&self.replay_header.as_ref())
54 )
55 }
56}
57
58#[derive(Debug, Serialize, Deserialize)]
63pub enum GBXOrigin {
64 File {
65 path: String,
66 },
67 Buffer,
68 Unknown,
69 Hidden,
71}
72
73impl Default for GBXOrigin {
74 fn default() -> Self {
75 GBXOrigin::Unknown
76 }
77}
78
79impl Display for GBXOrigin {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 GBXOrigin::File { path } => write!(f, "{}", path),
83 GBXOrigin::Buffer => write!(f, "<buffer>"),
84 GBXOrigin::Unknown => write!(f, "<unknown>"),
85 GBXOrigin::Hidden => write!(f, "<hidden>"),
86 }
87 }
88}
89
90#[derive(Debug, Serialize, Deserialize, Default)]
91pub struct ChallengeXMLHeader {
92 pub maptype: MapType,
93 pub mapversion: GBXVersion,
94 pub exever: String,
95 pub uid: String,
96 pub name: String,
97 pub author: String,
98 pub envir: Environment,
99 pub mood: Mood,
100 pub desctype: DescType,
101 pub nblaps: u32,
102 pub price: u32,
103 pub times: Option<Times>,
105 pub dependencies: Vec<Dependency>,
106}
107
108impl fmt::Display for ChallengeXMLHeader {
109 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
110 let dependency_files: Vec<&String> = self.dependencies.iter().map(|x| &x.file).collect();
111 write!(f, "Map is {:?}/{:?} made in {:?}/{}\nUID: {}\nName: {}\nAuthor: {}\nSetting: {:?}/{:?}\nNumber of laps: {}\nPrice: {}\nTimes: {}\nDependencies[{}]: {:?}",
112 self.maptype, self.desctype, self.mapversion, self.exever, self.uid, self.name, self.author, self.envir, self.mood, self.nblaps, self.price, self.times.as_ref().map_or(String::from("<not set>"), |x| format!("{}", x)), self.dependencies.len(), dependency_files,
113 )
114 }
115}
116
117#[derive(Debug, Serialize, Deserialize, Default)]
118pub struct GBXBinaryHeader {
119 pub version: u16,
120 pub class_id: u32,
121}
122
123impl Display for GBXBinaryHeader {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 write!(
126 f,
127 "v{}, class id: {:08x} ({:?})",
128 self.version,
129 self.class_id,
130 MapClass::try_from(self.class_id).map_or("unknown".to_owned(), |c| format!("{:?}", c))
131 )
132 }
133}
134
135#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
136pub struct ReplayXMLHeader {
137 pub version: GBXVersion,
139 pub exever: String,
141 pub challenge_uid: String,
143 pub score: ReplayScore,
145}
146
147impl Display for ReplayXMLHeader {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 write!(
150 f,
151 "Version: {:?}\nExever.: {}\nChallenge: {}\nScore: {}",
152 self.version, self.exever, self.challenge_uid, self.score
153 )
154 }
155}
156
157#[derive(Debug, Serialize, Deserialize, Default, PartialEq, Eq)]
158pub struct ReplayScore {
159 pub best: u32,
161 pub respawns: u32,
163 pub stuntscore: u32,
164 pub validable: bool,
165}
166
167impl Display for ReplayScore {
168 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
169 write!(
170 f,
171 "best={}, respawns={}, stuntscore={}, validable={}",
172 self.best, self.respawns, self.stuntscore, self.validable
173 )
174 }
175}
176
177#[derive(Debug, Serialize, Deserialize, Default)]
179pub struct Times {
180 pub bronze: Option<u32>,
181 pub silver: Option<u32>,
182 pub gold: Option<u32>,
183 pub authortime: Option<u32>,
184 pub authorscore: Option<u32>,
185}
186
187impl fmt::Display for Times {
188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
189 write!(
190 f,
191 "Bronze: {}, Silver: {}, Gold: {}, Authortime: {}, Authorscore: {}",
192 self.bronze
193 .map_or(String::from("<not set>"), |x| format!("{}", x)),
194 self.silver
195 .map_or(String::from("<not set>"), |x| format!("{}", x)),
196 self.gold
197 .map_or(String::from("<not set>"), |x| format!("{}", x)),
198 self.authortime
199 .map_or(String::from("<not set>"), |x| format!("{}", x)),
200 self.authorscore
201 .map_or(String::from("<not set>"), |x| format!("{}", x))
202 )
203 }
204}
205
206#[derive(Debug, Serialize, Deserialize, Default)]
207pub struct Dependency {
208 pub file: String,
209}
210
211#[derive(Debug, Serialize, Deserialize)]
212pub enum MapType {
213 Challenge,
214}
215
216impl TryFrom<&str> for MapType {
217 type Error = String;
218
219 fn try_from(value: &str) -> Result<Self, Self::Error> {
220 match value.to_lowercase().as_str() {
221 "challenge" => Ok(MapType::Challenge),
222 _ => Err(format!("Unknown map type: {}", value)),
223 }
224 }
225}
226
227impl Default for MapType {
228 fn default() -> Self {
229 MapType::Challenge
230 }
231}
232
233#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
234pub enum GBXVersion {
235 Unknown,
237 TMc6,
239 TMr7,
241}
242
243impl TryFrom<&str> for GBXVersion {
244 type Error = String;
245
246 fn try_from(value: &str) -> Result<Self, Self::Error> {
247 match value.to_lowercase().as_str() {
248 "tmc.6" => Ok(GBXVersion::TMc6),
249 "tmr.7" => Ok(GBXVersion::TMr7),
250 _ => Err(format!("Unknown GBX file version: {}", value)),
251 }
252 }
253}
254
255impl GBXVersion {
256 pub fn content_type(&self) -> GBXType {
258 match self {
259 GBXVersion::TMc6 => GBXType::Challenge,
260 GBXVersion::TMr7 => GBXType::Replay,
261 GBXVersion::Unknown => GBXType::Unknown,
262 }
263 }
264}
265
266#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
267pub enum GBXType {
268 Challenge,
269 Replay,
270 Unknown,
272}
273
274impl Default for GBXVersion {
275 fn default() -> Self {
276 GBXVersion::Unknown
277 }
278}
279
280#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
281pub enum Environment {
282 Stadium,
283}
284
285impl TryFrom<&str> for Environment {
286 type Error = String;
287
288 fn try_from(value: &str) -> Result<Self, Self::Error> {
289 match value.to_lowercase().as_str() {
290 "stadium" => Ok(Environment::Stadium),
291 _ => Err(format!("Unknown environment: {}", value)),
292 }
293 }
294}
295
296impl Default for Environment {
297 fn default() -> Self {
298 Environment::Stadium
299 }
300}
301
302#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
303pub enum Mood {
304 Day,
305 Sunset,
306 Sunrise,
307 Night,
308}
309
310impl TryFrom<&str> for Mood {
311 type Error = String;
312
313 fn try_from(value: &str) -> Result<Self, Self::Error> {
314 match value.to_lowercase().as_str() {
315 "day" => Ok(Mood::Day),
316 "sunset" => Ok(Mood::Sunset),
317 "sunrise" => Ok(Mood::Sunrise),
318 "night" => Ok(Mood::Night),
319 _ => Err(format!("Unknown mood: {}", value)),
320 }
321 }
322}
323
324impl Default for Mood {
325 fn default() -> Self {
326 Mood::Day
327 }
328}
329
330#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
331pub enum DescType {
332 Race,
333}
334
335impl TryFrom<&str> for DescType {
336 type Error = String;
337
338 fn try_from(value: &str) -> Result<Self, Self::Error> {
339 match value.to_lowercase().as_str() {
340 "race" => Ok(DescType::Race),
341 _ => Err(format!("Unknown desc.type: {}", value)),
342 }
343 }
344}
345
346impl Default for DescType {
347 fn default() -> Self {
348 DescType::Race
349 }
350}
351
352#[EnumRepr(type = "u32")]
353#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
355pub enum MapClass {
356 CGameCtnChallenge = 0x03043000,
357 CGameCtnCollectorList = 0x0301B000,
358 CGameCtnChallengeParameters = 0x0305B000,
359 CGameCtnBlockSkin = 0x03059000,
360 CGameWaypointSpecialProperty = 0x0313B000,
361 CGameCtnReplayRecord = 0x03093000,
362 CGameGhost = 0x0303F005,
363 CGameCtnGhost = 0x03092000,
364 CGameCtnCollector = 0x0301A000,
365 CGameCtnObjectInfo = 0x0301C000,
366 CGameCtnDecoration = 0x03038000,
367 CGameCtnCollection = 0x03033000,
368 CGameSkin = 0x03031000,
369 CGamePlayerProfile = 0x0308C000,
370 CMwNod = 0x01001000,
371}
372
373impl TryFrom<u32> for MapClass {
374 type Error = ();
375
376 fn try_from(value: u32) -> Result<Self, Self::Error> {
377 MapClass::from_repr(value).ok_or(())
378 }
379}