mirror of
https://github.com/xavo95/repak.git
synced 2025-01-18 19:04:07 +00:00
add encryption feature
This commit is contained in:
parent
079693af22
commit
aae65c12cf
5 changed files with 147 additions and 23 deletions
|
@ -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"
|
||||
|
|
|
@ -311,17 +311,22 @@ 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() {
|
||||
#[cfg(not(feature = "encryption"))]
|
||||
return Err(super::Error::Encryption);
|
||||
#[cfg(feature = "encryption")]
|
||||
{
|
||||
let Some(key) = key else {
|
||||
return Err(super::Error::Encrypted);
|
||||
};
|
||||
|
@ -331,6 +336,7 @@ impl Entry {
|
|||
}
|
||||
data.truncate(self.compressed as usize);
|
||||
}
|
||||
}
|
||||
|
||||
let ranges = match &self.blocks {
|
||||
Some(blocks) => blocks
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
116
repak/src/pak.rs
116
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<aes::Aes256>,
|
||||
}
|
||||
|
||||
|
@ -15,6 +17,7 @@ pub struct PakReader {
|
|||
pub struct PakWriter<W: Write + Seek> {
|
||||
pak: Pak,
|
||||
writer: W,
|
||||
#[cfg(feature = "encryption")]
|
||||
key: Option<aes::Aes256>,
|
||||
}
|
||||
|
||||
|
@ -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<R: Read + Seek>(
|
||||
#[cfg(not(feature = "encryption"))]
|
||||
pub fn new_any<R: Read + Seek>(reader: &mut R) -> Result<Self, super::Error> {
|
||||
Self::new_any_inner(reader)
|
||||
}
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
pub fn new_any_with_key<R: Read + Seek>(
|
||||
reader: &mut R,
|
||||
key: Option<aes::Aes256>,
|
||||
) -> Result<Self, super::Error> {
|
||||
Self::new_any_inner(reader, key)
|
||||
}
|
||||
|
||||
fn new_any_inner<R: Read + Seek>(
|
||||
reader: &mut R,
|
||||
#[cfg(feature = "encryption")] key: Option<aes::Aes256>,
|
||||
) -> Result<Self, super::Error> {
|
||||
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<R: Read + Seek>(
|
||||
reader: &mut R,
|
||||
version: super::Version,
|
||||
) -> Result<Self, super::Error> {
|
||||
Self::new_inner(reader, version)
|
||||
}
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
pub fn new_with_key<R: Read + Seek>(
|
||||
reader: &mut R,
|
||||
version: super::Version,
|
||||
key: Option<aes::Aes256>,
|
||||
) -> Result<Self, super::Error> {
|
||||
Pak::read(reader, version, key.as_ref()).map(|pak| Self { pak, key })
|
||||
Self::new_inner(reader, version, key)
|
||||
}
|
||||
|
||||
fn new_inner<R: Read + Seek>(
|
||||
reader: &mut R,
|
||||
version: super::Version,
|
||||
#[cfg(feature = "encryption")] key: Option<aes::Aes256>,
|
||||
) -> Result<Self, super::Error> {
|
||||
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<W: Write + Seek> PakWriter<W> {
|
||||
#[cfg(not(feature = "encryption"))]
|
||||
pub fn new(
|
||||
writer: W,
|
||||
version: Version,
|
||||
mount_point: String,
|
||||
path_hash_seed: Option<u64>,
|
||||
) -> Self {
|
||||
PakWriter {
|
||||
pak: Pak::new(version, mount_point, path_hash_seed),
|
||||
writer,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "encryption")]
|
||||
pub fn new_with_key(
|
||||
writer: W,
|
||||
key: Option<aes::Aes256>,
|
||||
version: Version,
|
||||
|
@ -179,6 +250,21 @@ impl<W: Write + Seek> PakWriter<W> {
|
|||
}
|
||||
}
|
||||
|
||||
fn new_inner(
|
||||
writer: W,
|
||||
#[cfg(feature = "encryption")] key: Option<aes::Aes256>,
|
||||
version: Version,
|
||||
mount_point: String,
|
||||
path_hash_seed: Option<u64>,
|
||||
) -> 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<W: Write + Seek> PakWriter<W> {
|
|||
}
|
||||
|
||||
pub fn write_index(mut self) -> Result<W, super::Error> {
|
||||
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<R: Read + Seek>(
|
||||
reader: &mut R,
|
||||
version: super::Version,
|
||||
key: Option<&aes::Aes256>,
|
||||
#[cfg(feature = "encryption")] key: Option<&aes::Aes256>,
|
||||
) -> Result<Self, super::Error> {
|
||||
// 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<W: Write + Seek>(
|
||||
&self,
|
||||
writer: &mut W,
|
||||
_key: Option<aes::Aes256>,
|
||||
#[cfg(feature = "encryption")] _key: Option<aes::Aes256>,
|
||||
) -> Result<(), super::Error> {
|
||||
let index_offset = writer.stream_position()?;
|
||||
|
||||
|
@ -594,6 +695,7 @@ fn pad_zeros_to_alignment(v: &mut Vec<u8>, 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) {
|
||||
|
|
|
@ -175,7 +175,10 @@ fn main() -> Result<(), repak::Error> {
|
|||
}
|
||||
|
||||
fn info(aes_key: Option<aes::Aes256>, 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<aes::Aes256>, action: ActionInfo) -> Result<(), repak::E
|
|||
}
|
||||
|
||||
fn list(aes_key: Option<aes::Aes256>, 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<aes::Aes256>, action: ActionList) -> Result<(), repak::E
|
|||
}
|
||||
|
||||
fn hash_list(aes_key: Option<aes::Aes256>, 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<aes::Aes256>, action: ActionHashList) -> Result<(),
|
|||
const STYLE: &str = "[{elapsed_precise}] [{wide_bar}] {pos}/{len} ({eta})";
|
||||
|
||||
fn unpack(aes_key: Option<aes::Aes256>, 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<aes::Aes256>, 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);
|
||||
|
||||
|
|
Loading…
Reference in a new issue