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 let track_code = track_code.get(10..)?;
124 let td_start = track_code.find("4p")?;
126 let track_data = track_code.get(td_start..)?;
127
128 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 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 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 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}