2
Fork 0
This repository has been archived on 2022-11-09. You can view files and clone it, but cannot push or open issues or pull requests.
oodle-cli/oodle-cli.cpp
Lucas Schwiderski 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

265 lines
7.9 KiB
C++

// oodle-cli.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <sstream>
#include <iterator>
#include <locale>
#include <codecvt>
#include <filesystem>
#include <stdlib.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_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 [OPTIONS] [--] <in_file> <out_file> <uncompressed_size>\n"
"Decompress a <in_file> to <out_file> using the Oodle algorithm.\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"
"The following environmental variables are recognized:\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 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;
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;
}
auto decompress = reinterpret_cast<decompress_func>(GetProcAddress((HMODULE)hDLL, "OodleLZ_Decompress"));
if (decompress == NULL) {
std::cerr << "ERROR: The library is incompatible!\n";
return 1;
}
if (check_lib) {
std::cout << "INFO: '" << LIB_FILE << "' found and loaded.\n";
return 0;
}
if (argc - i < 3) {
std::cerr << "ERROR: Arguments missing!\n\n";
usage();
return 1;
}
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<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;
}
std::streamsize compressed_buffer_size = in_file.tellg();
in_file.seekg(0, std::ios::beg);
std::vector<char> compressed_buffer(compressed_buffer_size);
in_file.read(compressed_buffer.data(), compressed_buffer_size);
if (in_file.fail()) {
std::cerr << "ERROR: Failed to read compressed file!\n";
return 1;
}
if (raw_buffer_size > 0) {
int res = do_decompress(decompress, compressed_buffer, out_name, raw_buffer_size, (OodleLZ_Verbosity)verbosity);
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;
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;
}
guessed_size++;
if (guessed_size > max) {
std::cerr << "ERROR: Exceeded maximum value for guessing uncompressed size!\n";
return 1;
}
}
}
std::cout << "INFO: Done!\n";
}