diff --git a/oodle-cli.cpp b/oodle-cli.cpp index d74b1b0..5dc4b09 100644 --- a/oodle-cli.cpp +++ b/oodle-cli.cpp @@ -22,39 +22,125 @@ const LPCWSTR LIB_NAME = L"oo2core_8_win64"; // It's not like this is going to change often. const char* LIB_FILE = "oo2core_8_win64.dll"; +typedef decltype(OodleLZ_Decompress)* decompress_func; +typedef decltype(OodleCore_Plugins_SetPrintf)* setprintf_func; + +#define OK 0 +#define GENERIC_ERROR -1 +#define DECOMPRESS_ERROR -2 + void usage() { std::cerr << - "Usage: oodle-cli \n" + "Usage: oodle-cli [OPTIONS] [--] \n" "Decompress a to using the Oodle algorithm.\n" - "The size of the uncompressed data must be known.\n" + "The size of the uncompressed data must be known. If 0 is given, the tool will\n" + "attempt to brute-force the correct number by incrementing from the compressed size.\n" + "\n" + "Options:\n" + " -v Increase Oodle's verbosity. May be specified up to three times.\n" + " -c Only check if the library can be found and used.\n" + " --guess When guessing the uncompressed size, a maximum number as multiple of the compressed size prevents an infinite loop. This changes the factor. Defaults to 10." "\n" "The following environmental variables are recognized:\n" - " VERBOSITY: An integer between 0 and 3, with 0 being no logging and 3 being maximum logging.\n" " DLL_SEARCH_PATH: A color (':') separated list of directories to search for the Oodle library. May be relative to the current 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(stdout, fmt, args); +} + +int do_decompress(decompress_func decompress, std::vector compressed_buffer, std::string out_name, size_t raw_buffer_size, OodleLZ_Verbosity verbosity) { + std::vector raw_buffer(raw_buffer_size); + + intptr_t res = decompress( + compressed_buffer.data(), + compressed_buffer.size(), + raw_buffer.data(), + raw_buffer_size, + OodleLZ_FuzzSafe_Yes, + OodleLZ_CheckCRC_No, + verbosity, + nullptr, + 0, + nullptr, + nullptr, + nullptr, + 0, + OodleLZ_Decode_Unthreaded + ); + + if (res != raw_buffer_size) { + return DECOMPRESS_ERROR; + } + + std::ofstream out_file(out_name, std::ios::binary); + if (!out_file) { + std::cerr << "ERROR: Failed to open output file!\n"; + return GENERIC_ERROR; + } + + out_file.write(raw_buffer.data(), res); + + if (out_file.fail()) { + std::cerr << "ERROR: Failed to write output file!\n"; + return GENERIC_ERROR; + } + + return OK; +} + int main(int argc, char* argv[]) { - if (argc < 4) { - std::cerr << "Arguments missing!\n\n"; - usage(); - return 1; + int verbosity = 0; + bool check_lib = FALSE; + int guess_factor = 10; + 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, "--guess") == 0) { + i++; + guess_factor = std::stoi(argv[i]); + } + else { + std::cerr << "WARN: Unknown option '" << arg << "'!\n\n"; + } } char* var; size_t len; - int verbosity = 0; - if (_dupenv_s(&var, &len, "VERBOSITY") == 0) { - if (var) { - verbosity = std::stoi(std::string(var, len)); - std::cout << "Setting Oodle verbosity to " << verbosity << ".\n"; - free(var); - } - } - else { - std::cerr << "Failed to read environment variable 'VERBOSITY'. Falling back to default.\n"; - } - DWORD load_flags = 0; if (_dupenv_s(&var, &len, "DLL_SEARCH_PATH") == 0) { if (var) { @@ -69,10 +155,10 @@ int main(int argc, char* argv[]) } if (!path.is_absolute() || !AddDllDirectory(path.c_str())) { - std::cerr << "Failed to add DLL search path: '" << token << "'!\n"; + std::cerr << "WARN: Failed to add DLL search path: '" << token << "'!\n"; } else { - std::cout << "Added DLL search path: '" << path << "'.\n"; + std::cout << "INFO: Added DLL search path: '" << path << "'.\n"; } } @@ -81,7 +167,7 @@ int main(int argc, char* argv[]) } } else { - std::cerr << "Failed to read environment variable 'DLL_SEARCH_PATH'. Skipping.\n"; + std::cerr << "ERROR: Failed to read environment variable 'DLL_SEARCH_PATH'. Skipping.\n"; } HINSTANCE hDLL = LoadLibraryEx(LIB_NAME, NULL, load_flags); @@ -90,27 +176,41 @@ int main(int argc, char* argv[]) return 1; } - auto decompress = (decltype(OodleLZ_Decompress)*)GetProcAddress((HMODULE)hDLL, "OodleLZ_Decompress"); + auto decompress = reinterpret_cast(GetProcAddress((HMODULE)hDLL, "OodleLZ_Decompress")); if (decompress == NULL) { std::cerr << "ERROR: The library is incompatible!\n"; return 1; } - std::string in_name = argv[1]; - std::string out_name = argv[2]; - size_t raw_buffer_size = std::stoi(argv[3]); + if (check_lib) { + std::cout << "INFO: '" << LIB_FILE << "' found and loaded.\n"; + return 0; + } - std::cout << "Attempting to decompress from '" << in_name << "' to '" << out_name << "' (" << raw_buffer_size << " bytes).\n"; - - std::ifstream in_file(in_name, std::ios::binary | std::ios::ate); - if (!in_file) { - std::cerr << "Failed to open compressed file!\n"; + if (argc - i < 3) { + std::cerr << "ERROR: Arguments missing!\n\n"; + usage(); return 1; } - std::ofstream out_file(out_name, std::ios::binary); - if (!out_file) { - std::cerr << "Failed to open output file!\n"; + std::string in_name = argv[i]; + std::string out_name = argv[i + 1]; + size_t raw_buffer_size = std::stoi(argv[i + 2]); + + std::cout << "INFO: Attempting to decompress from '" << in_name << "' to '" << out_name << "' (" << raw_buffer_size << " bytes).\n"; + + if (verbosity > 0) { + auto fn = reinterpret_cast(GetProcAddress((HMODULE)hDLL, "OodleCore_Plugins_SetPrintf")); + if (fn == NULL) { + std::cerr << "ERROR: The library is incompatible!\n"; + return 1; + } + reinterpret_cast(fn(printf_callback)); + } + + std::ifstream in_file(in_name, std::ios::binary | std::ios::ate); + if (!in_file) { + std::cerr << "ERROR: Failed to open compressed file!\n"; return 1; } @@ -121,40 +221,45 @@ int main(int argc, char* argv[]) in_file.read(compressed_buffer.data(), compressed_buffer_size); if (in_file.fail()) { - std::cerr << "Failed to read compressed file!\n"; + std::cerr << "ERROR: Failed to read compressed file!\n"; return 1; } - std::vector raw_buffer(raw_buffer_size); + if (raw_buffer_size > 0) { + int res = do_decompress(decompress, compressed_buffer, out_name, raw_buffer_size, (OodleLZ_Verbosity)verbosity); - int res = decompress( - compressed_buffer.data(), - compressed_buffer_size, - raw_buffer.data(), - raw_buffer_size, - OodleLZ_FuzzSafe_Yes, - OodleLZ_CheckCRC_No, - (OodleLZ_Verbosity)verbosity, - nullptr, - 0, - nullptr, - nullptr, - nullptr, - 0, - OodleLZ_Decode_Unthreaded - ); + if (res == DECOMPRESS_ERROR) { + std::cerr << "ERROR: Failed to decompress!\n"; + return 1; + } + else if (res == GENERIC_ERROR) { + return 1; + } + } + else { + size_t guessed_size = compressed_buffer.size(); + // I need some maximum value to avoid infinite loops. The factor is configurable. + size_t max = guessed_size * guess_factor; - out_file.write(raw_buffer.data(), res); + while (true) { + int res = do_decompress(decompress, compressed_buffer, out_name, guessed_size, (OodleLZ_Verbosity)verbosity); + + if (res == GENERIC_ERROR) { + return 1; + } + else if (res == OK) { + std::cout << "INFO: Found decompressed size as " << guessed_size << "\n"; + break; + } - if (out_file.fail()) { - std::cerr << "Failed to write output file!\n"; - return 1; + guessed_size++; + + if (guessed_size > max) { + std::cerr << "ERROR: Exceeded maximum value for guessing uncompressed size!\n"; + return 1; + } + } } - if (res != raw_buffer_size) { - std::cerr << "Failed to decompress. Expected " << raw_buffer_size << " bytes, got " << res << "!\n"; - return 1; - } - - std::cout << "Done!\n"; + std::cout << "INFO: Done!\n"; } diff --git a/oodle2.h b/oodle2.h index 1e77d92..3133263 100644 --- a/oodle2.h +++ b/oodle2.h @@ -7,12 +7,10 @@ // 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 +#define OODLE2_VERSION_MAJOR 8 +#define OODLE2_VERSION_MINOR 14 // OodleVersion string is 1 . MAJOR . MINOR -// don't make it from macros cuz the doc tool has to parse the string literal - #define OodleVersion "2.8.14" @@ -49,7 +47,6 @@ typedef enum OodleLZ_Compressor { } OodleLZ_Compressor; - typedef enum OodleLZ_CheckCRC { OodleLZ_CheckCRC_No = 0, OodleLZ_CheckCRC_Yes = 1, @@ -163,16 +160,16 @@ typedef enum OodleLZ_FuzzSafe { seek chunk boundaries are relative to _dictionaryBase_, not to _rawBuf_. */ -int OodleLZ_Compress( +extern "C" intptr_t __stdcall OodleLZ_Compress( OodleLZ_Compressor compressor, - const void* raw_buffer, + const void* raw_buffer, size_t raw_len, - void* compressed_buffer, + void* compressed_buffer, OodleLZ_CompressionLevel level, - void* options, // Default: null + void* options, // Default: null size_t dictionary_base, // Default: null - const void* lrm, // Default: null - void* scratch_memory, // Default: null + const void* lrm, // Default: null + void* scratch_memory, // Default: null size_t scratch_size // Default: null ); @@ -250,19 +247,41 @@ int OodleLZ_Compress( Use of OodleLZ_FuzzSafe_No is deprecated. */ -int OodleLZ_Decompress( +extern "C" intptr_t __stdcall OodleLZ_Decompress( const void* compressed_buffer, size_t compressed_length, - void* raw_buffer, + 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 + void* decBufBase, // Default: null size_t decBufSize, // Default: 0 - void* callback, // Default: null - void* callback_user_data, // Default: null - void* decoder_memory, // Default: null + 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