diff --git a/.gitignore b/.gitignore index d8ed9b1..4558df5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ subprojects/xxHash-0.8.2 subprojects/safetyhook subprojects/zxing-cpp-2.2.1 subprojects/stb +subprojects/zlib +libtomcrypt-1.18.2 dist.7z .vscode out/ \ No newline at end of file diff --git a/Makefile b/Makefile index 6be9a04..c2830ff 100644 --- a/Makefile +++ b/Makefile @@ -11,5 +11,5 @@ dist-no-7z: all @cp -r dist/* out/ dist: dist-no-7z - @cd out && 7z a -t7z ../dist.7z . + @cd out && 7zz a -t7z ../dist.7z . @rm -rf out diff --git a/dist/config.toml b/dist/config.toml index 63c1ab8..0b05ab5 100644 --- a/dist/config.toml +++ b/dist/config.toml @@ -48,3 +48,5 @@ jp_layout = false [layeredfs] enabled = false +datatable_key = "" +fumen_key = "" \ No newline at end of file diff --git a/meson.build b/meson.build index d1552d1..35b6104 100644 --- a/meson.build +++ b/meson.build @@ -40,6 +40,10 @@ stb = subproject('stb') opt_var.add_cmake_defines({'BUILD_EXAMPLES': false}) zxing_proj = cmake.subproject('zxing', options: opt_var) zxing_dep = zxing_proj.dependency('ZXing') +zlib_proj = subproject('zlib') +zlib_dep = zlib_proj.get_variable('zlib_dep') +libtomcrypt = subproject('libtomcrypt') +libtomcrypt_dep = libtomcrypt.get_variable('tomcrypt_dep') library( 'bnusio', @@ -48,6 +52,8 @@ library( tomlc99.get_variable('tomlc99_lib'), sdl2.get_variable('sdl2'), xxhash.get_variable('xxhash'), + zlib_proj.get_variable('zlib_lib'), + libtomcrypt.get_variable('tomcryptlib'), ], include_directories: [ 'src', @@ -55,11 +61,16 @@ library( tomlc99.get_variable('tomlc99_inc'), sdl2.get_variable('core_inc'), xxhash.get_variable('inc'), + zlib_proj.get_variable('zlib_inc'), + libtomcrypt.get_variable('core_inc'), ], - dependencies: [stb.get_variable('stb_dep'), + dependencies: [ + stb.get_variable('stb_dep'), zxing_dep, safetyhook_dep, zydis_dep, + zlib_dep, + libtomcrypt_dep, ], sources : [ 'src/dllmain.cpp', diff --git a/src/dllmain.cpp b/src/dllmain.cpp index 062b16c..39e7d61 100644 --- a/src/dllmain.cpp +++ b/src/dllmain.cpp @@ -3,6 +3,9 @@ #include "helpers.h" #include "patches/patches.h" #include "poll.h" +#include +#include +#define POLY 0x82f63b78 GameVersion gameVersion = GameVersion::UNKNOWN; std::vector plugins; @@ -24,6 +27,159 @@ char chipId2[33] = "00000000000000000000000000000002"; bool autoIME = false; bool jpLayout = false; bool useLayeredFs = false; +std::string datatableKey = "0000000000000000000000000000000000000000000000000000000000000000"; +std::string fumenKey = "0000000000000000000000000000000000000000000000000000000000000000"; + +uint32_t crc32c(uint32_t crc, const unsigned char *buf, size_t len) +{ + int k; + + crc = ~crc; + while (len--) { + crc ^= *buf++; + for (k = 0; k < 8; k++) + crc = (crc >> 1) ^ (POLY & (0 - (crc & 1))); + } + return ~crc; +} + +bool checkCRC(const std::string &path, uint32_t crc){ + if (std::filesystem::exists(path)){ + std::filesystem::path crc_path = path; + crc_path.replace_extension(".crc"); + std::ifstream crc_file(crc_path, std::ios::binary); + std::string crc_content((std::istreambuf_iterator(crc_file)), std::istreambuf_iterator()); + return std::stoul(crc_content) != crc; + } + return 1; +} + +void create_directories(const std::string &path) { + size_t pos = 0; + std::string delimiter = "\\"; + std::string current_path; + + while ((pos = path.find(delimiter, pos)) != std::string::npos) { + current_path = path.substr(0, pos++); + if (!current_path.empty() && !CreateDirectory(current_path.c_str(), NULL) && GetLastError() != ERROR_ALREADY_EXISTS) { + throw std::runtime_error("Error creating directory: " + current_path); + } + } + if (!path.empty() && !CreateDirectory(path.c_str(), NULL) && GetLastError() != ERROR_ALREADY_EXISTS) { + throw std::runtime_error("Error creating directory: " + path); + } +} + +void write_file(const std::string &filename, const std::vector &data, uint32_t original_crc) { + std::string::size_type pos = filename.find_last_of("\\"); + if (pos != std::string::npos) { + std::string directory = filename.substr(0, pos); + create_directories(directory); + } + + std::filesystem::path crc_path = filename; + crc_path.replace_extension(".crc"); + std::ofstream crc_file(crc_path.string()); + crc_file << std::to_string(original_crc); + crc_file.close(); + + std::ofstream file(filename, std::ios::binary); + file.write(reinterpret_cast(data.data()), data.size()); +} + +std::vector gzip_compress(const std::vector& data) { + z_stream deflate_stream; + deflate_stream.zalloc = Z_NULL; + deflate_stream.zfree = Z_NULL; + deflate_stream.opaque = Z_NULL; + deflate_stream.avail_in = data.size(); + deflate_stream.next_in = const_cast(data.data()); + + deflateInit2(&deflate_stream, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); + + std::vector compressed_data; + compressed_data.resize(deflateBound(&deflate_stream, data.size())); + + deflate_stream.avail_out = compressed_data.size(); + deflate_stream.next_out = compressed_data.data(); + + deflate(&deflate_stream, Z_FINISH); + deflateEnd(&deflate_stream); + + compressed_data.resize(deflate_stream.total_out); + return compressed_data; +} + +// Function to pad data according to PKCS7 +std::vector pad_data(const std::vector &data, size_t block_size) { + size_t padding = block_size - (data.size() % block_size); + std::vector padded_data = data; + padded_data.insert(padded_data.end(), padding, static_cast(padding)); + return padded_data; +} + +std::vector hex_to_bytes(const std::string &hex) { + std::vector bytes; + for (size_t i = 0; i < hex.length(); i += 2) { + uint8_t byte = static_cast(std::stoi(hex.substr(i, 2), nullptr, 16)); + bytes.push_back(byte); + } + return bytes; +} + +std::vector encrypt_file(const std::string &input_file, const std::string &hex_key) { + // Convert the key from hex to bytes + std::vector key = hex_to_bytes(hex_key); + + // Generate the 128 bits IV + std::vector iv(16); + for (size_t i = 0; i < iv.size(); ++i) iv[i] = static_cast(i); + + // Read the entire file into memory + std::ifstream file(input_file, std::ios::binary); + + std::vector data((std::istreambuf_iterator(file)), std::istreambuf_iterator()); + + // Compress the data + std::vector compressed_data = gzip_compress(data); + + // Pad the compressed data + std::vector padded_data = pad_data(compressed_data, 16); + + // Encrypt the data + symmetric_CBC cbc; + if (cbc_start(find_cipher("aes"), iv.data(), key.data(), key.size(), 0, &cbc) != CRYPT_OK) { + throw std::runtime_error("Error initializing CBC"); + } + + std::vector encrypted_data(padded_data.size()); + if (cbc_encrypt(padded_data.data(), encrypted_data.data(), padded_data.size(), &cbc) != CRYPT_OK) { + throw std::runtime_error("Error during encryption"); + } + + cbc_done(&cbc); + + // Return IV concatenated with the encrypted data + encrypted_data.insert(encrypted_data.begin(), iv.begin(), iv.end()); + return encrypted_data; +} + +bool is_fumen_encrypted(const std::string& filename) { + std::ifstream file(filename, std::ios::binary); + file.seekg(0x210, std::ios::beg); + std::vector buffer(32); + file.read(reinterpret_cast(buffer.data()), buffer.size()); + + // Check if the read bytes match the expected pattern + std::vector expected_bytes = { + 0x00, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00 + }; + + return buffer != expected_bytes; +} HOOK (i32, ShowMouse, PROC_ADDRESS ("user32.dll", "ShowCursor"), bool) { return originalShowMouse (true); } HOOK (i32, ExitWindows, PROC_ADDRESS ("user32.dll", "ExitWindowsEx")) { ExitProcess (0); } @@ -51,14 +207,68 @@ HOOK (HANDLE, CreateFileAHook, PROC_ADDRESS ("kernel32.dll", "CreateFileA"), LPC if (!path.is_absolute ()) path = std::filesystem::absolute (path); auto originalDataFolder = std::filesystem::current_path ().parent_path ().parent_path () / "Data" / "x64"; auto originalLayeredFsFolder = std::filesystem::current_path ().parent_path ().parent_path () / "Data_mods" / "x64"; + auto encryptedLayeredFsFolder = std::filesystem::current_path ().parent_path ().parent_path () / "Data_mods" / "x64_enc"; if (path.string ().find (originalDataFolder.string ()) == 0) { auto newPath = path.string (); + auto encPath = path.string (); newPath.replace (0, originalDataFolder.string ().length (), originalLayeredFsFolder.string ()); + encPath.replace (0, originalDataFolder.string ().length (), encryptedLayeredFsFolder.string ()); - if (std::filesystem::exists (newPath)) { - std::cout << "Redirecting " << path << " to " << newPath << std::endl; - return originalCreateFileAHook (newPath.c_str (), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, + //The following code handles file redirection and if need be, file encryption. + //It's a bit of a mess but it works well ! -Kit + + if (std::filesystem::exists (newPath)) { //If a file exists in the datamod folder + if(is_fumen_encrypted(newPath)){ //And if it's an encrypted fumen or a different type of file, use it. + std::cout << "Redirecting " << std::filesystem::relative (path).string() << std::endl; + return originalCreateFileAHook (newPath.c_str (), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, + dwFlagsAndAttributes, hTemplateFile); + } + else{ //Otherwise if it's an unencrypted fumen. + if (!std::filesystem::exists(encPath)) { //We check if we don't already have a cached file. + if(fumenKey.length() == 64){ + std::cout << "Encrypting " << std::filesystem::relative (newPath) << std::endl; // If we don't we encrypt the file + std::ifstream crc_file(newPath, std::ios::binary); + std::vector crc_vector((std::istreambuf_iterator(crc_file)), std::istreambuf_iterator()); + uint32_t crc = crc32c(0, crc_vector.data(), crc_vector.size()); + write_file(encPath, encrypt_file(newPath, fumenKey), crc); // And we save it + } else { + std::cout << "Missing or invalid fumen key : " << std::filesystem::relative (newPath) << " couldn't be encrypted." << std::endl; + encPath = path.string(); + } + } else std::cout << "Using cached file for " << std::filesystem::relative (newPath) << std::endl; + return originalCreateFileAHook (encPath.c_str (), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, + dwFlagsAndAttributes, hTemplateFile); + } + } + + //We check separately for unencrypted json files. + std::filesystem::path json_path = newPath; + json_path.replace_extension(".json"); + if (std::filesystem::exists(json_path)) { //If a json file exists in the folder + bool crcBool = false; + + if (std::filesystem::exists(encPath)){ + std::ifstream crc_file(json_path, std::ios::binary); + std::vector crc_vector((std::istreambuf_iterator(crc_file)), std::istreambuf_iterator()); + uint32_t crc = crc32c(0, crc_vector.data(), crc_vector.size()); + crcBool = checkCRC(encPath, crc); + } + + if (!std::filesystem::exists(encPath) || crcBool) { //And if it hasn't been encrypted before + if(datatableKey.length() == 64){ + std::cout << "Encrypting " << std::filesystem::relative (json_path) << std::endl; //Encrypt the file + std::ifstream crc_file(json_path.string(), std::ios::binary); + std::vector crc_vector((std::istreambuf_iterator(crc_file)), std::istreambuf_iterator()); + uint32_t crc = crc32c(0, crc_vector.data(), crc_vector.size()); + write_file(encPath, encrypt_file(json_path.string(), datatableKey), crc); //And save it + } else { + std::cout << "Missing or invalid datatable key : " << std::filesystem::relative (newPath) << " couldn't be encrypted." << std::endl; + encPath = path.string(); + } + } + else std::cout << "Using cached file for " << std::filesystem::relative (json_path) << std::endl; //Otherwise use the already encrypted file. + return originalCreateFileAHook (encPath.c_str (), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); } } @@ -67,32 +277,32 @@ HOOK (HANDLE, CreateFileAHook, PROC_ADDRESS ("kernel32.dll", "CreateFileA"), LPC hTemplateFile); } -HOOK (HANDLE, CreateFileWHook, PROC_ADDRESS ("kernel32.dll", "CreateFileW"), LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, - LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { - std::wstring_convert, wchar_t> converter; - std::string strFileName = converter.to_bytes (lpFileName); +// HOOK (HANDLE, CreateFileWHook, PROC_ADDRESS ("kernel32.dll", "CreateFileW"), LPCWSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, +// LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile) { +// std::wstring_convert, wchar_t> converter; +// std::string strFileName = converter.to_bytes (lpFileName); - std::filesystem::path path (strFileName); - if (!path.is_absolute ()) path = std::filesystem::absolute (path); +// std::filesystem::path path (strFileName); +// if (!path.is_absolute ()) path = std::filesystem::absolute (path); - auto originalDataFolder = std::filesystem::current_path ().parent_path ().parent_path () / "Data" / "x64"; - auto originalLayeredFsFolder = std::filesystem::current_path ().parent_path ().parent_path () / "Data_mods" / "x64"; +// auto originalDataFolder = std::filesystem::current_path ().parent_path ().parent_path () / "Data" / "x64"; +// auto originalLayeredFsFolder = std::filesystem::current_path ().parent_path ().parent_path () / "Data_mods" / "x64"; - if (path.string ().find (originalDataFolder.string ()) != std::string::npos) { - auto newPath = path.string (); - newPath.replace (0, originalDataFolder.string ().length (), originalLayeredFsFolder.string ()); +// if (path.string ().find (originalDataFolder.string ()) != std::string::npos) { +// auto newPath = path.string (); +// newPath.replace (0, originalDataFolder.string ().length (), originalLayeredFsFolder.string ()); - if (std::filesystem::exists (newPath)) { - std::wstring wNewPath = converter.from_bytes (newPath); - std::wcout << L"Redirecting " << lpFileName << L" to " << wNewPath << std::endl; - return originalCreateFileWHook (wNewPath.c_str (), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, - dwFlagsAndAttributes, hTemplateFile); - } - } +// if (std::filesystem::exists (newPath)) { +// std::wstring wNewPath = converter.from_bytes (newPath); +// std::wcout << L"Redirecting " << lpFileName << L" to " << wNewPath << std::endl; +// return originalCreateFileWHook (wNewPath.c_str (), dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, +// dwFlagsAndAttributes, hTemplateFile); +// } +// } - return originalCreateFileWHook (lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, - hTemplateFile); -} +// return originalCreateFileWHook (lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, +// hTemplateFile); +// } void GetGameVersion () { @@ -184,7 +394,11 @@ DllMain (HMODULE module, DWORD reason, LPVOID reserved) { } auto layeredFs = openConfigSection (config, "layeredfs"); - if (layeredFs) useLayeredFs = readConfigBool (layeredFs, "enabled", useLayeredFs); + if (layeredFs) { + useLayeredFs = readConfigBool (layeredFs, "enabled", useLayeredFs); + datatableKey = readConfigString (layeredFs, "datatable_key", datatableKey); + fumenKey = readConfigString (layeredFs, "fumen_key", fumenKey); + } } if (version == "auto") { @@ -243,8 +457,9 @@ DllMain (HMODULE module, DWORD reason, LPVOID reserved) { if (useLayeredFs) { std::wcout << "Using LayeredFs!" << std::endl; + register_cipher(&aes_desc); INSTALL_HOOK (CreateFileAHook); - INSTALL_HOOK (CreateFileWHook); + // INSTALL_HOOK (CreateFileWHook); } bnusio::Init (); diff --git a/src/patches/amauth.cpp b/src/patches/amauth.cpp index d726c5a..f130310 100644 --- a/src/patches/amauth.cpp +++ b/src/patches/amauth.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -8,7 +9,6 @@ #include #include #include -#include #include /* diff --git a/subprojects/libtomcrypt.wrap b/subprojects/libtomcrypt.wrap new file mode 100644 index 0000000..fe2ec5d --- /dev/null +++ b/subprojects/libtomcrypt.wrap @@ -0,0 +1,12 @@ +[wrap-file] +directory = libtomcrypt-1.18.2 +source_url = https://github.com/libtom/libtomcrypt/releases/download/v1.18.2/crypt-1.18.2.tar.xz +source_filename = crypt-1.18.2.tar.xz +source_hash = 96ad4c3b8336050993c5bc2cf6c057484f2b0f9f763448151567fbab5e767b84 +patch_filename = libtomcrypt_1.18.2-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/libtomcrypt_1.18.2-1/get_patch +patch_hash = 0ca889b864e5a23525774438439d9e385268ff7234d49029902375a6c5e18f14 + +[provide] +libtomcrypt = tomcrypt_dep + diff --git a/subprojects/packagefiles/zlib.patch b/subprojects/packagefiles/zlib.patch new file mode 100644 index 0000000..a10c909 --- /dev/null +++ b/subprojects/packagefiles/zlib.patch @@ -0,0 +1,33 @@ ++++ zlib/meson.build +@@ -0,0 +1,31 @@ ++project('zlib', 'c', version: '1.0.0') ++ ++zlib_inc = include_directories('.') ++zlib_sources = files( ++ 'adler32.c', ++ 'compress.c', ++ 'crc32.c', ++ 'deflate.c', ++ 'gzclose.c', ++ 'gzlib.c', ++ 'gzread.c', ++ 'gzwrite.c', ++ 'infback.c', ++ 'inffast.c', ++ 'inflate.c', ++ 'inftrees.c', ++ 'trees.c', ++ 'uncompr.c', ++ 'zutil.c' ++) ++ ++zlib_lib = static_library( ++ 'zlib', ++ zlib_sources, ++ include_directories: zlib_inc ++) ++ ++zlib_dep = declare_dependency( ++ include_directories: zlib_inc, ++ link_with: zlib_lib ++) diff --git a/subprojects/zlib.wrap b/subprojects/zlib.wrap new file mode 100755 index 0000000..109f743 --- /dev/null +++ b/subprojects/zlib.wrap @@ -0,0 +1,4 @@ +[wrap-git] +url = https://github.com/madler/zlib +revision = 51b7f2abdade71cd9bb0e7a373ef2610ec6f9daf +diff_files = zlib.patch \ No newline at end of file