mirror of
https://github.com/xavo95/repak.git
synced 2025-04-02 06:37:23 +00:00
Change Oodle source and add compression wrapper
This commit is contained in:
parent
cd349a95f5
commit
255486b962
6 changed files with 270 additions and 437 deletions
53
Cargo.lock
generated
53
Cargo.lock
generated
|
@ -229,21 +229,6 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69e6e4d7b33a94f0991c26729976b10ebde1d34c3ee82408fb536164fa10d636"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
|
@ -416,12 +401,6 @@ version = "0.4.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "hex-literal"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "1.5.0"
|
||||
|
@ -658,16 +637,6 @@ version = "0.4.22"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
|
||||
|
||||
[[package]]
|
||||
name = "lzma-rs"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "297e814c836ae64db86b36cf2a557ba54368d03f6afcd7d947c266692f71115e"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"crc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
|
@ -689,15 +658,6 @@ version = "0.4.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.36.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.20.2"
|
||||
|
@ -710,13 +670,8 @@ version = "0.2.2"
|
|||
dependencies = [
|
||||
"anyhow",
|
||||
"hex",
|
||||
"hex-literal",
|
||||
"libc",
|
||||
"libloading",
|
||||
"lzma-rs",
|
||||
"object",
|
||||
"seq-macro",
|
||||
"sha1",
|
||||
"sha2",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
|
@ -941,12 +896,6 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "seq-macro"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3f0bf26fd526d2a95683cd0f87bf103b8539e2ca1ef48ce002d67aad59aa0b4"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.217"
|
||||
|
|
|
@ -6,18 +6,9 @@ license.workspace = true
|
|||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
libloading = "0.8"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
object = { version = "0.36.7", default-features = false, features = ["std", "read"] }
|
||||
libc = "0.2.169"
|
||||
seq-macro = "0.3.5"
|
||||
|
||||
[dependencies]
|
||||
sha1 = { workspace = true }
|
||||
libloading = "0.8"
|
||||
ureq = "2.12"
|
||||
hex-literal = "0.4"
|
||||
hex = { workspace = true }
|
||||
anyhow = "1.0.95"
|
||||
lzma-rs = "0.3.0"
|
||||
sha2 = "0.10.8"
|
||||
|
|
|
@ -1,405 +1,298 @@
|
|||
use anyhow::{anyhow, Context, Result};
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use std::sync::OnceLock;
|
||||
use std::{
|
||||
io::{Read, Write},
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
type OodleDecompress = fn(comp_buf: &[u8], raw_buf: &mut [u8]) -> i32;
|
||||
pub use oodle_lz::{CompressionLevel, Compressor};
|
||||
|
||||
#[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;
|
||||
mod oodle_lz {
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(i32)]
|
||||
pub enum Compressor {
|
||||
/// None = memcpy, pass through uncompressed bytes
|
||||
None = 3,
|
||||
|
||||
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)?;
|
||||
/// Fast decompression and high compression ratios, amazing!
|
||||
Kraken = 8,
|
||||
/// Leviathan = Kraken's big brother with higher compression, slightly slower decompression.
|
||||
Leviathan = 13,
|
||||
/// Mermaid is between Kraken & Selkie - crazy fast, still decent compression.
|
||||
Mermaid = 9,
|
||||
/// Selkie is a super-fast relative of Mermaid. For maximum decode speed.
|
||||
Selkie = 11,
|
||||
/// Hydra, the many-headed beast = Leviathan, Kraken, Mermaid, or Selkie (see $OodleLZ_About_Hydra)
|
||||
Hydra = 12,
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
})?;
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[repr(i32)]
|
||||
pub enum CompressionLevel {
|
||||
/// don't compress, just copy raw bytes
|
||||
None = 0,
|
||||
/// super fast mode, lower compression ratio
|
||||
SuperFast = 1,
|
||||
/// fastest LZ mode with still decent compression ratio
|
||||
VeryFast = 2,
|
||||
/// fast - good for daily use
|
||||
Fast = 3,
|
||||
/// standard medium speed LZ mode
|
||||
Normal = 4,
|
||||
|
||||
Ok(oodle_path)
|
||||
/// optimal parse level 1 (faster optimal encoder)
|
||||
Optimal1 = 5,
|
||||
/// optimal parse level 2 (recommended baseline optimal encoder)
|
||||
Optimal2 = 6,
|
||||
/// optimal parse level 3 (slower optimal encoder)
|
||||
Optimal3 = 7,
|
||||
/// optimal parse level 4 (very slow optimal encoder)
|
||||
Optimal4 = 8,
|
||||
/// optimal parse level 5 (don't care about encode speed, maximum compression)
|
||||
Optimal5 = 9,
|
||||
|
||||
/// faster than SuperFast, less compression
|
||||
HyperFast1 = -1,
|
||||
/// faster than HyperFast1, less compression
|
||||
HyperFast2 = -2,
|
||||
/// faster than HyperFast2, less compression
|
||||
HyperFast3 = -3,
|
||||
/// fastest, less compression
|
||||
HyperFast4 = -4,
|
||||
}
|
||||
|
||||
pub type Compress = unsafe extern "system" fn(
|
||||
compressor: Compressor,
|
||||
rawBuf: *const u8,
|
||||
rawLen: usize,
|
||||
compBuf: *mut u8,
|
||||
level: CompressionLevel,
|
||||
pOptions: *const (),
|
||||
dictionaryBase: *const (),
|
||||
lrm: *const (),
|
||||
scratchMem: *mut u8,
|
||||
scratchSize: usize,
|
||||
) -> isize;
|
||||
|
||||
pub type Decompress = unsafe extern "system" 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,
|
||||
) -> isize;
|
||||
|
||||
pub type GetCompressedBufferSizeNeeded =
|
||||
unsafe extern "system" fn(compressor: Compressor, rawSize: usize) -> usize;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod windows_oodle {
|
||||
use super::*;
|
||||
static OODLE_VERSION: &str = "2.9.10";
|
||||
static OODLE_BASE_URL: &str = "https://github.com/WorkingRobot/OodleUE/raw/refs/heads/main/Engine/Source/Programs/Shared/EpicGames.Oodle/Sdk/";
|
||||
|
||||
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)
|
||||
}
|
||||
struct OodlePlatform {
|
||||
path: &'static str,
|
||||
name: &'static str,
|
||||
hash: &'static str,
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
mod linux_oodle {
|
||||
use super::*;
|
||||
static OODLE_PLATFORM: OodlePlatform = OodlePlatform {
|
||||
path: "linux/lib",
|
||||
name: "liboo2corelinux64.so.9",
|
||||
hash: "ed7e98f70be1254a80644efd3ae442ff61f854a2fe9debb0b978b95289884e9c",
|
||||
};
|
||||
|
||||
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};
|
||||
#[cfg(windows)]
|
||||
static OODLE_PLATFORM: OodlePlatform = OodlePlatform {
|
||||
path: "win/redist",
|
||||
name: "oo2core_9_win64.dll",
|
||||
hash: "6f5d41a7892ea6b2db420f2458dad2f84a63901c9a93ce9497337b16c195f457",
|
||||
};
|
||||
|
||||
use object::{LittleEndian as LE, Object, ObjectSection};
|
||||
use std::collections::HashMap;
|
||||
use std::ffi::{c_void, CStr};
|
||||
fn url() -> String {
|
||||
format!(
|
||||
"{OODLE_BASE_URL}/{}/{}/{}",
|
||||
OODLE_VERSION, OODLE_PLATFORM.path, OODLE_PLATFORM.name
|
||||
)
|
||||
}
|
||||
|
||||
#[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,
|
||||
fn check_hash(buffer: &[u8]) -> Result<()> {
|
||||
use sha2::{Digest, Sha256};
|
||||
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(buffer);
|
||||
let hash = hex::encode(hasher.finalize());
|
||||
if hash != OODLE_PLATFORM.hash {
|
||||
anyhow::bail!(
|
||||
"Oodle library hash mismatch: expected {} got {}",
|
||||
OODLE_PLATFORM.hash,
|
||||
hash
|
||||
);
|
||||
}
|
||||
|
||||
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(),
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
static DECOMPRESS: OnceLock<OodleLZ_Decompress> = OnceLock::new();
|
||||
fn fetch_oodle() -> Result<std::path::PathBuf> {
|
||||
let oodle_path = std::env::current_exe()?.with_file_name(OODLE_PLATFORM.name);
|
||||
if !oodle_path.exists() {
|
||||
let mut buffer = vec![];
|
||||
ureq::get(&url())
|
||||
.call()?
|
||||
.into_reader()
|
||||
.read_to_end(&mut buffer)?;
|
||||
check_hash(&buffer)?;
|
||||
std::fs::write(&oodle_path, buffer)?;
|
||||
}
|
||||
check_hash(&std::fs::read(&oodle_path)?)?;
|
||||
Ok(oodle_path)
|
||||
}
|
||||
|
||||
fn decompress_wrapper(comp_buf: &[u8], raw_buf: &mut [u8]) -> i32 {
|
||||
pub struct Oodle {
|
||||
_library: libloading::Library,
|
||||
compress: oodle_lz::Compress,
|
||||
decompress: oodle_lz::Decompress,
|
||||
get_compressed_buffer_size_needed: oodle_lz::GetCompressedBufferSizeNeeded,
|
||||
}
|
||||
impl Oodle {
|
||||
pub fn compress<S: Write>(
|
||||
&self,
|
||||
input: &[u8],
|
||||
mut output: S,
|
||||
compressor: Compressor,
|
||||
compression_level: CompressionLevel,
|
||||
) -> Result<usize> {
|
||||
unsafe {
|
||||
// Set GS register in calling thread
|
||||
const ARCH_SET_GS: i32 = 0x1001;
|
||||
libc::syscall(libc::SYS_arch_prctl, ARCH_SET_GS, &TIB);
|
||||
let buffer_size = self.get_compressed_buffer_size_needed(compressor, input.len());
|
||||
let mut buffer = vec![0; buffer_size];
|
||||
|
||||
// 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!(
|
||||
let len = (self.compress)(
|
||||
compressor,
|
||||
input.as_ptr(),
|
||||
input.len(),
|
||||
buffer.as_mut_ptr(),
|
||||
compression_level,
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
std::ptr::null(),
|
||||
std::ptr::null_mut(),
|
||||
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!(),
|
||||
}
|
||||
if len == -1 {
|
||||
bail!("Oodle compression failed");
|
||||
}
|
||||
let len = len as usize;
|
||||
|
||||
output.write_all(&buffer[..len])?;
|
||||
|
||||
Ok(len)
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
pub fn decompress(&self, input: &[u8], output: &mut [u8]) -> isize {
|
||||
unsafe {
|
||||
(self.decompress)(
|
||||
input.as_ptr(),
|
||||
input.len(),
|
||||
output.as_mut_ptr(),
|
||||
output.len(),
|
||||
1,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
std::ptr::null_mut(),
|
||||
0,
|
||||
3,
|
||||
)
|
||||
}
|
||||
}
|
||||
fn get_compressed_buffer_size_needed(
|
||||
&self,
|
||||
compressor: oodle_lz::Compressor,
|
||||
raw_buffer: usize,
|
||||
) -> usize {
|
||||
unsafe { (self.get_compressed_buffer_size_needed)(compressor, raw_buffer) }
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
static OODLE: OnceLock<Option<Oodle>> = OnceLock::new();
|
||||
|
||||
// Fix section permissions
|
||||
for section in obj_file.sections() {
|
||||
let address = section.address() as usize;
|
||||
let data = section.data()?;
|
||||
let size = data.len();
|
||||
fn load_oodle() -> Result<Oodle> {
|
||||
let path = fetch_oodle()?;
|
||||
unsafe {
|
||||
let library = libloading::Library::new(path)?;
|
||||
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(unsafe {
|
||||
std::mem::transmute::<*const c_void, OodleLZ_Decompress>(exports["OodleLZ_Decompress"])
|
||||
Ok(Oodle {
|
||||
compress: *library.get(b"OodleLZ_Compress")?,
|
||||
decompress: *library.get(b"OodleLZ_Decompress")?,
|
||||
get_compressed_buffer_size_needed: *library
|
||||
.get(b"OodleLZ_GetCompressedBufferSizeNeeded")?,
|
||||
_library: library,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn oodle() -> Result<&'static Oodle, Box<dyn std::error::Error>> {
|
||||
let mut result = None;
|
||||
let oodle = OODLE.get_or_init(|| match load_oodle() {
|
||||
Err(err) => {
|
||||
result = Some(Err(err));
|
||||
None
|
||||
}
|
||||
Ok(oodle) => Some(oodle),
|
||||
});
|
||||
match (result, oodle) {
|
||||
// oodle initialized so return
|
||||
(_, Some(oodle)) => Ok(oodle),
|
||||
// error during initialization
|
||||
(Some(result), _) => result?,
|
||||
// no error because initialization was tried and failed before
|
||||
_ => Err(anyhow::anyhow!("oodle failed to initialized previously").into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_oodle() {
|
||||
let oodle = oodle().unwrap();
|
||||
|
||||
let data = b"In tools and when compressing large inputs in one call, consider using
|
||||
$OodleXLZ_Compress_AsyncAndWait (in the Oodle2 Ext lib) instead to get parallelism. Alternatively,
|
||||
chop the data into small fixed size chunks (we recommend at least 256KiB, i.e. 262144 bytes) and
|
||||
call compress on each of them, which decreases compression ratio but makes for trivial parallel
|
||||
compression and decompression.";
|
||||
|
||||
let mut buffer = vec![];
|
||||
oodle
|
||||
.compress(
|
||||
data,
|
||||
&mut buffer,
|
||||
Compressor::Mermaid,
|
||||
CompressionLevel::Optimal5,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
std::fs::write("comp.bin", &buffer).unwrap();
|
||||
dbg!((data.len(), buffer.len()));
|
||||
|
||||
let mut uncomp = vec![0; data.len()];
|
||||
oodle.decompress(&buffer, &mut uncomp);
|
||||
|
||||
assert_eq!(data[..], uncomp[..]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -535,7 +535,7 @@ impl Entry {
|
|||
.min(self.uncompressed as usize - compress_offset)
|
||||
};
|
||||
let buffer = &mut data[range];
|
||||
let out = oodle(
|
||||
let out = oodle.decompress(
|
||||
buffer,
|
||||
&mut decompressed[decompress_offset..decompress_offset + decomp],
|
||||
);
|
||||
|
|
|
@ -11,7 +11,7 @@ pub const MAGIC: u32 = 0x5A6F12E1;
|
|||
|
||||
#[cfg(feature = "oodle")]
|
||||
mod oodle {
|
||||
pub type OodleGetter = fn() -> Result<OodleDecompress, Box<dyn std::error::Error>>;
|
||||
pub type OodleGetter = fn() -> Result<&'static oodle_loader::Oodle, Box<dyn std::error::Error>>;
|
||||
pub type OodleDecompress = fn(comp_buf: &[u8], raw_buf: &mut [u8]) -> i32;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ impl PakBuilder {
|
|||
#[cfg(not(feature = "oodle_implicit_dynamic"))]
|
||||
oodle: super::Oodle::None,
|
||||
#[cfg(feature = "oodle_implicit_dynamic")]
|
||||
oodle: super::Oodle::Some(oodle_loader::decompress),
|
||||
oodle: super::Oodle::Some(oodle_loader::oodle),
|
||||
allowed_compression: Default::default(),
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue