polytrack_codes/v2/
mod.rs1#![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 RawTrackInfo {
10 pub parts: Vec<RawPart>,
11}
12#[derive(Debug, PartialEq, Eq, Clone)]
13pub struct RawPart {
14 pub id: u8,
15 pub amount: u32,
16 pub blocks: Vec<RawBlock>,
17}
18#[derive(Debug, PartialEq, Eq, Clone)]
19pub struct RawBlock {
20 pub x: i32,
21 pub y: i32,
22 pub z: i32,
23 pub rotation: u8,
24}
25
26fn decode(input: &str) -> Option<Vec<u8>> {
27 let input = input.replace('-', "+").replace('_', "/");
28 let base64_decoded = BASE64_STANDARD_NO_PAD.decode(input).ok()?;
29 Some(base64_decoded)
30}
31
32fn encode(input: &[u8]) -> String {
33 let base64_encoded = BASE64_STANDARD_NO_PAD.encode(input);
34 base64_encoded.replace('+', "-").replace('/', "_")
35}
36
37#[must_use]
38pub fn decode_track_code(track_code: &str) -> Option<Track> {
39 let track_code = track_code.get(3..)?;
40 let metadata = decode(track_code.get(..2)?)?;
41 let name_len = *metadata.first()? as usize;
42 let name = track_code.get(2..2 + name_len)?.to_string();
43 let track_data = decode(track_code.get(2 + name_len..)?)?;
44 Some(Track {
45 name,
46 author: None,
47 last_modified: None,
48 track_data,
49 })
50}
51
52#[must_use]
53pub fn encode_track_code(track: &Track) -> Option<String> {
59 let track_data = encode(&track.track_data);
60
61 let metadata = encode(&[(track.name.len()) as u8]);
62
63 let track_code = String::from("v1n") + &metadata + &track.name + &track_data;
65 Some(track_code)
66}
67
68#[must_use]
69pub fn decode_track_data(data: &[u8]) -> Option<RawTrackInfo> {
70 let mut offset = 0;
71 let mut parts = Vec::new();
72 while offset < data.len() {
73 let id = read_u16(data, &mut offset)? as u8;
74 let amount = read_u32(data, &mut offset)?;
75
76 let mut blocks = Vec::new();
77 for _ in 0..amount {
78 let x = read_i24(data, &mut offset)? - i32::pow(2, 23);
79 let y = read_i24(data, &mut offset)?;
80 let z = read_i24(data, &mut offset)? - i32::pow(2, 23);
81
82 let rotation = read_u8(data, &mut offset)? & 3;
83
84 blocks.push(RawBlock { x, y, z, rotation });
85 }
86 parts.push(RawPart { id, amount, blocks });
87 }
88
89 Some(RawTrackInfo { parts })
90}
91
92#[must_use]
93pub fn encode_track_data(track_info: &RawTrackInfo) -> Option<Vec<u8>> {
95 let mut data = Vec::new();
96 for part in &track_info.parts {
97 write_u16(&mut data, part.id.into());
98 write_u32(&mut data, part.amount);
99 for block in &part.blocks {
100 write_u24(&mut data, (block.x + i32::pow(2, 23)).cast_unsigned());
101 write_u24(&mut data, block.y.cast_unsigned());
102 write_u24(&mut data, (block.z + i32::pow(2, 23)).cast_unsigned());
103 data.push(block.rotation);
104 }
105 }
106
107 Some(data)
108}