From e53b2faa0f07df17929933a6334c51f93e589824 Mon Sep 17 00:00:00 2001 From: Lucas Schwiderski Date: Wed, 9 Nov 2022 09:24:49 +0100 Subject: [PATCH] feat: Re-implement in Rust --- .gitignore | 9 +- Cargo.lock | 546 ++++++++++++++++++++++++++++++++++++++ Cargo.toml | 23 ++ oodle-cli.cpp | 534 ------------------------------------- oodle-cli.sln | 31 --- oodle-cli.vcxproj | 140 ---------- oodle-cli.vcxproj.filters | 27 -- oodle-cli.vcxproj.user | 7 - oodle2.h | 287 -------------------- rust-toolchain.toml | 2 + rustfmt.toml | 20 ++ src/binary.rs | 127 +++++++++ src/main.rs | 317 ++++++++++++++++++++++ src/oodle.rs | 104 ++++++++ src/types.rs | 190 +++++++++++++ 15 files changed, 1333 insertions(+), 1031 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml delete mode 100644 oodle-cli.cpp delete mode 100644 oodle-cli.sln delete mode 100644 oodle-cli.vcxproj delete mode 100644 oodle-cli.vcxproj.filters delete mode 100644 oodle-cli.vcxproj.user delete mode 100644 oodle2.h create mode 100644 rust-toolchain.toml create mode 100644 rustfmt.toml create mode 100644 src/binary.rs create mode 100644 src/main.rs create mode 100644 src/oodle.rs create mode 100644 src/types.rs diff --git a/.gitignore b/.gitignore index 0f0b22e..3d6ccfa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ -/.vs -/x64 -/test -/oo2core_8_win64.dll -/oo2net_9_win64.dll +/target +/data +oo2core_8_win64.dll +oo2net_9_win64.dll diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..c14c503 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,546 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "backtrace" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "426eed9136e68a14d9de937db20cfd79fcc25c09709872e8005897c618a8365e" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "once_cell", + "strsim", + "termcolor", + "unicase", + "unicode-width", +] + +[[package]] +name = "clap_derive" +version = "4.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685fbc59da060ed2cd3d79c86970ee95386b5e5fc69d9cad881912dca0c18807" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "color-eyre" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a667583cca8c4f8436db8de46ea8233c42a7d9ae424a82d338f2e4675229204" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "eyre" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7fcc620a3bff7cdd7a365be3376c97191aeaccc2a603e600951e452615bf89" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "miniz_oxide" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96590ba8f175222643a85693f33d26e9c8a015f599c216509b1a6894af675d34" +dependencies = [ + "adler", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" + +[[package]] +name = "oodle-cli" +version = "0.1.0" +dependencies = [ + "byteorder", + "clap", + "color-eyre", + "libloading", + "tracing", + "tracing-error", + "tracing-subscriber", +] + +[[package]] +name = "os_str_bytes" +version = "6.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3baf96e39c5359d2eb0dd6ccb42c62b91d9678aa68160d261b9e0ccbf9e9dea9" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ea3d908b0e36316caf9e9e2c4625cdde190a7e6f440d794667ed17a1855e725" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-error" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e" +dependencies = [ + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-ident" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" + +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e2a7d86 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "oodle-cli" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +byteorder = "1.4.3" +clap = { version = "4.0.20", features = ["cargo", "color", "unicode", "std", "derive"] } +color-eyre = "0.6.2" +libloading = "0.7.4" +tracing = "0.1.37" +tracing-error = "0.2.0" +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } + +[profile.release] +strip = "debuginfo" +lto = true + +#debug-assertions = true +#overflow-checks = true +#opt-level = 0 diff --git a/oodle-cli.cpp b/oodle-cli.cpp deleted file mode 100644 index 51196e0..0000000 --- a/oodle-cli.cpp +++ /dev/null @@ -1,534 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "oodle2.h" - -const LPCWSTR LIB_NAME = L"oo2core_8_win64"; -// Printing a `LPCWSTR` is a pain, so it's much easier to just have this second variable. -// It's not like this is going to change often. -const char* LIB_FILE = "oo2core_8_win64.dll"; - -typedef decltype(OodleLZ_Compress)* compress_func; -typedef decltype(OodleLZ_Decompress)* decompress_func; -typedef decltype(OodleCore_Plugins_SetPrintf)* setprintf_func; - -typedef std::uint8_t u8; -typedef std::uint32_t u32; -typedef std::uint64_t u64; - -const u32 CHUNK_RAW_SIZE = 512 * 1024; - - -void usage() { - std::cerr << - "Usage: oodle-cli [OPTIONS] [--] \n" - "Perform Oodle 2 compression or decompression for Darktide data.\n" - "Deflated data is a stream of chunks with 512k uncompressed size.\n" - "Inflated data is an arbitrary byte stream.\n" - "Data is read from stdin and written to stdout\n" - "\n" - " may be either 'compress' or 'decompress'.\n" - "\n" - "Options:\n" - " -v Increase Oodle's verbosity.\n" - " May be specified up to three times.\n" - " -c Only check if the library can be found and used.\n" - " --fuzz-safe Use fuzz safe decompression.\n" - " --check-crc Check CRC during decompression.\n" - " --padding The amount of padding to assume at the start of the data.\n" - " Set this to `(x % 16)`, where `x` is the position at which\n" - " the data passed to oodle - cli starts in the source file.\n" - " --help Show this help message.\n" - "\n" - "The following environmental variables are recognized:\n" - " DLL_SEARCH_PATH: A color (':') separated list of directories to search\n" - " for the Oodle library. May be relative to the current\n" - " working directory.\n"; -} - - -void printf_callback(int verboseLevel, const char* file, int line, const char* fmt, ...) { - std::cout << "[OODLE] "; - - // For some reason, this doesn't actually map to the config value. - switch (verboseLevel) { - case 0: - std::cout << "EXTRAORDINARILY IMPORTANT: "; - break; - case 1: - std::cout << "EXTERMELY IMPORTANT: "; - break; - case 2: - std::cout << "VERY IMPORTANT: "; - break; - } - - va_list args; - va_start(args, fmt); - vfprintf(stderr, fmt, args); -} - - -u32 read_u32(FILE* stream) { - u32 buf; - if (fread_s(&buf, 4, sizeof(u8), 4, stream) < 4) { - throw "ERROR: Failed to read u32 from stream!"; - } - - return _byteswap_ulong(buf); -} - - -size_t write_u32(FILE* stream, u32 val) { - val = _byteswap_ulong(val); - - if (fwrite(&val, 4, sizeof(u8), stream) < 4) { - throw "ERROR: Failed to read u32 from stream!"; - } - - return 4; -} - -size_t write_padding(FILE* stream, size_t offset) { - size_t pos = ftell(stream) + offset; - if (errno != 0) { - return 0; - } - - size_t padding_size = 16 - ((pos) % 16); - if (padding_size < 16) { - u64 padding = 0x0; - - _set_errno(0); - fwrite(&padding, sizeof(u8), padding_size, stream); - - return padding_size; - } - - return 0; -} - - -int do_compress(HMODULE hDLL, FILE* in_file, FILE* out_file, size_t padding_start) { - auto fn = reinterpret_cast(GetProcAddress(hDLL, "OodleLZ_Compress")); - if (fn == NULL) { - std::cerr << "ERROR: The library is incompatible, no 'OodleLZ_Compress'!\n"; - return 1; - } - - // I'm not sure how Fatshark does this efficiently. - // Within each chunk, there is a padding whos size depends on the position where the padding is written. - // And that position is affected by both the size of previous chunks and the total number of chunks, since - // list of chunk sizes is written before all chunks. - // Because of that, all chunks have to be compressed and buffered before any of them can be written to the output. - std::vector chunk_sizes; - std::vector final_buffer(CHUNK_RAW_SIZE); - - u8* write_address = final_buffer.data(); - - // This tracks the total amount of data written to `final_buffer`. - // It also doubles as the current position in the output stream, required - // to calculate padding sizes. - size_t final_size = 0; - - // Re-use the same buffers for each chunk. - // Allocating the full CHUNK_RAW_SIZE for compressed buffer is pretty - // much guranteed to be too much, but only slightly, and more performant than - // re-allocating anyways. - u8* raw_buffer = new u8[CHUNK_RAW_SIZE]; - - size_t chunk_index = 0; - boolean is_eof = false; - - do { - chunk_index++; - - _set_errno(0); - size_t read = fread_s(raw_buffer, CHUNK_RAW_SIZE, sizeof(u8), CHUNK_RAW_SIZE, in_file); - if (errno != 0) { - char msg[80]; - _strerror_s(msg, 80, NULL); - std::cerr << "ERROR: Failed to read chunk " << chunk_index << " from input file : " << msg << std::endl; - return 1; - } - - if (read < CHUNK_RAW_SIZE) { - is_eof = true; - } - - size_t remaining = CHUNK_RAW_SIZE - read; - - // Bitsquid always writes full chunks. If the last chunk can't be filled completely - // with data the remainder is filled with `0x0`. - memset(raw_buffer + read, 0x0, remaining); - - // Bitsquid also adds an end marker consisting of four `0x66`. - if (remaining >= 4) { - memset(raw_buffer + read, 0x66, 4); - } - else if (CHUNK_RAW_SIZE - read >= 0) { - std::cerr << "ERROR: Not enough space left in chunk to add end marker. Don't know how to proceed.\n"; - return 1; - } - - intptr_t res = fn( - OodleLZ_Compressor_Kraken, - raw_buffer, - CHUNK_RAW_SIZE, - write_address, - OodleLZ_CompressionLevel_Optimal2, - nullptr, - 0, - nullptr, - nullptr, - 0 - ); - - if (res <= 0) { - std::cerr << "ERROR: Failed to compress chunk " << chunk_index << "!\n"; - return 1; - } - - chunk_sizes.push_back(res); - write_address += res; - } while (!is_eof); - - _set_errno(0); - write_u32(out_file, chunk_sizes.size()); - if (errno != 0) { - char msg[80]; - _strerror_s(msg, 80, NULL); - std::cerr << "ERROR: Failed to write number of chunks to output file: " << msg << std::endl; - return 1; - } - - // `padding_start` is the point in the final file, passed from CLI. - // The `4` is added for the u32 at the start of our output, which is the - // number of chunks. - size_t pos = ftell(out_file) + 4; - _set_errno(0); - write_padding(out_file, pos); - if (errno != 0) { - char msg[80]; - _strerror_s(msg, 80, NULL); - std::cerr << "ERROR: Failed to write chunk size padding to output file: " << msg << std::endl; - return 1; - } - - chunk_index = 0; - u8* read_address = final_buffer.data(); - - for (auto size : chunk_sizes) { - chunk_index++; - - _set_errno(0); - write_u32(out_file, size); - if (errno != 0) { - char msg[80]; - _strerror_s(msg, 80, NULL); - std::cerr << "ERROR: Failed to write size for chunk " << chunk_index << " to output file : " << msg << std::endl; - return 1; - } - - size_t pos = ftell(out_file) + padding_start; - _set_errno(0); - write_padding(out_file, pos); - if (errno != 0) { - char msg[80]; - _strerror_s(msg, 80, NULL); - std::cerr << "ERROR: Failed to write chunk size padding to output file: " << msg << std::endl; - return 1; - } - - _set_errno(0); - fwrite(&read_address, sizeof(u8), size, out_file); - if (errno != 0) { - char msg[80]; - _strerror_s(msg, 80, NULL); - std::cerr << "ERROR: Failed to write data for chunk " << chunk_index << " to output file : " << msg << std::endl; - return 1; - } - - read_address += size; - } - - - delete[] raw_buffer; - - return 0; -} - - -int do_decompress( - HMODULE hDLL, - FILE* in_file, - FILE* out_file, - OodleLZ_FuzzSafe fuzz_safe, - OodleLZ_CheckCRC check_crc, - OodleLZ_Verbosity verbosity, - size_t padding_start, - size_t num_chunks -) { - auto fn = reinterpret_cast(GetProcAddress(hDLL, "OodleLZ_Decompress")); - if (fn == NULL) { - std::cerr << "ERROR: The library is incompatible, no 'OodleLZ_Decompress'!\n"; - return 1; - } - - // Re-use the same buffers for each chunk. - // Allocating the full CHUNK_RAW_SIZE for the compressed buffer is pretty - // much guranteed to be too much, but should be more performant than the - // exact size allocating each time. And who cares about <512k RAM wasted? - u8* raw_buffer = new u8[CHUNK_RAW_SIZE]; - u8* compressed_buffer = new u8[CHUNK_RAW_SIZE]; - if (verbosity >= OodleLZ_Verbosity_Some) { - std::cerr << "DEBUG: " << num_chunks << " chunks\n"; - } - - for (size_t chunk_index = 0; chunk_index < num_chunks; chunk_index++) { - u32 chunk_size = read_u32(in_file); - - _set_errno(0); - size_t pos = ftell(in_file) + padding_start; - if (errno != 0) { - std::cerr << "ERROR: Failed to get position in input file: " << strerror(errno) << std::endl; - return 1; - } - - size_t padding_size = 16 - (pos % 16); - if (padding_size < 16) { - _set_errno(0); - if (fseek(in_file, (long)padding_size, SEEK_CUR) != 0) { - std::cerr << "ERROR: Failed to seek input file for chunk " << chunk_index << ": " << strerror(errno) << std::endl; - return 1; - } - } - - if (verbosity >= OodleLZ_Verbosity_Some) { - std::cerr << "DEBUG: Chunk " << chunk_index + 1 << ": " << chunk_size << "\n"; - } - - _set_errno(0); - if (fread_s(compressed_buffer, chunk_size, sizeof(u8), chunk_size, in_file) < chunk_size) { - std::cerr << "ERROR: Failed to read compressed data for chunk " << chunk_index << " from input file: " << strerror(errno) << "\n"; - return 1; - } - - intptr_t res = fn( - compressed_buffer, - chunk_size, - raw_buffer, - CHUNK_RAW_SIZE, - fuzz_safe, - check_crc, - verbosity, - nullptr, - 0, - nullptr, - nullptr, - nullptr, - 0, - OodleLZ_Decode_Unthreaded - ); - - if (res != CHUNK_RAW_SIZE) { - std::cerr << "ERROR: Failed to decompress chunk " << chunk_index << "!\n"; - return 1; - } - - _set_errno(0); - if (fwrite(raw_buffer, sizeof(u8), CHUNK_RAW_SIZE, out_file) < CHUNK_RAW_SIZE) { - std::cerr << "ERROR: Failed to write decompressed chunk " << chunk_index << " to output file" << strerror(errno) << "\n"; - return 1; - } - } - - delete[] raw_buffer; - delete[] compressed_buffer; - - return 0; -} - - -int main(int argc, char* argv[]) -{ - int verbosity = 0; - bool check_lib = FALSE; - OodleLZ_FuzzSafe fuzz_safe = OodleLZ_FuzzSafe_No; - OodleLZ_CheckCRC check_crc = OodleLZ_CheckCRC_No; - size_t padding_start = 0; - size_t num_chunks = 0; - int i = 1; - - for (; i < argc; i++) { - char* arg = argv[i]; - - if (strcmp(arg, "--") == 0 || arg[0] != '-') { - break; - } - - if (strcmp(arg, "--help") == 0) { - usage(); - return 0; - } - else if (strcmp(arg, "-c") == 0) { - check_lib = TRUE; - } - else if (strcmp(arg, "-v") == 0) { - verbosity++; - } - else if (strcmp(arg, "--fuzz-safe") == 0) { - fuzz_safe = OodleLZ_FuzzSafe_Yes; - } - else if (strcmp(arg, "--check-crc") == 0) { - check_crc = OodleLZ_CheckCRC_Yes; - } - else if (strcmp(arg, "--padding") == 0) { - i++; - padding_start = std::stoi(argv[i]); - } - else if (strcmp(arg, "--chunks") == 0) { - i++; - num_chunks = std::stoi(argv[i]); - } - else { - std::cerr << "ERROR: Unknown option '" << arg << "'!\n\n"; - usage(); - return 1; - } - } - - char* var; - size_t len; - DWORD load_flags = 0; - if (_dupenv_s(&var, &len, "DLL_SEARCH_PATH") == 0) { - if (var) { - std::string search_path(var, len); - std::istringstream ss(search_path); - std::string token; - - while (std::getline(ss, token, ':')) { - auto path = std::filesystem::path(token); - if (!path.is_absolute()) { - path = std::filesystem::absolute(path); - } - - if (!path.is_absolute() || !AddDllDirectory(path.c_str())) { - std::cerr << "WARN: Failed to add DLL search path: '" << token << "'!\n"; - } - else { - std::cout << "INFO: Added DLL search path: '" << path << "'.\n"; - } - } - - load_flags = LOAD_LIBRARY_SEARCH_DEFAULT_DIRS; - free(var); - } - } - else { - std::cerr << "ERROR: Failed to read environment variable 'DLL_SEARCH_PATH'. Skipping.\n"; - } - - HINSTANCE hDLL = LoadLibraryEx(LIB_NAME, NULL, load_flags); - if (hDLL == NULL) { - std::cerr << "ERROR: Couldn't find library file '" << LIB_FILE << "'!\n"; - return 1; - } - - if (check_lib) { - std::cout << "INFO: '" << LIB_FILE << "' found and loaded.\n"; - return 0; - } - - if (num_chunks == 0) { - std::cerr << "ERROR: Number of chunks not specified!\n"; - usage(); - return 1; - } - - if (argc - i < 1) { - std::cerr << "ERROR: Arguments missing!\n\n"; - usage(); - return 1; - } - - if (verbosity > 0) { - auto fn = reinterpret_cast(GetProcAddress((HMODULE)hDLL, "OodleCore_Plugins_SetPrintf")); - if (fn == NULL) { - std::cerr << "ERROR: The library is incompatible, no 'OodleCore_Plugins_SetPrintf'!\n"; - return 1; - } - reinterpret_cast(fn(printf_callback)); - } - - std::string operation = argv[i]; - FILE* in_file = nullptr; - FILE* out_file = nullptr; - - if (argc - i >= 2) { - errno_t err = fopen_s(&in_file, argv[i + 1], "rbS"); - - if (err != 0) { - std::cerr << "ERROR: Failed to open input file: " << strerror(errno) << "\n"; - return 1; - } - } - else { - if (_setmode(_fileno(stdin), _O_BINARY) < 0) { - std::cerr << "ERROR: Failed to prepare stdin for binary reading!\n"; - return 1; - } - in_file = stdin; - } - - if (argc - i >= 3) { - errno_t err = fopen_s(&out_file, argv[i + 2], "wbS"); - - if (err != 0) { - std::cerr << "ERROR: Failed to open output file: " << strerror(errno) << "\n"; - return 1; - } - } - else { - if (_setmode(_fileno(stdout), _O_BINARY) < 0) { - std::cerr << "ERROR: Failed to prepare stdout for binary writing!\n"; - return 1; - } - out_file = stdout; - } - - int res; - - if (operation == "compress") { - res = do_compress(hDLL, in_file, out_file, padding_start); - } - else { - res = do_decompress(hDLL, in_file, out_file, fuzz_safe, check_crc, (OodleLZ_Verbosity)verbosity, padding_start, num_chunks); - } - - if (in_file != stdin) { - fclose(in_file); - } - - if (out_file != stdout) { - fclose(out_file); - } - - return res; -} diff --git a/oodle-cli.sln b/oodle-cli.sln deleted file mode 100644 index 408a05b..0000000 --- a/oodle-cli.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.3.32929.385 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "oodle-cli", "oodle-cli.vcxproj", "{59D962DA-674C-48D9-ADB5-DE09A89E449A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {59D962DA-674C-48D9-ADB5-DE09A89E449A}.Debug|x64.ActiveCfg = Debug|x64 - {59D962DA-674C-48D9-ADB5-DE09A89E449A}.Debug|x64.Build.0 = Debug|x64 - {59D962DA-674C-48D9-ADB5-DE09A89E449A}.Debug|x86.ActiveCfg = Debug|Win32 - {59D962DA-674C-48D9-ADB5-DE09A89E449A}.Debug|x86.Build.0 = Debug|Win32 - {59D962DA-674C-48D9-ADB5-DE09A89E449A}.Release|x64.ActiveCfg = Release|x64 - {59D962DA-674C-48D9-ADB5-DE09A89E449A}.Release|x64.Build.0 = Release|x64 - {59D962DA-674C-48D9-ADB5-DE09A89E449A}.Release|x86.ActiveCfg = Release|Win32 - {59D962DA-674C-48D9-ADB5-DE09A89E449A}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {21081A0D-BD2A-41A1-AE1D-90EBE2E6E87F} - EndGlobalSection -EndGlobal diff --git a/oodle-cli.vcxproj b/oodle-cli.vcxproj deleted file mode 100644 index 5ad73f3..0000000 --- a/oodle-cli.vcxproj +++ /dev/null @@ -1,140 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {59d962da-674c-48d9-adb5-de09a89e449a} - oodlecli - 10.0 - - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - - Level3 - true - _CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - - - - - Level3 - true - true - true - _CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - true - true - - - - - Level3 - true - _CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 - - - Console - true - - - - - Level3 - true - true - true - _CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp17 - - - Console - true - true - true - - - - - - - - - - - - \ No newline at end of file diff --git a/oodle-cli.vcxproj.filters b/oodle-cli.vcxproj.filters deleted file mode 100644 index 385a323..0000000 --- a/oodle-cli.vcxproj.filters +++ /dev/null @@ -1,27 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - Source Files - - - - - Header Files - - - \ No newline at end of file diff --git a/oodle-cli.vcxproj.user b/oodle-cli.vcxproj.user deleted file mode 100644 index 7b8be40..0000000 --- a/oodle-cli.vcxproj.user +++ /dev/null @@ -1,7 +0,0 @@ - - - - compress .\test\compress_test.in.bin .\test\compress_test.out.bin - WindowsLocalDebugger - - \ No newline at end of file diff --git a/oodle2.h b/oodle2.h deleted file mode 100644 index 3133263..0000000 --- a/oodle2.h +++ /dev/null @@ -1,287 +0,0 @@ -#pragma once - -#include -#include - -// header version : -// the DLL is incompatible when MAJOR is bumped -// MINOR is for internal revs and bug fixes that don't affect API compatibility -// TODO: Check if the DLL gives a minor version -#define OODLE2_VERSION_MAJOR 8 -#define OODLE2_VERSION_MINOR 14 - -// OodleVersion string is 1 . MAJOR . MINOR -#define OodleVersion "2.8.14" - - -// Default verbosity selection of 0 will not even log when it sees corruption -// Verbosity of LZ functions -typedef enum OodleLZ_Verbosity { - OodleLZ_Verbosity_None = 0, - OodleLZ_Verbosity_Minimal = 1, - OodleLZ_Verbosity_Some = 2, - OodleLZ_Verbosity_Lots = 3, - OodleLZ_Verbosity_Force32 = 0x40000000 -} OodleLZ_Verbosity; - - -typedef enum OodleLZ_Compressor { - OodleLZ_Compressor_Invalid = -1, - OodleLZ_Compressor_None = 3, // None = memcpy, pass through uncompressed bytes - // NEW COMPRESSORS : - OodleLZ_Compressor_Kraken = 8, // Fast decompression and high compression ratios, amazing! - OodleLZ_Compressor_Leviathan = 13,// Leviathan = Kraken's big brother with higher compression, slightly slower decompression. - OodleLZ_Compressor_Mermaid = 9, // Mermaid is between Kraken & Selkie - crazy fast, still decent compression. - OodleLZ_Compressor_Selkie = 11, // Selkie is a super-fast relative of Mermaid. For maximum decode speed. - OodleLZ_Compressor_Hydra = 12, // Hydra, the many-headed beast = Leviathan, Kraken, Mermaid, or Selkie (see $OodleLZ_About_Hydra) - OodleLZ_Compressor_BitKnit = 10, - OodleLZ_Compressor_LZB16 = 4, // DEPRECATED but still supported - OodleLZ_Compressor_LZNA = 7, - OodleLZ_Compressor_LZH = 0, - OodleLZ_Compressor_LZHLW = 1, - OodleLZ_Compressor_LZNIB = 2, - OodleLZ_Compressor_LZBLW = 5, - OodleLZ_Compressor_LZA = 6, - OodleLZ_Compressor_Count = 14, - OodleLZ_Compressor_Force32 = 0x40000000 -} OodleLZ_Compressor; - - -typedef enum OodleLZ_CheckCRC { - OodleLZ_CheckCRC_No = 0, - OodleLZ_CheckCRC_Yes = 1, - OodleLZ_CheckCRC_Force32 = 0x40000000 -} OodleLZ_CheckCRC; - -/* Selection of compression encoder complexity - - Higher numerical value of CompressionLevel = slower compression, but smaller compressed data. - - The compressed stream is always decodable with the same decompressors. - CompressionLevel controls the amount of work the encoder does to find the best compressed bit stream. - CompressionLevel does not primary affect decode speed, it trades off encode speed for compressed bit stream quality. - - I recommend starting with OodleLZ_CompressionLevel_Normal, then try up or down if you want - faster encoding or smaller output files. - - The Optimal levels are good for distribution when you compress rarely and decompress often; - they provide very high compression ratios but are slow to encode. Optimal2 is the recommended level - to start with of the optimal levels. - Optimal4 and 5 are not recommended for common use, they are very slow and provide the maximum compression ratio, - but the gain over Optimal3 is usually small. - - The HyperFast levels have negative numeric CompressionLevel values. - They are faster than SuperFast for when you're encoder CPU time constrained or want - something closer to symmetric compression vs. decompression time. - The HyperFast levels are currently only available in Kraken, Mermaid & Selkie. - Higher levels of HyperFast are faster to encode, eg. HyperFast4 is the fastest. - - The CompressionLevel does not affect decode speed much. Higher compression level does not mean - slower to decode. To trade off decode speed vs ratio, use _spaceSpeedTradeoffBytes_ in $OodleLZ_CompressOptions -*/ -typedef enum OodleLZ_CompressionLevel { - OodleLZ_CompressionLevel_None = 0, // don't compress, just copy raw bytes - OodleLZ_CompressionLevel_SuperFast = 1, // super fast mode, lower compression ratio - OodleLZ_CompressionLevel_VeryFast = 2, // fastest LZ mode with still decent compression ratio - OodleLZ_CompressionLevel_Fast = 3, // fast - good for daily use - OodleLZ_CompressionLevel_Normal = 4, // standard medium speed LZ mode - - OodleLZ_CompressionLevel_Optimal1 = 5, // optimal parse level 1 (faster optimal encoder) - OodleLZ_CompressionLevel_Optimal2 = 6, // optimal parse level 2 (recommended baseline optimal encoder) - OodleLZ_CompressionLevel_Optimal3 = 7, // optimal parse level 3 (slower optimal encoder) - OodleLZ_CompressionLevel_Optimal4 = 8, // optimal parse level 4 (very slow optimal encoder) - OodleLZ_CompressionLevel_Optimal5 = 9, // optimal parse level 5 (don't care about encode speed, maximum compression) - - OodleLZ_CompressionLevel_HyperFast1 = -1, // faster than SuperFast, less compression - OodleLZ_CompressionLevel_HyperFast2 = -2, // faster than HyperFast1, less compression - OodleLZ_CompressionLevel_HyperFast3 = -3, // faster than HyperFast2, less compression - OodleLZ_CompressionLevel_HyperFast4 = -4, // fastest, less compression - - // aliases : - OodleLZ_CompressionLevel_HyperFast = OodleLZ_CompressionLevel_HyperFast1, // alias hyperfast base level - OodleLZ_CompressionLevel_Optimal = OodleLZ_CompressionLevel_Optimal2, // alias optimal standard level - OodleLZ_CompressionLevel_Max = OodleLZ_CompressionLevel_Optimal5, // maximum compression level - OodleLZ_CompressionLevel_Min = OodleLZ_CompressionLevel_HyperFast4, // fastest compression level - - OodleLZ_CompressionLevel_Force32 = 0x40000000, - OodleLZ_CompressionLevel_Invalid = OodleLZ_CompressionLevel_Force32 -} OodleLZ_CompressionLevel; - - -typedef enum OodleLZ_Decode_ThreadPhase { - OodleLZ_Decode_ThreadPhase1 = 1, - OodleLZ_Decode_ThreadPhase2 = 2, - OodleLZ_Decode_ThreadPhaseAll = 3, - OodleLZ_Decode_Unthreaded = OodleLZ_Decode_ThreadPhaseAll -} OodleLZ_Decode_ThreadPhase; - - -typedef enum OodleLZ_FuzzSafe { - OodleLZ_FuzzSafe_No = 0, - OodleLZ_FuzzSafe_Yes = 1 -} OodleLZ_FuzzSafe; - - -/* Compress some data from memory to memory, synchronously, with OodleLZ - - $:compressor which OodleLZ variant to use in compression - $:rawBuf raw data to compress - $:rawLen number of bytes in rawBuf to compress - $:compBuf pointer to write compressed data to ; should be at least $OodleLZ_GetCompressedBufferSizeNeeded - $:level OodleLZ_CompressionLevel controls how much CPU effort is put into maximizing compression - $:pOptions (optional) options; if NULL, $OodleLZ_CompressOptions_GetDefault is used - $:dictionaryBase (optional) if not NULL, provides preceding data to prime the dictionary; must be contiguous with rawBuf, the data between the pointers _dictionaryBase_ and _rawBuf_ is used as the preconditioning data. The exact same precondition must be passed to encoder and decoder. - $:lrm (optional) long range matcher - $:scratchMem (optional) pointer to scratch memory - $:scratchSize (optional) size of scratch memory (see $OodleLZ_GetCompressScratchMemBound) - $:return size of compressed data written, or $OODLELZ_FAILED for failure - - Performs synchronous memory to memory LZ compression. - - In tools, you should generally use $OodleXLZ_Compress_AsyncAndWait instead to get parallelism. (in the Oodle2 Ext lib) - - You can compress a large buffer in several calls by setting _dictionaryBase_ to the start - of the buffer, and then making _rawBuf_ and _rawLen_ select portions of that buffer. As long - as _rawLen_ is a multiple of $OODLELZ_BLOCK_LEN , the compressed chunks can simply be - concatenated together. - - If _scratchMem_ is provided, it will be used for the compressor's scratch memory needs before OodleMalloc is - called. If the scratch is big enough, no malloc will be done. If the scratch is not big enough, the compress - will not fail, instead OodleMalloc will be used. OodleMalloc should not return null. There is currently no way - to make compress fail cleanly due to using too much memory, it must either succeed or abort the process. - - If _scratchSize_ is at least $OodleLZ_GetCompressScratchMemBound , additional allocations will not be needed. - - See $OodleLZ_About for tips on setting the compression options. - - If _dictionaryBase_ is provided, the backup distance from _rawBuf_ must be a multiple of $OODLELZ_BLOCK_LEN - - If $(OodleLZ_CompressOptions:seekChunkReset) is enabled, and _dictionaryBase_ is not NULL or _rawBuf_ , then the - seek chunk boundaries are relative to _dictionaryBase_, not to _rawBuf_. - -*/ -extern "C" intptr_t __stdcall OodleLZ_Compress( - OodleLZ_Compressor compressor, - const void* raw_buffer, - size_t raw_len, - void* compressed_buffer, - OodleLZ_CompressionLevel level, - void* options, // Default: null - size_t dictionary_base, // Default: null - const void* lrm, // Default: null - void* scratch_memory, // Default: null - size_t scratch_size // Default: null -); - -// Decompress returns raw (decompressed) len received -// Decompress returns 0 (OODLELZ_FAILED) if it detects corruption -/* Decompress a some data from memory to memory, synchronously. - - $:compBuf pointer to compressed data - $:compBufSize number of compressed bytes available (must be greater or equal to the number consumed) - $:rawBuf pointer to output uncompressed data into - $:rawLen number of uncompressed bytes to output - $:fuzzSafe (optional) should the decode fail if it contains non-fuzz safe codecs? - $:checkCRC (optional) if data could be corrupted and you want to know about it, pass OodleLZ_CheckCRC_Yes - $:verbosity (optional) if not OodleLZ_Verbosity_None, logs some info - $:decBufBase (optional) if not NULL, provides preceding data to prime the dictionary; must be contiguous with rawBuf, the data between the pointers _dictionaryBase_ and _rawBuf_ is used as the preconditioning data. The exact same precondition must be passed to encoder and decoder. The decBufBase must be a reset point. - $:decBufSize (optional) size of decode buffer starting at decBufBase, if 0, _rawLen_ is assumed - $:fpCallback (optional) OodleDecompressCallback to call incrementally as decode proceeds - $:callbackUserData (optional) passed as userData to fpCallback - $:decoderMemory (optional) pre-allocated memory for the Decoder, of size _decoderMemorySize_ - $:decoderMemorySize (optional) size of the buffer at _decoderMemory_; must be at least $OodleLZDecoder_MemorySizeNeeded bytes to be used - $:threadPhase (optional) for threaded decode; see $OodleLZ_About_ThreadPhasedDecode (default OodleLZ_Decode_Unthreaded) - $:return the number of decompressed bytes output, $OODLELZ_FAILED (0) if none can be decompressed - - Decodes data encoded with any $OodleLZ_Compressor. - - Note : _rawLen_ must be the actual number of bytes to output, the same as the number that were encoded with the corresponding - OodleLZ_Compress size. You must store this somewhere in your own header and pass it in to this call. _compBufSize_ does NOT - need to be the exact number of compressed bytes, is the number of bytes available in the buffer, it must be greater or equal to - the actual compressed length. - - Note that the new compressors (Kraken,Mermaid,Selkie,BitKnit) are all fuzz safe and you can use OodleLZ_FuzzSafe_Yes - with them and no padding of the decode target buffer. - - If checkCRC is OodleLZ_CheckCRC_Yes, then corrupt data will be detected and the decode aborted. - - If checkCRC is OodleLZ_CheckCRC_No, then corruption might result in invalid data, but no detection of any error (garbage in, garbage out). - - If corruption is possible, _fuzzSafe_ is No and _checkCRC_ is OodleLZ_CheckCRC_No, $OodleLZ_GetDecodeBufferSize must be used to allocate - _rawBuf_ large enough to prevent overrun. - - $OodleLZ_GetDecodeBufferSize should always be used to ensure _rawBuf_ is large enough, even when corruption is not - possible (when fuzzSafe is No). - - _compBuf_ and _rawBuf_ are allowed to overlap for "in place" decoding, but then _rawBuf_ must be allocated to - the size given by $OodleLZ_GetInPlaceDecodeBufferSize , and the compressed data must be at the end of that buffer. - - An easy way to take the next step to parallel decoding is with $OodleXLZ_Decompress_MakeSeekTable_Wide_Async (in the Oodle2 Ext lib) - - NOTE : the return value is the *total* number of decompressed bytes output so far. If rawBuf is > decBufBase, that means - the initial inset of (rawBuf - decBufBase) is included! (eg. you won't just get _rawLen_) - - If _decBufBase_ is provided, the backup distance from _rawBuf_ must be a multiple of $OODLELZ_BLOCK_LEN - - About fuzz safety: - - OodleLZ_Decompress is guaranteed not to crash even if the data is corrupted when _fuzzSafe_ is set to OodleLZ_FuzzSafe_Yes. - - When _fuzzSafe_ is Yes, the target buffer (_rawBuf_ and _rawLen_) will never be overrun. Note that corrupted data might not - be detected (the return value might indicate success). - - Fuzz Safe decodes will not crash on corrupt data. They may or may not return failure, and produce garbage output. - - Fuzz safe decodes will not read out of bounds. They won't put data on the stack or previously in memory - into the output buffer. - - Fuzz safe decodes will not output more than the uncompressed size. (eg. the output buffer does not need to - be padded like OodleLZ_GetDecodeBufferSize) - - If you ask for a fuzz safe decode and the compressor doesn't satisfy OodleLZ_Compressor_CanDecodeFuzzSafe - then it will return failure. - - The _fuzzSafe_ argument should always be OodleLZ_FuzzSafe_Yes as of Oodle 2.9.0 ; older compressors did not - support fuzz safety but they now all do. - - Use of OodleLZ_FuzzSafe_No is deprecated. - -*/ -extern "C" intptr_t __stdcall OodleLZ_Decompress( - const void* compressed_buffer, - size_t compressed_length, - void* raw_buffer, - size_t raw_length, - OodleLZ_FuzzSafe fuzz_safe, // Default: OodleLZ_FuzzSafe_Yes - OodleLZ_CheckCRC check_crc, // Default: OodleLZ_CheckCRC_No - OodleLZ_Verbosity verbosity, // Default: OodleLZ_Verbosity_None - void* decBufBase, // Default: null - size_t decBufSize, // Default: 0 - void* callback, // Default: null - void* callback_user_data, // Default: null - void* decoder_memory, // Default: null - size_t decoder_memory_size, // Default: 0 - OodleLZ_Decode_ThreadPhase thread_phase // Default: OodleLZ_Decode_Unthreaded -); - -/* Function pointer to Oodle Core printf - $:verboseLevel verbosity of the message; 0-2 ; lower = more important - $:file C file that sent the message - $:line C line that sent the message - $:fmt vararg printf format string - The logging function installed here must parse varargs like printf. - _verboseLevel_ may be used to omit verbose messages. -*/ -extern "C" typedef void(__stdcall t_fp_OodleCore_Plugin_Printf)(int verboseLevel, const char* file, int line, const char* fmt, ...); - -/* Install the callback used by Oodle Core for logging - $:fp_rrRawPrintf function pointer to your log function; may be NULL to disable all logging - $:return returns the previous function pointer - Use this function to install your own printf for Oodle Core. - The default implementation in debug builds, if you install nothing, uses the C stdio printf for logging. - On Microsoft platforms, it uses OutputDebugString and not stdio. - To disable all logging, call OodleCore_Plugins_SetPrintf(NULL) - WARNING : this function is NOT thread safe! It should be done only once and done in a place where the caller can guarantee thread safety. - In the debug build of Oodle, you can install OodleCore_Plugin_Printf_Verbose to get more verbose logging -*/ -t_fp_OodleCore_Plugin_Printf* __stdcall OodleCore_Plugins_SetPrintf(t_fp_OodleCore_Plugin_Printf* fp_rrRawPrintf); \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..5d56faf --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "nightly" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 0000000..a031646 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,20 @@ +# Copyright (C) 2022 Lucas Schwiderski +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +unstable_features = true +hard_tabs = false +max_width = 100 +edition = "2021" +use_field_init_shorthand = true diff --git a/src/binary.rs b/src/binary.rs new file mode 100644 index 0000000..1ed0659 --- /dev/null +++ b/src/binary.rs @@ -0,0 +1,127 @@ +#![allow(dead_code)] +use std::io::{Read, Seek, SeekFrom, Write}; + +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; +use color_eyre::eyre::WrapErr; +use color_eyre::{Help, Result, SectionExt}; + +fn read_u8_impl(r: &mut R) -> Result +where + R: Read, +{ + r.read_u8().wrap_err("failed to read u8") +} + +fn read_u32_le_impl(r: &mut R) -> Result +where + R: Read, +{ + r.read_u32::().wrap_err("failed to read u32") +} + +fn read_u64_le_impl(r: &mut R) -> Result +where + R: Read, +{ + r.read_u64::().wrap_err("failed to read u64") +} + +macro_rules! make_read { + ($func:ident, $op:ident, $type:ty) => { + pub(crate) fn $func(r: &mut R) -> Result<$type> + where + R: Read + Seek, + { + let res = $op(r); + + if res.is_err() { + let pos = r.stream_position(); + if pos.is_ok() { + res.with_section(|| { + format!("{pos:#X} ({pos})", pos = pos.unwrap()).header("Position: ") + }) + } else { + res + } + } else { + res + } + } + }; +} + +fn write_u32_le_impl(w: &mut W, val: u32) -> Result<()> +where + W: Write, +{ + w.write_u32::(val) + .wrap_err("failed to write u32") +} + +macro_rules! make_write { + ($func:ident, $op:ident, $type:ty) => { + pub(crate) fn $func(w: &mut W, val: $type) -> Result<()> + where + W: Write + Seek, + { + let res = $op(w, val); + + if res.is_err() { + let pos = w.stream_position(); + if pos.is_ok() { + res.with_section(|| { + format!("{pos:#X} ({pos})", pos = pos.unwrap()).header("Position: ") + }) + } else { + res + } + } else { + res + } + } + }; +} + +make_read!(read_u8, read_u8_impl, u8); +make_read!(read_u32, read_u32_le_impl, u32); +make_read!(read_u64, read_u64_le_impl, u64); + +make_write!(write_u32, write_u32_le_impl, u32); + +pub(crate) fn read_up_to(r: &mut R, buf: &mut Vec) -> Result +where + R: Read + Seek, +{ + let pos = r.stream_position()?; + + let err = { + match r.read_exact(buf) { + Ok(_) => return Ok(buf.len()), + Err(err) if err.kind() == std::io::ErrorKind::UnexpectedEof => { + r.seek(SeekFrom::Start(pos))?; + match r.read_to_end(buf) { + Ok(read) => return Ok(read), + Err(err) => err, + } + } + Err(err) => err, + } + }; + + Err(err).with_section(|| format!("{pos:#X} ({pos})", pos = pos).header("Position: ")) +} + +pub fn write_padding(w: &mut W, offset: u64) -> Result +where + W: Write + Seek, +{ + let pos = w.stream_position()? + offset; + let size = 16 - (pos % 16) as usize; + + if size > 0 && size < 16 { + w.seek(SeekFrom::Current(size as i64))?; + Ok(size) + } else { + Ok(0) + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7eacbce --- /dev/null +++ b/src/main.rs @@ -0,0 +1,317 @@ +#![feature(c_size_t)] + +use std::fs::File; +use std::io::{Cursor, Read, Seek, SeekFrom, Write}; +use std::path::PathBuf; + +use clap::{command, value_parser, Arg, ArgAction, ArgMatches, Command}; +use color_eyre::eyre::{self, Context}; +use color_eyre::Result; +use tracing_error::ErrorLayer; +use tracing_subscriber::prelude::*; +use tracing_subscriber::EnvFilter; + +mod binary; +mod oodle; +mod types; + +use binary::*; +use oodle::{Oodle, CHUNK_SIZE}; +use types::*; + +const LIB_NAME: &str = "oo2core_8_win64"; + +#[tracing::instrument] +fn main() -> Result<()> { + color_eyre::install()?; + + let matches = command!() + .about("Perform Oodle 2 compression and decompression for Darktide Bitsquid bundles.") + .subcommand_required(true) + .arg( + Arg::new("verbosity") + .help("Set verbosity for the Oodle library.") + .global(true) + .short('v') + .long("verbose") + .default_value("none") + .value_parser(value_parser!(OodleLZ_Verbosity)), + ) + .arg( + Arg::new("library") + .help( + "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.", + ) + .long("library") + .default_value(LIB_NAME), + ) + .subcommand( + Command::new("compress") + .about("Compress an arbitrary binary stream in chunks.") + .arg( + Arg::new("offset") + .help( + "The offset of the compressed data stream within the full bundle file.\ + \nThis needs to be known during decompression to correctly calculate \ + the padding within each chunk.", + ) + .long("offset") + .required(true) + .value_parser(value_parser!(u64)), + ) + .arg( + Arg::new("in_file") + .help("The input file") + .required(true) + .value_parser(value_parser!(PathBuf)), + ) + .arg( + Arg::new("out_file") + .help("The output file") + .required(true) + .value_parser(value_parser!(PathBuf)), + ), + ) + .subcommand( + Command::new("decompress") + .about("Decompress a stream of chunks.") + .arg( + Arg::new("offset") + .help( + "The offset of the compressed data stream within the full bundle file.\ + \nThis needs to be known during decompression to correctly calculate \ + the padding within each chunk.", + ) + .long("offset") + .required(true) + .value_parser(value_parser!(u64)), + ) + .arg( + Arg::new("fuzz_safe") + .long("fuzz-safe") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("check_crc") + .long("check-crc") + .action(ArgAction::SetTrue), + ) + .arg( + Arg::new("in_file") + .help("The input file") + .required(true) + .value_parser(value_parser!(PathBuf)), + ) + .arg( + Arg::new("out_file") + .help("The output file") + .required(true) + .value_parser(value_parser!(PathBuf)), + ), + ) + .get_matches(); + + { + let fmt_layer = tracing_subscriber::fmt::layer().pretty(); + let filter_layer = + EnvFilter::try_from_default_env().or_else(|_| EnvFilter::try_new("info"))?; + + tracing_subscriber::registry() + .with(filter_layer) + .with(fmt_layer) + .with(ErrorLayer::new( + tracing_subscriber::fmt::format::Pretty::default(), + )) + .init(); + } + + let lib = { + let name = matches.get_one::("library").unwrap(); + let lib = Oodle::new(name).wrap_err_with(|| format!("Failed to load library {}", name))?; + tracing::info!("Using library {}", name); + lib + }; + + match matches.subcommand() { + Some(("compress", sub_matches)) => compress(lib, sub_matches), + Some(("decompress", sub_matches)) => decompress(lib, sub_matches), + _ => unreachable!( + "clap is configured to require a subcommand, and they're all handled above" + ), + } +} + +fn skip_padding(stream: &mut S, offset: u64) -> Result<()> +where + S: Seek, +{ + let pos = stream.stream_position()? + offset; + let padding_size = 16 - (pos % 16); + + tracing::trace!(pos, padding_size); + + if padding_size < 16 && padding_size > 0 { + stream.seek(SeekFrom::Current(padding_size as i64))?; + } + + Ok(()) +} + +#[tracing::instrument(skip_all)] +fn decompress(lib: Oodle, matches: &ArgMatches) -> Result<()> { + let in_path = matches.get_one::("in_file").unwrap(); + let out_path = matches.get_one::("out_file").unwrap(); + + tracing::debug!("Reading from '{}'", in_path.display()); + tracing::debug!("Writing to '{}'", out_path.display()); + + let fuzz_safe = OodleLZ_FuzzSafe::from(matches.get_flag("fuzz_safe")); + let check_crc = OodleLZ_CheckCRC::from(matches.get_flag("check_crc")); + let verbosity = *matches + .get_one::("verbosity") + .unwrap_or(&OodleLZ_Verbosity::None); + + tracing::trace!(?fuzz_safe, ?check_crc, ?verbosity); + + let offset = *matches.get_one::("offset").unwrap(); + + tracing::debug!(offset); + + let mut in_file = File::open(in_path) + .wrap_err_with(|| format!("failed to open input file: {}", in_path.display()))?; + let mut out_file = File::create(out_path) + .wrap_err_with(|| format!("failed to open output file: {}", out_path.display()))?; + + let num_chunks = read_u32(&mut in_file)?; + skip_padding(&mut in_file, offset)?; + + tracing::debug!(num_chunks); + + for chunk_index in 0..num_chunks { + let _span = tracing::debug_span!("Decompressing chunk", chunk_index); + let chunk_size = read_u32(&mut in_file)? as usize; + + tracing::trace!(chunk_size); + + skip_padding(&mut in_file, offset)?; + + let mut compressed_buffer = vec![0u8; CHUNK_SIZE]; + read_up_to(&mut in_file, &mut compressed_buffer)?; + + // TODO: Optimize to not reallocate? + let raw_buffer = lib.decompress(&compressed_buffer, fuzz_safe, check_crc, verbosity)?; + + out_file.write_all(&raw_buffer)?; + } + + Ok(()) +} + +#[tracing::instrument(skip_all)] +fn compress(lib: Oodle, matches: &ArgMatches) -> Result<()> { + let in_path = matches.get_one::("in_file").unwrap(); + let out_path = matches.get_one::("out_file").unwrap(); + + tracing::info!("Reading from '{}'", in_path.display()); + tracing::info!("Writing to '{}'", out_path.display()); + + let offset = *matches.get_one::("offset").unwrap(); + + tracing::debug!(offset); + + let mut out_file = File::create(out_path) + .wrap_err_with(|| format!("failed to open output file: {}", out_path.display()))?; + + let mut r = { + let mut in_file = File::open(in_path) + .wrap_err_with(|| format!("failed to open input file: {}", in_path.display()))?; + + // TODO: Is it faster to check metadata for size and pre-allocate? + let mut buf = Vec::new(); + in_file + .read_to_end(&mut buf) + .wrap_err("failed to read input file into memory")?; + Cursor::new(buf) + }; + + // I'm not sure how Fatshark does this efficiently. + // Within each chunk, there is a padding whos size depends on the position where the padding is + // written. + // And that position is affected by both the size of previous chunks and the total number of + // chunks, since list of chunk sizes is written before all chunks. + // Because of that, all chunks have to be compressed and buffered before any of them can be + // written to the output. + let mut chunks: Vec> = Vec::new(); + + let mut chunk_index = 0; + + loop { + let _span = tracing::info_span!("decompress_chunk", chunk_index); + + let mut raw_buffer = vec![0u8; CHUNK_SIZE]; + let read = read_up_to(&mut r, &mut raw_buffer)?; + + let is_eof = read < CHUNK_SIZE; + + tracing::debug!(is_eof); + + { + let remaining = CHUNK_SIZE - read; + if remaining >= 4 { + for val in raw_buffer.iter_mut().skip(remaining).take(4) { + *val = 0x66; + } + } else { + eyre::bail!( + "Not enough space left in buffer to add end marker. \ + Don't know how to proceed." + ); + } + } + + let compressed = lib + .compress(raw_buffer) + .wrap_err_with(|| format!("failed to compress chunk {}", chunk_index))?; + + chunks.push(compressed); + + if is_eof { + break; + } + + chunk_index += 1; + } + + let out = &mut out_file; + + write_u32(out, chunks.len() as u32)?; + + { + let _span = tracing::trace_span!("write_chunk_sizes"); + for (chunk_index, chunk) in chunks.iter().enumerate() { + tracing::trace!(chunk_index); + write_u32(out, chunk.len() as u32)?; + } + } + write_padding(out, offset)?; + + let unpacked_size = chunks.iter().fold(0, |total, chunk| total + chunk.len()); + write_u32(out, unpacked_size as u32)?; + write_u32(out, 0)?; + + { + let _span = tracing::trace_span!("write_chunk_data"); + for (chunk_index, chunk) in chunks.iter().enumerate() { + tracing::trace!(chunk_index); + write_u32(out, chunk.len() as u32)?; + write_padding(out, offset)?; + out.write_all(chunk) + .wrap_err("failed to write chunk data")?; + } + } + + Ok(()) +} diff --git a/src/oodle.rs b/src/oodle.rs new file mode 100644 index 0000000..b14f6cb --- /dev/null +++ b/src/oodle.rs @@ -0,0 +1,104 @@ +use std::ffi::OsStr; +use std::ptr; + +use color_eyre::eyre; +use color_eyre::Result; +use libloading::{Library, Symbol}; + +use crate::types::*; + +// Hardcoded chunk size of Bitsquid's bundle compression +pub const CHUNK_SIZE: usize = 512 * 1024; + +pub struct Oodle { + lib: Library, +} + +impl Oodle { + pub fn new

(lib: P) -> Result + where + P: AsRef, + { + let lib = unsafe { Library::new(lib)? }; + // TODO: Register librarie's printf + Ok(Self { lib }) + } + + #[tracing::instrument(name = "Oodle::decompress", skip(self, data))] + pub fn decompress( + &self, + data: I, + fuzz_safe: OodleLZ_FuzzSafe, + check_crc: OodleLZ_CheckCRC, + verbosity: OodleLZ_Verbosity, + ) -> Result> + where + I: AsRef<[u8]>, + { + let data = data.as_ref(); + let mut out = vec![0; CHUNK_SIZE]; + + let ret = unsafe { + let decompress: Symbol = self.lib.get(b"OodleLZ_Decompress")?; + + decompress( + data.as_ptr() as *const _, + data.len(), + out.as_mut_ptr() as *mut _, + out.len(), + fuzz_safe, + check_crc, + verbosity, + ptr::null_mut(), + 0, + ptr::null_mut(), + ptr::null_mut(), + ptr::null_mut(), + 0, + OodleLZ_Decode_ThreadPhase::UNTHREADED, + ) + }; + + if ret < CHUNK_SIZE as u64 { + eyre::bail!("Failed to decompress chunk."); + } + + Ok(out) + } + + #[tracing::instrument(name = "Oodle::compress", skip(self, data))] + pub fn compress(&self, data: I) -> Result> + where + I: AsRef<[u8]>, + { + let raw = data.as_ref(); + // TODO: Query oodle for buffer size + let mut out = vec![0u8; CHUNK_SIZE]; + + let compressor = OodleLZ_Compressor::Kraken; + let level = OodleLZ_CompressionLevel::Optimal2; + + let ret = unsafe { + let compress: Symbol = self.lib.get(b"OodleLZ_Compress")?; + + compress( + compressor, + raw.as_ptr() as *const _, + raw.len(), + out.as_mut_ptr() as *mut _, + level, + ptr::null_mut(), + 0, + ptr::null_mut(), + ptr::null_mut(), + 0, + ) + }; + + if ret == 0 { + eyre::bail!("Failed to compress chunk."); + } + + Ok(out) + } +} diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..8718a83 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,190 @@ +#![allow(dead_code)] +use core::ffi::{c_char, c_int, c_size_t, c_ulonglong, c_void}; + +use clap::ValueEnum; + +// Type definitions taken from Unreal Engine's `oodle2.h` + +#[repr(C)] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug)] +pub enum OodleLZ_FuzzSafe { + No = 0, + Yes = 1, +} + +impl From for OodleLZ_FuzzSafe { + fn from(value: bool) -> Self { + if value { + Self::Yes + } else { + Self::No + } + } +} + +#[repr(C)] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug)] +pub enum OodleLZ_CheckCRC { + No = 0, + Yes = 1, + Force32 = 0x40000000, +} + +impl From for OodleLZ_CheckCRC { + fn from(value: bool) -> Self { + if value { + Self::Yes + } else { + Self::No + } + } +} + +#[repr(C)] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug, ValueEnum)] +pub enum OodleLZ_Verbosity { + None = 0, + Minimal = 1, + Some = 2, + Lots = 3, + #[clap(hide = true)] + Force32 = 0x40000000, +} + +#[repr(C)] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug, ValueEnum)] +pub enum OodleLZ_Decode_ThreadPhase { + Phase1 = 1, + Phase2 = 2, + PhaseAll = 3, +} + +impl OodleLZ_Decode_ThreadPhase { + pub const UNTHREADED: Self = OodleLZ_Decode_ThreadPhase::PhaseAll; +} + +#[repr(C)] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug, ValueEnum)] +pub enum OodleLZ_Compressor { + #[clap(hide = true)] + Invalid = -1, + // None = memcpy, pass through uncompressed bytes + None = 3, + + // NEW COMPRESSORS: + // 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, + BitKnit = 10, + // DEPRECATED but still supported + Lzb16 = 4, + Lzna = 7, + Lzh = 0, + Lzhlw = 1, + Lznib = 2, + Lzblw = 5, + Lza = 6, + Count = 14, + #[clap(hide = true)] + Force32 = 0x40000000, +} + +#[repr(C)] +#[allow(non_camel_case_types)] +#[derive(Clone, Copy, Debug, ValueEnum)] +pub enum OodleLZ_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, + // 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, + #[clap(hide = true)] + Force32 = 0x40000000, +} + +impl OodleLZ_CompressionLevel { + // alias hyperfast base level + pub const HYPERFAST: Self = OodleLZ_CompressionLevel::HyperFast1; + // alias optimal standard level + pub const OPTIMAL: Self = OodleLZ_CompressionLevel::Optimal2; + // maximum compression level + pub const MAX: Self = OodleLZ_CompressionLevel::Optimal5; + // fastest compression level + pub const MIN: Self = OodleLZ_CompressionLevel::HyperFast4; + pub const INVALID: Self = OodleLZ_CompressionLevel::Force32; +} + +#[allow(non_camel_case_types)] +pub type t_fp_OodleCore_Plugin_Printf = + extern "C" fn(level: c_int, file: *mut c_char, line: c_int, fmt: *mut c_char) -> c_void; + +#[allow(non_camel_case_types)] +pub type OodleLZ_Decompress = extern "C" fn( + compressed_buffer: *const c_void, + compressed_length: c_size_t, + raw_buffer: *mut c_void, + raw_length: c_size_t, + fuzz_safe: OodleLZ_FuzzSafe, + check_crc: OodleLZ_CheckCRC, + verbosity: OodleLZ_Verbosity, + decBufBase: *mut c_void, + decBufSize: c_size_t, + callback: *const c_void, + callback_user_data: *const c_void, + decoder_memory: *mut c_void, + decoder_memory_size: c_size_t, + thread_phase: OodleLZ_Decode_ThreadPhase, +) -> c_ulonglong; + +#[allow(non_camel_case_types)] +pub type OodleLZ_Compress = extern "C" fn( + compressor: OodleLZ_Compressor, + raw_buffer: *const c_void, + raw_len: c_size_t, + compressed_buffer: *mut c_void, + level: OodleLZ_CompressionLevel, + options: *const c_void, + dictionary_base: c_size_t, + lrm: *const c_void, + scratch_memory: *mut c_void, + scratch_size: c_size_t, +) -> c_ulonglong; + +#[allow(non_camel_case_types)] +pub type OodleCore_Plugins_SetPrintf = + extern "C" fn(f: t_fp_OodleCore_Plugin_Printf) -> t_fp_OodleCore_Plugin_Printf;