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> {
229 Self::save_with_level(path, world, Some(14))
230 }
231
232 pub fn save_with_level(
235 path: impl AsRef<std::path::Path>,
236 world: &World,
237 zstd_level: Option<i32>,
238 ) -> Result<(), BrError> {
239 let mut file = std::fs::File::create(path).map_err(BrzError::IO)?;
240 world
241 .to_unsaved()?
242 .to_pending()?
243 .to_brz_data(zstd_level)?
244 .write(&mut file, zstd_level)?;
245 Ok(())
246 }
247
248 pub fn save_uncompressed(
250 path: impl AsRef<std::path::Path>,
251 world: &World,
252 ) -> Result<(), BrError> {
253 Self::write_pending(path, world.to_unsaved()?.to_pending()?)
254 }
255
256 pub fn write(&self, w: &mut impl Write, zstd_level: Option<i32>) -> Result<(), BrzError> {
258 w.write(b"BRZ")?;
259 let mut index_data = self.index_data.to_vec()?;
260 let index_size_uncompressed = index_data.len() as i32;
261 let mut index_size_compressed = index_size_uncompressed;
262 let mut index_method = CompressionMethod::None;
263 let index_hash = BrBlob::hash(&index_data);
264
265 if let Some(level) = zstd_level {
266 let compressed_data = crate::compression::compress(&index_data, level)?;
267 if (compressed_data.len() as i32) < index_size_uncompressed {
269 index_size_compressed = compressed_data.len() as i32;
270 index_method = CompressionMethod::GenericZstd;
271 index_data = compressed_data;
272 }
273 }
274
275 BrzArchiveHeader {
276 version: FormatVersion::Initial,
277 index_method,
278 index_size_uncompressed,
279 index_size_compressed,
280 index_hash,
281 }
282 .write(w)?;
283 w.write_all(&index_data)?;
284 w.write_all(&self.blob_data)?;
285 Ok(())
286 }
287}
288
289impl BrzArchiveHeader {
290 pub fn read(r: &mut impl Read) -> Result<BrzArchiveHeader, BrzError> {
291 let version = FormatVersion::try_from(read_u8(r)?)?;
292 let index_method = CompressionMethod::try_from(read_u8(r)?)?;
293
294 let index_size_uncompressed = read_i32(r)?;
295 if index_size_uncompressed < 0 {
296 return Err(BrzError::InvalidIndexDecompressedLength(
297 index_size_uncompressed,
298 ));
299 }
300 let index_size_compressed = read_i32(r)?;
301 if index_size_compressed < 0 {
302 return Err(BrzError::InvalidIndexCompressedLength(
303 index_size_compressed,
304 ));
305 }
306 let mut index_hash = [0u8; 32];
307 r.read_exact(&mut index_hash).map_err(BrzError::IO)?;
308
309 Ok(BrzArchiveHeader {
310 version,
311 index_method,
312 index_size_uncompressed,
313 index_size_compressed,
314 index_hash,
315 })
316 }
317
318 pub fn write(&self, buf: &mut impl Write) -> Result<(), BrzError> {
319 buf.write_all(&[self.version as u8, self.index_method as u8])?;
320 buf.write_all(&self.index_size_uncompressed.to_le_bytes())?;
321 buf.write_all(&self.index_size_compressed.to_le_bytes())?;
322 buf.write_all(&self.index_hash)?;
323 Ok(())
324 }
325}
326
327impl BrzIndexData {
328 pub fn read(r: &mut impl Read) -> Result<BrzIndexData, BrzError> {
329 let num_folders = read_i32(r)?;
330 if num_folders < 0 {
331 return Err(BrzError::InvalidNumFolders(num_folders));
332 }
333 let num_files = read_i32(r)?;
334 if num_files < 0 {
335 return Err(BrzError::InvalidNumFiles(num_files));
336 }
337 let num_blobs = read_i32(r)?;
338 if num_blobs < 0 {
339 return Err(BrzError::InvalidNumBlobs(num_blobs));
340 }
341
342 let mut folder_parent_ids = Vec::with_capacity(num_folders as usize);
343 for _ in 0..num_folders {
344 folder_parent_ids.push(read_i32(r)?);
345 }
346 let folder_name_lengths = (0..num_folders)
347 .map(|_| read_u16(r))
348 .collect::<Result<Vec<_>, _>>()?;
349 let folder_names = folder_name_lengths
350 .iter()
351 .map(|&len| read_string(r, len))
352 .collect::<Result<Vec<_>, _>>()?;
353 let mut file_parent_ids = Vec::with_capacity(num_files as usize);
354 for _ in 0..num_files {
355 file_parent_ids.push(read_i32(r)?);
356 }
357 let mut file_content_ids = Vec::with_capacity(num_files as usize);
358 for _ in 0..num_files {
359 file_content_ids.push(read_i32(r)?);
360 }
361 let file_name_lengths = (0..num_files)
362 .map(|_| read_u16(r))
363 .collect::<Result<Vec<_>, _>>()?;
364 let file_names = file_name_lengths
365 .iter()
366 .map(|&len| read_string(r, len))
367 .collect::<Result<Vec<_>, _>>()?;
368 let mut compression_methods = Vec::with_capacity(num_blobs as usize);
369 for _ in 0..num_blobs {
370 compression_methods.push(CompressionMethod::try_from(read_u8(r)?)?);
371 }
372 let mut sizes_uncompressed = Vec::with_capacity(num_blobs as usize);
373 for _ in 0..num_blobs {
374 let len = read_i32(r)?;
375 if len < 0 {
376 return Err(BrzError::InvalidBlobDecompressedLength(len));
377 }
378 sizes_uncompressed.push(len);
379 }
380 let mut sizes_compressed = Vec::with_capacity(num_blobs as usize);
381 for _ in 0..num_blobs {
382 let len = read_i32(r)?;
383 if len < 0 {
384 return Err(BrzError::InvalidBlobCompressedLength(len));
385 }
386 sizes_compressed.push(len);
387 }
388 let mut blob_hashes = Vec::with_capacity(num_blobs as usize);
389 for _ in 0..num_blobs {
390 let mut hash = [0u8; 32];
391 r.read_exact(&mut hash).map_err(BrzError::IO)?;
392 blob_hashes.push(hash);
393 }
394
395 let mut blob_ranges = Vec::with_capacity(num_blobs as usize);
396 let mut current_offset = 0;
397 for i in 0..num_blobs as usize {
398 let start = current_offset;
399 let length = match compression_methods[i] {
402 CompressionMethod::None => sizes_uncompressed[i] as usize,
403 CompressionMethod::GenericZstd => sizes_compressed[i] as usize,
404 };
405 let end = start + length;
406 blob_ranges.push((start, end));
407 current_offset = end;
408 }
409
410 Ok(BrzIndexData {
411 num_folders,
412 num_files,
413 num_blobs,
414 folder_parent_ids,
415 folder_names,
416 file_parent_ids,
417 file_content_ids,
418 file_names,
419 compression_methods,
420 sizes_uncompressed,
421 sizes_compressed,
422 blob_hashes,
423 blob_ranges,
424 blob_total_size: current_offset,
425 })
426 }
427
428 pub fn to_vec(&self) -> Result<Vec<u8>, BrzError> {
429 let mut buf = Vec::new();
430 buf.write_all(&self.num_folders.to_le_bytes())?;
431 buf.write_all(&self.num_files.to_le_bytes())?;
432 buf.write_all(&self.num_blobs.to_le_bytes())?;
433
434 for &parent_id in &self.folder_parent_ids {
435 buf.write_all(&parent_id.to_le_bytes())?;
436 }
437
438 for name in &self.folder_names {
440 buf.write_all(&(name.len() as u16).to_le_bytes())?;
441 }
442 for name in &self.folder_names {
443 buf.write_all(name.as_bytes())?;
444 }
445
446 for &parent_id in &self.file_parent_ids {
447 buf.write_all(&parent_id.to_le_bytes())?;
448 }
449 for &content_id in &self.file_content_ids {
450 buf.write_all(&content_id.to_le_bytes())?;
451 }
452
453 for name in &self.file_names {
455 buf.write_all(&(name.len() as u16).to_le_bytes())?;
456 }
457 for name in &self.file_names {
458 buf.write_all(name.as_bytes())?;
459 }
460
461 for method in &self.compression_methods {
462 buf.write_all(&[*method as u8])?;
463 }
464 for &size in &self.sizes_uncompressed {
465 buf.write_all(&size.to_le_bytes())?;
466 }
467 for &size in &self.sizes_compressed {
468 buf.write_all(&size.to_le_bytes())?;
469 }
470 for hash in &self.blob_hashes {
471 buf.write_all(hash)?;
472 }
473
474 Ok(buf)
475 }
476}