polytrack_codes/v4/
mod.rs1#![allow(clippy::cast_possible_truncation)]
2#[cfg(test)]
3mod tests;
4
5use crate::tools::{self, Track, hash_vec};
6
7pub const CP_IDS: [u8; 4] = [52, 65, 75, 77];
8
9#[derive(Debug, PartialEq, Eq)]
10pub struct TrackInfo {
11 pub parts: Vec<Part>,
12}
13
14#[derive(Debug, PartialEq, Eq)]
15pub struct Part {
16 pub id: u8,
17 pub amount: u32,
18 pub blocks: Vec<Block>,
19}
20
21#[derive(Debug, PartialEq, Eq)]
22pub struct Block {
23 pub x: i32,
24 pub y: i32,
25 pub z: i32,
26
27 pub rotation: u8,
28 pub cp_order: Option<u16>,
29}
30
31#[must_use]
32pub fn decode_track_code(track_code: &str) -> Option<Track> {
33 let track_code = track_code.get(2..)?;
34 let metadata = tools::decode(track_code.get(..2)?)?;
35 let name_len = *metadata.first()? as usize;
36 let track_name_raw = tools::decode(track_code.get(2..2 + name_len)?)?;
37 let name = String::from_utf8(track_name_raw).ok()?;
38 let track_data = tools::decompress(&tools::decode(track_code.get(2 + name_len..)?)?)?;
39 Some(Track {
40 name,
41 author: None,
42 track_data,
43 })
44}
45
46#[must_use]
47pub fn decode_track_data(data: &[u8]) -> Option<TrackInfo> {
48 #[inline]
49 fn read_u8(buf: &[u8], offset: &mut usize) -> Option<u8> {
50 let res = buf.get(*offset).copied();
51 *offset += 1;
52 res
53 }
54 #[inline]
55 fn read_u16(buf: &[u8], offset: &mut usize) -> Option<u16> {
56 let res = Some(u16::from(*buf.get(*offset)?) | (u16::from(*buf.get(*offset + 1)?) << 8));
57 *offset += 2;
58 res
59 }
60 #[inline]
61 fn read_u32(buf: &[u8], offset: &mut usize) -> Option<u32> {
62 let res = Some(
63 u32::from(*buf.get(*offset)?)
64 | (u32::from(*buf.get(*offset + 1)?) << 8)
65 | (u32::from(*buf.get(*offset + 2)?) << 16)
66 | (u32::from(*buf.get(*offset + 3)?) << 24),
67 );
68 *offset += 4;
69 res
70 }
71 #[inline]
72 fn read_i24(buf: &[u8], offset: &mut usize) -> Option<i32> {
73 let res = Some(
74 i32::from(*buf.get(*offset)?)
75 | (i32::from(*buf.get(*offset + 1)?) << 8)
76 | (i32::from(*buf.get(*offset + 2)?) << 16),
77 );
78 *offset += 3;
79 res
80 }
81
82 let mut offset = 0;
83 let mut parts = Vec::new();
84 while offset < data.len() {
85 let id = read_u16(data, &mut offset)? as u8;
86 let amount = read_u32(data, &mut offset)?;
87
88 let mut blocks = Vec::new();
89 for _ in 0..amount {
90 let x = read_i24(data, &mut offset)? - i32::pow(2, 23);
91 let y = read_i24(data, &mut offset)?;
92 let z = read_i24(data, &mut offset)? - i32::pow(2, 23);
93
94 let rotation = read_u8(data, &mut offset)? & 3;
95
96 let cp_order = if CP_IDS.contains(&id) {
97 Some(read_u16(data, &mut offset)?)
98 } else {
99 None
100 };
101 blocks.push(Block {
102 x,
103 y,
104 z,
105 rotation,
106 cp_order,
107 });
108 }
109 parts.push(Part { id, amount, blocks });
110 }
111
112 Some(TrackInfo { parts })
113}
114
115#[must_use]
116pub fn export_to_id(track_code: &str) -> Option<String> {
117 let track_data = decode_track_code(track_code)?;
118 let id = hash_vec(track_data.track_data);
119 Some(id)
120}