diff --git a/Cargo.lock b/Cargo.lock index f6995a9..8e6159a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -668,6 +668,15 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "lz4_flex" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75761162ae2b0e580d7e7c390558127e5f01b4194debd6221fd8c207fc80e3f5" +dependencies = [ + "twox-hash", +] + [[package]] name = "memchr" version = "2.7.4" @@ -843,6 +852,7 @@ dependencies = [ "byteorder", "flate2", "hex", + "lz4_flex", "oodle_loader", "pariter", "paste", @@ -1016,6 +1026,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "strsim" version = "0.11.1" @@ -1122,6 +1138,16 @@ dependencies = [ "zerovec", ] +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "static_assertions", +] + [[package]] name = "typenum" version = "1.17.0" diff --git a/repak/Cargo.toml b/repak/Cargo.toml index bb3fcb9..cdb7adf 100644 --- a/repak/Cargo.toml +++ b/repak/Cargo.toml @@ -9,8 +9,8 @@ keywords.workspace = true [features] default = ["compression", "encryption"] -compression = ["dep:flate2", "dep:zstd"] -oodle = ["dep:oodle_loader"] +compression = ["dep:flate2", "dep:zstd", "dep:lz4_flex"] +oodle = ["dep:oodle_loader", "compression"] encryption = ["dep:aes"] [dependencies] @@ -18,6 +18,7 @@ byteorder = "1.5" aes = { workspace = true, optional = true } flate2 = { version = "1.0", optional = true } zstd = { version = "0.13", optional = true } +lz4_flex = { version = "0.11.3", optional = true } oodle_loader = { path = "../oodle_loader", optional = true} thiserror = "2.0" sha1 = { workspace = true } diff --git a/repak/src/data.rs b/repak/src/data.rs index 7832db7..42951d5 100644 --- a/repak/src/data.rs +++ b/repak/src/data.rs @@ -174,6 +174,7 @@ fn compress(compression: Compression, data: &[u8]) -> Result> { compress.finish()? } Compression::Zstd => zstd::stream::encode_all(data, 0)?, + Compression::LZ4 => lz4_flex::block::compress(data), Compression::Oodle => { #[cfg(not(feature = "oodle"))] return Err(super::Error::Oodle); diff --git a/repak/src/entry.rs b/repak/src/entry.rs index 3c9fb76..abac48c 100644 --- a/repak/src/entry.rs +++ b/repak/src/entry.rs @@ -376,7 +376,7 @@ impl Entry { } } - #[cfg(any(feature = "compression", feature = "oodle"))] + #[cfg(feature = "compression")] let ranges = { let offset = |index: u64| -> usize { (match version.version_major() >= VersionMajor::RelativeChunkOffsets { @@ -406,52 +406,46 @@ impl Entry { match self.compression_slot.and_then(|c| compression[c as usize]) { None => buf.write_all(&data)?, - #[cfg(feature = "compression")] - Some(Compression::Zlib) => decompress!(flate2::read::ZlibDecoder<&[u8]>), - #[cfg(feature = "compression")] - Some(Compression::Gzip) => decompress!(flate2::read::GzDecoder<&[u8]>), - #[cfg(feature = "compression")] - Some(Compression::Zstd) => { - for range in ranges { - io::copy(&mut zstd::stream::read::Decoder::new(&data[range])?, buf)?; - } - } - #[cfg(feature = "oodle")] - Some(Compression::Oodle) => { - let mut decompressed = vec![0; self.uncompressed as usize]; - - let mut compress_offset = 0; - let mut decompress_offset = 0; - let block_count = ranges.len(); - for range in ranges { - let decomp = if block_count == 1 { - self.uncompressed as usize - } else { - (self.compression_block_size as usize) - .min(self.uncompressed as usize - compress_offset) - }; - let buffer = &mut data[range]; - let out = oodle_loader::oodle()?.decompress( - buffer, - &mut decompressed[decompress_offset..decompress_offset + decomp], - ); - if out == 0 { - return Err(super::Error::DecompressionFailed(Compression::Oodle)); - } - compress_offset += self.compression_block_size as usize; - decompress_offset += out as usize; - } - - debug_assert_eq!( - decompress_offset, self.uncompressed as usize, - "Oodle decompression length mismatch" - ); - buf.write_all(&decompressed)?; - } - #[cfg(not(feature = "oodle"))] - Some(Compression::Oodle) => return Err(super::Error::Oodle), #[cfg(not(feature = "compression"))] _ => return Err(super::Error::Compression), + #[cfg(feature = "compression")] + Some(comp) => match comp { + Compression::Zlib => decompress!(flate2::read::ZlibDecoder<&[u8]>), + Compression::Gzip => decompress!(flate2::read::GzDecoder<&[u8]>), + Compression::Zstd => { + for range in ranges { + io::copy(&mut zstd::stream::read::Decoder::new(&data[range])?, buf)?; + } + } + Compression::LZ4 => { + let mut decompressed = vec![0; self.uncompressed as usize]; + for (decomp_chunk, comp_range) in decompressed + .chunks_mut(self.compression_block_size as usize) + .zip(ranges) + { + lz4_flex::block::decompress_into(&data[comp_range], decomp_chunk) + .map_err(|_| Error::DecompressionFailed(Compression::LZ4))?; + } + buf.write_all(&decompressed)?; + } + #[cfg(feature = "oodle")] + Compression::Oodle => { + let mut decompressed = vec![0; self.uncompressed as usize]; + for (decomp_chunk, comp_range) in decompressed + .chunks_mut(self.compression_block_size as usize) + .zip(ranges) + { + let out = + oodle_loader::oodle()?.decompress(&data[comp_range], decomp_chunk); + if out == 0 { + return Err(Error::DecompressionFailed(Compression::Oodle)); + } + } + buf.write_all(&decompressed)?; + } + #[cfg(not(feature = "oodle"))] + Compression::Oodle => return Err(super::Error::Oodle), + }, } buf.flush()?; Ok(()) diff --git a/repak/src/lib.rs b/repak/src/lib.rs index 643381c..6445a97 100644 --- a/repak/src/lib.rs +++ b/repak/src/lib.rs @@ -119,6 +119,7 @@ pub enum Compression { Gzip, Oodle, Zstd, + LZ4, } #[allow(clippy::large_enum_variant)]