use super::{Compression, ReadExt, Version}; use byteorder::{ReadBytesExt, LE}; use std::io; #[derive(Debug)] pub struct Block { pub start: u64, pub end: u64, } impl Block { pub fn new(reader: &mut R) -> Result { Ok(Self { start: reader.read_u64::()?, end: reader.read_u64::()?, }) } } #[derive(Debug)] pub struct Entry { pub offset: u64, pub compressed: u64, pub uncompressed: u64, pub compression: Compression, pub timestamp: Option, pub hash: [u8; 20], pub blocks: Option>, pub encrypted: bool, pub block_uncompressed: Option, } impl Entry { pub fn new(reader: &mut R, version: super::Version) -> Result { // since i need the compression flags, i have to store these as variables which is mildly annoying let offset = reader.read_u64::()?; let compressed = reader.read_u64::()?; let uncompressed = reader.read_u64::()?; let compression = match reader.read_u32::()? { 0x01 | 0x10 | 0x20 => Compression::Zlib, _ => Compression::None, }; Ok(Self { offset, compressed, uncompressed, compression, timestamp: match version == Version::Initial { true => Some(reader.read_u64::()?), false => None, }, hash: reader.read_guid()?, blocks: match version >= Version::CompressionEncryption && compression != Compression::None { true => Some(reader.read_array(Block::new)?), false => None, }, encrypted: version >= Version::CompressionEncryption && reader.read_bool()?, block_uncompressed: match version >= Version::CompressionEncryption { true => Some(reader.read_u32::()?), false => None, }, }) } pub fn read( &self, reader: &mut R, version: super::Version, key: Option<&aes::Aes256Dec>, ) -> Result, super::Error> { let mut buf = io::BufWriter::new(Vec::with_capacity(self.uncompressed as usize)); reader.seek(io::SeekFrom::Start(self.offset))?; let mut data = reader.read_len(match self.encrypted { // add alignment (aes block size: 16) then zero out alignment bits true => (self.compressed + 15) & !17, false => self.compressed, } as usize)?; if self.encrypted { if let Some(key) = key { use aes::cipher::BlockDecrypt; for block in data.chunks_mut(16) { key.decrypt_block(aes::Block::from_mut_slice(block)) } data.truncate(self.compressed as usize); } } use io::Write; match self.compression { Compression::None => buf.write_all(&data)?, Compression::Zlib => { let mut decoder = flate2::write::ZlibDecoder::new(buf); match &self.blocks { Some(blocks) => { for block in blocks { decoder.write( &data[match version >= Version::RelativeChunkOffsets { true => { (block.start - self.offset) as usize ..(block.end - self.offset) as usize } false => block.start as usize..block.end as usize, }], )?; } } None => decoder.write_all(&data)?, } buf = decoder.finish()?; } Compression::Gzip => todo!(), Compression::Oodle => todo!(), } buf.flush()?; Ok(buf.into_inner()?) } }