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
25impl 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 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(×tamp.to_be_bytes());
67 } else {
68 raw_data.extend_from_slice(&0u32.to_be_bytes()); raw_data.extend_from_slice(&0u32.to_be_bytes()); }
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)?; let mut buffer = BufWriter::new(file);
83 let chunk_count: i16 = self.count_chunks();
85 buffer.write_i64::<BigEndian>(LINEAR_SIGNATURE)?; buffer.write_i8(LINEAR_VERSION)?; buffer.write_i64::<BigEndian>(self.newest_timestamp)?; buffer.write_i8(compression_level as i8)?; buffer.write_i16::<BigEndian>(chunk_count)?; buffer.write_i32::<BigEndian>(encoded.len() as i32)?; buffer.write_i64::<BigEndian>(0)?; buffer.write_all(encoded.as_slice())?;
95
96 buffer.write_i64::<BigEndian>(LINEAR_SIGNATURE)?;
98
99 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 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 buffer.seek(SeekFrom::Current(1))?;
124 let chunk_count = buffer.read_i16::<BigEndian>()?;
125 let compressed_length = buffer.read_i32::<BigEndian>()?;
126 buffer.seek(SeekFrom::Current(8))?;
128
129 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 let mut raw = vec![0u8; compressed_length as usize];
142 buffer.read_exact(&mut raw)?;
143 let raw_cursor = Cursor::new(&raw);
144 let decoded: Vec<u8> = decode_all(raw_cursor)?;
146 let mut cursor = Cursor::new(&decoded);
147
148 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 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 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}