1use std::io::{Read, Write};
2
3use crate::{
4 BrFsReader, BrReader, IntoReader, World, brz::reader::BrzIndex, compression::decompress,
5 errors::BrError, pending::BrPendingFs, tables::BrBlob,
6};
7
8mod errors;
9pub use errors::*;
10
11mod reader;
12#[cfg(test)]
13mod tests;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18#[repr(u8)]
19pub enum FormatVersion {
20 Initial = 0,
21}
22
23impl TryFrom<u8> for FormatVersion {
24 type Error = BrzError;
25
26 fn try_from(value: u8) -> Result<Self, Self::Error> {
27 match value {
28 0 => Ok(FormatVersion::Initial),
29 _ => Err(BrzError::InvalidFormat(value)),
30 }
31 }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
36#[repr(u8)]
37pub enum CompressionMethod {
38 None = 0,
39 GenericZstd = 1,
40}
41
42impl TryFrom<u8> for CompressionMethod {
43 type Error = BrzError;
44
45 fn try_from(value: u8) -> Result<Self, Self::Error> {
46 match value {
47 0 => Ok(CompressionMethod::None),
48 1 => Ok(CompressionMethod::GenericZstd),
49 _ => Err(BrzError::InvalidCompressionMethod(value)),
50 }
51 }
52}
53
54pub struct BrzArchiveHeader {
55 pub version: FormatVersion,
56 pub index_method: CompressionMethod,
57 pub index_size_uncompressed: i32,
58 pub index_size_compressed: i32,
59 pub index_hash: [u8; 32],
61}
62
63#[derive(Default, Clone, Debug)]
64pub struct BrzIndexData {
65 pub num_folders: i32,
66 pub num_files: i32,
67 pub num_blobs: i32,
68 pub folder_parent_ids: Vec<i32>,
71 pub folder_names: Vec<String>,
74 pub file_parent_ids: Vec<i32>,
77 pub file_content_ids: Vec<i32>,
80 pub file_names: Vec<String>,
83 pub compression_methods: Vec<CompressionMethod>,
85 pub sizes_uncompressed: Vec<i32>,
87 pub sizes_compressed: Vec<i32>,
89 pub blob_hashes: Vec<[u8; 32]>,
91 pub blob_ranges: Vec<(usize, usize)>,
93 pub blob_total_size: usize,
95}
96
97#[derive(Clone)]
98pub struct Brz {
99 pub index_data: BrzIndexData,
100 pub blob_data: Vec<u8>,
102}
103
104impl IntoReader for Brz {
105 type Inner = BrzIndex<Brz>;
106
107 fn into_reader(self) -> BrReader<Self::Inner> {
108 BrzIndex::new(self).into_reader()
109 }
110}
111
112impl AsRef<Brz> for Brz {
113 fn as_ref(&self) -> &Brz {
114 self
115 }
116}
117
118impl<'a> IntoReader for &'a Brz {
119 type Inner = BrzIndex<&'a Brz>;
120
121 fn into_reader(self) -> BrReader<Self::Inner> {
122 BrzIndex::new(self).into_reader()
123 }
124}
125
126fn read_u8(r: &mut impl Read) -> Result<u8, BrzError> {
127 let mut buf = [0u8; 1];
128 r.read_exact(&mut buf).map_err(BrzError::IO)?;
129 Ok(buf[0])
130}
131fn read_i32(r: &mut impl Read) -> Result<i32, BrzError> {
132 let mut buf = [0u8; 4];
133 r.read_exact(&mut buf).map_err(BrzError::IO)?;
134 Ok(i32::from_le_bytes(buf))
135}
136fn read_u16(r: &mut impl Read) -> Result<u16, BrzError> {
137 let mut buf = [0u8; 2];
138 r.read_exact(&mut buf).map_err(BrzError::IO)?;
139 Ok(u16::from_le_bytes(buf))
140}
141fn read_string(r: &mut impl Read, len: u16) -> Result<String, BrzError> {
142 let mut buf = vec![0u8; len as usize];
143 r.read_exact(&mut buf).map_err(BrzError::IO)?;
144 Ok(String::from_utf8(buf)?)
145}
146
147impl Brz {
148 pub fn open(path: impl AsRef<std::path::Path>) -> Result<Brz, BrzError> {
150 Self::read(&mut std::fs::File::open(path).map_err(BrzError::IO)?)
151 }
152
153 pub fn new(path: impl AsRef<std::path::Path>) -> Result<Brz, BrzError> {
155 Self::open(path)
156 }
157
158 pub fn read_slice(buf: &[u8]) -> Result<Brz, BrzError> {
160 let mut cursor = std::io::Cursor::new(buf);
161 Self::read(&mut cursor)
162 }
163
164 pub fn read(r: &mut impl Read) -> Result<Brz, BrzError> {
166 let magic = [read_u8(r)?, read_u8(r)?, read_u8(r)?];
167 if magic != *b"BRZ" {
168 return Err(BrzError::InvalidMagic(magic));
169 }
170
171 let header = BrzArchiveHeader::read(r)?;
172
173 let index_buf = match header.index_method {
174 CompressionMethod::None => {
175 let mut buf = vec![0u8; header.index_size_uncompressed as usize];
176 r.read_exact(&mut buf).map_err(BrzError::IO)?;
177 buf
178 }
179 CompressionMethod::GenericZstd => {
180 let mut compressed_buf = vec![0u8; header.index_size_compressed as usize];
181 r.read_exact(&mut compressed_buf).map_err(BrzError::IO)?;
182 decompress(&compressed_buf, header.index_size_uncompressed as usize)
183 .map_err(BrzError::Decompress)?
184 }
185 };
186
187 let index_hash = BrBlob::hash(&index_buf);
189 if index_hash != header.index_hash {
190 return Err(BrzError::InvalidIndexHash(index_hash, header.index_hash));
191 }
192
193 let index_data = BrzIndexData::read(&mut index_buf.as_slice())?;
194 let mut blob_data = Vec::with_capacity(index_data.blob_total_size);
195 r.read_to_end(&mut blob_data).map_err(BrzError::IO)?;
196
197 Ok(Brz {
198 index_data,
199 blob_data,
200 })
201 }
202
203 pub fn to_vec(&self, zstd_level: Option<i32>) -> Result<Vec<u8>, BrzError> {
205 let mut buf = Vec::new();
206 self.write(&mut buf, zstd_level)?;
207 Ok(buf)
208 }
209
210 pub fn to_pending(&self) -> Result<BrPendingFs, BrError> {
212 let reader = self.into_reader();
213 Ok(reader.get_fs()?.to_pending(&*reader)?)
214 }
215
216 pub fn write_pending(
218 path: impl AsRef<std::path::Path>,
219 pending: BrPendingFs,
220 ) -> Result<(), BrError> {
221 let mut file = std::fs::File::create(path).map_err(BrzError::IO)?;
222 pending.to_brz_data(Some(14))?.write(&mut file, Some(14))?;
223 Ok(())
224 }
225
226 pub fn save(path: impl AsRef<std::path::Path>, world: &World) -> Result<(), BrError> {
228 let mut file = std::fs::File::create(path).map_err(BrzError::IO)?;
229 world
230 .to_unsaved()?
231 .to_pending()?
232 .to_brz_data(Some(14))?
233 .write(&mut file, Some(14))?;
234 Ok(())
235 }
236
237 pub fn save_uncompressed(
239 path: impl AsRef<std::path::Path>,
240 world: &World,
241 ) -> Result<(), BrError> {
242 Self::write_pending(path, world.to_unsaved()?.to_pending()?)
243 }
244
245 pub fn write(&self, w: &mut impl Write, zstd_level: Option<i32>) -> Result<(), BrzError> {
247 w.write(b"BRZ")?;
248 let mut index_data = self.index_data.to_vec()?;
249 let index_size_uncompressed = index_data.len() as i32;
250 #[allow(unused)]
251 let mut index_size_compressed = index_size_uncompressed;
252 let mut index_method = CompressionMethod::None;
253 let index_hash = BrBlob::hash(&index_data);
254
255 if let Some(level) = zstd_level {
256 let compressed_data = crate::compression::compress(&index_data, level)?;
257 if (index_data.len() as i32) < index_size_uncompressed {
259 index_size_compressed = compressed_data.len() as i32;
260 index_method = CompressionMethod::GenericZstd;
261 index_data = compressed_data;
262 }
263 };
264
265 BrzArchiveHeader {
266 version: FormatVersion::Initial,
267 index_method,
268 index_size_uncompressed,
269 index_size_compressed,
270 index_hash,
271 }
272 .write(w)?;
273 w.write_all(&index_data)?;
274 w.write_all(&self.blob_data)?;
275 Ok(())
276 }
277}
278
279impl BrzArchiveHeader {
280 pub fn read(r: &mut impl Read) -> Result<BrzArchiveHeader, BrzError> {
281 let version = FormatVersion::try_from(read_u8(r)?)?;
282 let index_method = CompressionMethod::try_from(read_u8(r)?)?;
283
284 let index_size_uncompressed = read_i32(r)?;
285 if index_size_uncompressed < 0 {
286 return Err(BrzError::InvalidIndexDecompressedLength(
287 index_size_uncompressed,
288 ));
289 }
290 let index_size_compressed = read_i32(r)?;
291 if index_size_compressed < 0 {
292 return Err(BrzError::InvalidIndexCompressedLength(
293 index_size_compressed,
294 ));
295 }
296 let mut index_hash = [0u8; 32];
297 r.read_exact(&mut index_hash).map_err(BrzError::IO)?;
298
299 Ok(BrzArchiveHeader {
300 version,
301 index_method,
302 index_size_uncompressed,
303 index_size_compressed,
304 index_hash,
305 })
306 }
307
308 pub fn write(&self, buf: &mut impl Write) -> Result<(), BrzError> {
309 buf.write_all(&[self.version as u8, self.index_method as u8])?;
310 buf.write_all(&self.index_size_uncompressed.to_le_bytes())?;
311 buf.write_all(&self.index_size_compressed.to_le_bytes())?;
312 buf.write_all(&self.index_hash)?;
313 Ok(())
314 }
315}
316
317impl BrzIndexData {
318 pub fn read(r: &mut impl Read) -> Result<BrzIndexData, BrzError> {
319 let num_folders = read_i32(r)?;
320 if num_folders < 0 {
321 return Err(BrzError::InvalidNumFolders(num_folders));
322 }
323 let num_files = read_i32(r)?;
324 if num_files < 0 {
325 return Err(BrzError::InvalidNumFiles(num_files));
326 }
327 let num_blobs = read_i32(r)?;
328 if num_blobs < 0 {
329 return Err(BrzError::InvalidNumBlobs(num_blobs));
330 }
331
332 let mut folder_parent_ids = Vec::with_capacity(num_folders as usize);
333 for _ in 0..num_folders {
334 folder_parent_ids.push(read_i32(r)?);
335 }
336 let folder_name_lengths = (0..num_folders)
337 .map(|_| read_u16(r))
338 .collect::<Result<Vec<_>, _>>()?;
339 let folder_names = folder_name_lengths
340 .iter()
341 .map(|&len| read_string(r, len))
342 .collect::<Result<Vec<_>, _>>()?;
343 let mut file_parent_ids = Vec::with_capacity(num_files as usize);
344 for _ in 0..num_files {
345 file_parent_ids.push(read_i32(r)?);
346 }
347 let mut file_content_ids = Vec::with_capacity(num_files as usize);
348 for _ in 0..num_files {
349 file_content_ids.push(read_i32(r)?);
350 }
351 let file_name_lengths = (0..num_files)
352 .map(|_| read_u16(r))
353 .collect::<Result<Vec<_>, _>>()?;
354 let file_names = file_name_lengths
355 .iter()
356 .map(|&len| read_string(r, len))
357 .collect::<Result<Vec<_>, _>>()?;
358 let mut compression_methods = Vec::with_capacity(num_blobs as usize);
359 for _ in 0..num_blobs {
360 compression_methods.push(CompressionMethod::try_from(read_u8(r)?)?);
361 }
362 let mut sizes_uncompressed = Vec::with_capacity(num_blobs as usize);
363 for _ in 0..num_blobs {
364 let len = read_i32(r)?;
365 if len < 0 {
366 return Err(BrzError::InvalidBlobDecompressedLength(len));
367 }
368 sizes_uncompressed.push(len);
369 }
370 let mut sizes_compressed = Vec::with_capacity(num_blobs as usize);
371 for _ in 0..num_blobs {
372 let len = read_i32(r)?;
373 if len < 0 {
374 return Err(BrzError::InvalidBlobCompressedLength(len));
375 }
376 sizes_compressed.push(len);
377 }
378 let mut blob_hashes = Vec::with_capacity(num_blobs as usize);
379 for _ in 0..num_blobs {
380 let mut hash = [0u8; 32];
381 r.read_exact(&mut hash).map_err(BrzError::IO)?;
382 blob_hashes.push(hash);
383 }
384
385 let mut blob_ranges = Vec::with_capacity(num_blobs as usize);
386 let mut current_offset = 0;
387 for i in 0..num_blobs as usize {
388 let start = current_offset;
389 let length = match compression_methods[i] {
392 CompressionMethod::None => sizes_uncompressed[i] as usize,
393 CompressionMethod::GenericZstd => sizes_compressed[i] as usize,
394 };
395 let end = start + length;
396 blob_ranges.push((start, end));
397 current_offset = end;
398 }
399
400 Ok(BrzIndexData {
401 num_folders,
402 num_files,
403 num_blobs,
404 folder_parent_ids,
405 folder_names,
406 file_parent_ids,
407 file_content_ids,
408 file_names,
409 compression_methods,
410 sizes_uncompressed,
411 sizes_compressed,
412 blob_hashes,
413 blob_ranges,
414 blob_total_size: current_offset,
415 })
416 }
417
418 pub fn to_vec(&self) -> Result<Vec<u8>, BrzError> {
419 let mut buf = Vec::new();
420 buf.write_all(&self.num_folders.to_le_bytes())?;
421 buf.write_all(&self.num_files.to_le_bytes())?;
422 buf.write_all(&self.num_blobs.to_le_bytes())?;
423
424 for &parent_id in &self.folder_parent_ids {
425 buf.write_all(&parent_id.to_le_bytes())?;
426 }
427
428 for name in &self.folder_names {
430 buf.write_all(&(name.len() as u16).to_le_bytes())?;
431 }
432 for name in &self.folder_names {
433 buf.write_all(name.as_bytes())?;
434 }
435
436 for &parent_id in &self.file_parent_ids {
437 buf.write_all(&parent_id.to_le_bytes())?;
438 }
439 for &content_id in &self.file_content_ids {
440 buf.write_all(&content_id.to_le_bytes())?;
441 }
442
443 for name in &self.file_names {
445 buf.write_all(&(name.len() as u16).to_le_bytes())?;
446 }
447 for name in &self.file_names {
448 buf.write_all(name.as_bytes())?;
449 }
450
451 for method in &self.compression_methods {
452 buf.write_all(&[*method as u8])?;
453 }
454 for &size in &self.sizes_uncompressed {
455 buf.write_all(&size.to_le_bytes())?;
456 }
457 for &size in &self.sizes_compressed {
458 buf.write_all(&size.to_le_bytes())?;
459 }
460 for hash in &self.blob_hashes {
461 buf.write_all(hash)?;
462 }
463
464 Ok(buf)
465 }
466}