Compare commits

..

3 Commits

14 changed files with 262 additions and 48 deletions

View File

@ -2,9 +2,17 @@ cmake_minimum_required(VERSION 3.10)
project(kgg-dec VERSION 0.6.0 LANGUAGES CXX)
option(USE_WIN_SQLITE3 "Use Windows SQLite3 (MSVC Only)" ${MSVC})
option(USE_WIN_CRYPTO "Use Windows Crypto API" ${WIN32})
add_subdirectory(third-party/aes)
add_subdirectory(third-party/md5)
add_subdirectory(third-party/sqlite3)
include(cmake/FindWinSQLite3.cmake)
if (NOT WinSQLite3_Found)
message("including sqlite3 to the build")
add_subdirectory(third-party/sqlite3)
endif ()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -27,6 +35,16 @@ target_include_directories(kgg-dec
src/tc_tea
)
target_link_libraries(kgg-dec PRIVATE shell32 ole32 libaes libmd5 sqlite3)
target_link_libraries(kgg-dec PRIVATE shell32 ole32 libaes libmd5)
if (WinSQLite3_Found)
target_link_libraries(kgg-dec PRIVATE WinSQLite3)
target_include_directories(kgg-dec PRIVATE ${WindowsKitInclude})
else ()
target_link_libraries(kgg-dec PRIVATE sqlite3)
endif ()
target_compile_definitions(kgg-dec PRIVATE NOMINMAX)
target_compile_definitions(kgg-dec PRIVATE KGGDEC_PROJECT_VERSION="${PROJECT_VERSION}")
if (USE_WIN_CRYPTO)
target_compile_definitions(kgg-dec PRIVATE USE_WIN_CRYPTO=1)
endif ()

View File

