refactor: Extract Oodle into separate library
The library utilizes an internal global singleton to allow using the functions without having to lug around an instance of `libloading::Library`.
This commit is contained in:
parent
5eebced362
commit
9f84340b73
17 changed files with 184 additions and 102 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -378,6 +378,7 @@ dependencies = [
|
|||
"glob",
|
||||
"libloading",
|
||||
"nanorand",
|
||||
"oodle-sys",
|
||||
"pin-project-lite",
|
||||
"promptly",
|
||||
"sdk",
|
||||
|
@ -840,6 +841,15 @@ version = "1.17.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
||||
|
||||
[[package]]
|
||||
name = "oodle-sys"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"libloading",
|
||||
"thiserror",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
|
@ -1096,6 +1106,7 @@ dependencies = [
|
|||
"glob",
|
||||
"libloading",
|
||||
"nanorand",
|
||||
"oodle-sys",
|
||||
"pin-project-lite",
|
||||
"serde",
|
||||
"serde_sjson",
|
||||
|
|
|
@ -15,6 +15,7 @@ libloading = "0.7.4"
|
|||
nanorand = "0.7.0"
|
||||
pin-project-lite = "0.2.9"
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
oodle-sys = { path = "../../lib/oodle-sys", version = "*" }
|
||||
serde_sjson = { path = "../../lib/serde_sjson", version = "*" }
|
||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "fs", "process", "macros", "tracing", "io-util", "io-std"] }
|
||||
tokio-stream = { version = "0.1.11", features = ["fs", "io-util"] }
|
||||
|
|
|
@ -7,7 +7,7 @@ use color_eyre::{Help, Report};
|
|||
use futures::future::try_join_all;
|
||||
use futures::StreamExt;
|
||||
use sdk::filetype::package::Package;
|
||||
use sdk::{Bundle, BundleFile, Oodle};
|
||||
use sdk::{Bundle, BundleFile};
|
||||
use serde::Deserialize;
|
||||
use tokio::fs::{self, File};
|
||||
use tokio::io::AsyncReadExt;
|
||||
|
@ -28,17 +28,12 @@ pub(crate) fn command_definition() -> Command {
|
|||
If omitted, dtmt will search from the current working directory upward.",
|
||||
),
|
||||
)
|
||||
.arg(
|
||||
Arg::new("oodle")
|
||||
.long("oodle")
|
||||
.default_value(super::OODLE_LIB_NAME)
|
||||
.help(
|
||||
.arg(Arg::new("oodle").long("oodle").help(
|
||||
"The oodle library to load. This may either be:\n\
|
||||
- A library name that will be searched for in the system's default paths.\n\
|
||||
- A file path relative to the current working directory.\n\
|
||||
- An absolute file path.",
|
||||
),
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
|
@ -175,10 +170,9 @@ where
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn run(mut ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
|
||||
if let Some(name) = matches.get_one::<String>("oodle") {
|
||||
let oodle = Oodle::new(name)?;
|
||||
ctx.oodle = Some(oodle);
|
||||
pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
|
||||
unsafe {
|
||||
oodle_sys::init(matches.get_one::<String>("oodle"));
|
||||
}
|
||||
|
||||
let cfg = {
|
||||
|
@ -237,7 +231,7 @@ pub(crate) async fn run(mut ctx: sdk::Context, matches: &ArgMatches) -> Result<(
|
|||
}
|
||||
|
||||
archive
|
||||
.write(&ctx, dest.as_ref())
|
||||
.write(dest.as_ref())
|
||||
.wrap_err("failed to write mod archive")
|
||||
})
|
||||
.await??;
|
||||
|
|
|
@ -98,7 +98,7 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
|
|||
|
||||
let out_path = matches.get_one::<PathBuf>("output").unwrap_or(bundle_path);
|
||||
let data = bundle
|
||||
.to_binary(&ctx)
|
||||
.to_binary()
|
||||
.wrap_err("failed to write changed bundle to output")?;
|
||||
|
||||
fs::write(out_path, &data)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
use clap::{Arg, ArgMatches, Command};
|
||||
use color_eyre::eyre::Result;
|
||||
use sdk::Oodle;
|
||||
|
||||
mod decompress;
|
||||
mod extract;
|
||||
|
@ -11,17 +10,12 @@ pub(crate) fn command_definition() -> Command {
|
|||
Command::new("bundle")
|
||||
.subcommand_required(true)
|
||||
.about("Manipulate the game's bundle files")
|
||||
.arg(
|
||||
Arg::new("oodle")
|
||||
.long("oodle")
|
||||
.default_value(super::OODLE_LIB_NAME)
|
||||
.help(
|
||||
.arg(Arg::new("oodle").long("oodle").help(
|
||||
"The oodle library to load. This may either be:\n\
|
||||
- A library name that will be searched for in the system's default paths.\n\
|
||||
- A file path relative to the current working directory.\n\
|
||||
- An absolute file path.",
|
||||
),
|
||||
)
|
||||
))
|
||||
.subcommand(decompress::command_definition())
|
||||
.subcommand(extract::command_definition())
|
||||
.subcommand(inject::command_definition())
|
||||
|
@ -29,10 +23,9 @@ pub(crate) fn command_definition() -> Command {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub(crate) async fn run(mut ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
|
||||
if let Some(name) = matches.get_one::<String>("oodle") {
|
||||
let oodle = Oodle::new(name)?;
|
||||
ctx.oodle = Some(oodle);
|
||||
pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
|
||||
unsafe {
|
||||
oodle_sys::init(matches.get_one::<String>("oodle"));
|
||||
}
|
||||
|
||||
match matches.subcommand() {
|
||||
|
|
|
@ -18,12 +18,6 @@ use tracing_subscriber::prelude::*;
|
|||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
mod cmd {
|
||||
#[cfg(target_os = "windows")]
|
||||
const OODLE_LIB_NAME: &str = "oo2core_8_win64";
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
const OODLE_LIB_NAME: &str = "liboo2corelinux64.so";
|
||||
|
||||
pub mod build;
|
||||
pub mod bundle;
|
||||
pub mod dictionary;
|
||||
|
|
|
@ -32,7 +32,7 @@ impl Archive {
|
|||
self.mod_file = Some(content);
|
||||
}
|
||||
|
||||
pub fn write<P>(&self, ctx: &sdk::Context, path: P) -> Result<()>
|
||||
pub fn write<P>(&self, path: P) -> Result<()>
|
||||
where
|
||||
P: AsRef<Path>,
|
||||
{
|
||||
|
@ -76,7 +76,7 @@ impl Archive {
|
|||
|
||||
zip.start_file(path.to_string_lossy(), Default::default())?;
|
||||
|
||||
let data = bundle.to_binary(ctx)?;
|
||||
let data = bundle.to_binary()?;
|
||||
zip.write_all(&data)?;
|
||||
}
|
||||
|
||||
|
|
2
lib/oodle-sys/.gitignore
vendored
Normal file
2
lib/oodle-sys/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/Cargo.lock
|
11
lib/oodle-sys/Cargo.toml
Normal file
11
lib/oodle-sys/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "oodle-sys"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
libloading = "0.7.4"
|
||||
thiserror = "1.0.38"
|
||||
tracing = "0.1.37"
|
77
lib/oodle-sys/src/lib.rs
Normal file
77
lib/oodle-sys/src/lib.rs
Normal file
|
@ -0,0 +1,77 @@
|
|||
#![feature(c_size_t)]
|
||||
#![feature(once_cell)]
|
||||
|
||||
use std::ffi::OsStr;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
mod library;
|
||||
mod types;
|
||||
|
||||
pub use library::Library;
|
||||
pub use library::CHUNK_SIZE;
|
||||
pub use types::*;
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum OodleError {
|
||||
#[error("{0}")]
|
||||
Oodle(String),
|
||||
#[error(transparent)]
|
||||
Library(#[from] libloading::Error),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, OodleError>;
|
||||
|
||||
static LIB: OnceLock<Library> = OnceLock::new();
|
||||
|
||||
/// Initialize the global library handle that this module's
|
||||
/// functions operate on.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The safety concerns as described by [`libloading::Library::new`] apply.
|
||||
pub unsafe fn init<P: AsRef<OsStr>>(name: Option<P>) {
|
||||
let lib = match name {
|
||||
Some(name) => Library::with_name(name),
|
||||
None => Library::new(),
|
||||
};
|
||||
|
||||
let lib = lib.expect("Failed to load library.");
|
||||
if LIB.set(lib).is_err() {
|
||||
panic!("Library was already initialized. Did you call `init` twice?");
|
||||
}
|
||||
}
|
||||
|
||||
fn get() -> Result<&'static Library> {
|
||||
match LIB.get() {
|
||||
Some(lib) => Ok(lib),
|
||||
None => {
|
||||
let err = OodleError::Oodle(String::from("Library has not been initialized, yet."));
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn decompress<I>(
|
||||
data: I,
|
||||
fuzz_safe: OodleLZ_FuzzSafe,
|
||||
check_crc: OodleLZ_CheckCRC,
|
||||
) -> Result<Vec<u8>>
|
||||
where
|
||||
I: AsRef<[u8]>,
|
||||
{
|
||||
let lib = get()?;
|
||||
lib.decompress(data, fuzz_safe, check_crc)
|
||||
}
|
||||
|
||||
pub fn compress<I>(data: I) -> Result<Vec<u8>>
|
||||
where
|
||||
I: AsRef<[u8]>,
|
||||
{
|
||||
let lib = get()?;
|
||||
lib.compress(data)
|
||||
}
|
||||
|
||||
pub fn get_decode_buffer_size(raw_size: usize, corruption_possible: bool) -> Result<usize> {
|
||||
let lib = get()?;
|
||||
lib.get_decode_buffer_size(raw_size, corruption_possible)
|
||||
}
|
|
@ -1,43 +1,52 @@
|
|||
use std::ffi::OsStr;
|
||||
use std::ops::Deref;
|
||||
use std::ptr;
|
||||
use std::{ffi::OsStr, ptr};
|
||||
|
||||
use color_eyre::eyre;
|
||||
use color_eyre::Result;
|
||||
use libloading::{Library, Symbol};
|
||||
use libloading::Symbol;
|
||||
|
||||
pub mod types;
|
||||
use types::*;
|
||||
use super::Result;
|
||||
use crate::{types::*, OodleError};
|
||||
|
||||
// Hardcoded chunk size of Bitsquid's bundle compression
|
||||
pub const CHUNK_SIZE: usize = 512 * 1024;
|
||||
pub const COMPRESSOR: OodleLZ_Compressor = OodleLZ_Compressor::Kraken;
|
||||
pub const LEVEL: OodleLZ_CompressionLevel = OodleLZ_CompressionLevel::Optimal2;
|
||||
|
||||
pub struct Oodle {
|
||||
lib: Library,
|
||||
#[cfg(target_os = "windows")]
|
||||
const OODLE_LIB_NAME: &str = "oo2core_8_win64";
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
const OODLE_LIB_NAME: &str = "liboo2corelinux64.so";
|
||||
|
||||
pub struct Library {
|
||||
inner: libloading::Library,
|
||||
}
|
||||
|
||||
impl Oodle {
|
||||
pub fn new<P>(lib: P) -> Result<Self>
|
||||
where
|
||||
P: AsRef<OsStr>,
|
||||
{
|
||||
let lib = unsafe { Library::new(lib)? };
|
||||
|
||||
unsafe {
|
||||
let fun: Symbol<OodleCore_Plugins_SetPrintf> =
|
||||
lib.get(b"OodleCore_Plugins_SetPrintf\0")?;
|
||||
let printf: Symbol<t_fp_OodleCore_Plugin_Printf> =
|
||||
lib.get(b"OodleCore_Plugin_Printf_Verbose\0")?;
|
||||
|
||||
fun(*printf.deref());
|
||||
impl Library {
|
||||
/// Load the Oodle library by its default name.
|
||||
///
|
||||
/// The default name is platform-specific:
|
||||
/// - Windows: `oo2core_8_win64`
|
||||
/// - Linux: `liboo2corelinux64.so`
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The safety concerns as described by [`libloading::Library::new`] apply.
|
||||
pub unsafe fn new() -> Result<Self> {
|
||||
Self::with_name(OODLE_LIB_NAME)
|
||||
}
|
||||
|
||||
Ok(Self { lib })
|
||||
/// Load the Oodle library by the given name or path.
|
||||
///
|
||||
/// See [`libloading::Library::new`] for how the `name` parameter is handled.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The safety concerns as described by [`libloading::Library::new`] apply.
|
||||
pub unsafe fn with_name<P: AsRef<OsStr>>(name: P) -> Result<Self> {
|
||||
let inner = libloading::Library::new(name)?;
|
||||
Ok(Self { inner })
|
||||
}
|
||||
|
||||
#[tracing::instrument(name = "Oodle::decompress", skip(self, data))]
|
||||
#[tracing::instrument(skip(self, data))]
|
||||
pub fn decompress<I>(
|
||||
&self,
|
||||
data: I,
|
||||
|
@ -61,7 +70,7 @@ impl Oodle {
|
|||
};
|
||||
|
||||
let ret = unsafe {
|
||||
let decompress: Symbol<OodleLZ_Decompress> = self.lib.get(b"OodleLZ_Decompress\0")?;
|
||||
let decompress: Symbol<OodleLZ_Decompress> = self.inner.get(b"OodleLZ_Decompress\0")?;
|
||||
|
||||
decompress(
|
||||
data.as_ptr() as *const _,
|
||||
|
@ -82,7 +91,8 @@ impl Oodle {
|
|||
};
|
||||
|
||||
if ret == 0 {
|
||||
eyre::bail!("Decompression failed.");
|
||||
let err = OodleError::Oodle(String::from("Decompression failed."));
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
Ok(out)
|
||||
|
@ -100,7 +110,7 @@ impl Oodle {
|
|||
let mut out = vec![0u8; CHUNK_SIZE];
|
||||
|
||||
let ret = unsafe {
|
||||
let compress: Symbol<OodleLZ_Compress> = self.lib.get(b"OodleLZ_Compress\0")?;
|
||||
let compress: Symbol<OodleLZ_Compress> = self.inner.get(b"OodleLZ_Compress\0")?;
|
||||
|
||||
compress(
|
||||
COMPRESSOR,
|
||||
|
@ -119,7 +129,8 @@ impl Oodle {
|
|||
tracing::debug!(compressed_size = ret, "Compressed chunk");
|
||||
|
||||
if ret == 0 {
|
||||
eyre::bail!("Compression failed.");
|
||||
let err = OodleError::Oodle(String::from("Compression failed."));
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
out.resize(ret as usize, 0);
|
||||
|
@ -134,7 +145,7 @@ impl Oodle {
|
|||
) -> Result<usize> {
|
||||
unsafe {
|
||||
let f: Symbol<OodleLZ_GetDecodeBufferSize> =
|
||||
self.lib.get(b"OodleLZ_GetDecodeBufferSize\0")?;
|
||||
self.inner.get(b"OodleLZ_GetDecodeBufferSize\0")?;
|
||||
|
||||
let size = f(COMPRESSOR, raw_size, corruption_possible);
|
||||
Ok(size)
|
|
@ -16,6 +16,7 @@ nanorand = "0.7.0"
|
|||
pin-project-lite = "0.2.9"
|
||||
serde = { version = "1.0.147", features = ["derive"] }
|
||||
serde_sjson = { path = "../../lib/serde_sjson", version = "*" }
|
||||
oodle-sys = { path = "../../lib/oodle-sys", version = "*" }
|
||||
tokio = { version = "1.21.2", features = ["rt-multi-thread", "fs", "process", "macros", "tracing", "io-util", "io-std"] }
|
||||
tokio-stream = { version = "0.1.11", features = ["fs", "io-util"] }
|
||||
tracing = { version = "0.1.37", features = ["async-await"] }
|
||||
|
|
|
@ -3,11 +3,10 @@ use std::path::Path;
|
|||
|
||||
use color_eyre::eyre::{self, Context, Result};
|
||||
use color_eyre::{Help, Report, SectionExt};
|
||||
use oodle_sys::{OodleLZ_CheckCRC, OodleLZ_FuzzSafe, CHUNK_SIZE};
|
||||
|
||||
use crate::binary::sync::*;
|
||||
use crate::murmur::{HashGroup, Murmur64};
|
||||
use crate::oodle::types::{OodleLZ_CheckCRC, OodleLZ_FuzzSafe};
|
||||
use crate::oodle::CHUNK_SIZE;
|
||||
|
||||
pub(crate) mod file;
|
||||
|
||||
|
@ -198,9 +197,7 @@ impl Bundle {
|
|||
decompressed.append(&mut compressed_buffer);
|
||||
} else {
|
||||
// TODO: Optimize to not reallocate?
|
||||
let oodle_lib = ctx.oodle.as_ref().unwrap();
|
||||
let mut raw_buffer = oodle_lib
|
||||
.decompress(
|
||||
let mut raw_buffer = oodle_sys::decompress(
|
||||
&compressed_buffer,
|
||||
OodleLZ_FuzzSafe::No,
|
||||
OodleLZ_CheckCRC::No,
|
||||
|
@ -246,7 +243,7 @@ impl Bundle {
|
|||
}
|
||||
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn to_binary(&self, ctx: &crate::Context) -> Result<Vec<u8>> {
|
||||
pub fn to_binary(&self) -> Result<Vec<u8>> {
|
||||
let mut w = Cursor::new(Vec::new());
|
||||
w.write_u32(self.format.into())?;
|
||||
// TODO: Find out what this is.
|
||||
|
@ -293,12 +290,10 @@ impl Bundle {
|
|||
w.write_u32(0)?;
|
||||
|
||||
let chunks = unpacked_data.chunks(CHUNK_SIZE);
|
||||
|
||||
let oodle_lib = ctx.oodle.as_ref().expect("oodle library not defined");
|
||||
let mut chunk_sizes = Vec::with_capacity(num_chunks);
|
||||
|
||||
for chunk in chunks {
|
||||
let compressed = oodle_lib.compress(chunk)?;
|
||||
let compressed = oodle_sys::compress(chunk)?;
|
||||
tracing::trace!(
|
||||
raw_chunk_size = chunk.len(),
|
||||
compressed_chunk_size = compressed.len()
|
||||
|
@ -335,7 +330,7 @@ impl Bundle {
|
|||
/// This is mainly useful for debugging purposes or
|
||||
/// to manullay inspect the raw data.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn decompress<B>(ctx: &crate::Context, binary: B) -> Result<Vec<u8>>
|
||||
pub fn decompress<B>(_ctx: &crate::Context, binary: B) -> Result<Vec<u8>>
|
||||
where
|
||||
B: AsRef<[u8]>,
|
||||
{
|
||||
|
@ -399,9 +394,8 @@ where
|
|||
let mut compressed_buffer = vec![0u8; chunk_size];
|
||||
r.read_exact(&mut compressed_buffer)?;
|
||||
|
||||
let oodle_lib = ctx.oodle.as_ref().unwrap();
|
||||
// TODO: Optimize to not reallocate?
|
||||
let mut raw_buffer = oodle_lib.decompress(
|
||||
let mut raw_buffer = oodle_sys::decompress(
|
||||
&compressed_buffer,
|
||||
OodleLZ_FuzzSafe::No,
|
||||
OodleLZ_CheckCRC::No,
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
use std::path::PathBuf;
|
||||
|
||||
use crate::murmur::{Dictionary, HashGroup, Murmur32, Murmur64};
|
||||
use crate::oodle::Oodle;
|
||||
|
||||
pub struct Context {
|
||||
pub lookup: Dictionary,
|
||||
pub oodle: Option<Oodle>,
|
||||
pub ljd: Option<String>,
|
||||
pub revorb: Option<String>,
|
||||
pub ww2ogg: Option<String>,
|
||||
|
@ -16,7 +14,6 @@ impl Context {
|
|||
pub fn new() -> Self {
|
||||
Self {
|
||||
lookup: Dictionary::new(),
|
||||
oodle: None,
|
||||
ljd: None,
|
||||
revorb: None,
|
||||
ww2ogg: None,
|
||||
|
|
|
@ -84,8 +84,8 @@ type PackageDefinition = HashMap<String, HashSet<String>>;
|
|||
|
||||
#[derive(Default)]
|
||||
pub struct Package {
|
||||
name: String,
|
||||
root: PathBuf,
|
||||
_name: String,
|
||||
_root: PathBuf,
|
||||
inner: PackageType,
|
||||
}
|
||||
|
||||
|
@ -159,8 +159,8 @@ impl Package {
|
|||
|
||||
let pkg = Self {
|
||||
inner,
|
||||
name,
|
||||
root: root.to_path_buf(),
|
||||
_name: name,
|
||||
_root: root.to_path_buf(),
|
||||
};
|
||||
|
||||
Ok(pkg)
|
||||
|
@ -206,8 +206,8 @@ impl Package {
|
|||
|
||||
let pkg = Self {
|
||||
inner,
|
||||
name,
|
||||
root: PathBuf::new(),
|
||||
_name: name,
|
||||
_root: PathBuf::new(),
|
||||
};
|
||||
|
||||
Ok(pkg)
|
||||
|
|
|
@ -1,13 +1,9 @@
|
|||
#![feature(c_size_t)]
|
||||
|
||||
mod binary;
|
||||
mod bundle;
|
||||
mod context;
|
||||
pub mod filetype;
|
||||
pub mod murmur;
|
||||
mod oodle;
|
||||
|
||||
pub use bundle::decompress;
|
||||
pub use bundle::{Bundle, BundleFile, BundleFileType};
|
||||
pub use context::Context;
|
||||
pub use oodle::Oodle;
|
||||
|
|
Loading…
Add table
Reference in a new issue