polytrack_codes/v3/
mod.rs1#![allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
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_step1 = *metadata.first()? as usize;
36 let name_len = (name_len_step1 * 4).div_ceil(3);
37 let track_name_raw = tools::decode(track_code.get(2..2 + name_len)?)?;
38 let name = String::from_utf8(track_name_raw).ok()?;
39 let track_data = tools::decompress(&tools::decode(track_code.get(2 + name_len..)?)?)?;
40 Some(Track {
41 name,
42 author: None,
43 last_modified: None,
44 track_data,
45 })
46}
47
48#[must_use]
49pub fn encode_track_code(track: &Track) -> Option<String> {
55 let track_data = tools::encode(&tools::compress_final(&track.track_data)?)?;
56
57 let name_raw = track.name.as_bytes().to_vec();
58 let name = tools::encode(&name_raw)?;
59 let metadata = tools::encode(&[(name.len() * 3 / 4) as u8])?;
60
61 let track_code = String::from("v2") + &metadata + &name + &track_data;
63 Some(track_code)
64}
65
66#[must_use]
67pub fn decode_track_data(data: &[u8]) -> Option<TrackInfo> {
68 let mut offset = 0;
69 let mut parts = Vec::new();
70 while offset < data.len() {
71 let id = read_u16(data, &mut offset)? as u8;
72 let amount = read_u32(data, &mut offset)?;
73
74 let mut blocks = Vec::new();
75 for _ in 0..amount {
76 let x = read_i24(data, &mut offset)? - i32::pow(2, 23);
77 let y = read_i24(data, &mut offset)?;
78 let z = read_i24(data, &mut offset)? - i32::pow(2, 23);
79
80 let rotation = read_u8(data, &mut offset)? & 3;
81
82 let cp_order = if CP_IDS.contains(&id) {
83 Some(read_u16(data, &mut offset)?)
84 } else {
85 None
86 };
87 blocks.push(Block {
88 x,
89 y,
90 z,
91 rotation,
92 cp_order,
93 });
94 }
95 parts.push(Part { id, amount, blocks });
96 }
97
98 Some(TrackInfo { parts })
99}
100
101#[must_use]
102pub fn encode_track_data(track_info: &TrackInfo) -> Option<Vec<u8>> {
104 let mut data = Vec::new();
105 for part in &track_info.parts {
106 write_u16(&mut data, part.id.into());
107 write_u32(&mut data, part.amount);
108 for block in &part.blocks {
109 write_u24(&mut data, (block.x + i32::pow(2, 23)).cast_unsigned());
110 write_u24(&mut data, block.y.cast_unsigned());
111 write_u24(&mut data, (block.z + i32::pow(2, 23)).cast_unsigned());
112 data.push(block.rotation);
113 if let Some(cp_order) = block.cp_order {
114 write_u16(&mut data, cp_order.into());
115 }
116 }
117 }
118
119 Some(data)
120}
121
122#[must_use]
123pub fn export_to_id(track_code: &str) -> Option<String> {
124 let track_data = decode_track_code(track_code)?;
125 let data = track_data.track_data;
126 let id = hash_vec(data);
127 Some(id)
128}