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",
"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",

View file

@ -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"] }

View file

@ -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??;

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

View file

@ -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() {

View file

@ -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;

View file

@ -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
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::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)

View file

@ -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"] }

View file

@ -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,

View file

@ -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,

View file

@ -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)

View file

@ -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;