diff --git a/Cargo.lock b/Cargo.lock index 53a5013..acbedb7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/crates/dtmt/Cargo.toml b/crates/dtmt/Cargo.toml index 1a0a1e6..73d2d72 100644 --- a/crates/dtmt/Cargo.toml +++ b/crates/dtmt/Cargo.toml @@ -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"] } diff --git a/crates/dtmt/src/cmd/build.rs b/crates/dtmt/src/cmd/build.rs index 6cc1469..618d9d2 100644 --- a/crates/dtmt/src/cmd/build.rs +++ b/crates/dtmt/src/cmd/build.rs @@ -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( - "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.", - ), - ) + .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::("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::("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??; diff --git a/crates/dtmt/src/cmd/bundle/inject.rs b/crates/dtmt/src/cmd/bundle/inject.rs index f8e001c..6d583b7 100644 --- a/crates/dtmt/src/cmd/bundle/inject.rs +++ b/crates/dtmt/src/cmd/bundle/inject.rs @@ -98,7 +98,7 @@ pub(crate) async fn run(ctx: sdk::Context, matches: &ArgMatches) -> Result<()> { let out_path = matches.get_one::("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) diff --git a/crates/dtmt/src/cmd/bundle/mod.rs b/crates/dtmt/src/cmd/bundle/mod.rs index 17f7a8c..03ab3f5 100644 --- a/crates/dtmt/src/cmd/bundle/mod.rs +++ b/crates/dtmt/src/cmd/bundle/mod.rs @@ -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( - "The oodle library to load. This may either be:\n\ + .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::("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::("oodle")); } match matches.subcommand() { diff --git a/crates/dtmt/src/main.rs b/crates/dtmt/src/main.rs index 22399dc..830550d 100644 --- a/crates/dtmt/src/main.rs +++ b/crates/dtmt/src/main.rs @@ -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; diff --git a/crates/dtmt/src/mods/archive.rs b/crates/dtmt/src/mods/archive.rs index 5e0c268..16d74d8 100644 --- a/crates/dtmt/src/mods/archive.rs +++ b/crates/dtmt/src/mods/archive.rs @@ -32,7 +32,7 @@ impl Archive { self.mod_file = Some(content); } - pub fn write

(&self, ctx: &sdk::Context, path: P) -> Result<()> + pub fn write

(&self, path: P) -> Result<()> where P: AsRef, { @@ -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)?; } diff --git a/lib/oodle-sys/.gitignore b/lib/oodle-sys/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/lib/oodle-sys/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/lib/oodle-sys/Cargo.toml b/lib/oodle-sys/Cargo.toml new file mode 100644 index 0000000..539427d --- /dev/null +++ b/lib/oodle-sys/Cargo.toml @@ -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" diff --git a/lib/oodle-sys/src/lib.rs b/lib/oodle-sys/src/lib.rs new file mode 100644 index 0000000..8346c5f --- /dev/null +++ b/lib/oodle-sys/src/lib.rs @@ -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 = std::result::Result; + +static LIB: OnceLock = 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>(name: Option

) { + 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( + data: I, + fuzz_safe: OodleLZ_FuzzSafe, + check_crc: OodleLZ_CheckCRC, +) -> Result> +where + I: AsRef<[u8]>, +{ + let lib = get()?; + lib.decompress(data, fuzz_safe, check_crc) +} + +pub fn compress(data: I) -> Result> +where + I: AsRef<[u8]>, +{ + let lib = get()?; + lib.compress(data) +} + +pub fn get_decode_buffer_size(raw_size: usize, corruption_possible: bool) -> Result { + let lib = get()?; + lib.get_decode_buffer_size(raw_size, corruption_possible) +} diff --git a/lib/sdk/src/oodle/mod.rs b/lib/oodle-sys/src/library.rs similarity index 62% rename from lib/sdk/src/oodle/mod.rs rename to lib/oodle-sys/src/library.rs index 0dbb114..ef773e4 100644 --- a/lib/sdk/src/oodle/mod.rs +++ b/lib/oodle-sys/src/library.rs @@ -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

(lib: P) -> Result - where - P: AsRef, - { - let lib = unsafe { Library::new(lib)? }; - - unsafe { - let fun: Symbol = - lib.get(b"OodleCore_Plugins_SetPrintf\0")?; - let printf: Symbol = - lib.get(b"OodleCore_Plugin_Printf_Verbose\0")?; - - fun(*printf.deref()); - } - - Ok(Self { lib }) +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::with_name(OODLE_LIB_NAME) } - #[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>(name: P) -> Result { + let inner = libloading::Library::new(name)?; + Ok(Self { inner }) + } + + #[tracing::instrument(skip(self, data))] pub fn decompress( &self, data: I, @@ -61,7 +70,7 @@ impl Oodle { }; let ret = unsafe { - let decompress: Symbol = self.lib.get(b"OodleLZ_Decompress\0")?; + let decompress: Symbol = 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 = self.lib.get(b"OodleLZ_Compress\0")?; + let compress: Symbol = 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 { unsafe { let f: Symbol = - self.lib.get(b"OodleLZ_GetDecodeBufferSize\0")?; + self.inner.get(b"OodleLZ_GetDecodeBufferSize\0")?; let size = f(COMPRESSOR, raw_size, corruption_possible); Ok(size) diff --git a/lib/sdk/src/oodle/types.rs b/lib/oodle-sys/src/types.rs similarity index 100% rename from lib/sdk/src/oodle/types.rs rename to lib/oodle-sys/src/types.rs diff --git a/lib/sdk/Cargo.toml b/lib/sdk/Cargo.toml index d1bed0e..02f3964 100644 --- a/lib/sdk/Cargo.toml +++ b/lib/sdk/Cargo.toml @@ -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"] } diff --git a/lib/sdk/src/bundle/mod.rs b/lib/sdk/src/bundle/mod.rs index 5d48281..000df1c 100644 --- a/lib/sdk/src/bundle/mod.rs +++ b/lib/sdk/src/bundle/mod.rs @@ -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,14 +197,12 @@ 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( - &compressed_buffer, - OodleLZ_FuzzSafe::No, - OodleLZ_CheckCRC::No, - ) - .wrap_err_with(|| format!("failed to decompress chunk {chunk_index}"))?; + let mut raw_buffer = oodle_sys::decompress( + &compressed_buffer, + OodleLZ_FuzzSafe::No, + OodleLZ_CheckCRC::No, + ) + .wrap_err_with(|| format!("failed to decompress chunk {chunk_index}"))?; if unpacked_size_tracked < CHUNK_SIZE { raw_buffer.resize(unpacked_size_tracked, 0); @@ -246,7 +243,7 @@ impl Bundle { } #[tracing::instrument(skip_all)] - pub fn to_binary(&self, ctx: &crate::Context) -> Result> { + pub fn to_binary(&self) -> Result> { 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(ctx: &crate::Context, binary: B) -> Result> +pub fn decompress(_ctx: &crate::Context, binary: B) -> Result> 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, diff --git a/lib/sdk/src/context.rs b/lib/sdk/src/context.rs index 81a99f7..0116c4a 100644 --- a/lib/sdk/src/context.rs +++ b/lib/sdk/src/context.rs @@ -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, pub ljd: Option, pub revorb: Option, pub ww2ogg: Option, @@ -16,7 +14,6 @@ impl Context { pub fn new() -> Self { Self { lookup: Dictionary::new(), - oodle: None, ljd: None, revorb: None, ww2ogg: None, diff --git a/lib/sdk/src/filetype/package.rs b/lib/sdk/src/filetype/package.rs index 00b7185..36e3575 100644 --- a/lib/sdk/src/filetype/package.rs +++ b/lib/sdk/src/filetype/package.rs @@ -84,8 +84,8 @@ type PackageDefinition = HashMap>; #[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) diff --git a/lib/sdk/src/lib.rs b/lib/sdk/src/lib.rs index 2ecbfd7..6890317 100644 --- a/lib/sdk/src/lib.rs +++ b/lib/sdk/src/lib.rs @@ -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;