polytrack_codes/v2/
mod.rs

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