use std::io::{Read, Seek, SeekFrom};
use crate::{chunk::ModpkgChunk, error::ModpkgError, ModpkgCompression};
pub struct ModpkgDecoder<'modpkg, TSource: Read + Seek> {
pub(crate) source: &'modpkg mut TSource,
}
impl<TSource> ModpkgDecoder<'_, TSource>
where
TSource: Read + Seek,
{
pub fn load_chunk_raw(&mut self, chunk: &ModpkgChunk) -> Result<Box<[u8]>, ModpkgError> {
let mut data = vec![0; chunk.compressed_size as usize];
self.source.seek(SeekFrom::Start(chunk.data_offset))?;
self.source.read_exact(&mut data)?;
Ok(data.into_boxed_slice())
}
pub fn load_chunk_decompressed(
&mut self,
chunk: &ModpkgChunk,
) -> Result<Box<[u8]>, ModpkgError> {
match chunk.compression {
ModpkgCompression::None => self.load_chunk_raw(chunk),
ModpkgCompression::Zstd => self.decode_zstd_chunk(chunk),
}
}
fn decode_zstd_chunk(&mut self, chunk: &ModpkgChunk) -> Result<Box<[u8]>, ModpkgError> {
let compressed = self.load_chunk_raw(chunk)?;
let data = zstd::bulk::decompress(&compressed, chunk.uncompressed_size as usize)
.map_err(|e| ModpkgError::Io(std::io::Error::other(e)))?;
Ok(data.into_boxed_slice())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
builder::{ModpkgBuilder, ModpkgChunkBuilder, ModpkgLayerBuilder},
Modpkg,
};
use std::io::Cursor;
#[test]
fn test_decoder() {
let scratch = Vec::new();
let mut cursor = Cursor::new(scratch);
let test_data = [0xAA; 100];
let builder = ModpkgBuilder::default()
.with_layer(ModpkgLayerBuilder::base())
.with_chunk(
ModpkgChunkBuilder::new()
.with_path("test.bin")
.with_compression(ModpkgCompression::Zstd),
);
builder
.build_to_writer(&mut cursor, |_| Ok(test_data.to_vec()))
.expect("Failed to build Modpkg");
cursor.set_position(0);
let mut modpkg = Modpkg::mount_from_reader(cursor).unwrap();
let key = crate::ChunkKey::new(
crate::ChunkPath::new("test.bin").hash(),
crate::LayerHash::from_name("base"),
);
let chunk = *modpkg.chunks().get(&key).expect("test.bin chunk not found");
let mut decoder = ModpkgDecoder {
source: &mut modpkg.source,
};
let raw_data = decoder.load_chunk_raw(&chunk).unwrap();
assert_eq!(raw_data.len(), chunk.compressed_size as usize);
let decompressed_data = decoder.load_chunk_decompressed(&chunk).unwrap();
assert_eq!(decompressed_data.len(), chunk.uncompressed_size as usize);
assert_eq!(&decompressed_data[..], &test_data[..]);
}
}