polytrack_codes/v3/
mod.rs

1#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
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_step1 = f64::from(*metadata.first()?);
36    let name_len = (name_len_step1 * 4.0 / 3.0).ceil() as usize;
37    let track_name_raw = tools::decode(track_code.get(2..2 + name_len)?)?;
38    let name = String::from_utf8(track_name_raw).ok()?;
39    let track_data = tools::decompress(&tools::decode(track_code.get(2 + name_len..)?)?)?;
40    Some(Track {
41        name,
42        author: None,
43        track_data,
44    })
45}
46
47#[must_use]
48pub fn decode_track_data(data: &[u8]) -> Option<TrackInfo> {
49    #[inline]
50    fn read_u8(buf: &[u8], offset: &mut usize) -> Option<u8> {
51        let res = buf.get(*offset).copied();
52        *offset += 1;
53        res
54    }
55    #[inline]
56    fn read_u16(buf: &[u8], offset: &mut usize) -> Option<u16> {
57        let res = Some(u16::from(*buf.get(*offset)?) | (u16::from(*buf.get(*offset + 1)?) << 8));
58        *offset += 2;
59        res
60    }
61    #[inline]
62    fn read_u32(buf: &[u8], offset: &mut usize) -> Option<u32> {
63        let res = Some(
64            u32::from(*buf.get(*offset)?)
65                | (u32::from(*buf.get(*offset + 1)?) << 8)
66                | (u32::from(*buf.get(*offset + 2)?) << 16)
67                | (u32::from(*buf.get(*offset + 3)?) << 24),
68        );
69        *offset += 4;
70        res
71    }
72    #[inline]
73    fn read_i24(buf: &[u8], offset: &mut usize) -> Option<i32> {
74        let res = Some(
75            i32::from(*buf.get(*offset)?)
76                | (i32::from(*buf.get(*offset + 1)?) << 8)
77                | (i32::from(*buf.get(*offset + 2)?) << 16),
78        );
79        *offset += 3;
80        res
81    }
82
83    let mut offset = 0;
84    let mut parts = Vec::new();
85    while offset < data.len() {
86        let id = read_u16(data, &mut offset)? as u8;
87        let amount = read_u32(data, &mut offset)?;
88
89        let mut blocks = Vec::new();
90        for _ in 0..amount {
91            let x = read_i24(data, &mut offset)? - i32::pow(2, 23);
92            let y = read_i24(data, &mut offset)?;
93            let z = read_i24(data, &mut offset)? - i32::pow(2, 23);
94
95            let rotation = read_u8(data, &mut offset)? & 3;
96
97            let cp_order = if CP_IDS.contains(&id) {
98                Some(read_u16(data, &mut offset)?)
99            } else {
100                None
101            };
102            blocks.push(Block {
103                x,
104                y,
105                z,
106                rotation,
107                cp_order,
108            });
109        }
110        parts.push(Part { id, amount, blocks });
111    }
112
113    Some(TrackInfo { parts })
114}
115
116#[must_use]
117pub fn export_to_id(track_code: &str) -> Option<String> {
118    let track_data = decode_track_code(track_code)?;
119    let data = track_data.track_data;
120    let id = hash_vec(data);
121    Some(id)
122}