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)]
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
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        track_data,
51    })
52}
53
54#[must_use]
55/// Encodes the given track struct into a track code.
56/// Returns [`None`] if something failed in the process.
57///
58/// Output might differ slightly from Polytrack's output
59/// because of Zlib shenanigans, but is still compatible.
60pub fn encode_track_code(track: &Track) -> Option<String> {
61    let track_data = encode(&track.track_data);
62
63    let metadata = encode(&[(track.name.len()) as u8]);
64
65    // prepend the "v1n"
66    let track_code = String::from("v1n") + &metadata + &track.name + &track_data;
67    Some(track_code)
68}
69
70#[must_use]
71pub fn decode_track_data(data: &[u8]) -> Option<TrackInfo> {
72    let mut offset = 0;
73    let mut parts = Vec::new();
74    while offset < data.len() {
75        let id = read_u16(data, &mut offset)? as u8;
76        let amount = read_u32(data, &mut offset)?;
77
78        let mut blocks = Vec::new();
79        for _ in 0..amount {
80            let x = read_i24(data, &mut offset)? - i32::pow(2, 23);
81            let y = read_i24(data, &mut offset)?;
82            let z = read_i24(data, &mut offset)? - i32::pow(2, 23);
83
84            let rotation = read_u8(data, &mut offset)? & 3;
85
86            blocks.push(Block { x, y, z, rotation });
87        }
88        parts.push(Part { id, amount, blocks });
89    }
90
91    Some(TrackInfo { parts })
92}
93
94#[must_use]
95/// Encodes the `TrackInfo` struct into raw binary data.
96pub fn encode_track_data(track_info: &TrackInfo) -> Option<Vec<u8>> {
97    let mut data = Vec::new();
98    for part in &track_info.parts {
99        write_u16(&mut data, part.id.into());
100        write_u32(&mut data, part.amount);
101        for block in &part.blocks {
102            write_u24(&mut data, (block.x + i32::pow(2, 23)).cast_unsigned());
103            write_u24(&mut data, block.y.cast_unsigned());
104            write_u24(&mut data, (block.z + i32::pow(2, 23)).cast_unsigned());
105            data.push(block.rotation);
106        }
107    }
108
109    Some(data)
110}