diff --git a/repak/Cargo.toml b/repak/Cargo.toml index 5c31a19..1e35868 100644 --- a/repak/Cargo.toml +++ b/repak/Cargo.toml @@ -7,13 +7,14 @@ version.workspace = true edition.workspace = true [features] -default = ["compression"] +default = ["compression", "encryption"] compression = ["dep:flate2", "dep:zstd"] oodle = ["dep:libloading", "dep:ureq", "dep:once_cell", "dep:hex-literal", "dep:hex"] +encryption = ["dep:aes"] [dependencies] byteorder = "1.4" -aes = "0.8" +aes = { version = "0.8", optional = true } flate2 = { version = "1.0", optional = true } zstd = { version = "0.12", optional = true } thiserror = "1.0" diff --git a/repak/src/entry.rs b/repak/src/entry.rs index 71b5af2..b198c58 100644 --- a/repak/src/entry.rs +++ b/repak/src/entry.rs @@ -311,25 +311,31 @@ impl Entry { reader: &mut R, version: Version, compression: &[Compression], - key: Option<&aes::Aes256>, + #[cfg(feature = "encryption")] key: Option<&aes::Aes256>, buf: &mut W, ) -> Result<(), super::Error> { reader.seek(io::SeekFrom::Start(self.offset))?; Entry::read(reader, version)?; let data_offset = reader.stream_position()?; + #[allow(unused_mut)] let mut data = reader.read_len(match self.is_encrypted() { true => align(self.compressed), false => self.compressed, } as usize)?; if self.is_encrypted() { - let Some(key) = key else { - return Err(super::Error::Encrypted); - }; - use aes::cipher::BlockDecrypt; - for block in data.chunks_mut(16) { - key.decrypt_block(aes::Block::from_mut_slice(block)) + #[cfg(not(feature = "encryption"))] + return Err(super::Error::Encryption); + #[cfg(feature = "encryption")] + { + let Some(key) = key else { + return Err(super::Error::Encrypted); + }; + 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); } - data.truncate(self.compressed as usize); } let ranges = match &self.blocks { diff --git a/repak/src/error.rs b/repak/src/error.rs index a961fad..4bfd7df 100644 --- a/repak/src/error.rs +++ b/repak/src/error.rs @@ -13,6 +13,9 @@ pub enum Error { #[error("enable the compression feature to read compressed paks")] Compression, + #[error("enable the encryption feature to read encrypted paks")] + Encryption, + #[error("enable the oodle feature to read oodle paks")] Oodle, diff --git a/repak/src/pak.rs b/repak/src/pak.rs index 1785f81..8161b42 100644 --- a/repak/src/pak.rs +++ b/repak/src/pak.rs @@ -1,5 +1,6 @@ use super::ext::{ReadExt, WriteExt}; use super::{Version, VersionMajor}; +#[cfg(feature = "encryption")] use aes::Aes256; use byteorder::{ReadBytesExt, WriteBytesExt, LE}; use std::collections::BTreeMap; @@ -8,6 +9,7 @@ use std::io::{self, Read, Seek, Write}; #[derive(Debug)] pub struct PakReader { pak: Pak, + #[cfg(feature = "encryption")] key: Option, } @@ -15,6 +17,7 @@ pub struct PakReader { pub struct PakWriter { pak: Pak, writer: W, + #[cfg(feature = "encryption")] key: Option, } @@ -70,6 +73,7 @@ impl Index { } } +#[cfg(feature = "encryption")] fn decrypt(key: Option<&aes::Aes256>, bytes: &mut [u8]) -> Result<(), super::Error> { if let Some(key) = key { use aes::cipher::BlockDecrypt; @@ -83,28 +87,79 @@ fn decrypt(key: Option<&aes::Aes256>, bytes: &mut [u8]) -> Result<(), super::Err } impl PakReader { - pub fn new_any( + #[cfg(not(feature = "encryption"))] + pub fn new_any(reader: &mut R) -> Result { + Self::new_any_inner(reader) + } + + #[cfg(feature = "encryption")] + pub fn new_any_with_key( reader: &mut R, key: Option, + ) -> Result { + Self::new_any_inner(reader, key) + } + + fn new_any_inner( + reader: &mut R, + #[cfg(feature = "encryption")] key: Option, ) -> Result { use std::fmt::Write; let mut log = "\n".to_owned(); for ver in Version::iter() { - match Pak::read(&mut *reader, ver, key.as_ref()) { - Ok(pak) => return Ok(Self { pak, key }), + match Pak::read( + &mut *reader, + ver, + #[cfg(feature = "encryption")] + key.as_ref(), + ) { + Ok(pak) => { + return Ok(Self { + pak, + #[cfg(feature = "encryption")] + key, + }) + } Err(err) => writeln!(log, "trying version {} failed: {}", ver, err)?, } } Err(super::Error::UnsupportedOrEncrypted(log)) } + #[cfg(not(feature = "encryption"))] pub fn new( reader: &mut R, version: super::Version, + ) -> Result { + Self::new_inner(reader, version) + } + + #[cfg(feature = "encryption")] + pub fn new_with_key( + reader: &mut R, + version: super::Version, key: Option, ) -> Result { - Pak::read(reader, version, key.as_ref()).map(|pak| Self { pak, key }) + Self::new_inner(reader, version, key) + } + + fn new_inner( + reader: &mut R, + version: super::Version, + #[cfg(feature = "encryption")] key: Option, + ) -> Result { + Pak::read( + reader, + version, + #[cfg(feature = "encryption")] + key.as_ref(), + ) + .map(|pak| Self { + pak, + #[cfg(feature = "encryption")] + key, + }) } pub fn version(&self) -> super::Version { @@ -140,6 +195,7 @@ impl PakReader { reader, self.pak.version, &self.pak.compression, + #[cfg(feature = "encryption")] self.key.as_ref(), writer, ), @@ -158,6 +214,7 @@ impl PakReader { writer.seek(io::SeekFrom::Start(self.pak.index_offset.unwrap()))?; Ok(PakWriter { pak: self.pak, + #[cfg(feature = "encryption")] key: self.key, writer, }) @@ -165,7 +222,21 @@ impl PakReader { } impl PakWriter { + #[cfg(not(feature = "encryption"))] pub fn new( + writer: W, + version: Version, + mount_point: String, + path_hash_seed: Option, + ) -> Self { + PakWriter { + pak: Pak::new(version, mount_point, path_hash_seed), + writer, + } + } + + #[cfg(feature = "encryption")] + pub fn new_with_key( writer: W, key: Option, version: Version, @@ -179,6 +250,21 @@ impl PakWriter { } } + fn new_inner( + writer: W, + #[cfg(feature = "encryption")] key: Option, + version: Version, + mount_point: String, + path_hash_seed: Option, + ) -> Self { + PakWriter { + pak: Pak::new(version, mount_point, path_hash_seed), + writer, + #[cfg(feature = "encryption")] + key, + } + } + pub fn into_writer(self) -> W { self.writer } @@ -219,7 +305,11 @@ impl PakWriter { } pub fn write_index(mut self) -> Result { - self.pak.write(&mut self.writer, self.key)?; + self.pak.write( + &mut self.writer, + #[cfg(feature = "encryption")] + self.key, + )?; Ok(self.writer) } } @@ -228,17 +318,21 @@ impl Pak { fn read( reader: &mut R, version: super::Version, - key: Option<&aes::Aes256>, + #[cfg(feature = "encryption")] key: Option<&aes::Aes256>, ) -> Result { // read footer to get index, encryption & compression info reader.seek(io::SeekFrom::End(-version.size()))?; let footer = super::footer::Footer::read(reader, version)?; // read index to get all the entry info reader.seek(io::SeekFrom::Start(footer.index_offset))?; + #[allow(unused_mut)] let mut index = reader.read_len(footer.index_size as usize)?; // decrypt index if needed if footer.encrypted { + #[cfg(not(feature = "encryption"))] + return Err(super::Error::Encryption); + #[cfg(feature = "encryption")] decrypt(key, &mut index)?; } @@ -260,6 +354,9 @@ impl Pak { // TODO verify hash if footer.encrypted { + #[cfg(not(feature = "encryption"))] + return Err(super::Error::Encryption); + #[cfg(feature = "encryption")] decrypt(key, &mut path_hash_index_buf)?; } @@ -283,11 +380,15 @@ impl Pak { let _full_directory_index_hash = index.read_len(20)?; reader.seek(io::SeekFrom::Start(full_directory_index_offset))?; + #[allow(unused_mut)] let mut full_directory_index = reader.read_len(full_directory_index_size as usize)?; // TODO verify hash if footer.encrypted { + #[cfg(not(feature = "encryption"))] + return Err(super::Error::Encryption); + #[cfg(feature = "encryption")] decrypt(key, &mut full_directory_index)?; } let mut fdi = io::Cursor::new(full_directory_index); @@ -367,7 +468,7 @@ impl Pak { fn write( &self, writer: &mut W, - _key: Option, + #[cfg(feature = "encryption")] _key: Option, ) -> Result<(), super::Error> { let index_offset = writer.stream_position()?; @@ -594,6 +695,7 @@ fn pad_zeros_to_alignment(v: &mut Vec, alignment: usize) { assert!(v.len() % alignment == 0); } +#[cfg(feature = "encryption")] fn encrypt(key: Aes256, bytes: &mut [u8]) { use aes::cipher::BlockEncrypt; for chunk in bytes.chunks_mut(16) { diff --git a/repak_cli/src/main.rs b/repak_cli/src/main.rs index 7b68d61..78ddbd8 100644 --- a/repak_cli/src/main.rs +++ b/repak_cli/src/main.rs @@ -175,7 +175,10 @@ fn main() -> Result<(), repak::Error> { } fn info(aes_key: Option, action: ActionInfo) -> Result<(), repak::Error> { - let pak = repak::PakReader::new_any(&mut BufReader::new(File::open(action.input)?), aes_key)?; + let pak = repak::PakReader::new_any_with_key( + &mut BufReader::new(File::open(action.input)?), + aes_key, + )?; println!("mount point: {}", pak.mount_point()); println!("version: {}", pak.version()); println!("version major: {}", pak.version().version_major()); @@ -186,7 +189,10 @@ fn info(aes_key: Option, action: ActionInfo) -> Result<(), repak::E } fn list(aes_key: Option, action: ActionList) -> Result<(), repak::Error> { - let pak = repak::PakReader::new_any(&mut BufReader::new(File::open(action.input)?), aes_key)?; + let pak = repak::PakReader::new_any_with_key( + &mut BufReader::new(File::open(action.input)?), + aes_key, + )?; let mount_point = PathBuf::from(pak.mount_point()); let prefix = Path::new(&action.strip_prefix); @@ -215,7 +221,10 @@ fn list(aes_key: Option, action: ActionList) -> Result<(), repak::E } fn hash_list(aes_key: Option, action: ActionHashList) -> Result<(), repak::Error> { - let pak = repak::PakReader::new_any(&mut BufReader::new(File::open(&action.input)?), aes_key)?; + let pak = repak::PakReader::new_any_with_key( + &mut BufReader::new(File::open(&action.input)?), + aes_key, + )?; let mount_point = PathBuf::from(pak.mount_point()); let prefix = Path::new(&action.strip_prefix); @@ -270,7 +279,10 @@ fn hash_list(aes_key: Option, action: ActionHashList) -> Result<(), const STYLE: &str = "[{elapsed_precise}] [{wide_bar}] {pos}/{len} ({eta})"; fn unpack(aes_key: Option, action: ActionUnpack) -> Result<(), repak::Error> { - let pak = repak::PakReader::new_any(&mut BufReader::new(File::open(&action.input)?), aes_key)?; + let pak = repak::PakReader::new_any_with_key( + &mut BufReader::new(File::open(&action.input)?), + aes_key, + )?; let output = action .output .map(PathBuf::from) @@ -388,7 +400,7 @@ fn pack(args: ActionPack) -> Result<(), repak::Error> { collect_files(&mut paths, input_path)?; paths.sort(); - let mut pak = repak::PakWriter::new( + let mut pak = repak::PakWriter::new_with_key( BufWriter::new(File::create(&output)?), None, args.version, @@ -423,7 +435,7 @@ fn pack(args: ActionPack) -> Result<(), repak::Error> { fn get(aes_key: Option, args: ActionGet) -> Result<(), repak::Error> { let mut reader = BufReader::new(File::open(&args.input)?); - let pak = repak::PakReader::new_any(&mut reader, aes_key)?; + let pak = repak::PakReader::new_any_with_key(&mut reader, aes_key)?; let mount_point = PathBuf::from(pak.mount_point()); let prefix = Path::new(&args.strip_prefix);