linearify/
lib.rs

1use std::error::Error;
2use std::fmt;
3use std::fs;
4use std::fs::File;
5use std::fs::OpenOptions;
6use std::io::prelude::*;
7use std::io::{BufReader, BufWriter, Cursor, Read, SeekFrom};
8
9use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
10use zstd::stream::decode_all;
11use zstd::stream::encode_all;
12
13const LINEAR_SIGNATURE: i64 = -4323716122432332390;
14const LINEAR_VERSION: i8 = 2;
15const LINEAR_SUPPORTED: [i8; 2] = [1, 2];
16const HEADER_SIZE: i32 = 8192;
17
18#[derive(Clone)]
19pub struct Chunk {
20    pub raw_chunk: Vec<u8>,
21    pub x: i64,
22    pub z: i64,
23}
24
25// Don't print raw_chunk
26impl fmt::Debug for Chunk {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        write!(
29            f,
30            "Chunk {{ x: {}, z: {} }}",
31            self.x, self.z
32        )
33    }
34}
35
36#[derive(Clone, Debug)]
37pub struct Region {
38    pub chunks: Vec<Option<Chunk>>,
39    pub region_x: i64,
40    pub region_z: i64,
41    pub timestamps: Vec<i32>,
42    pub newest_timestamp: i64,
43}
44
45impl Region {
46    fn count_chunks(&self) -> i16 {
47        self.chunks.iter().filter(|&chunk| chunk.is_some()).count() as i16
48    }
49
50    pub fn write_linear(&self, dir: &str, compression_level: i32) -> Result<(), Box<dyn Error>> {
51        let path = format!("{}/r.{}.{}.linear", dir, self.region_x, self.region_z);
52        let wip_path = format!("{}/r.{}.{}.linear.wip", dir, self.region_x, self.region_z);
53        let file = OpenOptions::new()
54            .write(true)
55            .create(true)
56            .truncate(true)
57            .open(&wip_path)?;
58
59        // Get chunks data
60        let mut raw_data: Vec<u8> = Vec::new();
61        for i in 0..1024 {
62            if let Some(chunk) = &self.chunks[i] {
63                let size: i32 = chunk.raw_chunk.len() as i32;
64                let timestamp: i32 = self.timestamps[i];
65                raw_data.extend_from_slice(&size.to_be_bytes());
66                raw_data.extend_from_slice(&timestamp.to_be_bytes());
67            } else {
68                raw_data.extend_from_slice(&0u32.to_be_bytes()); // Write size 0 for empty chunks
69                raw_data.extend_from_slice(&0u32.to_be_bytes()); // Write timestamp 0 for empty chunks
70            }
71        }
72
73        for i in 0..1024 {
74            if let Some(chunk) = &self.chunks[i] {
75                raw_data.extend_from_slice(chunk.raw_chunk.as_slice());
76            }
77        }
78        let raw_cursor = Cursor::new(&raw_data);
79        let encoded: Vec<u8> = encode_all(raw_cursor, compression_level)?; // Encode it
80
81        // Write file
82        let mut buffer = BufWriter::new(file);
83        // Header
84        let chunk_count: i16 = self.count_chunks();
85        buffer.write_i64::<BigEndian>(LINEAR_SIGNATURE)?; // Superblock
86        buffer.write_i8(LINEAR_VERSION)?; // Version
87        buffer.write_i64::<BigEndian>(self.newest_timestamp)?; // Newest timestamp
88        buffer.write_i8(compression_level as i8)?; // Compression level
89        buffer.write_i16::<BigEndian>(chunk_count)?; // Chunk count
90        buffer.write_i32::<BigEndian>(encoded.len() as i32)?; // Compressed size
91        buffer.write_i64::<BigEndian>(0)?; // Datahash: skip, unimplemented
92
93        // Chunk data
94        buffer.write_all(encoded.as_slice())?;
95
96        // Write signature footer
97        buffer.write_i64::<BigEndian>(LINEAR_SIGNATURE)?;
98
99        // Flush & move
100        buffer.flush()?;
101        fs::rename(wip_path, path)?;
102        Ok(())
103    }
104}
105
106pub fn open_linear(path: &str) -> Result<Region, Box<dyn Error>> {
107    let coords: Vec<&str> = path.split('/').last().unwrap().split('.').collect();
108    let region_x: i64 = coords[1].parse::<i64>()?;
109    let region_z: i64 = coords[2].parse::<i64>()?;
110
111    let file = File::open(path)?;
112    let mut buffer = BufReader::new(file);
113
114    // Read chunk data
115    // Go to the end 8 bytes before to read signature footer
116    buffer.seek(SeekFrom::End(-8))?;
117    let signature_footer = buffer.read_i64::<BigEndian>()?;
118    buffer.seek(SeekFrom::Start(0))?; 
119    let signature = buffer.read_i64::<BigEndian>()?;
120    let version = buffer.read_i8()?;
121    let newest_timestamp = buffer.read_i64::<BigEndian>()?;
122    // Skip compression level (Byte): Unused
123    buffer.seek(SeekFrom::Current(1))?;
124    let chunk_count = buffer.read_i16::<BigEndian>()?;
125    let compressed_length = buffer.read_i32::<BigEndian>()?;
126    // Skip datahash (Long): Unused
127    buffer.seek(SeekFrom::Current(8))?;
128
129    // Verify data
130    if signature != LINEAR_SIGNATURE {
131        return Err(format!("Invalid signature: {}", signature).into());
132    }
133    if !LINEAR_SUPPORTED.iter().any(|&num| num == version) {
134        return Err(format!("Invalid version: {}", version).into());
135    }
136    if signature_footer != LINEAR_SIGNATURE {
137        return Err(format!("Invalid footer signature: {}", signature_footer).into());
138    }
139
140    // Read raw chunk
141    let mut raw = vec![0u8; compressed_length as usize];
142    buffer.read_exact(&mut raw)?;
143    let raw_cursor = Cursor::new(&raw);
144    // Decode data
145    let decoded: Vec<u8> = decode_all(raw_cursor)?;
146    let mut cursor = Cursor::new(&decoded);
147
148    // Start deserializing
149    let mut sizes: Vec<usize> = Vec::new();
150    let mut timestamps: Vec<i32> = Vec::new();
151    let mut chunks: Vec<Option<Chunk>> = vec![None; 1024];
152
153    let mut real_chunk_count = 0;
154    let mut total_size = 0;
155    for _ in 0..1024 {
156        let size = cursor.read_i32::<BigEndian>()?;
157        let timestamp = cursor.read_i32::<BigEndian>()?;
158        total_size += size;
159        real_chunk_count += (size != 0) as i16;
160        sizes.push(size as usize);
161        timestamps.push(timestamp);
162    }
163
164    // Check if chunk data is corrupted
165    if total_size + HEADER_SIZE != decoded.len() as i32 {
166        return Err("Invalid decompressed size: {}".into());
167    }
168
169    if real_chunk_count != chunk_count {
170        return Err(format!("Invalid chunk count {}/{}", real_chunk_count, chunk_count).into());
171    }
172
173    // Save raw chunk data
174    for i in 0..1024 {
175        if sizes[i] > 0 {
176            let mut raw_chunk = vec![0u8; sizes[i]];
177            cursor.read_exact(&mut raw_chunk)?;
178            chunks[i] = Some(Chunk {
179                raw_chunk,
180                x: 32 * region_x + (i as i64) % 32,
181                z: 32 * region_z + (i as i64) / 32,
182            });
183        }
184    }
185
186    Ok(Region {
187        chunks,
188        region_x,
189        region_z,
190        timestamps,
191        newest_timestamp,
192    })
193}