2
Fork 0

feat: Re-implement in Rust

This commit is contained in:
Lucas Schwiderski 2022-11-09 09:24:49 +01:00
parent 04979ce11d
commit e53b2faa0f
Signed by: lucas
GPG key ID: AA12679AAA6DF4D8
15 changed files with 1333 additions and 1031 deletions

9
.gitignore vendored
View file

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

546
Cargo.lock generated Normal file
View file

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

23
Cargo.toml Normal file
View file

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

View file

@ -1,534 +0,0 @@
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
#include <iterator>
#include <locale>
#include <codecvt>
#include <filesystem>
#include <stdlib.h>
#include <io.h>
#include <fcntl.h>
#include <share.h>
#include <sys\stat.h>
#include <string.h>
#include <windows.h>
#include <libloaderapi.h>
#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] [--] <operation>\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"
"<operation> 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<compress_func>(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<u32> chunk_sizes;
std::vector<u8> 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<decompress_func>(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<setprintf_func>(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<void*>(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;
}

View file

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

View file

@ -1,140 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<VCProjectVersion>16.0</VCProjectVersion>
<Keyword>Win32Proj</Keyword>
<ProjectGuid>{59d962da-674c-48d9-adb5-de09a89e449a}</ProjectGuid>
<RootNamespace>oodlecli</RootNamespace>
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>true</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>Application</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>true</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="Shared">
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<ClCompile>
<WarningLevel>Level3</WarningLevel>
<FunctionLevelLinking>true</FunctionLevelLinking>
<IntrinsicFunctions>true</IntrinsicFunctions>
<SDLCheck>true</SDLCheck>
<PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<ConformanceMode>true</ConformanceMode>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OptimizeReferences>true</OptimizeReferences>
<GenerateDebugInformation>true</GenerateDebugInformation>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="oodle-cli.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="oodle2.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View file

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd</Extensions>
</Filter>
<Filter Include="Resource Files">
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="oodle-cli.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="oodle2.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

View file

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<LocalDebuggerCommandArguments>compress .\test\compress_test.in.bin .\test\compress_test.out.bin</LocalDebuggerCommandArguments>
<DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
</PropertyGroup>
</Project>

287
oodle2.h
View file

@ -1,287 +0,0 @@
#pragma once
#include <stdint.h>
#include <stdlib.h>
// 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);

2
rust-toolchain.toml Normal file
View file

@ -0,0 +1,2 @@
[toolchain]
channel = "nightly"

20
rustfmt.toml Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
#
unstable_features = true
hard_tabs = false
max_width = 100
edition = "2021"
use_field_init_shorthand = true

127
src/binary.rs Normal file
View file

@ -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>(r: &mut R) -> Result<u8>
where
R: Read,
{
r.read_u8().wrap_err("failed to read u8")
}
fn read_u32_le_impl<R>(r: &mut R) -> Result<u32>
where
R: Read,
{
r.read_u32::<LittleEndian>().wrap_err("failed to read u32")
}
fn read_u64_le_impl<R>(r: &mut R) -> Result<u64>
where
R: Read,
{
r.read_u64::<LittleEndian>().wrap_err("failed to read u64")
}
macro_rules! make_read {
($func:ident, $op:ident, $type:ty) => {
pub(crate) fn $func<R>(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>(w: &mut W, val: u32) -> Result<()>
where
W: Write,
{
w.write_u32::<LittleEndian>(val)
.wrap_err("failed to write u32")
}
macro_rules! make_write {
($func:ident, $op:ident, $type:ty) => {
pub(crate) fn $func<W>(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>(r: &mut R, buf: &mut Vec<u8>) -> Result<usize>
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>(w: &mut W, offset: u64) -> Result<usize>
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)
}
}

317
src/main.rs Normal file
View file

@ -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::<String>("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<S>(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::<PathBuf>("in_file").unwrap();
let out_path = matches.get_one::<PathBuf>("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::<OodleLZ_Verbosity>("verbosity")
.unwrap_or(&OodleLZ_Verbosity::None);
tracing::trace!(?fuzz_safe, ?check_crc, ?verbosity);
let offset = *matches.get_one::<u64>("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::<PathBuf>("in_file").unwrap();
let out_path = matches.get_one::<PathBuf>("out_file").unwrap();
tracing::info!("Reading from '{}'", in_path.display());
tracing::info!("Writing to '{}'", out_path.display());
let offset = *matches.get_one::<u64>("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<u8>> = 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(())
}

104
src/oodle.rs Normal file
View file

@ -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<P>(lib: P) -> Result<Self>
where
P: AsRef<OsStr>,
{
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<I>(
&self,
data: I,
fuzz_safe: OodleLZ_FuzzSafe,
check_crc: OodleLZ_CheckCRC,
verbosity: OodleLZ_Verbosity,
) -> Result<Vec<u8>>
where
I: AsRef<[u8]>,
{
let data = data.as_ref();
let mut out = vec![0; CHUNK_SIZE];
let ret = unsafe {
let decompress: Symbol<OodleLZ_Decompress> = 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<I>(&self, data: I) -> Result<Vec<u8>>
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<OodleLZ_Compress> = 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)
}
}

190
src/types.rs Normal file
View file

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