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