mirror of
https://github.com/xavo95/repak.git
synced 2025-01-18 19:04:07 +00:00
Merge pull request #6 from bananaturtlesandwich/features-redo
Move functionality behind `compression`, `encryption`, and `oodle` features
This commit is contained in:
commit
a749449f8c
8 changed files with 239 additions and 54 deletions
|
@ -6,20 +6,29 @@ license.workspace = true
|
||||||
version.workspace = true
|
version.workspace = true
|
||||||
edition.workspace = true
|
edition.workspace = true
|
||||||
|
|
||||||
|
[features]
|
||||||
|
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]
|
[dependencies]
|
||||||
byteorder = "1.4"
|
byteorder = "1.4"
|
||||||
aes = "0.8"
|
aes = { version = "0.8", optional = true }
|
||||||
flate2 = "1.0"
|
flate2 = { version = "1.0", optional = true }
|
||||||
|
zstd = { version = "0.12", optional = true }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
sha1 = "0.10.5"
|
sha1 = "0.10.5"
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
libloading = "0.7.4"
|
libloading = { version = "0.7", optional = true }
|
||||||
ureq = "2.6.2"
|
ureq = { version = "2.6", optional = true }
|
||||||
hex-literal = "0.4.1"
|
once_cell = { version = "1.17", optional = true}
|
||||||
hex = { workspace = true }
|
hex-literal = { version = "0.4", optional = true }
|
||||||
once_cell = "1.17.1"
|
hex = { workspace = true, optional = true }
|
||||||
zstd = "0.12.3"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
paste = "1.0.11"
|
paste = "1.0.11"
|
||||||
|
|
||||||
|
[package.metadata.cargo-all-features]
|
||||||
|
denylist = ["oodle"]
|
||||||
|
|
|
@ -3,13 +3,13 @@ use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
pub enum EntryLocation {
|
pub(crate) enum EntryLocation {
|
||||||
Data,
|
Data,
|
||||||
Index,
|
Index,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Block {
|
pub(crate) struct Block {
|
||||||
pub start: u64,
|
pub start: u64,
|
||||||
pub end: u64,
|
pub end: u64,
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ fn align(offset: u64) -> u64 {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Entry {
|
pub(crate) struct Entry {
|
||||||
pub offset: u64,
|
pub offset: u64,
|
||||||
pub compressed: u64,
|
pub compressed: u64,
|
||||||
pub uncompressed: u64,
|
pub uncompressed: u64,
|
||||||
|
@ -311,27 +311,35 @@ impl Entry {
|
||||||
reader: &mut R,
|
reader: &mut R,
|
||||||
version: Version,
|
version: Version,
|
||||||
compression: &[Compression],
|
compression: &[Compression],
|
||||||
key: Option<&aes::Aes256>,
|
#[allow(unused)] key: &super::Key,
|
||||||
buf: &mut W,
|
buf: &mut W,
|
||||||
) -> Result<(), super::Error> {
|
) -> Result<(), super::Error> {
|
||||||
reader.seek(io::SeekFrom::Start(self.offset))?;
|
reader.seek(io::SeekFrom::Start(self.offset))?;
|
||||||
Entry::read(reader, version)?;
|
Entry::read(reader, version)?;
|
||||||
|
#[cfg(any(feature = "compression", feature = "oodle"))]
|
||||||
let data_offset = reader.stream_position()?;
|
let data_offset = reader.stream_position()?;
|
||||||
|
#[allow(unused_mut)]
|
||||||
let mut data = reader.read_len(match self.is_encrypted() {
|
let mut data = reader.read_len(match self.is_encrypted() {
|
||||||
true => align(self.compressed),
|
true => align(self.compressed),
|
||||||
false => self.compressed,
|
false => self.compressed,
|
||||||
} as usize)?;
|
} as usize)?;
|
||||||
if self.is_encrypted() {
|
if self.is_encrypted() {
|
||||||
let Some(key) = key else {
|
#[cfg(not(feature = "encryption"))]
|
||||||
return Err(super::Error::Encrypted);
|
return Err(super::Error::Encryption);
|
||||||
};
|
#[cfg(feature = "encryption")]
|
||||||
use aes::cipher::BlockDecrypt;
|
{
|
||||||
for block in data.chunks_mut(16) {
|
let super::Key::Some(key) = key else {
|
||||||
key.decrypt_block(aes::Block::from_mut_slice(block))
|
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "compression", feature = "oodle"))]
|
||||||
let ranges = match &self.blocks {
|
let ranges = match &self.blocks {
|
||||||
Some(blocks) => blocks
|
Some(blocks) => blocks
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -347,9 +355,11 @@ impl Entry {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
|
#[allow(clippy::single_range_in_vec_init)]
|
||||||
None => vec![0..data.len()],
|
None => vec![0..data.len()],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
macro_rules! decompress {
|
macro_rules! decompress {
|
||||||
($decompressor: ty) => {
|
($decompressor: ty) => {
|
||||||
for range in ranges {
|
for range in ranges {
|
||||||
|
@ -359,14 +369,18 @@ impl Entry {
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.compression.map(|c| compression[c as usize]) {
|
match self.compression.map(|c| compression[c as usize]) {
|
||||||
None => buf.write_all(&data)?,
|
None | Some(Compression::None) => buf.write_all(&data)?,
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
Some(Compression::Zlib) => decompress!(flate2::read::ZlibDecoder<&[u8]>),
|
Some(Compression::Zlib) => decompress!(flate2::read::ZlibDecoder<&[u8]>),
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
Some(Compression::Gzip) => decompress!(flate2::read::GzDecoder<&[u8]>),
|
Some(Compression::Gzip) => decompress!(flate2::read::GzDecoder<&[u8]>),
|
||||||
|
#[cfg(feature = "compression")]
|
||||||
Some(Compression::Zstd) => {
|
Some(Compression::Zstd) => {
|
||||||
for range in ranges {
|
for range in ranges {
|
||||||
io::copy(&mut zstd::stream::read::Decoder::new(&data[range])?, buf)?;
|
io::copy(&mut zstd::stream::read::Decoder::new(&data[range])?, buf)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[cfg(feature = "oodle")]
|
||||||
Some(Compression::Oodle) => {
|
Some(Compression::Oodle) => {
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
return Err(super::Error::Oodle);
|
return Err(super::Error::Oodle);
|
||||||
|
@ -410,6 +424,7 @@ impl Entry {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
let OodleLZ_Decompress: libloading::Symbol<
|
let OodleLZ_Decompress: libloading::Symbol<
|
||||||
extern "C" fn(
|
extern "C" fn(
|
||||||
compBuf: *mut u8,
|
compBuf: *mut u8,
|
||||||
|
@ -472,18 +487,25 @@ impl Entry {
|
||||||
buf.write_all(&decompressed)?;
|
buf.write_all(&decompressed)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => todo!(),
|
#[cfg(not(feature = "oodle"))]
|
||||||
|
Some(Compression::Oodle) => return Err(super::Error::Oodle),
|
||||||
|
#[cfg(not(feature = "compression"))]
|
||||||
|
_ => return Err(super::Error::Compression),
|
||||||
}
|
}
|
||||||
buf.flush()?;
|
buf.flush()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "oodle")]
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
#[cfg(feature = "oodle")]
|
||||||
static OODLE: Lazy<Result<libloading::Library, String>> =
|
static OODLE: Lazy<Result<libloading::Library, String>> =
|
||||||
Lazy::new(|| get_oodle().map_err(|e| e.to_string()));
|
Lazy::new(|| get_oodle().map_err(|e| e.to_string()));
|
||||||
|
#[cfg(feature = "oodle")]
|
||||||
static OODLE_HASH: [u8; 20] = hex_literal::hex!("4bcc73614cb8fd2b0bce8d0f91ee5f3202d9d624");
|
static OODLE_HASH: [u8; 20] = hex_literal::hex!("4bcc73614cb8fd2b0bce8d0f91ee5f3202d9d624");
|
||||||
|
|
||||||
|
#[cfg(feature = "oodle")]
|
||||||
fn get_oodle() -> Result<libloading::Library, super::Error> {
|
fn get_oodle() -> Result<libloading::Library, super::Error> {
|
||||||
use sha1::{Digest, Sha1};
|
use sha1::{Digest, Sha1};
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,23 @@ pub enum Error {
|
||||||
#[error("expect 256 bit AES key as base64 or hex string")]
|
#[error("expect 256 bit AES key as base64 or hex string")]
|
||||||
Aes,
|
Aes,
|
||||||
|
|
||||||
|
// feature errors
|
||||||
|
#[error("enable the compression feature to read compressed paks")]
|
||||||
|
Compression,
|
||||||
|
|
||||||
|
#[error("enable the encryption feature to read encrypted paks")]
|
||||||
|
Encryption,
|
||||||
|
|
||||||
|
#[cfg_attr(
|
||||||
|
windows,
|
||||||
|
error("enable the oodle feature to read Oodle compressed paks")
|
||||||
|
)]
|
||||||
|
#[cfg_attr(
|
||||||
|
not(windows),
|
||||||
|
error("Oodle compression only supported on Windows (or WINE)")
|
||||||
|
)]
|
||||||
|
Oodle,
|
||||||
|
|
||||||
// std errors
|
// std errors
|
||||||
#[error("io error: {0}")]
|
#[error("io error: {0}")]
|
||||||
Io(#[from] std::io::Error),
|
Io(#[from] std::io::Error),
|
||||||
|
@ -22,6 +39,7 @@ pub enum Error {
|
||||||
#[error("utf16 conversion: {0}")]
|
#[error("utf16 conversion: {0}")]
|
||||||
Utf16(#[from] std::string::FromUtf16Error),
|
Utf16(#[from] std::string::FromUtf16Error),
|
||||||
|
|
||||||
|
#[cfg(feature = "oodle")]
|
||||||
#[error("ureq error: {0}")]
|
#[error("ureq error: {0}")]
|
||||||
Ureq(#[from] Box<ureq::Error>), // boxed because ureq::Error is quite large
|
Ureq(#[from] Box<ureq::Error>), // boxed because ureq::Error is quite large
|
||||||
|
|
||||||
|
@ -35,9 +53,6 @@ pub enum Error {
|
||||||
#[error("found magic of {0:#x} instead of {:#x}", super::MAGIC)]
|
#[error("found magic of {0:#x} instead of {:#x}", super::MAGIC)]
|
||||||
Magic(u32),
|
Magic(u32),
|
||||||
|
|
||||||
#[error("Oodle compression only supported on Windows (or WINE)")]
|
|
||||||
Oodle,
|
|
||||||
|
|
||||||
#[error("Could not load oo2core_9_win64.dll")]
|
#[error("Could not load oo2core_9_win64.dll")]
|
||||||
OodleFailed,
|
OodleFailed,
|
||||||
|
|
||||||
|
@ -72,7 +87,7 @@ pub enum Error {
|
||||||
OsString(std::ffi::OsString),
|
OsString(std::ffi::OsString),
|
||||||
|
|
||||||
#[error("{0}version unsupported or is encrypted (possibly missing --aes-key?)")]
|
#[error("{0}version unsupported or is encrypted (possibly missing --aes-key?)")]
|
||||||
UnsuportedOrEncrypted(String),
|
UnsupportedOrEncrypted(String),
|
||||||
|
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Other(String),
|
Other(String),
|
||||||
|
|
|
@ -7,6 +7,9 @@ mod pak;
|
||||||
|
|
||||||
pub use {error::*, pak::*};
|
pub use {error::*, pak::*};
|
||||||
|
|
||||||
|
#[cfg(all(feature = "oodle", not(target_os = "windows")))]
|
||||||
|
compile_error!("Oodle compression only supported on Windows (or WINE)");
|
||||||
|
|
||||||
pub const MAGIC: u32 = 0x5A6F12E1;
|
pub const MAGIC: u32 = 0x5A6F12E1;
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
|
@ -119,3 +122,18 @@ pub enum Compression {
|
||||||
Oodle,
|
Oodle,
|
||||||
Zstd,
|
Zstd,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::large_enum_variant)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) enum Key {
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
|
Some(aes::Aes256),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
|
impl From<aes::Aes256> for Key {
|
||||||
|
fn from(value: aes::Aes256) -> Self {
|
||||||
|
Self::Some(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
141
repak/src/pak.rs
141
repak/src/pak.rs
|
@ -1,6 +1,5 @@
|
||||||
use super::ext::{ReadExt, WriteExt};
|
use super::ext::{ReadExt, WriteExt};
|
||||||
use super::{Version, VersionMajor};
|
use super::{Version, VersionMajor};
|
||||||
use aes::Aes256;
|
|
||||||
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
use byteorder::{ReadBytesExt, WriteBytesExt, LE};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::io::{self, Read, Seek, Write};
|
use std::io::{self, Read, Seek, Write};
|
||||||
|
@ -8,18 +7,18 @@ use std::io::{self, Read, Seek, Write};
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PakReader {
|
pub struct PakReader {
|
||||||
pak: Pak,
|
pak: Pak,
|
||||||
key: Option<aes::Aes256>,
|
key: super::Key,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PakWriter<W: Write + Seek> {
|
pub struct PakWriter<W: Write + Seek> {
|
||||||
pak: Pak,
|
pak: Pak,
|
||||||
writer: W,
|
writer: W,
|
||||||
key: Option<aes::Aes256>,
|
key: super::Key,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Pak {
|
pub(crate) struct Pak {
|
||||||
version: Version,
|
version: Version,
|
||||||
mount_point: String,
|
mount_point: String,
|
||||||
index_offset: Option<u64>,
|
index_offset: Option<u64>,
|
||||||
|
@ -44,7 +43,7 @@ impl Pak {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Index {
|
pub(crate) struct Index {
|
||||||
path_hash_seed: Option<u64>,
|
path_hash_seed: Option<u64>,
|
||||||
entries: BTreeMap<String, super::entry::Entry>,
|
entries: BTreeMap<String, super::entry::Entry>,
|
||||||
}
|
}
|
||||||
|
@ -70,8 +69,9 @@ impl Index {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decrypt(key: Option<&aes::Aes256>, bytes: &mut [u8]) -> Result<(), super::Error> {
|
#[cfg(feature = "encryption")]
|
||||||
if let Some(key) = key {
|
fn decrypt(key: &super::Key, bytes: &mut [u8]) -> Result<(), super::Error> {
|
||||||
|
if let super::Key::Some(key) = key {
|
||||||
use aes::cipher::BlockDecrypt;
|
use aes::cipher::BlockDecrypt;
|
||||||
for chunk in bytes.chunks_mut(16) {
|
for chunk in bytes.chunks_mut(16) {
|
||||||
key.decrypt_block(aes::Block::from_mut_slice(chunk))
|
key.decrypt_block(aes::Block::from_mut_slice(chunk))
|
||||||
|
@ -83,28 +83,79 @@ fn decrypt(key: Option<&aes::Aes256>, bytes: &mut [u8]) -> Result<(), super::Err
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PakReader {
|
impl PakReader {
|
||||||
pub fn new_any<R: Read + Seek>(
|
pub fn new_any<R: Read + Seek>(reader: &mut R) -> Result<Self, super::Error> {
|
||||||
|
Self::new_any_inner(reader, super::Key::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
|
pub fn new_any_with_key<R: Read + Seek>(
|
||||||
|
reader: &mut R,
|
||||||
|
key: aes::Aes256,
|
||||||
|
) -> Result<Self, super::Error> {
|
||||||
|
Self::new_any_inner(reader, key.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
|
pub fn new_any_with_optional_key<R: Read + Seek>(
|
||||||
reader: &mut R,
|
reader: &mut R,
|
||||||
key: Option<aes::Aes256>,
|
key: Option<aes::Aes256>,
|
||||||
|
) -> Result<Self, super::Error> {
|
||||||
|
match key {
|
||||||
|
Some(key) => Self::new_any_with_key(reader, key),
|
||||||
|
None => Self::new_any(reader),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_any_inner<R: Read + Seek>(
|
||||||
|
reader: &mut R,
|
||||||
|
key: super::Key,
|
||||||
) -> Result<Self, super::Error> {
|
) -> Result<Self, super::Error> {
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
let mut log = "\n".to_owned();
|
let mut log = "\n".to_owned();
|
||||||
|
|
||||||
for ver in Version::iter() {
|
for ver in Version::iter() {
|
||||||
match Pak::read(&mut *reader, ver, key.as_ref()) {
|
match Pak::read(&mut *reader, ver, &key) {
|
||||||
Ok(pak) => return Ok(Self { pak, key }),
|
Ok(pak) => return Ok(Self { pak, key }),
|
||||||
Err(err) => writeln!(log, "trying version {} failed: {}", ver, err)?,
|
Err(err) => writeln!(log, "trying version {} failed: {}", ver, err)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(super::Error::UnsuportedOrEncrypted(log))
|
Err(super::Error::UnsupportedOrEncrypted(log))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new<R: Read + Seek>(
|
pub fn new<R: Read + Seek>(
|
||||||
reader: &mut R,
|
reader: &mut R,
|
||||||
version: super::Version,
|
version: super::Version,
|
||||||
|
) -> Result<Self, super::Error> {
|
||||||
|
Self::new_inner(reader, version, super::Key::None)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
|
pub fn new_with_key<R: Read + Seek>(
|
||||||
|
reader: &mut R,
|
||||||
|
version: super::Version,
|
||||||
|
key: aes::Aes256,
|
||||||
|
) -> Result<Self, super::Error> {
|
||||||
|
Self::new_inner(reader, version, key.into())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
|
pub fn new_with_optional_key<R: Read + Seek>(
|
||||||
|
reader: &mut R,
|
||||||
|
version: super::Version,
|
||||||
key: Option<aes::Aes256>,
|
key: Option<aes::Aes256>,
|
||||||
) -> Result<Self, super::Error> {
|
) -> Result<Self, super::Error> {
|
||||||
Pak::read(reader, version, key.as_ref()).map(|pak| Self { pak, key })
|
match key {
|
||||||
|
Some(key) => Self::new_with_key(reader, version, key),
|
||||||
|
None => Self::new(reader, version),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_inner<R: Read + Seek>(
|
||||||
|
reader: &mut R,
|
||||||
|
version: super::Version,
|
||||||
|
key: super::Key,
|
||||||
|
) -> Result<Self, super::Error> {
|
||||||
|
Pak::read(reader, version, &key).map(|pak| Self { pak, key })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn version(&self) -> super::Version {
|
pub fn version(&self) -> super::Version {
|
||||||
|
@ -140,7 +191,7 @@ impl PakReader {
|
||||||
reader,
|
reader,
|
||||||
self.pak.version,
|
self.pak.version,
|
||||||
&self.pak.compression,
|
&self.pak.compression,
|
||||||
self.key.as_ref(),
|
&self.key,
|
||||||
writer,
|
writer,
|
||||||
),
|
),
|
||||||
None => Err(super::Error::MissingEntry(path.to_owned())),
|
None => Err(super::Error::MissingEntry(path.to_owned())),
|
||||||
|
@ -166,11 +217,53 @@ impl PakReader {
|
||||||
|
|
||||||
impl<W: Write + Seek> PakWriter<W> {
|
impl<W: Write + Seek> PakWriter<W> {
|
||||||
pub fn new(
|
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,
|
||||||
|
key: super::Key::None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
|
pub fn new_with_key(
|
||||||
|
writer: W,
|
||||||
|
key: aes::Aes256,
|
||||||
|
version: Version,
|
||||||
|
mount_point: String,
|
||||||
|
path_hash_seed: Option<u64>,
|
||||||
|
) -> Self {
|
||||||
|
PakWriter {
|
||||||
|
pak: Pak::new(version, mount_point, path_hash_seed),
|
||||||
|
writer,
|
||||||
|
key: key.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
|
pub fn new_with_optional_key(
|
||||||
writer: W,
|
writer: W,
|
||||||
key: Option<aes::Aes256>,
|
key: Option<aes::Aes256>,
|
||||||
version: Version,
|
version: Version,
|
||||||
mount_point: String,
|
mount_point: String,
|
||||||
path_hash_seed: Option<u64>,
|
path_hash_seed: Option<u64>,
|
||||||
|
) -> Self {
|
||||||
|
match key {
|
||||||
|
Some(key) => Self::new_with_key(writer, key, version, mount_point, path_hash_seed),
|
||||||
|
None => Self::new(writer, version, mount_point, path_hash_seed),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_inner(
|
||||||
|
writer: W,
|
||||||
|
key: super::Key,
|
||||||
|
version: Version,
|
||||||
|
mount_point: String,
|
||||||
|
path_hash_seed: Option<u64>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
PakWriter {
|
PakWriter {
|
||||||
pak: Pak::new(version, mount_point, path_hash_seed),
|
pak: Pak::new(version, mount_point, path_hash_seed),
|
||||||
|
@ -219,26 +312,30 @@ impl<W: Write + Seek> PakWriter<W> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_index(mut self) -> Result<W, super::Error> {
|
pub fn write_index(mut self) -> Result<W, super::Error> {
|
||||||
self.pak.write(&mut self.writer, self.key)?;
|
self.pak.write(&mut self.writer, &self.key)?;
|
||||||
Ok(self.writer)
|
Ok(self.writer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Pak {
|
impl Pak {
|
||||||
fn read<R: Read + Seek>(
|
fn read<R: Read + Seek>(
|
||||||
mut reader: R,
|
reader: &mut R,
|
||||||
version: super::Version,
|
version: super::Version,
|
||||||
key: Option<&aes::Aes256>,
|
#[allow(unused)] key: &super::Key,
|
||||||
) -> Result<Self, super::Error> {
|
) -> Result<Self, super::Error> {
|
||||||
// read footer to get index, encryption & compression info
|
// read footer to get index, encryption & compression info
|
||||||
reader.seek(io::SeekFrom::End(-version.size()))?;
|
reader.seek(io::SeekFrom::End(-version.size()))?;
|
||||||
let footer = super::footer::Footer::read(&mut reader, version)?;
|
let footer = super::footer::Footer::read(reader, version)?;
|
||||||
// read index to get all the entry info
|
// read index to get all the entry info
|
||||||
reader.seek(io::SeekFrom::Start(footer.index_offset))?;
|
reader.seek(io::SeekFrom::Start(footer.index_offset))?;
|
||||||
|
#[allow(unused_mut)]
|
||||||
let mut index = reader.read_len(footer.index_size as usize)?;
|
let mut index = reader.read_len(footer.index_size as usize)?;
|
||||||
|
|
||||||
// decrypt index if needed
|
// decrypt index if needed
|
||||||
if footer.encrypted {
|
if footer.encrypted {
|
||||||
|
#[cfg(not(feature = "encryption"))]
|
||||||
|
return Err(super::Error::Encryption);
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
decrypt(key, &mut index)?;
|
decrypt(key, &mut index)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,6 +357,9 @@ impl Pak {
|
||||||
// TODO verify hash
|
// TODO verify hash
|
||||||
|
|
||||||
if footer.encrypted {
|
if footer.encrypted {
|
||||||
|
#[cfg(not(feature = "encryption"))]
|
||||||
|
return Err(super::Error::Encryption);
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
decrypt(key, &mut path_hash_index_buf)?;
|
decrypt(key, &mut path_hash_index_buf)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -283,11 +383,15 @@ impl Pak {
|
||||||
let _full_directory_index_hash = index.read_len(20)?;
|
let _full_directory_index_hash = index.read_len(20)?;
|
||||||
|
|
||||||
reader.seek(io::SeekFrom::Start(full_directory_index_offset))?;
|
reader.seek(io::SeekFrom::Start(full_directory_index_offset))?;
|
||||||
|
#[allow(unused_mut)]
|
||||||
let mut full_directory_index =
|
let mut full_directory_index =
|
||||||
reader.read_len(full_directory_index_size as usize)?;
|
reader.read_len(full_directory_index_size as usize)?;
|
||||||
// TODO verify hash
|
// TODO verify hash
|
||||||
|
|
||||||
if footer.encrypted {
|
if footer.encrypted {
|
||||||
|
#[cfg(not(feature = "encryption"))]
|
||||||
|
return Err(super::Error::Encryption);
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
decrypt(key, &mut full_directory_index)?;
|
decrypt(key, &mut full_directory_index)?;
|
||||||
}
|
}
|
||||||
let mut fdi = io::Cursor::new(full_directory_index);
|
let mut fdi = io::Cursor::new(full_directory_index);
|
||||||
|
@ -367,7 +471,7 @@ impl Pak {
|
||||||
fn write<W: Write + Seek>(
|
fn write<W: Write + Seek>(
|
||||||
&self,
|
&self,
|
||||||
writer: &mut W,
|
writer: &mut W,
|
||||||
_key: Option<aes::Aes256>,
|
_key: &super::Key,
|
||||||
) -> Result<(), super::Error> {
|
) -> Result<(), super::Error> {
|
||||||
let index_offset = writer.stream_position()?;
|
let index_offset = writer.stream_position()?;
|
||||||
|
|
||||||
|
@ -594,7 +698,8 @@ fn pad_zeros_to_alignment(v: &mut Vec<u8>, alignment: usize) {
|
||||||
assert!(v.len() % alignment == 0);
|
assert!(v.len() % alignment == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encrypt(key: Aes256, bytes: &mut [u8]) {
|
#[cfg(feature = "encryption")]
|
||||||
|
fn encrypt(key: aes::Aes256, bytes: &mut [u8]) {
|
||||||
use aes::cipher::BlockEncrypt;
|
use aes::cipher::BlockEncrypt;
|
||||||
for chunk in bytes.chunks_mut(16) {
|
for chunk in bytes.chunks_mut(16) {
|
||||||
key.encrypt_block(aes::Block::from_mut_slice(chunk))
|
key.encrypt_block(aes::Block::from_mut_slice(chunk))
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
#![cfg(feature = "default")]
|
||||||
use byteorder::{ReadBytesExt, WriteBytesExt};
|
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||||
use paste::paste;
|
use paste::paste;
|
||||||
use std::io::{self, Cursor, Read, Seek, SeekFrom};
|
use std::io::{self, Cursor, Read, Seek, SeekFrom};
|
||||||
|
@ -96,7 +97,7 @@ fn test_read(version: repak::Version, _file_name: &str, bytes: &[u8]) {
|
||||||
let len = inner_reader.seek(SeekFrom::End(0)).unwrap();
|
let len = inner_reader.seek(SeekFrom::End(0)).unwrap();
|
||||||
let mut reader = ReadCounter::new_size(inner_reader, len as usize);
|
let mut reader = ReadCounter::new_size(inner_reader, len as usize);
|
||||||
|
|
||||||
let pak = repak::PakReader::new_any(&mut reader, Some(key)).unwrap();
|
let pak = repak::PakReader::new_any_with_key(&mut reader, key).unwrap();
|
||||||
|
|
||||||
assert_eq!(pak.mount_point(), "../mount/point/root/");
|
assert_eq!(pak.mount_point(), "../mount/point/root/");
|
||||||
assert_eq!(pak.version(), version);
|
assert_eq!(pak.version(), version);
|
||||||
|
@ -159,12 +160,11 @@ fn test_write(_version: repak::Version, _file_name: &str, bytes: &[u8]) {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut reader = std::io::Cursor::new(bytes);
|
let mut reader = std::io::Cursor::new(bytes);
|
||||||
let pak_reader = repak::PakReader::new_any(&mut reader, Some(key)).unwrap();
|
let pak_reader = repak::PakReader::new_any_with_key(&mut reader, key).unwrap();
|
||||||
|
|
||||||
let writer = Cursor::new(vec![]);
|
let writer = Cursor::new(vec![]);
|
||||||
let mut pak_writer = repak::PakWriter::new(
|
let mut pak_writer = repak::PakWriter::new(
|
||||||
writer,
|
writer,
|
||||||
None,
|
|
||||||
pak_reader.version(),
|
pak_reader.version(),
|
||||||
pak_reader.mount_point().to_owned(),
|
pak_reader.mount_point().to_owned(),
|
||||||
Some(0x205C5A7D),
|
Some(0x205C5A7D),
|
||||||
|
@ -191,7 +191,7 @@ fn test_rewrite_index(_version: repak::Version, _file_name: &str, bytes: &[u8])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mut buf = std::io::Cursor::new(bytes.to_vec());
|
let mut buf = std::io::Cursor::new(bytes.to_vec());
|
||||||
let pak_reader = repak::PakReader::new_any(&mut buf, Some(key)).unwrap();
|
let pak_reader = repak::PakReader::new_any_with_key(&mut buf, key).unwrap();
|
||||||
|
|
||||||
let rewrite = pak_reader
|
let rewrite = pak_reader
|
||||||
.into_pakwriter(buf)
|
.into_pakwriter(buf)
|
||||||
|
|
|
@ -10,6 +10,11 @@ edition.workspace = true
|
||||||
name = "repak"
|
name = "repak"
|
||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
repak = { path = "../repak", features = ["oodle"] }
|
||||||
|
[target.'cfg(not(windows))'.dependencies]
|
||||||
|
repak = { path = "../repak" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes = { workspace = true }
|
aes = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
|
@ -19,6 +24,5 @@ indicatif = { version = "0.17.3", features = ["rayon"] }
|
||||||
path-clean = "0.1.0"
|
path-clean = "0.1.0"
|
||||||
path-slash = "0.2.1"
|
path-slash = "0.2.1"
|
||||||
rayon = "1.6.1"
|
rayon = "1.6.1"
|
||||||
repak = { version = "0.1.7", path = "../repak" }
|
|
||||||
sha2 = "0.10.7"
|
sha2 = "0.10.7"
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
|
|
|
@ -175,7 +175,10 @@ fn main() -> Result<(), repak::Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn info(aes_key: Option<aes::Aes256>, action: ActionInfo) -> 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_optional_key(
|
||||||
|
&mut BufReader::new(File::open(action.input)?),
|
||||||
|
aes_key,
|
||||||
|
)?;
|
||||||
println!("mount point: {}", pak.mount_point());
|
println!("mount point: {}", pak.mount_point());
|
||||||
println!("version: {}", pak.version());
|
println!("version: {}", pak.version());
|
||||||
println!("version major: {}", pak.version().version_major());
|
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> {
|
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_optional_key(
|
||||||
|
&mut BufReader::new(File::open(action.input)?),
|
||||||
|
aes_key,
|
||||||
|
)?;
|
||||||
|
|
||||||
let mount_point = PathBuf::from(pak.mount_point());
|
let mount_point = PathBuf::from(pak.mount_point());
|
||||||
let prefix = Path::new(&action.strip_prefix);
|
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> {
|
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_optional_key(
|
||||||
|
&mut BufReader::new(File::open(&action.input)?),
|
||||||
|
aes_key,
|
||||||
|
)?;
|
||||||
|
|
||||||
let mount_point = PathBuf::from(pak.mount_point());
|
let mount_point = PathBuf::from(pak.mount_point());
|
||||||
let prefix = Path::new(&action.strip_prefix);
|
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})";
|
const STYLE: &str = "[{elapsed_precise}] [{wide_bar}] {pos}/{len} ({eta})";
|
||||||
|
|
||||||
fn unpack(aes_key: Option<aes::Aes256>, action: ActionUnpack) -> Result<(), repak::Error> {
|
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_optional_key(
|
||||||
|
&mut BufReader::new(File::open(&action.input)?),
|
||||||
|
aes_key,
|
||||||
|
)?;
|
||||||
let output = action
|
let output = action
|
||||||
.output
|
.output
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
|
@ -388,7 +400,7 @@ fn pack(args: ActionPack) -> Result<(), repak::Error> {
|
||||||
collect_files(&mut paths, input_path)?;
|
collect_files(&mut paths, input_path)?;
|
||||||
paths.sort();
|
paths.sort();
|
||||||
|
|
||||||
let mut pak = repak::PakWriter::new(
|
let mut pak = repak::PakWriter::new_with_optional_key(
|
||||||
BufWriter::new(File::create(&output)?),
|
BufWriter::new(File::create(&output)?),
|
||||||
None,
|
None,
|
||||||
args.version,
|
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> {
|
fn get(aes_key: Option<aes::Aes256>, args: ActionGet) -> Result<(), repak::Error> {
|
||||||
let mut reader = BufReader::new(File::open(&args.input)?);
|
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_optional_key(&mut reader, aes_key)?;
|
||||||
let mount_point = PathBuf::from(pak.mount_point());
|
let mount_point = PathBuf::from(pak.mount_point());
|
||||||
let prefix = Path::new(&args.strip_prefix);
|
let prefix = Path::new(&args.strip_prefix);
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue