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:
Lucas Schwiderski 2023-02-08 14:22:18 +01:00
parent 5eebced362
commit 9f84340b73
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
17 changed files with 184 additions and 102 deletions

11
Cargo.lock generated
View file

@ -378,6 +378,7 @@ dependencies = [
"glob", "glob",
"libloading", "libloading",
"nanorand", "nanorand",
"oodle-sys",
"pin-project-lite", "pin-project-lite",
"promptly", "promptly",
"sdk", "sdk",
@ -840,6 +841,15 @@ version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "oodle-sys"
version = "0.1.0"
dependencies = [
"libloading",
"thiserror",
"tracing",
]
[[package]] [[package]]
name = "opaque-debug" name = "opaque-debug"
version = "0.3.0" version = "0.3.0"
@ -1096,6 +1106,7 @@ dependencies = [
"glob", "glob",
"libloading", "libloading",
"nanorand", "nanorand",
"oodle-sys",
"pin-project-lite", "pin-project-lite",
"serde", "serde",
"serde_sjson", "serde_sjson",

View file

@ -15,6 +15,7 @@ libloading = "0.7.4"
nanorand = "0.7.0" nanorand = "0.7.0"
pin-project-lite = "0.2.9" pin-project-lite = "0.2.9"
serde = { version = "1.0.147", features = ["derive"] } serde = { version = "1.0.147", features = ["derive"] }
oodle-sys = { path = "../../lib/oodle-sys", version = "*" }
serde_sjson = { path = "../../lib/serde_sjson", 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 = { 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"] } tokio-stream = { version = "0.1.11", features = ["fs", "io-util"] }

View file

@ -7,7 +7,7 @@ use color_eyre::{Help, Report};
use futures::future::try_join_all; use futures::future::try_join_all;
use futures::StreamExt; use futures::StreamExt;
use sdk::filetype::package::Package; use sdk::filetype::package::Package;
use sdk::{Bundle, BundleFile, Oodle}; use sdk::{Bundle, BundleFile};
use serde::Deserialize; use serde::Deserialize;
use tokio::fs::{self, File}; use tokio::fs::{self, File};
use tokio::io::AsyncReadExt; 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.", If omitted, dtmt will search from the current working directory upward.",
), ),
) )
.arg( .arg(Arg::new("oodle").long("oodle").help(
Arg::new("oodle") "The oodle library to load. This may either be:\n\
.long("oodle") - A library name that will be searched for in the system's default paths.\n\
.default_value(super::OODLE_LIB_NAME) - A file path relative to the current working directory.\n\
.help( - An absolute file path.",
"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)] #[derive(Debug, Default, Deserialize)]
@ -175,10 +170,9 @@ where
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub(crate) async fn run(mut ctx: sdk::Context, matches: &ArgMatches) -> Result<()> { pub(crate) async fn run(_ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
if let Some(name) = matches.get_one::<String>("oodle") { unsafe {
let oodle = Oodle::new(name)?; oodle_sys::init(matches.get_one::<String>("oodle"));
ctx.oodle = Some(oodle);
} }
let cfg = { let cfg = {
@ -237,7 +231,7 @@ pub(crate) async fn run(mut ctx: sdk::Context, matches: &ArgMatches) -> Result<(
} }
archive archive
.write(&ctx, dest.as_ref()) .write(dest.as_ref())
.wrap_err("failed to write mod archive") .wrap_err("failed to write mod archive")
}) })
.await??; .await??;

View file

@ -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 out_path = matches.get_one::<PathBuf>("output").unwrap_or(bundle_path);
let data = bundle let data = bundle
.to_binary(&ctx) .to_binary()
.wrap_err("failed to write changed bundle to output")?; .wrap_err("failed to write changed bundle to output")?;
fs::write(out_path, &data) fs::write(out_path, &data)

View file

@ -1,6 +1,5 @@
use clap::{Arg, ArgMatches, Command}; use clap::{Arg, ArgMatches, Command};
use color_eyre::eyre::Result; use color_eyre::eyre::Result;
use sdk::Oodle;
mod decompress; mod decompress;
mod extract; mod extract;
@ -11,17 +10,12 @@ pub(crate) fn command_definition() -> Command {
Command::new("bundle") Command::new("bundle")
.subcommand_required(true) .subcommand_required(true)
.about("Manipulate the game's bundle files") .about("Manipulate the game's bundle files")
.arg( .arg(Arg::new("oodle").long("oodle").help(
Arg::new("oodle") "The oodle library to load. This may either be:\n\
.long("oodle")
.default_value(super::OODLE_LIB_NAME)
.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 library name that will be searched for in the system's default paths.\n\
- A file path relative to the current working directory.\n\ - A file path relative to the current working directory.\n\
- An absolute file path.", - An absolute file path.",
), ))
)
.subcommand(decompress::command_definition()) .subcommand(decompress::command_definition())
.subcommand(extract::command_definition()) .subcommand(extract::command_definition())
.subcommand(inject::command_definition()) .subcommand(inject::command_definition())
@ -29,10 +23,9 @@ pub(crate) fn command_definition() -> Command {
} }
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
pub(crate) async fn run(mut ctx: sdk::Context, matches: &ArgMatches) -> Result<()> { pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> {
if let Some(name) = matches.get_one::<String>("oodle") { unsafe {
let oodle = Oodle::new(name)?; oodle_sys::init(matches.get_one::<String>("oodle"));
ctx.oodle = Some(oodle);
} }
match matches.subcommand() { match matches.subcommand() {

View file

@ -18,12 +18,6 @@ use tracing_subscriber::prelude::*;
use tracing_subscriber::EnvFilter; use tracing_subscriber::EnvFilter;
mod cmd { 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 build;
pub mod bundle; pub mod bundle;
pub mod dictionary; pub mod dictionary;

View file

@ -32,7 +32,7 @@ impl Archive {
self.mod_file = Some(content); 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 where
P: AsRef<Path>, P: AsRef<Path>,
{ {
@ -76,7 +76,7 @@ impl Archive {
zip.start_file(path.to_string_lossy(), Default::default())?; zip.start_file(path.to_string_lossy(), Default::default())?;
let data = bundle.to_binary(ctx)?; let data = bundle.to_binary()?;
zip.write_all(&data)?; zip.write_all(&data)?;
} }

2
lib/oodle-sys/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
/Cargo.lock

11
lib/oodle-sys/Cargo.toml Normal file
View 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
View 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)
}

View file

@ -1,43 +1,52 @@
use std::ffi::OsStr; use std::{ffi::OsStr, ptr};
use std::ops::Deref;
use std::ptr;
use color_eyre::eyre; use libloading::Symbol;
use color_eyre::Result;
use libloading::{Library, Symbol};
pub mod types; use super::Result;
use types::*; use crate::{types::*, OodleError};
// Hardcoded chunk size of Bitsquid's bundle compression // Hardcoded chunk size of Bitsquid's bundle compression
pub const CHUNK_SIZE: usize = 512 * 1024; pub const CHUNK_SIZE: usize = 512 * 1024;
pub const COMPRESSOR: OodleLZ_Compressor = OodleLZ_Compressor::Kraken; pub const COMPRESSOR: OodleLZ_Compressor = OodleLZ_Compressor::Kraken;
pub const LEVEL: OodleLZ_CompressionLevel = OodleLZ_CompressionLevel::Optimal2; pub const LEVEL: OodleLZ_CompressionLevel = OodleLZ_CompressionLevel::Optimal2;
pub struct Oodle { #[cfg(target_os = "windows")]
lib: Library, 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 { impl Library {
pub fn new<P>(lib: P) -> Result<Self> /// Load the Oodle library by its default name.
where ///
P: AsRef<OsStr>, /// The default name is platform-specific:
{ /// - Windows: `oo2core_8_win64`
let lib = unsafe { Library::new(lib)? }; /// - Linux: `liboo2corelinux64.so`
///
unsafe { /// # Safety
let fun: Symbol<OodleCore_Plugins_SetPrintf> = ///
lib.get(b"OodleCore_Plugins_SetPrintf\0")?; /// The safety concerns as described by [`libloading::Library::new`] apply.
let printf: Symbol<t_fp_OodleCore_Plugin_Printf> = pub unsafe fn new() -> Result<Self> {
lib.get(b"OodleCore_Plugin_Printf_Verbose\0")?; Self::with_name(OODLE_LIB_NAME)
fun(*printf.deref());
}
Ok(Self { lib })
} }
#[tracing::instrument(name = "Oodle::decompress", skip(self, data))] /// 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(skip(self, data))]
pub fn decompress<I>( pub fn decompress<I>(
&self, &self,
data: I, data: I,
@ -61,7 +70,7 @@ impl Oodle {
}; };
let ret = unsafe { 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( decompress(
data.as_ptr() as *const _, data.as_ptr() as *const _,
@ -82,7 +91,8 @@ impl Oodle {
}; };
if ret == 0 { if ret == 0 {
eyre::bail!("Decompression failed."); let err = OodleError::Oodle(String::from("Decompression failed."));
return Err(err);
} }
Ok(out) Ok(out)
@ -100,7 +110,7 @@ impl Oodle {
let mut out = vec![0u8; CHUNK_SIZE]; let mut out = vec![0u8; CHUNK_SIZE];
let ret = unsafe { 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( compress(
COMPRESSOR, COMPRESSOR,
@ -119,7 +129,8 @@ impl Oodle {
tracing::debug!(compressed_size = ret, "Compressed chunk"); tracing::debug!(compressed_size = ret, "Compressed chunk");
if ret == 0 { if ret == 0 {
eyre::bail!("Compression failed."); let err = OodleError::Oodle(String::from("Compression failed."));
return Err(err);
} }
out.resize(ret as usize, 0); out.resize(ret as usize, 0);
@ -134,7 +145,7 @@ impl Oodle {
) -> Result<usize> { ) -> Result<usize> {
unsafe { unsafe {
let f: Symbol<OodleLZ_GetDecodeBufferSize> = 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); let size = f(COMPRESSOR, raw_size, corruption_possible);
Ok(size) Ok(size)

View file

@ -16,6 +16,7 @@ nanorand = "0.7.0"
pin-project-lite = "0.2.9" pin-project-lite = "0.2.9"
serde = { version = "1.0.147", features = ["derive"] } serde = { version = "1.0.147", features = ["derive"] }
serde_sjson = { path = "../../lib/serde_sjson", version = "*" } 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 = { 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"] } tokio-stream = { version = "0.1.11", features = ["fs", "io-util"] }
tracing = { version = "0.1.37", features = ["async-await"] } tracing = { version = "0.1.37", features = ["async-await"] }

View file

@ -3,11 +3,10 @@ use std::path::Path;
use color_eyre::eyre::{self, Context, Result}; use color_eyre::eyre::{self, Context, Result};
use color_eyre::{Help, Report, SectionExt}; use color_eyre::{Help, Report, SectionExt};
use oodle_sys::{OodleLZ_CheckCRC, OodleLZ_FuzzSafe, CHUNK_SIZE};
use crate::binary::sync::*; use crate::binary::sync::*;
use crate::murmur::{HashGroup, Murmur64}; use crate::murmur::{HashGroup, Murmur64};
use crate::oodle::types::{OodleLZ_CheckCRC, OodleLZ_FuzzSafe};
use crate::oodle::CHUNK_SIZE;
pub(crate) mod file; pub(crate) mod file;
@ -198,14 +197,12 @@ impl Bundle {
decompressed.append(&mut compressed_buffer); decompressed.append(&mut compressed_buffer);
} else { } else {
// TODO: Optimize to not reallocate? // TODO: Optimize to not reallocate?
let oodle_lib = ctx.oodle.as_ref().unwrap(); let mut raw_buffer = oodle_sys::decompress(
let mut raw_buffer = oodle_lib &compressed_buffer,
.decompress( OodleLZ_FuzzSafe::No,
&compressed_buffer, OodleLZ_CheckCRC::No,
OodleLZ_FuzzSafe::No, )
OodleLZ_CheckCRC::No, .wrap_err_with(|| format!("failed to decompress chunk {chunk_index}"))?;
)
.wrap_err_with(|| format!("failed to decompress chunk {chunk_index}"))?;
if unpacked_size_tracked < CHUNK_SIZE { if unpacked_size_tracked < CHUNK_SIZE {
raw_buffer.resize(unpacked_size_tracked, 0); raw_buffer.resize(unpacked_size_tracked, 0);
@ -246,7 +243,7 @@ impl Bundle {
} }
#[tracing::instrument(skip_all)] #[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()); let mut w = Cursor::new(Vec::new());
w.write_u32(self.format.into())?; w.write_u32(self.format.into())?;
// TODO: Find out what this is. // TODO: Find out what this is.
@ -293,12 +290,10 @@ impl Bundle {
w.write_u32(0)?; w.write_u32(0)?;
let chunks = unpacked_data.chunks(CHUNK_SIZE); 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); let mut chunk_sizes = Vec::with_capacity(num_chunks);
for chunk in chunks { for chunk in chunks {
let compressed = oodle_lib.compress(chunk)?; let compressed = oodle_sys::compress(chunk)?;
tracing::trace!( tracing::trace!(
raw_chunk_size = chunk.len(), raw_chunk_size = chunk.len(),
compressed_chunk_size = compressed.len() compressed_chunk_size = compressed.len()
@ -335,7 +330,7 @@ impl Bundle {
/// This is mainly useful for debugging purposes or /// This is mainly useful for debugging purposes or
/// to manullay inspect the raw data. /// to manullay inspect the raw data.
#[tracing::instrument(skip_all)] #[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 where
B: AsRef<[u8]>, B: AsRef<[u8]>,
{ {
@ -399,9 +394,8 @@ where
let mut compressed_buffer = vec![0u8; chunk_size]; let mut compressed_buffer = vec![0u8; chunk_size];
r.read_exact(&mut compressed_buffer)?; r.read_exact(&mut compressed_buffer)?;
let oodle_lib = ctx.oodle.as_ref().unwrap();
// TODO: Optimize to not reallocate? // TODO: Optimize to not reallocate?
let mut raw_buffer = oodle_lib.decompress( let mut raw_buffer = oodle_sys::decompress(
&compressed_buffer, &compressed_buffer,
OodleLZ_FuzzSafe::No, OodleLZ_FuzzSafe::No,
OodleLZ_CheckCRC::No, OodleLZ_CheckCRC::No,

View file

@ -1,11 +1,9 @@
use std::path::PathBuf; use std::path::PathBuf;
use crate::murmur::{Dictionary, HashGroup, Murmur32, Murmur64}; use crate::murmur::{Dictionary, HashGroup, Murmur32, Murmur64};
use crate::oodle::Oodle;
pub struct Context { pub struct Context {
pub lookup: Dictionary, pub lookup: Dictionary,
pub oodle: Option<Oodle>,
pub ljd: Option<String>, pub ljd: Option<String>,
pub revorb: Option<String>, pub revorb: Option<String>,
pub ww2ogg: Option<String>, pub ww2ogg: Option<String>,
@ -16,7 +14,6 @@ impl Context {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
lookup: Dictionary::new(), lookup: Dictionary::new(),
oodle: None,
ljd: None, ljd: None,
revorb: None, revorb: None,
ww2ogg: None, ww2ogg: None,

View file

@ -84,8 +84,8 @@ type PackageDefinition = HashMap<String, HashSet<String>>;
#[derive(Default)] #[derive(Default)]
pub struct Package { pub struct Package {
name: String, _name: String,
root: PathBuf, _root: PathBuf,
inner: PackageType, inner: PackageType,
} }
@ -159,8 +159,8 @@ impl Package {
let pkg = Self { let pkg = Self {
inner, inner,
name, _name: name,
root: root.to_path_buf(), _root: root.to_path_buf(),
}; };
Ok(pkg) Ok(pkg)
@ -206,8 +206,8 @@ impl Package {
let pkg = Self { let pkg = Self {
inner, inner,
name, _name: name,
root: PathBuf::new(), _root: PathBuf::new(),
}; };
Ok(pkg) Ok(pkg)

View file

@ -1,13 +1,9 @@
#![feature(c_size_t)]
mod binary; mod binary;
mod bundle; mod bundle;
mod context; mod context;
pub mod filetype; pub mod filetype;
pub mod murmur; pub mod murmur;
mod oodle;
pub use bundle::decompress; pub use bundle::decompress;
pub use bundle::{Bundle, BundleFile, BundleFileType}; pub use bundle::{Bundle, BundleFile, BundleFileType};
pub use context::Context; pub use context::Context;
pub use oodle::Oodle;