mirror of
https://github.com/xavo95/repak.git
synced 2025-01-18 19:04:07 +00:00
Merge pull request #14 from bananaturtlesandwich/oodle
Split oodle loading into dedicated crate
This commit is contained in:
commit
9362db07e2
12 changed files with 575 additions and 290 deletions
|
@ -12,8 +12,9 @@ edition = "2021"
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
aes = "0.8.2"
|
aes = "0.8.2"
|
||||||
base64 = "0.21.0"
|
base64 = "0.21.0"
|
||||||
hex = "0.4.3"
|
|
||||||
strum = { version = "0.24", features = ["derive"] }
|
strum = { version = "0.24", features = ["derive"] }
|
||||||
|
sha1 = "0.10"
|
||||||
|
hex = "0.4"
|
||||||
|
|
||||||
# generated by 'cargo dist init'
|
# generated by 'cargo dist init'
|
||||||
[profile.dist]
|
[profile.dist]
|
||||||
|
|
23
oodle_loader/Cargo.toml
Normal file
23
oodle_loader/Cargo.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[package]
|
||||||
|
name = "oodle_loader"
|
||||||
|
repository.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
|
||||||
|
[target.'cfg(windows)'.dependencies]
|
||||||
|
libloading = "0.7"
|
||||||
|
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
object = { version = "0.32.1", default-features = false, features = ["std", "read"] }
|
||||||
|
libc = "0.2.148"
|
||||||
|
seq-macro = "0.3.5"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
sha1 = { workspace = true }
|
||||||
|
ureq = "2.6"
|
||||||
|
hex-literal = "0.4"
|
||||||
|
hex = { workspace = true }
|
||||||
|
anyhow = "1.0.75"
|
||||||
|
lzma-rs = "0.3.0"
|
BIN
oodle_loader/section.bin
Normal file
BIN
oodle_loader/section.bin
Normal file
Binary file not shown.
407
oodle_loader/src/lib.rs
Normal file
407
oodle_loader/src/lib.rs
Normal file
|
@ -0,0 +1,407 @@
|
||||||
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
type OodleDecompress = fn(comp_buf: &[u8], raw_buf: &mut [u8]) -> i32;
|
||||||
|
|
||||||
|
#[allow(non_camel_case_types)]
|
||||||
|
type OodleLZ_Decompress = unsafe extern "win64" fn(
|
||||||
|
compBuf: *const u8,
|
||||||
|
compBufSize: usize,
|
||||||
|
rawBuf: *mut u8,
|
||||||
|
rawLen: usize,
|
||||||
|
fuzzSafe: u32,
|
||||||
|
checkCRC: u32,
|
||||||
|
verbosity: u32,
|
||||||
|
decBufBase: u64,
|
||||||
|
decBufSize: usize,
|
||||||
|
fpCallback: u64,
|
||||||
|
callbackUserData: u64,
|
||||||
|
decoderMemory: *mut u8,
|
||||||
|
decoderMemorySize: usize,
|
||||||
|
threadPhase: u32,
|
||||||
|
) -> i32;
|
||||||
|
|
||||||
|
pub fn decompress() -> Result<OodleDecompress, Box<dyn std::error::Error>> {
|
||||||
|
#[cfg(windows)]
|
||||||
|
return Ok(windows_oodle::decompress_wrapper_windows);
|
||||||
|
#[cfg(unix)]
|
||||||
|
return Ok(linux_oodle::oodle_loader_linux());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call_decompress(comp_buf: &[u8], raw_buf: &mut [u8], decompress: OodleLZ_Decompress) -> i32 {
|
||||||
|
unsafe {
|
||||||
|
decompress(
|
||||||
|
comp_buf.as_ptr(),
|
||||||
|
comp_buf.len(),
|
||||||
|
raw_buf.as_mut_ptr(),
|
||||||
|
raw_buf.len(),
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
0,
|
||||||
|
3,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static OODLE_HASH: [u8; 20] = hex_literal::hex!("4bcc73614cb8fd2b0bce8d0f91ee5f3202d9d624");
|
||||||
|
static OODLE_DLL_NAME: &str = "oo2core_9_win64.dll";
|
||||||
|
|
||||||
|
fn fetch_oodle() -> Result<std::path::PathBuf> {
|
||||||
|
use sha1::{Digest, Sha1};
|
||||||
|
|
||||||
|
let oodle_path = std::env::current_exe()?.with_file_name(OODLE_DLL_NAME);
|
||||||
|
|
||||||
|
if !oodle_path.exists() {
|
||||||
|
let mut compressed = vec![];
|
||||||
|
ureq::get("https://origin.warframe.com/origin/50F7040A/index.txt.lzma")
|
||||||
|
.call()?
|
||||||
|
.into_reader()
|
||||||
|
.read_to_end(&mut compressed)?;
|
||||||
|
|
||||||
|
let mut decompressed = vec![];
|
||||||
|
lzma_rs::lzma_decompress(&mut std::io::Cursor::new(compressed), &mut decompressed).unwrap();
|
||||||
|
let index = String::from_utf8(decompressed)?;
|
||||||
|
let line = index
|
||||||
|
.lines()
|
||||||
|
.find(|l| l.contains(OODLE_DLL_NAME))
|
||||||
|
.with_context(|| format!("{OODLE_DLL_NAME} not found in index"))?;
|
||||||
|
let path = line.split_once(',').context("failed to parse index")?.0;
|
||||||
|
|
||||||
|
let mut compressed = vec![];
|
||||||
|
ureq::get(&format!("https://content.warframe.com{path}"))
|
||||||
|
.call()?
|
||||||
|
.into_reader()
|
||||||
|
.read_to_end(&mut compressed)?;
|
||||||
|
|
||||||
|
let mut decompressed = vec![];
|
||||||
|
lzma_rs::lzma_decompress(&mut std::io::Cursor::new(compressed), &mut decompressed).unwrap();
|
||||||
|
|
||||||
|
std::fs::write(&oodle_path, decompressed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut hasher = Sha1::new();
|
||||||
|
hasher.update(std::fs::read(&oodle_path)?);
|
||||||
|
let hash = hasher.finalize();
|
||||||
|
(hash[..] == OODLE_HASH).then_some(()).ok_or_else(|| {
|
||||||
|
anyhow!(
|
||||||
|
"oodle hash mismatch expected: {} got: {} ",
|
||||||
|
hex::encode(OODLE_HASH),
|
||||||
|
hex::encode(hash)
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(oodle_path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(windows)]
|
||||||
|
mod windows_oodle {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use anyhow::Context;
|
||||||
|
|
||||||
|
static DECOMPRESS: OnceLock<(OodleLZ_Decompress, libloading::Library)> = OnceLock::new();
|
||||||
|
|
||||||
|
pub fn decompress_wrapper_windows(comp_buf: &[u8], raw_buf: &mut [u8]) -> i32 {
|
||||||
|
let decompress = DECOMPRESS.get_or_init(|| {
|
||||||
|
let path = fetch_oodle().context("failed to fetch oodle").unwrap();
|
||||||
|
|
||||||
|
let lib = unsafe { libloading::Library::new(path) }
|
||||||
|
.context("failed to load oodle")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
(*unsafe { lib.get(b"OodleLZ_Decompress") }.unwrap(), lib)
|
||||||
|
});
|
||||||
|
call_decompress(comp_buf, raw_buf, decompress.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
mod linux_oodle {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use object::pe::{
|
||||||
|
ImageNtHeaders64, IMAGE_REL_BASED_DIR64, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ,
|
||||||
|
IMAGE_SCN_MEM_WRITE,
|
||||||
|
};
|
||||||
|
use object::read::pe::{ImageOptionalHeader, ImageThunkData, PeFile64};
|
||||||
|
|
||||||
|
use object::{LittleEndian as LE, Object, ObjectSection};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::ffi::{c_void, CStr};
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
struct ThreadInformationBlock {
|
||||||
|
exception_list: *const c_void,
|
||||||
|
stack_base: *const c_void,
|
||||||
|
stack_limit: *const c_void,
|
||||||
|
sub_system_tib: *const c_void,
|
||||||
|
fiber_data: *const c_void,
|
||||||
|
arbitrary_user_pointer: *const c_void,
|
||||||
|
teb: *const c_void,
|
||||||
|
}
|
||||||
|
|
||||||
|
const TIB: ThreadInformationBlock = ThreadInformationBlock {
|
||||||
|
exception_list: std::ptr::null(),
|
||||||
|
stack_base: std::ptr::null(),
|
||||||
|
stack_limit: std::ptr::null(),
|
||||||
|
sub_system_tib: std::ptr::null(),
|
||||||
|
fiber_data: std::ptr::null(),
|
||||||
|
arbitrary_user_pointer: std::ptr::null(),
|
||||||
|
teb: std::ptr::null(),
|
||||||
|
};
|
||||||
|
|
||||||
|
static DECOMPRESS: OnceLock<OodleLZ_Decompress> = OnceLock::new();
|
||||||
|
|
||||||
|
fn decompress_wrapper(comp_buf: &[u8], raw_buf: &mut [u8]) -> i32 {
|
||||||
|
unsafe {
|
||||||
|
// Set GS register in calling thread
|
||||||
|
const ARCH_SET_GS: i32 = 0x1001;
|
||||||
|
libc::syscall(libc::SYS_arch_prctl, ARCH_SET_GS, &TIB);
|
||||||
|
|
||||||
|
// Call actual decompress function
|
||||||
|
call_decompress(comp_buf, raw_buf, *DECOMPRESS.get().unwrap())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
mod imports {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub unsafe extern "win64" fn OutputDebugStringA(string: *const std::ffi::c_char) {
|
||||||
|
print!("[OODLE] {}", CStr::from_ptr(string).to_string_lossy());
|
||||||
|
}
|
||||||
|
pub unsafe extern "win64" fn GetProcessHeap() -> *const c_void {
|
||||||
|
0x12345678 as *const c_void
|
||||||
|
}
|
||||||
|
pub unsafe extern "win64" fn HeapAlloc(
|
||||||
|
_heap: *const c_void,
|
||||||
|
flags: i32,
|
||||||
|
size: usize,
|
||||||
|
) -> *const c_void {
|
||||||
|
assert_eq!(0, flags);
|
||||||
|
libc::malloc(size)
|
||||||
|
}
|
||||||
|
pub unsafe extern "win64" fn HeapFree(
|
||||||
|
_heap: *const c_void,
|
||||||
|
_flags: i32,
|
||||||
|
ptr: *mut c_void,
|
||||||
|
) -> bool {
|
||||||
|
libc::free(ptr);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
pub unsafe extern "win64" fn memset(
|
||||||
|
ptr: *mut c_void,
|
||||||
|
value: i32,
|
||||||
|
num: usize,
|
||||||
|
) -> *const c_void {
|
||||||
|
libc::memset(ptr, value, num)
|
||||||
|
}
|
||||||
|
pub unsafe extern "win64" fn memmove(
|
||||||
|
destination: *mut c_void,
|
||||||
|
source: *const c_void,
|
||||||
|
num: usize,
|
||||||
|
) -> *const c_void {
|
||||||
|
libc::memmove(destination, source, num)
|
||||||
|
}
|
||||||
|
pub unsafe extern "win64" fn memcpy(
|
||||||
|
destination: *mut c_void,
|
||||||
|
source: *const c_void,
|
||||||
|
num: usize,
|
||||||
|
) -> *const c_void {
|
||||||
|
libc::memcpy(destination, source, num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create some unique function pointers to use for unimplemented imports
|
||||||
|
const DEBUG_FNS: [*const fn(); 100] = gen_debug_fns();
|
||||||
|
static mut DEBUG_NAMES: [&str; 100] = [""; 100];
|
||||||
|
const fn gen_debug_fns() -> [*const fn(); 100] {
|
||||||
|
fn log<const I: usize>() {
|
||||||
|
unimplemented!("import {:?}", unsafe { DEBUG_NAMES[I] });
|
||||||
|
}
|
||||||
|
let mut array = [std::ptr::null(); 100];
|
||||||
|
seq_macro::seq!(N in 0..100 {
|
||||||
|
array[N] = log::<N> as *const fn();
|
||||||
|
});
|
||||||
|
array
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn oodle_loader_linux() -> OodleDecompress {
|
||||||
|
DECOMPRESS.get_or_init(|| get_decompress_inner().unwrap());
|
||||||
|
decompress_wrapper
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_decompress_inner() -> Result<OodleLZ_Decompress> {
|
||||||
|
fetch_oodle()?;
|
||||||
|
let oodle = std::env::current_exe()
|
||||||
|
.unwrap()
|
||||||
|
.with_file_name(OODLE_DLL_NAME);
|
||||||
|
let dll = std::fs::read(oodle)?;
|
||||||
|
|
||||||
|
let obj_file = PeFile64::parse(&*dll)?;
|
||||||
|
|
||||||
|
let size = obj_file.nt_headers().optional_header.size_of_image() as usize;
|
||||||
|
let header_size = obj_file.nt_headers().optional_header.size_of_headers() as usize;
|
||||||
|
|
||||||
|
let image_base = obj_file.relative_address_base() as usize;
|
||||||
|
|
||||||
|
// Create map
|
||||||
|
let mmap = unsafe {
|
||||||
|
std::slice::from_raw_parts_mut(
|
||||||
|
libc::mmap(
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
size,
|
||||||
|
libc::PROT_READ | libc::PROT_WRITE,
|
||||||
|
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS,
|
||||||
|
-1,
|
||||||
|
0,
|
||||||
|
) as *mut u8,
|
||||||
|
size,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
let map_base = mmap.as_ptr();
|
||||||
|
|
||||||
|
// Copy header to map
|
||||||
|
mmap[0..header_size].copy_from_slice(&dll[0..header_size]);
|
||||||
|
unsafe {
|
||||||
|
assert_eq!(
|
||||||
|
0,
|
||||||
|
libc::mprotect(
|
||||||
|
mmap.as_mut_ptr() as *mut c_void,
|
||||||
|
header_size,
|
||||||
|
libc::PROT_READ
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy section data to map
|
||||||
|
for section in obj_file.sections() {
|
||||||
|
let address = section.address() as usize;
|
||||||
|
let data = section.data()?;
|
||||||
|
mmap[(address - image_base)..(address - image_base + data.len())]
|
||||||
|
.copy_from_slice(section.data()?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply relocations
|
||||||
|
let sections = obj_file.section_table();
|
||||||
|
let mut blocks = obj_file
|
||||||
|
.data_directories()
|
||||||
|
.relocation_blocks(&*dll, §ions)?
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
while let Some(block) = blocks.next()? {
|
||||||
|
let block_address = block.virtual_address();
|
||||||
|
let block_data = sections.pe_data_at(&*dll, block_address).map(object::Bytes);
|
||||||
|
for reloc in block {
|
||||||
|
let offset = (reloc.virtual_address - block_address) as usize;
|
||||||
|
match reloc.typ {
|
||||||
|
IMAGE_REL_BASED_DIR64 => {
|
||||||
|
let addend = block_data
|
||||||
|
.and_then(|data| data.read_at::<object::U64Bytes<LE>>(offset).ok())
|
||||||
|
.map(|addend| addend.get(LE));
|
||||||
|
if let Some(addend) = addend {
|
||||||
|
mmap[reloc.virtual_address as usize
|
||||||
|
..reloc.virtual_address as usize + 8]
|
||||||
|
.copy_from_slice(&u64::to_le_bytes(
|
||||||
|
addend - image_base as u64 + map_base as u64,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => unimplemented!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix up imports
|
||||||
|
let import_table = obj_file.import_table()?.unwrap();
|
||||||
|
let mut import_descs = import_table.descriptors()?;
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while let Some(import_desc) = import_descs.next()? {
|
||||||
|
let mut thunks = import_table.thunks(import_desc.original_first_thunk.get(LE))?;
|
||||||
|
|
||||||
|
let mut address = import_desc.first_thunk.get(LE) as usize;
|
||||||
|
while let Some(thunk) = thunks.next::<ImageNtHeaders64>()? {
|
||||||
|
let (_hint, name) = import_table.hint_name(thunk.address())?;
|
||||||
|
let name = String::from_utf8_lossy(name).to_string();
|
||||||
|
|
||||||
|
use imports::*;
|
||||||
|
|
||||||
|
let fn_addr = match name.as_str() {
|
||||||
|
"OutputDebugStringA" => OutputDebugStringA as usize,
|
||||||
|
"GetProcessHeap" => GetProcessHeap as usize,
|
||||||
|
"HeapAlloc" => HeapAlloc as usize,
|
||||||
|
"HeapFree" => HeapFree as usize,
|
||||||
|
"memset" => memset as usize,
|
||||||
|
"memcpy" => memcpy as usize,
|
||||||
|
"memmove" => memmove as usize,
|
||||||
|
_ => {
|
||||||
|
unsafe { DEBUG_NAMES[i] = name.leak() }
|
||||||
|
let a = DEBUG_FNS[i] as usize;
|
||||||
|
i += 1;
|
||||||
|
a
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
mmap[address..address + 8].copy_from_slice(&usize::to_le_bytes(fn_addr));
|
||||||
|
|
||||||
|
address += 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build export table
|
||||||
|
let mut exports = HashMap::new();
|
||||||
|
for export in obj_file.exports()? {
|
||||||
|
let name = String::from_utf8_lossy(export.name());
|
||||||
|
let address = export.address() - image_base as u64 + map_base as u64;
|
||||||
|
exports.insert(name, address as *const c_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fix section permissions
|
||||||
|
for section in obj_file.sections() {
|
||||||
|
let address = section.address() as usize;
|
||||||
|
let data = section.data()?;
|
||||||
|
let size = data.len();
|
||||||
|
|
||||||
|
let mut permissions = 0;
|
||||||
|
|
||||||
|
let flags = match section.flags() {
|
||||||
|
object::SectionFlags::Coff { characteristics } => characteristics,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if 0 != flags & IMAGE_SCN_MEM_READ {
|
||||||
|
permissions |= libc::PROT_READ;
|
||||||
|
}
|
||||||
|
if 0 != flags & IMAGE_SCN_MEM_WRITE {
|
||||||
|
permissions |= libc::PROT_WRITE;
|
||||||
|
}
|
||||||
|
if 0 != flags & IMAGE_SCN_MEM_EXECUTE {
|
||||||
|
permissions |= libc::PROT_EXEC;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
assert_eq!(
|
||||||
|
0,
|
||||||
|
libc::mprotect(
|
||||||
|
mmap.as_mut_ptr().add(address - image_base) as *mut c_void,
|
||||||
|
size,
|
||||||
|
permissions
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Break things!
|
||||||
|
Ok(unsafe { std::mem::transmute(exports["OodleLZ_Decompress"]) })
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,26 +9,20 @@ edition.workspace = true
|
||||||
[features]
|
[features]
|
||||||
default = ["compression", "encryption"]
|
default = ["compression", "encryption"]
|
||||||
compression = ["dep:flate2", "dep:zstd"]
|
compression = ["dep:flate2", "dep:zstd"]
|
||||||
oodle = ["dep:libloading", "dep:ureq", "dep:once_cell", "dep:hex-literal", "dep:hex"]
|
oodle = []
|
||||||
|
oodle_loader = ["dep:oodle_loader"]
|
||||||
encryption = ["dep:aes"]
|
encryption = ["dep:aes"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = "1.4"
|
byteorder = "1.4"
|
||||||
aes = { version = "0.8", optional = true }
|
aes = { workspace = true, optional = true }
|
||||||
flate2 = { version = "1.0", optional = true }
|
flate2 = { version = "1.0", optional = true }
|
||||||
zstd = { version = "0.12", optional = true }
|
zstd = { version = "0.12", optional = true }
|
||||||
|
oodle_loader = { path = "../oodle_loader", optional = true}
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
sha1 = "0.10.5"
|
sha1 = { workspace = true }
|
||||||
strum = { workspace = true }
|
strum = { workspace = true }
|
||||||
libloading = { version = "0.7", optional = true }
|
|
||||||
ureq = { version = "2.6", optional = true }
|
|
||||||
once_cell = { version = "1.17", optional = true}
|
|
||||||
hex-literal = { version = "0.4", optional = true }
|
|
||||||
hex = { workspace = true, optional = true }
|
|
||||||
|
|
||||||
[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"]
|
|
||||||
|
|
|
@ -306,6 +306,7 @@ impl Entry {
|
||||||
version: Version,
|
version: Version,
|
||||||
compression: &[Compression],
|
compression: &[Compression],
|
||||||
#[allow(unused)] key: &super::Key,
|
#[allow(unused)] key: &super::Key,
|
||||||
|
#[allow(unused)] oodle: &super::Oodle,
|
||||||
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))?;
|
||||||
|
@ -376,110 +377,39 @@ impl Entry {
|
||||||
}
|
}
|
||||||
#[cfg(feature = "oodle")]
|
#[cfg(feature = "oodle")]
|
||||||
Some(Compression::Oodle) => {
|
Some(Compression::Oodle) => {
|
||||||
#[cfg(not(target_os = "windows"))]
|
let oodle = match oodle {
|
||||||
return Err(super::Error::Oodle);
|
crate::Oodle::Some(getter) => getter().map_err(|_| super::Error::OodleFailed),
|
||||||
|
crate::Oodle::None => Err(super::Error::OodleFailed),
|
||||||
|
}?;
|
||||||
|
let mut decompressed = vec![0; self.uncompressed as usize];
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
let mut compress_offset = 0;
|
||||||
unsafe {
|
let mut decompress_offset = 0;
|
||||||
use std::ops::Deref;
|
let block_count = ranges.len();
|
||||||
let lib = match OODLE.deref() {
|
for range in ranges {
|
||||||
Ok(lib) => Ok(lib),
|
let decomp = if block_count == 1 {
|
||||||
Err(e) => Err(super::Error::Other(e.to_string())),
|
self.uncompressed as usize
|
||||||
}?;
|
} else {
|
||||||
|
(self.compression_block_size as usize)
|
||||||
/*
|
.min(self.uncompressed as usize - compress_offset)
|
||||||
let set_printf: libloading::Symbol<
|
};
|
||||||
unsafe extern "C" fn(
|
let buffer = &mut data[range];
|
||||||
unsafe extern "C" fn(
|
let out = oodle(
|
||||||
i32,
|
buffer,
|
||||||
*const std::ffi::c_char,
|
&mut decompressed[decompress_offset..decompress_offset + decomp],
|
||||||
i32,
|
|
||||||
*const std::ffi::c_char,
|
|
||||||
...
|
|
||||||
) -> std::ffi::c_int,
|
|
||||||
),
|
|
||||||
> = lib.get(b"OodleCore_Plugins_SetPrintf").unwrap();
|
|
||||||
|
|
||||||
pub unsafe extern "C" fn printf(
|
|
||||||
a: i32,
|
|
||||||
b: *const std::ffi::c_char,
|
|
||||||
c: i32,
|
|
||||||
str: *const std::ffi::c_char,
|
|
||||||
mut args: ...
|
|
||||||
) -> std::ffi::c_int {
|
|
||||||
use printf_compat::{format, output};
|
|
||||||
let mut s = String::new();
|
|
||||||
let bytes_written = format(str, args.as_va_list(), output::fmt_write(&mut s));
|
|
||||||
print!("[OODLE]: {}", s);
|
|
||||||
bytes_written
|
|
||||||
}
|
|
||||||
|
|
||||||
set_printf(printf);
|
|
||||||
*/
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
let OodleLZ_Decompress: libloading::Symbol<
|
|
||||||
extern "C" fn(
|
|
||||||
compBuf: *mut u8,
|
|
||||||
compBufSize: usize,
|
|
||||||
rawBuf: *mut u8,
|
|
||||||
rawLen: usize,
|
|
||||||
fuzzSafe: u32,
|
|
||||||
checkCRC: u32,
|
|
||||||
verbosity: u32,
|
|
||||||
decBufBase: u64,
|
|
||||||
decBufSize: usize,
|
|
||||||
fpCallback: u64,
|
|
||||||
callbackUserData: u64,
|
|
||||||
decoderMemory: *mut u8,
|
|
||||||
decoderMemorySize: usize,
|
|
||||||
threadPhase: u32,
|
|
||||||
) -> i32,
|
|
||||||
> = lib.get(b"OodleLZ_Decompress").unwrap();
|
|
||||||
|
|
||||||
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 = OodleLZ_Decompress(
|
|
||||||
buffer.as_mut_ptr(),
|
|
||||||
buffer.len(),
|
|
||||||
decompressed.as_mut_ptr().offset(decompress_offset),
|
|
||||||
decomp,
|
|
||||||
1,
|
|
||||||
1,
|
|
||||||
0, //verbose 3
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
std::ptr::null_mut(),
|
|
||||||
0,
|
|
||||||
3,
|
|
||||||
);
|
|
||||||
if out == 0 {
|
|
||||||
return Err(super::Error::DecompressionFailed(Compression::Oodle));
|
|
||||||
}
|
|
||||||
compress_offset += self.compression_block_size as usize;
|
|
||||||
decompress_offset += out as isize;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
decompress_offset, self.uncompressed as isize,
|
|
||||||
"Oodle decompression length mismatch"
|
|
||||||
);
|
);
|
||||||
buf.write_all(&decompressed)?;
|
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"))]
|
#[cfg(not(feature = "oodle"))]
|
||||||
Some(Compression::Oodle) => return Err(super::Error::Oodle),
|
Some(Compression::Oodle) => return Err(super::Error::Oodle),
|
||||||
|
@ -491,42 +421,6 @@ impl Entry {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "oodle")]
|
|
||||||
use once_cell::sync::Lazy;
|
|
||||||
#[cfg(feature = "oodle")]
|
|
||||||
static OODLE: Lazy<Result<libloading::Library, String>> =
|
|
||||||
Lazy::new(|| get_oodle().map_err(|e| e.to_string()));
|
|
||||||
#[cfg(feature = "oodle")]
|
|
||||||
static OODLE_HASH: [u8; 20] = hex_literal::hex!("4bcc73614cb8fd2b0bce8d0f91ee5f3202d9d624");
|
|
||||||
|
|
||||||
#[cfg(feature = "oodle")]
|
|
||||||
fn get_oodle() -> Result<libloading::Library, super::Error> {
|
|
||||||
use sha1::{Digest, Sha1};
|
|
||||||
|
|
||||||
let oodle = std::env::current_exe()?.with_file_name("oo2core_9_win64.dll");
|
|
||||||
if !oodle.exists() {
|
|
||||||
let mut data = vec![];
|
|
||||||
ureq::get("https://cdn.discordapp.com/attachments/817251677086285848/992648087371792404/oo2core_9_win64.dll")
|
|
||||||
.call().map_err(Box::new)?
|
|
||||||
.into_reader().read_to_end(&mut data)?;
|
|
||||||
|
|
||||||
std::fs::write(&oodle, data)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut hasher = Sha1::new();
|
|
||||||
hasher.update(std::fs::read(&oodle)?);
|
|
||||||
let hash = hasher.finalize();
|
|
||||||
(hash[..] == OODLE_HASH).then_some(()).ok_or_else(|| {
|
|
||||||
super::Error::Other(format!(
|
|
||||||
"oodle hash mismatch expected: {} got: {} ",
|
|
||||||
hex::encode(OODLE_HASH),
|
|
||||||
hex::encode(hash)
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
|
|
||||||
unsafe { libloading::Library::new(oodle) }.map_err(|_| super::Error::OodleFailed)
|
|
||||||
}
|
|
||||||
|
|
||||||
mod test {
|
mod test {
|
||||||
#[test]
|
#[test]
|
||||||
fn test_entry() {
|
fn test_entry() {
|
||||||
|
|
|
@ -16,14 +16,7 @@ pub enum Error {
|
||||||
#[error("enable the encryption feature to read encrypted paks")]
|
#[error("enable the encryption feature to read encrypted paks")]
|
||||||
Encryption,
|
Encryption,
|
||||||
|
|
||||||
#[cfg_attr(
|
#[error("enable the oodle feature to read Oodle compressed paks")]
|
||||||
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,
|
Oodle,
|
||||||
|
|
||||||
// std errors
|
// std errors
|
||||||
|
@ -39,10 +32,6 @@ 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}")]
|
|
||||||
Ureq(#[from] Box<ureq::Error>), // boxed because ureq::Error is quite large
|
|
||||||
|
|
||||||
#[error("bufwriter dereference: {0}")]
|
#[error("bufwriter dereference: {0}")]
|
||||||
IntoInner(#[from] std::io::IntoInnerError<std::io::BufWriter<Vec<u8>>>),
|
IntoInner(#[from] std::io::IntoInnerError<std::io::BufWriter<Vec<u8>>>),
|
||||||
|
|
||||||
|
@ -53,7 +42,7 @@ 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("Could not load oo2core_9_win64.dll")]
|
#[error("pointer to OodleLZ_Decompress was not provided")]
|
||||||
OodleFailed,
|
OodleFailed,
|
||||||
|
|
||||||
#[error("No entry found at {0}")]
|
#[error("No entry found at {0}")]
|
||||||
|
|
|
@ -7,11 +7,17 @@ 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;
|
||||||
|
|
||||||
|
#[cfg(feature = "oodle")]
|
||||||
|
mod oodle {
|
||||||
|
pub type OodleGetter = fn() -> Result<OodleDecompress, Box<dyn std::error::Error>>;
|
||||||
|
pub type OodleDecompress = fn(comp_buf: &[u8], raw_buf: &mut [u8]) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "oodle_loader")]
|
||||||
|
pub use oodle_loader;
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Clone,
|
Clone,
|
||||||
Copy,
|
Copy,
|
||||||
|
@ -124,10 +130,11 @@ pub enum Compression {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
#[allow(clippy::large_enum_variant)]
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) enum Key {
|
pub(crate) enum Key {
|
||||||
#[cfg(feature = "encryption")]
|
#[cfg(feature = "encryption")]
|
||||||
Some(aes::Aes256),
|
Some(aes::Aes256),
|
||||||
|
#[default]
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,3 +144,11 @@ impl From<aes::Aes256> for Key {
|
||||||
Self::Some(value)
|
Self::Some(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) enum Oodle {
|
||||||
|
#[cfg(feature = "oodle")]
|
||||||
|
Some(oodle::OodleGetter),
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
142
repak/src/pak.rs
142
repak/src/pak.rs
|
@ -4,10 +4,52 @@ 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};
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct PakBuilder {
|
||||||
|
key: super::Key,
|
||||||
|
oodle: super::Oodle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PakBuilder {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
#[cfg(feature = "encryption")]
|
||||||
|
pub fn key(mut self, key: aes::Aes256) -> Self {
|
||||||
|
self.key = super::Key::Some(key);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
#[cfg(feature = "oodle")]
|
||||||
|
pub fn oodle(mut self, oodle_getter: super::oodle::OodleGetter) -> Self {
|
||||||
|
self.oodle = super::Oodle::Some(oodle_getter);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
pub fn reader<R: Read + Seek>(self, reader: &mut R) -> Result<PakReader, super::Error> {
|
||||||
|
PakReader::new_any_inner(reader, self.key, self.oodle)
|
||||||
|
}
|
||||||
|
pub fn reader_with_version<R: Read + Seek>(
|
||||||
|
self,
|
||||||
|
reader: &mut R,
|
||||||
|
version: super::Version,
|
||||||
|
) -> Result<PakReader, super::Error> {
|
||||||
|
PakReader::new_inner(reader, version, self.key, self.oodle)
|
||||||
|
}
|
||||||
|
pub fn writer<W: Write + Seek>(
|
||||||
|
self,
|
||||||
|
writer: W,
|
||||||
|
version: super::Version,
|
||||||
|
mount_point: String,
|
||||||
|
path_hash_seed: Option<u64>,
|
||||||
|
) -> PakWriter<W> {
|
||||||
|
PakWriter::new_inner(writer, self.key, version, mount_point, path_hash_seed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct PakReader {
|
pub struct PakReader {
|
||||||
pak: Pak,
|
pak: Pak,
|
||||||
key: super::Key,
|
key: super::Key,
|
||||||
|
oodle: super::Oodle,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -83,79 +125,30 @@ fn decrypt(key: &super::Key, bytes: &mut [u8]) -> Result<(), super::Error> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PakReader {
|
impl PakReader {
|
||||||
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,
|
|
||||||
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>(
|
fn new_any_inner<R: Read + Seek>(
|
||||||
reader: &mut R,
|
reader: &mut R,
|
||||||
key: super::Key,
|
key: super::Key,
|
||||||
|
oodle: super::Oodle,
|
||||||
) -> 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) {
|
match Pak::read(&mut *reader, ver, &key) {
|
||||||
Ok(pak) => return Ok(Self { pak, key }),
|
Ok(pak) => return Ok(Self { pak, key, oodle }),
|
||||||
Err(err) => writeln!(log, "trying version {} failed: {}", ver, err)?,
|
Err(err) => writeln!(log, "trying version {} failed: {}", ver, err)?,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(super::Error::UnsupportedOrEncrypted(log))
|
Err(super::Error::UnsupportedOrEncrypted(log))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new<R: Read + Seek>(
|
|
||||||
reader: &mut R,
|
|
||||||
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>,
|
|
||||||
) -> Result<Self, super::Error> {
|
|
||||||
match key {
|
|
||||||
Some(key) => Self::new_with_key(reader, version, key),
|
|
||||||
None => Self::new(reader, version),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_inner<R: Read + Seek>(
|
fn new_inner<R: Read + Seek>(
|
||||||
reader: &mut R,
|
reader: &mut R,
|
||||||
version: super::Version,
|
version: super::Version,
|
||||||
key: super::Key,
|
key: super::Key,
|
||||||
|
oodle: super::Oodle,
|
||||||
) -> Result<Self, super::Error> {
|
) -> Result<Self, super::Error> {
|
||||||
Pak::read(reader, version, &key).map(|pak| Self { pak, key })
|
Pak::read(reader, version, &key).map(|pak| Self { pak, key, oodle })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn version(&self) -> super::Version {
|
pub fn version(&self) -> super::Version {
|
||||||
|
@ -192,6 +185,7 @@ impl PakReader {
|
||||||
self.pak.version,
|
self.pak.version,
|
||||||
&self.pak.compression,
|
&self.pak.compression,
|
||||||
&self.key,
|
&self.key,
|
||||||
|
&self.oodle,
|
||||||
writer,
|
writer,
|
||||||
),
|
),
|
||||||
None => Err(super::Error::MissingEntry(path.to_owned())),
|
None => Err(super::Error::MissingEntry(path.to_owned())),
|
||||||
|
@ -216,48 +210,6 @@ impl PakReader {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Write + Seek> PakWriter<W> {
|
impl<W: Write + Seek> PakWriter<W> {
|
||||||
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,
|
|
||||||
key: Option<aes::Aes256>,
|
|
||||||
version: Version,
|
|
||||||
mount_point: String,
|
|
||||||
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(
|
fn new_inner(
|
||||||
writer: W,
|
writer: W,
|
||||||
key: super::Key,
|
key: super::Key,
|
||||||
|
|
|
@ -97,7 +97,10 @@ 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_with_key(&mut reader, key).unwrap();
|
let pak = repak::PakBuilder::new()
|
||||||
|
.key(key)
|
||||||
|
.reader(&mut reader)
|
||||||
|
.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);
|
||||||
|
@ -160,10 +163,13 @@ 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_with_key(&mut reader, key).unwrap();
|
let pak_reader = repak::PakBuilder::new()
|
||||||
|
.key(key)
|
||||||
|
.reader(&mut reader)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let writer = Cursor::new(vec![]);
|
let writer = Cursor::new(vec![]);
|
||||||
let mut pak_writer = repak::PakWriter::new(
|
let mut pak_writer = repak::PakBuilder::new().writer(
|
||||||
writer,
|
writer,
|
||||||
pak_reader.version(),
|
pak_reader.version(),
|
||||||
pak_reader.mount_point().to_owned(),
|
pak_reader.mount_point().to_owned(),
|
||||||
|
@ -189,7 +195,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_with_key(&mut buf, key).unwrap();
|
let pak_reader = repak::PakBuilder::new().key(key).reader(&mut buf).unwrap();
|
||||||
|
|
||||||
let rewrite = pak_reader
|
let rewrite = pak_reader
|
||||||
.into_pakwriter(buf)
|
.into_pakwriter(buf)
|
||||||
|
|
|
@ -10,12 +10,9 @@ 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]
|
||||||
|
oodle_loader = { path = "../oodle_loader" }
|
||||||
|
repak = { path = "../repak", features = ["oodle"] }
|
||||||
aes = { workspace = true }
|
aes = { workspace = true }
|
||||||
base64 = { workspace = true }
|
base64 = { workspace = true }
|
||||||
clap = { version = "4.1.4", features = ["derive"] }
|
clap = { version = "4.1.4", features = ["derive"] }
|
||||||
|
|
|
@ -175,10 +175,11 @@ 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_with_optional_key(
|
let mut builder = repak::PakBuilder::new();
|
||||||
&mut BufReader::new(File::open(action.input)?),
|
if let Some(aes_key) = aes_key {
|
||||||
aes_key,
|
builder = builder.key(aes_key);
|
||||||
)?;
|
}
|
||||||
|
let pak = builder.reader(&mut BufReader::new(File::open(action.input)?))?;
|
||||||
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());
|
||||||
|
@ -189,10 +190,11 @@ 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_with_optional_key(
|
let mut builder = repak::PakBuilder::new();
|
||||||
&mut BufReader::new(File::open(action.input)?),
|
if let Some(aes_key) = aes_key {
|
||||||
aes_key,
|
builder = builder.key(aes_key);
|
||||||
)?;
|
}
|
||||||
|
let pak = builder.reader(&mut BufReader::new(File::open(action.input)?))?;
|
||||||
|
|
||||||
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);
|
||||||
|
@ -221,10 +223,11 @@ 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_with_optional_key(
|
let mut builder = repak::PakBuilder::new();
|
||||||
&mut BufReader::new(File::open(&action.input)?),
|
if let Some(aes_key) = aes_key {
|
||||||
aes_key,
|
builder = builder.key(aes_key);
|
||||||
)?;
|
}
|
||||||
|
let pak = builder.reader(&mut BufReader::new(File::open(&action.input)?))?;
|
||||||
|
|
||||||
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);
|
||||||
|
@ -248,7 +251,6 @@ fn hash_list(aes_key: Option<aes::Aes256>, action: ActionHashList) -> Result<(),
|
||||||
|
|
||||||
let hashes: std::sync::Arc<std::sync::Mutex<BTreeMap<std::borrow::Cow<'_, str>, Vec<u8>>>> =
|
let hashes: std::sync::Arc<std::sync::Mutex<BTreeMap<std::borrow::Cow<'_, str>, Vec<u8>>>> =
|
||||||
Default::default();
|
Default::default();
|
||||||
|
|
||||||
full_paths.par_iter().zip(stripped).try_for_each_init(
|
full_paths.par_iter().zip(stripped).try_for_each_init(
|
||||||
|| (hashes.clone(), File::open(&action.input)),
|
|| (hashes.clone(), File::open(&action.input)),
|
||||||
|(hashes, file), ((_full_path, path), stripped)| -> Result<(), repak::Error> {
|
|(hashes, file), ((_full_path, path), stripped)| -> Result<(), repak::Error> {
|
||||||
|
@ -280,10 +282,11 @@ 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> {
|
||||||
for input in &action.input {
|
for input in &action.input {
|
||||||
let pak = repak::PakReader::new_any_with_optional_key(
|
let mut builder = repak::PakBuilder::new().oodle(oodle_loader::decompress);
|
||||||
&mut BufReader::new(File::open(input)?),
|
if let Some(aes_key) = aes_key.clone() {
|
||||||
aes_key.clone(),
|
builder = builder.key(aes_key);
|
||||||
)?;
|
}
|
||||||
|
let pak = builder.reader(&mut BufReader::new(File::open(input)?))?;
|
||||||
let output = action
|
let output = action
|
||||||
.output
|
.output
|
||||||
.as_ref()
|
.as_ref()
|
||||||
|
@ -354,6 +357,7 @@ fn unpack(aes_key: Option<aes::Aes256>, action: ActionUnpack) -> Result<(), repa
|
||||||
|
|
||||||
let progress = indicatif::ProgressBar::new(entries.len() as u64)
|
let progress = indicatif::ProgressBar::new(entries.len() as u64)
|
||||||
.with_style(indicatif::ProgressStyle::with_template(STYLE).unwrap());
|
.with_style(indicatif::ProgressStyle::with_template(STYLE).unwrap());
|
||||||
|
|
||||||
entries.par_iter().try_for_each_init(
|
entries.par_iter().try_for_each_init(
|
||||||
|| (progress.clone(), File::open(input)),
|
|| (progress.clone(), File::open(input)),
|
||||||
|(progress, file), entry| -> Result<(), repak::Error> {
|
|(progress, file), entry| -> Result<(), repak::Error> {
|
||||||
|
@ -415,9 +419,8 @@ 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_with_optional_key(
|
let mut pak = repak::PakBuilder::new().writer(
|
||||||
BufWriter::new(File::create(&output)?),
|
BufWriter::new(File::create(&output)?),
|
||||||
None,
|
|
||||||
args.version,
|
args.version,
|
||||||
args.mount_point,
|
args.mount_point,
|
||||||
Some(args.path_hash_seed),
|
Some(args.path_hash_seed),
|
||||||
|
@ -450,7 +453,11 @@ 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_with_optional_key(&mut reader, aes_key)?;
|
let mut builder = repak::PakBuilder::new().oodle(oodle_loader::decompress);
|
||||||
|
if let Some(aes_key) = aes_key {
|
||||||
|
builder = builder.key(aes_key);
|
||||||
|
}
|
||||||
|
let pak = builder.reader(&mut reader)?;
|
||||||
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