Skip to main content

polytrack_codes/v2/
mod.rs

1#![allow(clippy::cast_possible_truncation)]
2#[cfg(test)]
3mod tests;
4
5use crate::tools::prelude::*;
6use base64::prelude::*;
7
8#[derive(Debug, PartialEq, Eq, Clone)]
9pub struct TrackInfo {
10    pub parts: Vec<Part>,
11}
12
13#[derive(Debug, PartialEq, Eq, Clone)]
14pub struct Part {
15    pub id: u8,
16    pub amount: u32,
17    pub blocks: Vec<Block>,
18}
19
20#[derive(Debug, PartialEq, Eq, Clone)]
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
35fn encode(input: &[u8]) -> String {
36    let base64_encoded = BASE64_STANDARD_NO_PAD.encode(input);
37    base64_encoded.replace('+', "-").replace('/', "_")
38}
39
40#[must_use]
41pub fn decode_track_code(track_code: &str) -> Option<Track> {
42    let track_code = track_code.get(3..)?;
43    let metadata = decode(track_code.get(..2)?)?;
44    let name_len = *metadata.first()? as usize;
45    let name = track_code.get(2..2 + name_len)?.to_string();
46    let track_data = decode(track_code.get(2 + name_len..)?)?;
47    Some(Track {
48        name,
49        author: None,
50        last_modified: None,
51        track_data,
52    })
53}
54
55#[must_use]
56/// Encodes the given track struct into a track code.
57/// Returns [`None`] if something failed in the process.
58///
59/// Output might differ slightly from Polytrack's output
60/// because of Zlib shenanigans, but is still compatible.
61pub fn encode_track_code(track: &Track) -> Option<String> {
62    let track_data = encode(&track.track_data);
63
64    let metadata = encode(&[(track.name.len()) as u8]);
65
66    // prepend the "v1n"
67    let track_code = String::from("v1n") + &metadata + &track.name + &track_data;
68    Some(track_code)
69}
70
71#[must_use]
72pub fn decode_track_data(data: &[u8]) -> Option<TrackInfo> {
73    let mut offset = 0;
74    let mut parts = Vec::new();
75    while offset < data.len() {
76        let id = read_u16(data, &mut offset)? as u8;
77        let amount = read_u32(data, &mut offset)?;
78
79        let mut blocks = Vec::new();
80        for _ in 0..amount {
81            let x = read_i24(data, &mut offset)? - i32::pow(2, 23);
82            let y = read_i24(data, &mut offset)?;
83            let z = read_i24(data, &mut offset)? - i32::pow(2, 23);
84
85            let rotation = read_u8(data, &mut offset)? & 3;
86
87            blocks.push(Block { x, y, z, rotation });
88        }
89        parts.push(Part { id, amount, blocks });
90    }
91
92    Some(TrackInfo { parts })
93}
94
95#[must_use]
96/// Encodes the `TrackInfo` struct into raw binary data.
97pub fn encode_track_data(track_info: &TrackInfo) -> Option<Vec<u8>> {
98    let mut data = Vec::new();
99    for part in &track_info.parts {
100        write_u16(&mut data, part.id.into());
101        write_u32(&mut data, part.amount);
102        for block in &part.blocks {
103            write_u24(&mut data, (block.x + i32::pow(2, 23)).cast_unsigned());
104            write_u24(&mut data, block.y.cast_unsigned());
105            write_u24(&mut data, (block.z + i32::pow(2, 23)).cast_unsigned());
106            data.push(block.rotation);
107        }
108    }
109
110    Some(data)
111}