Skip to main content

polytrack_codes/v6/
mod.rs

1#[cfg(test)]
2mod tests;
3
4use std::fmt::Display;
5
6use num_enum::TryFromPrimitive;
7
8use crate::tools::{self, prelude::*};
9use crate::{Block, Part, Track};
10
11pub const CP_IDS: [u8; 4] = [52, 65, 75, 77];
12pub const START_IDS: [u8; 4] = [5, 91, 92, 93];
13
14#[derive(Debug, PartialEq, Eq, Clone)]
15pub struct V6TrackInfo {
16    author: String,
17    last_modified: Option<u32>,
18}
19#[derive(Debug, PartialEq, Eq, Clone)]
20pub struct V6Track {
21    pub metadata: V6TrackMetadata,
22    pub parts: Vec<V6Part>,
23}
24#[derive(Debug, PartialEq, Eq, Clone, Copy)]
25pub struct V6TrackMetadata {
26    pub env: V6Environment,
27    pub sun_dir: u8,
28
29    pub min_x: i32,
30    pub min_y: i32,
31    pub min_z: i32,
32
33    pub data_bytes: u8,
34}
35
36#[derive(TryFromPrimitive, Debug, PartialEq, Eq, Clone, Copy)]
37#[repr(u8)]
38pub enum V6Environment {
39    Summer,
40    Winter,
41    Desert,
42}
43
44#[derive(Debug, PartialEq, Eq, Clone)]
45pub struct V6Part {
46    pub id: u8,
47    pub amount: u32,
48    pub blocks: Vec<V6Block>,
49}
50
51#[derive(Debug, PartialEq, Eq, Clone)]
52pub struct V6Block {
53    pub x: u32,
54    pub y: u32,
55    pub z: u32,
56
57    pub rotation: u8,
58    pub dir: V6Direction,
59
60    pub color: u8,
61    pub cp_order: Option<u16>,
62    pub start_order: Option<u32>,
63}
64
65#[derive(TryFromPrimitive, Debug, PartialEq, Eq, Clone, Copy)]
66#[repr(u8)]
67pub enum V6Direction {
68    YPos,
69    YNeg,
70    XPos,
71    XNeg,
72    ZPos,
73    ZNeg,
74}
75
76impl Track for V6Track {
77    type Part = V6Part;
78
79    type Metadata = V6TrackMetadata;
80    fn meta(&self) -> Self::Metadata {
81        self.metadata
82    }
83    fn parts(&self) -> Vec<Self::Part> {
84        self.parts.clone()
85    }
86
87    fn decode_meta(data: &[u8], offset: &mut usize) -> Option<Self::Metadata> {
88        let env = V6Environment::try_from(read_u8(data, offset)?).ok()?;
89        let sun_dir = read_u8(data, offset)?;
90
91        let min_x = read_u32(data, offset)?.cast_signed();
92        let min_y = read_u32(data, offset)?.cast_signed();
93        let min_z = read_u32(data, offset)?.cast_signed();
94
95        let data_bytes = read_u8(data, offset)?;
96
97        Some(V6TrackMetadata {
98            env,
99            sun_dir,
100
101            min_x,
102            min_y,
103            min_z,
104
105            data_bytes,
106        })
107    }
108    fn encode_meta(&self, data: &mut Vec<u8>) {
109        data.push(self.metadata.env as u8);
110        data.push(self.metadata.sun_dir);
111        write_u32(data, self.metadata.min_x.cast_unsigned());
112        write_u32(data, self.metadata.min_y.cast_unsigned());
113        write_u32(data, self.metadata.min_z.cast_unsigned());
114        data.push(self.metadata.data_bytes);
115    }
116    fn from_data(metadata: Self::Metadata, parts: Vec<Self::Part>) -> Self {
117        Self { metadata, parts }
118    }
119
120    type TrackInfo = V6TrackInfo;
121    fn decode_track_code(track_code: &str) -> Option<(String, Self::TrackInfo, Vec<u8>)> {
122        // only use the actual data, skipping the "PolyTrack2"
123        let track_code = track_code.get(10..)?;
124        // ZLIB header 0x78DA is always encoded to `4p` and other stuff
125        let td_start = track_code.find("4p")?;
126        let track_data = track_code.get(td_start..)?;
127
128        // (base64-decode and then decompress using zlib) x2
129        let step1 = tools::decode(track_data)?;
130        let step2 = tools::decompress(&step1)?;
131        let step2_str = String::from_utf8(step2).ok()?;
132        let step3 = tools::decode(&step2_str)?;
133        let step4 = tools::decompress(&step3)?;
134
135        let name_len = *step4.first()? as usize;
136        let name = String::from_utf8(step4.get(1..=name_len)?.to_vec()).ok()?;
137
138        let author_len = *step4.get(1 + name_len)? as usize;
139        let author = String::from_utf8(
140            step4
141                .get((name_len + 2)..(name_len + author_len + 2))?
142                .to_vec(),
143        )
144        .ok()?;
145
146        let lastmod_exists = *step4.get(2 + name_len + author_len)? as usize;
147        if lastmod_exists > 1 {
148            return None;
149        }
150        let last_modified = if lastmod_exists == 1 {
151            let pos = 3 + name_len + author_len;
152            Some(
153                u32::from(*step4.get(pos)?)
154                    | u32::from(*step4.get(pos + 1)?) << 8
155                    | u32::from(*step4.get(pos + 2)?) << 16
156                    | u32::from(*step4.get(pos + 3)?) << 24,
157            )
158        } else {
159            None
160        };
161        let track_data = step4
162            .get((name_len + author_len + 3 + if last_modified.is_some() { 4 } else { 0 })..)?
163            .to_vec();
164
165        Some((
166            name,
167            V6TrackInfo {
168                author,
169                last_modified,
170            },
171            track_data,
172        ))
173    }
174    fn encode_track_code(name: String, info: Self::TrackInfo, track_data: &[u8]) -> Option<String> {
175        let mut data: Vec<u8> = Vec::new();
176
177        let mut name = name.as_bytes().to_vec();
178        data.push(name.len().try_into().ok()?);
179        data.append(&mut name);
180
181        let mut author = info.author.as_bytes().to_vec();
182        data.push(author.len().try_into().ok()?);
183        data.append(&mut author);
184
185        if let Some(last_modified) = info.last_modified {
186            data.push(1);
187            data.append(&mut last_modified.to_le_bytes().to_vec());
188        } else {
189            data.push(0);
190        }
191
192        data.append(&mut track_data.into());
193
194        // (compress using zlib and then base62-encode) x2
195        let step1 = tools::compress_first(&data)?;
196        let step2_str = tools::encode(&step1)?;
197        let step2 = step2_str.as_bytes();
198        let step3 = tools::compress_final(step2)?;
199        let step4 = tools::encode(&step3)?;
200
201        // prepend the "PolyTrack2"
202        let track_code = String::from("PolyTrack2") + &step4;
203        Some(track_code)
204    }
205}
206impl Part for V6Part {
207    type Block = V6Block;
208    fn id(&self) -> u8 {
209        self.id
210    }
211    fn amount(&self) -> u32 {
212        self.amount
213    }
214    fn blocks(&self) -> Vec<Self::Block> {
215        self.blocks.clone()
216    }
217    fn from_data(id: u8, amount: u32, blocks: Vec<Self::Block>) -> Self {
218        Self { id, amount, blocks }
219    }
220
221    fn decode_header(data: &[u8], offset: &mut usize) -> Option<(u8, u32)> {
222        Some((read_u8(data, offset)?, read_u32(data, offset)?))
223    }
224    fn encode_header(&self, data: &mut Vec<u8>) {
225        data.push(self.id);
226        write_u32(data, self.amount);
227    }
228}
229impl Block for V6Block {
230    type Track = V6Track;
231
232    type Extra = (V6Direction, u8, Option<u16>, Option<u32>);
233    fn extra_data(&self) -> Self::Extra {
234        (self.dir, self.color, self.cp_order, self.start_order)
235    }
236
237    type Coord = u32;
238    fn pos(&self) -> (Self::Coord, Self::Coord, Self::Coord) {
239        (self.x, self.y, self.z)
240    }
241    fn rot(&self) -> u8 {
242        self.rotation
243    }
244
245    fn decode(
246        data: &[u8],
247        offset: &mut usize,
248        id: u8,
249        meta: <Self::Track as Track>::Metadata,
250    ) -> Option<Self> {
251        let data_bytes = meta.data_bytes;
252        let x_bytes = data_bytes & 3;
253        let y_bytes = (data_bytes >> 2) & 3;
254        let z_bytes = (data_bytes >> 4) & 3;
255
256        let mut x = 0;
257        for i in 0..x_bytes {
258            x |= u32::from(*data.get(*offset + (i as usize))?) << (8 * i);
259        }
260        *offset += x_bytes as usize;
261
262        let mut y = 0;
263        for i in 0..y_bytes {
264            y |= u32::from(*data.get(*offset + (i as usize))?) << (8 * i);
265        }
266        *offset += y_bytes as usize;
267
268        let mut z = 0;
269        for i in 0..z_bytes {
270            z |= u32::from(*data.get(*offset + (i as usize))?) << (8 * i);
271        }
272        *offset += z_bytes as usize;
273
274        let rot_dir = read_u8(data, offset)?;
275        let rotation = rot_dir & 3;
276        if rotation > 3 {
277            return None;
278        }
279        let dir = V6Direction::try_from((rot_dir >> 2) & 7).ok()?;
280        let color = read_u8(data, offset)?;
281        // no custom color support for now
282        if color > 3 && color < 32 && color > 40 {
283            return None;
284        }
285
286        let cp_order = if CP_IDS.contains(&id) {
287            Some(read_u16(data, offset)?)
288        } else {
289            None
290        };
291        let start_order = if START_IDS.contains(&id) {
292            Some(read_u32(data, offset)?)
293        } else {
294            None
295        };
296
297        Some(Self {
298            x,
299            y,
300            z,
301
302            rotation,
303            dir,
304
305            color,
306            cp_order,
307            start_order,
308        })
309    }
310    fn encode(&self, data: &mut Vec<u8>, meta: <Self::Track as Track>::Metadata) {
311        let data_bytes = meta.data_bytes;
312        let x_bytes = data_bytes & 3;
313        let y_bytes = (data_bytes >> 2) & 3;
314        let z_bytes = (data_bytes >> 4) & 3;
315
316        match x_bytes {
317            1 => write_u8(data, self.x),
318            2 => write_u16(data, self.x),
319            3 => write_u24(data, self.x),
320            4 => write_u32(data, self.x),
321            _ => {}
322        }
323        match y_bytes {
324            1 => write_u8(data, self.y),
325            2 => write_u16(data, self.y),
326            3 => write_u24(data, self.y),
327            4 => write_u32(data, self.y),
328            _ => {}
329        }
330        match z_bytes {
331            1 => write_u8(data, self.z),
332            2 => write_u16(data, self.z),
333            3 => write_u24(data, self.z),
334            4 => write_u32(data, self.z),
335            _ => {}
336        }
337        data.push(self.rotation & 3 | (self.dir as u8 & 7) << 2);
338        data.push(self.color);
339        if let Some(cp_order) = self.cp_order {
340            write_u16(data, cp_order.into());
341        }
342        if let Some(start_order) = self.start_order {
343            write_u32(data, start_order);
344        }
345    }
346}
347
348impl Display for V6Environment {
349    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
350        match self {
351            Self::Summer => write!(f, "Summer"),
352            Self::Winter => write!(f, "Winter"),
353            Self::Desert => write!(f, "Desert"),
354        }
355    }
356}