Skip to main content

polytrack_codes/v4/
mod.rs

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