@ -35,7 +35,10 @@
"description": "Configure for Visual Studio",
"generator": "Visual Studio 17 2022",
"binaryDir": "${sourceDir}/build/vs2022",
"architecture": "x64"
"architecture": "x64,version=10.0",
"toolset": {
"value": "v143"
}
}
],
"buildPresets": [

View File

@ -0,0 +1,14 @@
set(WinSQLite3_Found FALSE)
if (MSVC AND USE_WIN_SQLITE3)
set(ProgramFiles_x86 "$ENV{ProgramFiles\(x86\)}")
file(GLOB WindowsKitLibs "${ProgramFiles_x86}/Windows Kits/10/Lib/10.0.*/um/${CMAKE_VS_PLATFORM_NAME}")
find_library(LibWinSQLite3 WinSQLite3 PATHS ${WindowsKitLibs})
if (LibWinSQLite3)
set(WinSQLite3_Found TRUE)
get_filename_component(WindowsKitVersion "${LibWinSQLite3}/../../.." ABSOLUTE)
get_filename_component(WindowsKitVersion ${WindowsKitVersion} NAME)
set(WindowsKitInclude "${ProgramFiles_x86}/Windows Kits/10/Include/${WindowsKitVersion}/um")
message("Using WinSQLite3 from Windows SDK (${WindowsKitVersion}).")
endif ()
endif ()

View File

@ -0,0 +1,7 @@
#pragma once
#if __has_include(<sqlite3.h>)
#include <sqlite3.h>
#else
#include <winsqlite/winsqlite3.h>
#endif

View File

@ -3,10 +3,15 @@
#include <aes.h>
#include <endian_helper.h>
#include <md5.h>
#include <sqlite3.h>
#include <sqlite3_wrapper.h>
#include <algorithm>
#include <array>
#include <fstream>
#include <vector>
namespace Infra {
using std::size_t;
constexpr size_t kPageSize = 0x400;
@ -18,15 +23,15 @@ inline bool is_valid_page_1_header(const uint8_t* page1) {
}
void derive_page_key(uint8_t* aes_key, uint8_t* aes_iv, const uint8_t* p_master_key, const uint32_t page_no) {
uint8_t buffer[0x18];
std::array<uint8_t, 0x18> buffer{};
// Setup buffer
memcpy(buffer, p_master_key, 0x10);
std::copy_n(p_master_key, 0x10, buffer.begin());
Endian::le_write(&buffer[0x10], page_no);
Endian::le_write(&buffer[0x14], 0x546C4173);
// Derive Key
md5(aes_key, buffer, 24);
md5(aes_key, buffer.data(), buffer.size());
// Derive IV
for (uint32_t ebx{page_no + 1}, i = 0; i < 16; i += 4) {
@ -38,10 +43,10 @@ void derive_page_key(uint8_t* aes_key, uint8_t* aes_iv, const uint8_t* p_master_
ebx = ecx;
Endian::le_write(&buffer[i], ebx);
}
md5(aes_iv, buffer, 16);
md5(aes_iv, buffer.data(), 0x10);
// Cleanup
memset(buffer, 0xCC, sizeof(buffer));
std::ranges::fill(buffer, 0xcc);
}
static const uint8_t kDefaultMasterKey[0x10] = {
@ -49,8 +54,8 @@ static const uint8_t kDefaultMasterKey[0x10] = {
0x3d, 0x18, 0x96, 0x72, 0x14, 0x4f, 0xe4, 0xbf, //
};
static constexpr uint8_t kSQLiteDatabaseHeader[0x10] = {'S', 'Q', 'L', 'i', 't', 'e', ' ', 'f',
'o', 'r', 'm', 'a', 't', ' ', '3', 0};
static constexpr std::array<uint8_t, 0x10> kSQLiteDatabaseHeader = { //
'S', 'Q', 'L', 'i', 't', 'e', ' ', 'f', 'o', 'r', 'm', 'a', 't', ' ', '3', 0};
int load_db(std::vector<uint8_t>& db_data, const std::filesystem::path& db_path) {
using namespace AES;
@ -87,28 +92,31 @@ int load_db(std::vector<uint8_t>& db_data, const std::filesystem::path& db_path)
}
if (page_no == 1) [[unlikely]] {
if (memcmp(p_page, kSQLiteDatabaseHeader, 0x10) == 0) {
if (std::equal(kSQLiteDatabaseHeader.cbegin(), kSQLiteDatabaseHeader.cend(), p_page)) {
ifs_db.read(reinterpret_cast<char*>(p_page + kPageSize),
static_cast<std::streamsize>(db_size - kPageSize));
AES_cleanup(&ctx_aes);
return SQLITE_OK; // no encryption
}
if (!is_valid_page_1_header(p_page)) [[unlikely]] {
if (!is_valid_page_1_header(p_page)) {
AES_cleanup(&ctx_aes);
db_data.clear();
return SQLITE_CORRUPT; // header validation failed
}
uint8_t backup[0x08]; // backup magic numbers
memcpy(&backup, &p_page[0x10], 0x08);
memcpy(&p_page[0x10], &p_page[0x08], 0x08);
std::array<uint8_t, 8> backup{}; // backup magic numbers
std::copy_n(&p_page[0x10], 0x08, backup.begin());
std::copy_n(&p_page[0x08], 0x08, &p_page[0x10]);
AES_CBC_decrypt_buffer(&ctx_aes, p_page + 0x10, kPageSize - 0x10);
if (memcmp(backup, &p_page[0x10], 0x08) != 0) {
if (!std::equal(backup.cbegin(), backup.cend(), &p_page[0x10])) {
db_data.clear();
return SQLITE_CORRUPT; // header validation failed
}
memcpy(p_page, kSQLiteDatabaseHeader, 0x10);
std::ranges::copy(kSQLiteDatabaseHeader, p_page);
} else {
AES_CBC_decrypt_buffer(&ctx_aes, p_page, kPageSize);
}
AES_cleanup(&ctx_aes);
}
return SQLITE_OK;

View File

@ -1,31 +1,35 @@
#pragma once
#include <windows.h>
#include "qmc2/qmc2.h"
#include <windows.h>
#include <array>
#include <condition_variable>
#include <filesystem>
#include <format>
#include <fstream>
#include <mutex>
#include <queue>
#include <string>
#include <string_view>
#include <thread>
#include <utility>
#include <vector>
#include "qmc2/qmc2.h"
class KggTask {
public:
explicit KggTask(std::filesystem::path kgg_path, std::filesystem::path out_dir)
: kgg_path_(std::move(kgg_path)), out_dir_(std::move(out_dir)) {}
void warning(const wchar_t* msg) const { fwprintf(stderr, L"[WARN] %s (%s)\n", msg, kgg_path_.filename().c_str()); }
void warning(const std::wstring& msg) const { warning(msg.c_str()); }
void error(const wchar_t* msg) const { fwprintf(stderr, L"[ERR ] %s (%s)\n", msg, kgg_path_.filename().c_str()); }
void error(const std::wstring& msg) const { error(msg.c_str()); }
void info(const wchar_t* msg) const { fwprintf(stderr, L"[INFO] %s (%s)\n", msg, kgg_path_.filename().c_str()); }
void info(const std::wstring& msg) const { info(msg.c_str()); }
static void log(const std::wstring& msg) { fputws(msg.c_str(), stderr); }
void warning(const std::wstring& msg) const {
log(std::format(L"[WARN] {} ({})\n", msg, kgg_path_.filename().wstring()));
}
void error(const std::wstring& msg) const {
log(std::format(L"[ERR ] {} ({})\n", msg, kgg_path_.filename().wstring()));
}
void info(const std::wstring& msg) const {
log(std::format(L"[INFO] {} ({})\n", msg, kgg_path_.filename().wstring()));
}
void Execute(const Infra::kgm_ekey_db_t& ekey_db, const std::wstring_view suffix) const {
constexpr static std::array<uint8_t, 16> kMagicHeader{0x7C, 0xD5, 0x32, 0xEB, 0x86, 0x02, 0x7F, 0x4B,

View File

@ -1,4 +1,5 @@
#include <sqlite3.h>
#include <sqlite3_wrapper.h>
#include "infra/infra.h"
#include "jobs.hpp"
#include "utils/cli.h"
@ -29,7 +30,7 @@ void WalkFileOrDir(KggTaskQueue& queue, const std::filesystem::path& input_path,
continue;
}
fwprintf(stderr, L"[WARN] invalid path: %s\n", target_path.c_str());
fputws(std::format(L"[WARN] invalid path: {}\n", target_path.wstring()).c_str(), stderr);
}
}

View File

@ -5,10 +5,22 @@ project(libaes VERSION 0.0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Tiny AES in C (https://github.com/kokke/tiny-AES-c/) is licensed under the Unlicense license.
add_library(libaes STATIC aes.cpp)
set(SOURCES)
if (USE_WIN_CRYPTO)
list(APPEND SOURCES aes_win32.cpp)
else ()
# Tiny AES in C (https://github.com/kokke/tiny-AES-c/)
# is licensed under the Unlicense license.
list(APPEND SOURCES aes.cpp)
endif ()
add_library(libaes STATIC ${SOURCES})
target_include_directories(libaes
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
if (USE_WIN_CRYPTO)
target_link_libraries(libaes PRIVATE bcrypt)
target_compile_definitions(libaes PRIVATE USE_WIN_CRYPTO=1)
endif ()

View File

@ -129,9 +129,10 @@ void KeyExpansion(uint8_t* RoundKey, const uint8_t* Key) {
}
}
void AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) {
bool AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) {
KeyExpansion(ctx->RoundKey, key);
memcpy(ctx->Iv, iv, kBlockLen);
return true;
}
// This function adds the round key to state.
@ -331,7 +332,7 @@ inline void XorWithIv(uint8_t* buf, const uint8_t* Iv) {
}
}
void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) {
size_t AES_CBC_encrypt_buffer(AES_ctx* ctx, uint8_t* buf, size_t length) {
uint8_t* Iv = ctx->Iv;
for (size_t i = 0; i < length; i += kBlockLen) {
XorWithIv(buf, Iv);
@ -341,9 +342,10 @@ void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) {
}
/* store Iv in ctx for next call */
memcpy(ctx->Iv, Iv, kBlockLen);
return length;
}
void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) {
size_t AES_CBC_decrypt_buffer(AES_ctx* ctx, uint8_t* buf, size_t length) {
for (size_t i = 0; i < length; i += kBlockLen) {
uint8_t storeNextIv[kBlockLen];
memcpy(storeNextIv, buf, kBlockLen);
@ -352,6 +354,7 @@ void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) {
memcpy(ctx->Iv, storeNextIv, kBlockLen);
buf += kBlockLen;
}
return length;
}
} // namespace AES

24
third-party/aes/aes.h vendored
View File

@ -2,6 +2,12 @@
#include <cstdint>
#if USE_WIN_CRYPTO
#include <windows.h>
#include <bcrypt.h>
#endif
namespace AES {
constexpr size_t kKeyLen = 16; // Key length in bytes
@ -9,17 +15,29 @@ constexpr size_t kKeyExpansionSize = 176;
constexpr size_t kBlockLen = 16; // Block length in bytes - AES is 128b block only
struct AES_ctx {
#if USE_WIN_CRYPTO
BCRYPT_ALG_HANDLE hAlg;
BCRYPT_KEY_HANDLE hKey;
uint8_t iv[0x10];
#else
uint8_t RoundKey[kKeyExpansionSize];
uint8_t Iv[16];
#endif
};
void AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv);
bool AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv);
// buffer size MUST be mutile of AES_BLOCKLEN;
// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme
// NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv()
// no IV should ever be reused with the same key
void AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
void AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length);
size_t AES_CBC_encrypt_buffer(AES_ctx* ctx, uint8_t* buf, size_t length);
size_t AES_CBC_decrypt_buffer(AES_ctx* ctx, uint8_t* buf, size_t length);
#if USE_WIN_CRYPTO
bool AES_cleanup(AES_ctx* ctx);
#else
inline bool AES_cleanup(AES_ctx* ctx) {return true;}
#endif
} // namespace AES

59
third-party/aes/aes_win32.cpp vendored Normal file
View File

@ -0,0 +1,59 @@
#include <algorithm>
#include <cassert>
#include <cstring>
#include "aes.h"
namespace AES {
// ReSharper disable CppCStyleCast
bool AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) {
memset(ctx, 0, sizeof(AES_ctx));
if (BCryptOpenAlgorithmProvider(&ctx->hAlg, BCRYPT_AES_ALGORITHM, nullptr, 0) != 0) {
return false;
}
if (BCryptSetProperty(ctx->hAlg, BCRYPT_CHAINING_MODE, (PUCHAR)BCRYPT_CHAIN_MODE_CBC, sizeof(BCRYPT_CHAIN_MODE_CBC),
0) != 0) {
BCryptCloseAlgorithmProvider(ctx->hAlg, 0);
return false;
}
if (BCryptGenerateSymmetricKey(ctx->hAlg, &ctx->hKey, nullptr, 0, (PUCHAR)key, 0x10, 0) != 0) {
BCryptCloseAlgorithmProvider(ctx->hAlg, 0);
return false;
}
std::copy_n(iv, 0x10, ctx->iv);
return true;
}
// buffer size MUST be mutile of AES_BLOCKLEN;
// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme
// NOTES: you need to set IV in ctx via AES_init_ctx_iv() or AES_ctx_set_iv()
// no IV should ever be reused with the same key
size_t AES_CBC_encrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) {
// not implemented
return 0;
}
size_t AES_CBC_decrypt_buffer(struct AES_ctx* ctx, uint8_t* buf, size_t length) {
ULONG cbData{0};
const auto len = static_cast<ULONG>(length);
if (BCryptDecrypt(ctx->hKey, buf, len, nullptr, ctx->iv, sizeof(ctx->iv), buf, len, &cbData, 0) != 0) {
assert(false && "BCryptDecrypt failed");
return 0;
}
assert(cbData == len && "AES_CBC_decrypt_buffer: cbData != length");
return cbData;
}
bool AES_cleanup(AES_ctx* ctx) {
BCryptDestroyKey(ctx->hKey);
BCryptCloseAlgorithmProvider(ctx->hAlg, 0);
memset(ctx, 0, sizeof(AES_ctx));
return true;
}
} // namespace AES

View File

@ -5,11 +5,23 @@ project(md5 VERSION 0.0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Derived from the "RSA Data Security, Inc. MD5 Message-Digest Algorithm":
# https://github.com/freebsd/freebsd-src/blob/release/14.2.0/sys/kern/md5c.c
add_library(libmd5 STATIC md5.cpp)
set(SOURCES)
if (USE_WIN_CRYPTO)
list(APPEND SOURCES md5_win32.cpp)
else ()
# Derived from the "RSA Data Security, Inc. MD5 Message-Digest Algorithm":
# https://github.com/freebsd/freebsd-src/blob/release/14.2.0/sys/kern/md5c.c
list(APPEND SOURCES md5.cpp)
endif ()
add_library(libmd5 STATIC ${SOURCES})
target_include_directories(libmd5
PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
if (USE_WIN_CRYPTO)
target_link_libraries(libmd5 PRIVATE crypt32)
target_compile_definitions(libmd5 PRIVATE USE_WIN_CRYPTO=1)
endif ()

32
third-party/md5/md5.h vendored
View File

@ -1,7 +1,10 @@
#pragma once
// Derived from the "RSA Data Security, Inc. MD5 Message-Digest Algorithm":
// src: https://github.com/freebsd/freebsd-src/blob/release/14.2.0/sys/kern/md5c.c
#if USE_WIN_CRYPTO
#include <windows.h>
#include <wincrypt.h>
#endif
#include <cstdint>
@ -9,15 +12,23 @@
#define MD5_DIGEST_LENGTH 16
#define MD5_DIGEST_STRING_LENGTH (MD5_DIGEST_LENGTH * 2 + 1)
/* MD5 context. */
struct MD5_CTX {
#if USE_WIN_CRYPTO
HCRYPTPROV hProv;
HCRYPTHASH hHash;
#else
uint64_t count; /* number of bits, modulo 2^64 (lsb first) */
uint32_t state[4]; /* state (ABCD) */
unsigned char buffer[64]; /* input buffer */
#endif
};
#if USE_WIN_CRYPTO
bool md5_init(MD5_CTX* context);
bool md5_cleanup(MD5_CTX* ctx);
#else
/* MD5 initialization. Begins an MD5 operation, writing a new context. */
inline void md5_init(MD5_CTX* context) {
inline bool md5_init(MD5_CTX* context) {
context->count = 0;
/* Load magic initialization constants. */
@ -25,22 +36,31 @@ inline void md5_init(MD5_CTX* context) {
context->state[1] = 0xefcdab89;
context->state[2] = 0x98badcfe;
context->state[3] = 0x10325476;
return true;
}
inline bool md5_cleanup(MD5_CTX* ctx) {
return true;
}
#endif
void md5_update(MD5_CTX* ctx, const uint8_t* in, size_t len);
void md5_final(MD5_CTX* ctx, uint8_t* digest);
inline void md5(uint8_t* digest, const uint8_t* in, const size_t len) {
MD5_CTX ctx;
MD5_CTX ctx{};
md5_init(&ctx);
md5_update(&ctx, in, len);
md5_final(&ctx, digest);
md5_cleanup(&ctx);
}
inline void md5(uint8_t* digest, const uint8_t* in, const size_t len, const uint8_t* in2, size_t len2) {
MD5_CTX ctx;
MD5_CTX ctx{};
md5_init(&ctx);
md5_update(&ctx, in, len);
md5_update(&ctx, in2, len2);
md5_final(&ctx, digest);
md5_cleanup(&ctx);
}

35
third-party/md5/md5_win32.cpp vendored Normal file
View File

@ -0,0 +1,35 @@
#include "md5.h"
bool md5_init(MD5_CTX* ctx) {
if (!CryptAcquireContext(&ctx->hProv, nullptr, nullptr, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
return false;
}
// Create the hash object
if (!CryptCreateHash(ctx->hProv, CALG_MD5, 0, 0, &ctx->hHash)) {
CryptReleaseContext(ctx->hProv, 0);
return false;
}
return true;
}
void md5_update(MD5_CTX* ctx, const uint8_t* in, size_t len) {
CryptHashData(ctx->hHash, in, static_cast<DWORD>(len), 0);
}
void md5_final(MD5_CTX* ctx, uint8_t* digest) {
DWORD dataLen = MD5_DIGEST_LENGTH;
CryptGetHashParam(ctx->hHash, HP_HASHVAL, digest, &dataLen, 0);
CryptDestroyHash(ctx->hHash);
ctx->hHash = 0;
}
bool md5_cleanup(MD5_CTX* ctx) {
if (ctx->hHash) {
CryptDestroyHash(ctx->hHash);
}
CryptReleaseContext(ctx->hProv, 0);
memset(ctx, 0, sizeof(MD5_CTX));
return true;
}