2
Fork 0

Compare commits

..

4 commits

Author SHA1 Message Date
2bc7751d3b
feat: Implement brute-forcing the raw size
Oodle must know the size of the uncompressed data when decompressing
data. If you don't know that size, the only option is to guess.
And since it needs to be the exact number, you simply have to try
every number.
2022-10-20 17:47:42 +02:00
36edf93538
feat: Implement Oodle logging
The `verbosity` parameter hadn't worked before because there is
a callback that needs to be set. Since the tool runs under Wine,
the Windows default behaviour applied, which logged to the
Event Viewer.
To get output to stdout/stderr, a custom logging handler needs
to be provided.
2022-10-20 17:45:45 +02:00
a615ae0354
feat: Add argument to check library
This mode allows checking if the DLL can be found, without having to
create any kind of dummy data or parse stderr.
2022-10-20 17:43:37 +02:00
7c74a3e786
feat: Re-implement argument parsing 2022-10-20 17:41:23 +02:00
2 changed files with 203 additions and 79 deletions

View file

@ -22,39 +22,125 @@ const LPCWSTR LIB_NAME = L"oo2core_8_win64";
// It's not like this is going to change often. // It's not like this is going to change often.
const char* LIB_FILE = "oo2core_8_win64.dll"; 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() { void usage() {
std::cerr << std::cerr <<
"Usage: oodle-cli <in_file> <out_file> <uncompressed_size>\n" "Usage: oodle-cli [OPTIONS] [--] <in_file> <out_file> <uncompressed_size>\n"
"Decompress a <in_file> to <out_file> using the Oodle algorithm.\n" "Decompress a <in_file> to <out_file> 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 <number> 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" "\n"
"The following environmental variables are recognized:\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"; " 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<char> compressed_buffer, std::string out_name, size_t raw_buffer_size, OodleLZ_Verbosity verbosity) {
std::vector<char> 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[]) int main(int argc, char* argv[])
{ {
if (argc < 4) { int verbosity = 0;
std::cerr << "Arguments missing!\n\n"; bool check_lib = FALSE;
usage(); int guess_factor = 10;
return 1; 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; char* var;
size_t len; 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; DWORD load_flags = 0;
if (_dupenv_s(&var, &len, "DLL_SEARCH_PATH") == 0) { if (_dupenv_s(&var, &len, "DLL_SEARCH_PATH") == 0) {
if (var) { if (var) {
@ -69,10 +155,10 @@ int main(int argc, char* argv[])
} }
if (!path.is_absolute() || !AddDllDirectory(path.c_str())) { 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 { 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 { 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); HINSTANCE hDLL = LoadLibraryEx(LIB_NAME, NULL, load_flags);
@ -90,27 +176,41 @@ int main(int argc, char* argv[])
return 1; return 1;
} }
auto decompress = (decltype(OodleLZ_Decompress)*)GetProcAddress((HMODULE)hDLL, "OodleLZ_Decompress"); auto decompress = reinterpret_cast<decompress_func>(GetProcAddress((HMODULE)hDLL, "OodleLZ_Decompress"));
if (decompress == NULL) { if (decompress == NULL) {
std::cerr << "ERROR: The library is incompatible!\n"; std::cerr << "ERROR: The library is incompatible!\n";
return 1; return 1;
} }
std::string in_name = argv[1]; if (check_lib) {
std::string out_name = argv[2]; std::cout << "INFO: '" << LIB_FILE << "' found and loaded.\n";
size_t raw_buffer_size = std::stoi(argv[3]); return 0;
}
std::cout << "Attempting to decompress from '" << in_name << "' to '" << out_name << "' (" << raw_buffer_size << " bytes).\n"; if (argc - i < 3) {
std::cerr << "ERROR: Arguments missing!\n\n";
std::ifstream in_file(in_name, std::ios::binary | std::ios::ate); usage();
if (!in_file) {
std::cerr << "Failed to open compressed file!\n";
return 1; return 1;
} }
std::ofstream out_file(out_name, std::ios::binary); std::string in_name = argv[i];
if (!out_file) { std::string out_name = argv[i + 1];
std::cerr << "Failed to open output file!\n"; 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<setprintf_func>(GetProcAddress((HMODULE)hDLL, "OodleCore_Plugins_SetPrintf"));
if (fn == NULL) {
std::cerr << "ERROR: The library is incompatible!\n";
return 1;
}
reinterpret_cast<void*>(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; return 1;
} }
@ -121,40 +221,45 @@ int main(int argc, char* argv[])
in_file.read(compressed_buffer.data(), compressed_buffer_size); in_file.read(compressed_buffer.data(), compressed_buffer_size);
if (in_file.fail()) { if (in_file.fail()) {
std::cerr << "Failed to read compressed file!\n"; std::cerr << "ERROR: Failed to read compressed file!\n";
return 1; return 1;
} }
std::vector<char> 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( if (res == DECOMPRESS_ERROR) {
compressed_buffer.data(), std::cerr << "ERROR: Failed to decompress!\n";
compressed_buffer_size, return 1;
raw_buffer.data(), }
raw_buffer_size, else if (res == GENERIC_ERROR) {
OodleLZ_FuzzSafe_Yes, return 1;
OodleLZ_CheckCRC_No, }
(OodleLZ_Verbosity)verbosity, }
nullptr, else {
0, size_t guessed_size = compressed_buffer.size();
nullptr, // I need some maximum value to avoid infinite loops. The factor is configurable.
nullptr, size_t max = guessed_size * guess_factor;
nullptr,
0,
OodleLZ_Decode_Unthreaded
);
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()) { guessed_size++;
std::cerr << "Failed to write output file!\n";
return 1; if (guessed_size > max) {
std::cerr << "ERROR: Exceeded maximum value for guessing uncompressed size!\n";
return 1;
}
}
} }
if (res != raw_buffer_size) { std::cout << "INFO: Done!\n";
std::cerr << "Failed to decompress. Expected " << raw_buffer_size << " bytes, got " << res << "!\n";
return 1;
}
std::cout << "Done!\n";
} }

View file

@ -7,12 +7,10 @@
// the DLL is incompatible when MAJOR is bumped // the DLL is incompatible when MAJOR is bumped
// MINOR is for internal revs and bug fixes that don't affect API compatibility // MINOR is for internal revs and bug fixes that don't affect API compatibility
// TODO: Check if the DLL gives a minor version // TODO: Check if the DLL gives a minor version
#define OODLE2_VERSION_MAJOR 8 #define OODLE2_VERSION_MAJOR 8
#define OODLE2_VERSION_MINOR 14 #define OODLE2_VERSION_MINOR 14
// OodleVersion string is 1 . MAJOR . MINOR // 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" #define OodleVersion "2.8.14"
@ -49,7 +47,6 @@ typedef enum OodleLZ_Compressor {
} OodleLZ_Compressor; } OodleLZ_Compressor;
typedef enum OodleLZ_CheckCRC { typedef enum OodleLZ_CheckCRC {
OodleLZ_CheckCRC_No = 0, OodleLZ_CheckCRC_No = 0,
OodleLZ_CheckCRC_Yes = 1, OodleLZ_CheckCRC_Yes = 1,
@ -163,16 +160,16 @@ typedef enum OodleLZ_FuzzSafe {
seek chunk boundaries are relative to _dictionaryBase_, not to _rawBuf_. seek chunk boundaries are relative to _dictionaryBase_, not to _rawBuf_.
*/ */
int OodleLZ_Compress( extern "C" intptr_t __stdcall OodleLZ_Compress(
OodleLZ_Compressor compressor, OodleLZ_Compressor compressor,
const void* raw_buffer, const void* raw_buffer,
size_t raw_len, size_t raw_len,
void* compressed_buffer, void* compressed_buffer,
OodleLZ_CompressionLevel level, OodleLZ_CompressionLevel level,
void* options, // Default: null void* options, // Default: null
size_t dictionary_base, // Default: null size_t dictionary_base, // Default: null
const void* lrm, // Default: null const void* lrm, // Default: null
void* scratch_memory, // Default: null void* scratch_memory, // Default: null
size_t scratch_size // Default: null size_t scratch_size // Default: null
); );
@ -250,19 +247,41 @@ int OodleLZ_Compress(
Use of OodleLZ_FuzzSafe_No is deprecated. Use of OodleLZ_FuzzSafe_No is deprecated.
*/ */
int OodleLZ_Decompress( extern "C" intptr_t __stdcall OodleLZ_Decompress(
const void* compressed_buffer, const void* compressed_buffer,
size_t compressed_length, size_t compressed_length,
void* raw_buffer, void* raw_buffer,
size_t raw_length, size_t raw_length,
OodleLZ_FuzzSafe fuzz_safe, // Default: OodleLZ_FuzzSafe_Yes OodleLZ_FuzzSafe fuzz_safe, // Default: OodleLZ_FuzzSafe_Yes
OodleLZ_CheckCRC check_crc, // Default: OodleLZ_CheckCRC_No OodleLZ_CheckCRC check_crc, // Default: OodleLZ_CheckCRC_No
OodleLZ_Verbosity verbosity, // Default: OodleLZ_Verbosity_None OodleLZ_Verbosity verbosity, // Default: OodleLZ_Verbosity_None
void* decBufBase, // Default: null void* decBufBase, // Default: null
size_t decBufSize, // Default: 0 size_t decBufSize, // Default: 0
void* callback, // Default: null void* callback, // Default: null
void* callback_user_data, // Default: null void* callback_user_data, // Default: null
void* decoder_memory, // Default: null void* decoder_memory, // Default: null
size_t decoder_memory_size, // Default: 0 size_t decoder_memory_size, // Default: 0
OodleLZ_Decode_ThreadPhase thread_phase // Default: OodleLZ_Decode_Unthreaded 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);