Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
2554fc8e41 | |||
aeaa8933cd | |||
b258cf41d0 | |||
6f9d4c6aa6 | |||
10e0c7446b | |||
395d5628d8 | |||
d19ed2d57f | |||
f458c053c8 | |||
f364acaec3 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
build/
|
||||
.cache/
|
||||
cmake-build-*
|
||||
.vscode/
|
||||
|
@ -1,10 +1,26 @@
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
|
||||
project(kgg-dec VERSION 0.6.0 LANGUAGES CXX)
|
||||
project(kgg-dec VERSION 0.6.1 LANGUAGES CXX)
|
||||
|
||||
option(USE_SYSTEM_SQLITE3 "Use system SQLite3 (if not using WinSQLite3)" ON)
|
||||
option(USE_OPENSSL "Use OpenSSL API (if not using WinCrypto API)" ON)
|
||||
|
||||
if(WIN32)
|
||||
option(USE_WIN_SQLITE3 "Use Windows SQLite3 (MSVC Only)" ${MSVC})
|
||||
option(USE_WIN_CRYPTO "Use Windows Crypto API" ${WIN32})
|
||||
else()
|
||||
set(USE_WIN_SQLITE3 OFF)
|
||||
set(USE_WIN_CRYPTO OFF)
|
||||
endif()
|
||||
|
||||
# Setup CryptoAPI
|
||||
if (NOT USE_WIN_CRYPTO AND USE_OPENSSL)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
endif()
|
||||
|
||||
include(cmake/SetupSQLite3.cmake)
|
||||
add_subdirectory(third-party/aes)
|
||||
add_subdirectory(third-party/md5)
|
||||
add_subdirectory(third-party/sqlite3)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
@ -27,6 +43,29 @@ target_include_directories(kgg-dec
|
||||
src/tc_tea
|
||||
)
|
||||
|
||||
target_link_libraries(kgg-dec PRIVATE shell32 ole32 libaes libmd5 sqlite3)
|
||||
target_compile_definitions(kgg-dec PRIVATE NOMINMAX)
|
||||
# Base crypto implementations
|
||||
target_link_libraries(kgg-dec PRIVATE libaes libmd5)
|
||||
if (USE_WIN_CRYPTO)
|
||||
target_compile_definitions(kgg-dec PRIVATE USE_WIN_CRYPTO=1)
|
||||
elseif(USE_OPENSSL)
|
||||
target_compile_definitions(kgg-dec PRIVATE USE_OPENSSL=1)
|
||||
endif ()
|
||||
|
||||
# Win32 specific
|
||||
if (WIN32)
|
||||
target_link_libraries(kgg-dec PRIVATE shell32 ole32)
|
||||
target_compile_definitions(kgg-dec PRIVATE NOMINMAX)
|
||||
endif ()
|
||||
|
||||
# SQLite3
|
||||
if (WinSQLite3_Found)
|
||||
target_link_libraries(kgg-dec PRIVATE WinSQLite3)
|
||||
target_include_directories(kgg-dec PRIVATE ${WindowsKitInclude})
|
||||
elseif(SQLite3_FOUND)
|
||||
target_link_libraries(kgg-dec PRIVATE SQLite::SQLite3)
|
||||
else ()
|
||||
target_link_libraries(kgg-dec PRIVATE sqlite3)
|
||||
endif ()
|
||||
|
||||
# Extra definitions
|
||||
target_compile_definitions(kgg-dec PRIVATE KGGDEC_PROJECT_VERSION="${PROJECT_VERSION}")
|
||||
|
@ -9,7 +9,7 @@
|
||||
{
|
||||
"name": "default",
|
||||
"hidden": true,
|
||||
"generator": "Ninja",
|
||||
"generator": "Ninja Multi-Config",
|
||||
"cacheVariables": {
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
}
|
||||
@ -29,13 +29,22 @@
|
||||
"CMAKE_EXE_LINKER_FLAGS": "-static -static-libgcc -static-libstdc++ -lucrt"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ninja",
|
||||
"inherits": "default",
|
||||
"description": "Default Ninja Configuration",
|
||||
"binaryDir": "${sourceDir}/build/default"
|
||||
},
|
||||
{
|
||||
"name": "vs",
|
||||
"inherits": "default",
|
||||
"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": [
|
||||
@ -51,6 +60,18 @@
|
||||
"description": "Build using MinGW (Release)",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "ninja-debug",
|
||||
"configurePreset": "ninja",
|
||||
"description": "Build using Ninja (Debug)",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "ninja-release",
|
||||
"configurePreset": "ninja",
|
||||
"description": "Build using Ninja (Release)",
|
||||
"configuration": "Release"
|
||||
},
|
||||
{
|
||||
"name": "vs-debug",
|
||||
"configurePreset": "vs",
|
||||
|
32
README.MD
32
README.MD
@ -50,7 +50,30 @@ cmake --build --preset vs-release --config Release
|
||||
copy /y README.MD .\\build\\vs2022\\
|
||||
```
|
||||
|
||||
## 第三方软件
|
||||
### CMake 参数
|
||||
|
||||
CMake 支持以下参数:
|
||||
|
||||
- `USE_WIN_SQLITE3` - 使用 Windows 内置的 SQLite3 链接库(仅限 Windows + MSVC 编译环境)。
|
||||
- `USE_WIN_CRYPTO` - 使用 Windows 内置的加密/哈希实现,而非软件实现(仅限 Windows 目标)。
|
||||
- `USE_SYSTEM_SQLITE3` - 使用外部的 SQLite3 实现 (Debian: `libsqlite3-dev`),找不到时会报错。
|
||||
- `USE_OpenSSL` - 使用 OpenSSL 的加密/哈希实现 (Debian: `libssl-dev`),找不到时会报错。
|
||||
|
||||
Ubuntu 下编译发布版,不使用外部库:
|
||||
|
||||
```sh
|
||||
cmake -G "Ninja Multi-Config" -B build/linux-all -DUSE_SYSTEM_SQLITE3=0 -DUSE_OPENSSL=0
|
||||
cmake --build build/linux-all --config Release -j
|
||||
```
|
||||
|
||||
Ubuntu 下编译发布版,链接到现有的库:
|
||||
|
||||
```sh
|
||||
cmake -G "Ninja Multi-Config" -B build/linux-ext -DUSE_SYSTEM_SQLITE3=1 -DUSE_OPENSSL=1
|
||||
cmake --build build/linux-ext --config Release -j
|
||||
```
|
||||
|
||||
### 第三方软件
|
||||
|
||||
该程序用到了以下第三方软件:
|
||||
|
||||
@ -58,3 +81,10 @@ copy /y README.MD .\\build\\vs2022\\
|
||||
- [Tiny AES in C](https://github.com/kokke/tiny-AES-c) (Public Domain)
|
||||
- [MD5.c](https://github.com/freebsd/freebsd-src/blob/release/14.2.0/sys/kern/md5c.c) (from FreeBSD)
|
||||
- Derived from the "RSA Data Security, Inc. MD5 Message-Digest Algorithm".
|
||||
|
||||
### Windows 7 用户注意
|
||||
|
||||
请从 SQLite 官网下载 [`sqlite-dll-win-x64-*.zip`](https://www.sqlite.org/download.html#win32)
|
||||
并将压缩包内的 `sqlite3.dll` 放置到 `kgg-dec.exe` 同目录下,并更名为 `winsqlite3.dll`。
|
||||
|
||||
Windows 10 或更新版本不需要此操作,因为 Windows 10 或以上的版本内置该文件。
|
||||
|
24
cmake/SetupSQLite3.cmake
Normal file
24
cmake/SetupSQLite3.cmake
Normal file
@ -0,0 +1,24 @@
|
||||
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 ()
|
||||
|
||||
if (NOT WinSQLite3_Found)
|
||||
if (USE_SYSTEM_SQLITE3)
|
||||
message("Using existing SQLite3.")
|
||||
find_package(SQLite3 REQUIRED)
|
||||
else()
|
||||
message("including sqlite3 to the build")
|
||||
add_subdirectory(third-party/sqlite3)
|
||||
endif()
|
||||
endif()
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
|
7
src/common/sqlite3_wrapper.h
Normal file
7
src/common/sqlite3_wrapper.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#if __has_include(<sqlite3.h>)
|
||||
#include <sqlite3.h>
|
||||
#else
|
||||
#include <winsqlite/winsqlite3.h>
|
||||
#endif
|
38
src/common/str_helper.h
Normal file
38
src/common/str_helper.h
Normal file
@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace lsr {
|
||||
|
||||
#if _WIN32
|
||||
typedef wchar_t character;
|
||||
typedef std::wstring string;
|
||||
typedef std::wstring_view string_view;
|
||||
#define LSR_STR(x) L##x
|
||||
|
||||
inline void write_stderr(const string& msg) {
|
||||
fputws(msg.c_str(), stderr);
|
||||
}
|
||||
#else
|
||||
typedef char character;
|
||||
typedef std::string string;
|
||||
typedef std::string_view string_view;
|
||||
#define LSR_STR(x) x
|
||||
|
||||
inline void write_stderr(const string& msg) {
|
||||
fputs(msg.c_str(), stderr);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace lsr
|
||||
|
||||
#if _WIN32
|
||||
#define lsr___fprintf fwprintf
|
||||
#else
|
||||
#define lsr___fprintf fprintf
|
||||
#endif
|
||||
|
||||
#define lsr_eprintf(fmt, ...) lsr___fprintf(stderr, LSR_STR(fmt), __VA_ARGS__)
|
||||
#define lsr_printf(fmt, ...) lsr___fprintf(stdout, LSR_STR(fmt), __VA_ARGS__)
|
@ -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;
|
||||
|
@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Infra {
|
||||
|
129
src/jobs.hpp
129
src/jobs.hpp
@ -1,52 +1,58 @@
|
||||
#pragma once
|
||||
#include <windows.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include "infra/infra.h"
|
||||
#include "qmc2/qmc2.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#include <endian_helper.h>
|
||||
#include <str_helper.h>
|
||||
#include <array>
|
||||
#include <condition_variable>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#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 log(const lsr::string& level, const lsr::string& msg) const {
|
||||
lsr_eprintf("[%s] %s (%s)\n", level.c_str(), msg.c_str(), kgg_path_.filename().c_str());
|
||||
}
|
||||
void warning(const lsr::string& msg) const { log(LSR_STR("WARN"), msg); }
|
||||
void error(const lsr::string& msg) const { log(LSR_STR("ERR "), msg); }
|
||||
void info(const lsr::string& msg) const { log(LSR_STR("INFO"), msg); }
|
||||
|
||||
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()); }
|
||||
|
||||
void Execute(const Infra::kgm_ekey_db_t& ekey_db, const std::wstring_view suffix) const {
|
||||
void Execute(const Infra::kgm_ekey_db_t& ekey_db, const lsr::string_view suffix) const {
|
||||
constexpr static std::array<uint8_t, 16> kMagicHeader{0x7C, 0xD5, 0x32, 0xEB, 0x86, 0x02, 0x7F, 0x4B,
|
||||
0xA8, 0xAF, 0xA6, 0x8E, 0x0F, 0xFF, 0x99, 0x14};
|
||||
|
||||
std::ifstream kgg_stream_in(kgg_path_, std::ios::binary);
|
||||
char header[0x100]{};
|
||||
kgg_stream_in.read(header, sizeof(header));
|
||||
if (std::equal(kMagicHeader.cbegin(), kMagicHeader.cend(), header)) {
|
||||
warning(L"invalid kgg header");
|
||||
std::array<uint8_t, 0x400> header{};
|
||||
kgg_stream_in.read(reinterpret_cast<char*>(header.data()), header.size());
|
||||
if (!std::equal(kMagicHeader.cbegin(), kMagicHeader.cend(), header.cbegin())) {
|
||||
warning(LSR_STR("invalid kgg header (not a kgg file)"));
|
||||
return;
|
||||
}
|
||||
const uint32_t offset_to_audio = *reinterpret_cast<uint32_t*>(&header[0x10]);
|
||||
const uint32_t encrypt_mode = *reinterpret_cast<uint32_t*>(&header[0x14]);
|
||||
if (encrypt_mode != 5) {
|
||||
warning(std::format(L"unsupported enc_version (expect=0x05, got 0x{:02x})", encrypt_mode));
|
||||
const auto offset_to_audio = Endian::le_read<uint32_t>(&header[0x10]);
|
||||
if (const auto mode = Endian::le_read<uint32_t>(&header[0x14]); mode != 5) {
|
||||
lsr_eprintf("[WARN] unsupported enc_version (expect=0x05, got 0x%02x) (%s)\n", mode,
|
||||
kgg_path_.filename().c_str());
|
||||
return;
|
||||
}
|
||||
uint32_t audio_hash_len = *reinterpret_cast<uint32_t*>(&header[0x44]);
|
||||
if (audio_hash_len != 0x20) {
|
||||
warning(std::format(L"audio hash length invalid (expect=0x20, got 0x{:02x})", audio_hash_len));
|
||||
lsr_eprintf("audio hash length invalid (expect=0x20, got 0x%02x) (%s)\n", audio_hash_len,
|
||||
kgg_path_.filename().c_str());
|
||||
return;
|
||||
}
|
||||
std::string audio_hash(&header[0x48], &header[0x48 + audio_hash_len]);
|
||||
@ -54,70 +60,77 @@ class KggTask {
|
||||
if (auto it = ekey_db.find(audio_hash); it != ekey_db.end()) {
|
||||
ekey = it->second;
|
||||
} else {
|
||||
warning(L"ekey not found");
|
||||
warning(LSR_STR("ekey not found"));
|
||||
return;
|
||||
}
|
||||
|
||||
auto qmc2 = QMC2::Create(ekey);
|
||||
if (!qmc2) {
|
||||
error(L"create qmc2 instance failed (ekey decode error?)");
|
||||
error(LSR_STR("create qmc2 instance failed (ekey decode error?)"));
|
||||
fprintf(stderr, "%s\n", ekey.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
std::string magic(4, 0);
|
||||
std::array<uint8_t, 4> magic{};
|
||||
kgg_stream_in.seekg(offset_to_audio, std::ios::beg);
|
||||
kgg_stream_in.read(magic.data(), 4);
|
||||
qmc2->Decrypt(std::span(reinterpret_cast<uint8_t*>(magic.data()), 4), 0);
|
||||
kgg_stream_in.read(reinterpret_cast<char*>(magic.data()), 4);
|
||||
qmc2->Decrypt(magic, 0);
|
||||
auto real_ext = DetectRealExt(magic);
|
||||
auto out_path = out_dir_ / std::format(L"{}{}.{}", kgg_path_.stem().wstring(), suffix, real_ext);
|
||||
|
||||
lsr::string new_name = kgg_path_.stem().native() + lsr::string(suffix) + LSR_STR(".") + real_ext;
|
||||
auto out_path = out_dir_ / new_name;
|
||||
|
||||
if (exists(out_path)) {
|
||||
warning(std::format(L"output file already exists: {}", out_path.filename().wstring()));
|
||||
warning(lsr::string(LSR_STR("output file already exists: ")) + new_name);
|
||||
return;
|
||||
}
|
||||
|
||||
kgg_stream_in.seekg(0, std::ios::end);
|
||||
const auto file_size = static_cast<size_t>(kgg_stream_in.tellg());
|
||||
kgg_stream_in.seekg(offset_to_audio, std::ios::beg);
|
||||
std::ofstream ofs_decrypted(out_path, std::ios::binary);
|
||||
if (!ofs_decrypted.is_open()) {
|
||||
error(L"failed to open output file");
|
||||
error(LSR_STR("failed to open output file"));
|
||||
return;
|
||||
}
|
||||
|
||||
size_t offset{0};
|
||||
thread_local std::vector<uint8_t> temp_buffer(1024 * 1024, 0);
|
||||
auto buf_signed = std::span(reinterpret_cast<char*>(temp_buffer.data()), temp_buffer.size());
|
||||
auto buf_unsigned = std::span(temp_buffer);
|
||||
auto read_page_len = static_cast<std::streamsize>(temp_buffer.size());
|
||||
|
||||
while (!kgg_stream_in.eof()) {
|
||||
kgg_stream_in.read(buf_signed.data(), buf_signed.size());
|
||||
const auto n = static_cast<size_t>(kgg_stream_in.gcount());
|
||||
qmc2->Decrypt(buf_unsigned.subspan(0, n), offset);
|
||||
ofs_decrypted.write(buf_signed.data(), n);
|
||||
kgg_stream_in.read(reinterpret_cast<char*>(temp_buffer.data()), read_page_len);
|
||||
const auto n = kgg_stream_in.gcount();
|
||||
qmc2->Decrypt(std::span(temp_buffer.begin(), temp_buffer.begin() + n), offset);
|
||||
ofs_decrypted.write(reinterpret_cast<char*>(temp_buffer.data()), n);
|
||||
offset += n;
|
||||
}
|
||||
|
||||
info(std::format(L"** OK ** -> {}", out_path.filename().wstring()));
|
||||
if (offset + offset_to_audio != file_size) {
|
||||
warning(LSR_STR("OK (size mismatch)"));
|
||||
} else {
|
||||
info(lsr::string(LSR_STR("** OK ** -> ")) + out_path.filename().native());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::filesystem::path kgg_path_;
|
||||
std::filesystem::path out_dir_;
|
||||
|
||||
static const wchar_t* DetectRealExt(const std::string_view magic) {
|
||||
if (magic == "fLaC") {
|
||||
return L"flac";
|
||||
static const lsr::character* DetectRealExt(const std::span<uint8_t> magic) {
|
||||
if (std::equal(magic.begin(), magic.end(), "fLaC")) {
|
||||
return LSR_STR("flac");
|
||||
}
|
||||
if (magic == "OggS") {
|
||||
return L"ogg";
|
||||
if (std::equal(magic.begin(), magic.end(), "OggS")) {
|
||||
return LSR_STR("ogg");
|
||||
}
|
||||
return L"mp3";
|
||||
return LSR_STR("mp3");
|
||||
}
|
||||
};
|
||||
|
||||
class KggTaskQueue {
|
||||
public:
|
||||
explicit KggTaskQueue(Infra::kgm_ekey_db_t ekey_db, const std::wstring_view suffix)
|
||||
explicit KggTaskQueue(Infra::kgm_ekey_db_t ekey_db, const lsr::string_view suffix)
|
||||
: ekey_db_(std::move(ekey_db)), suffix_(suffix) {}
|
||||
|
||||
void Push(std::unique_ptr<KggTask> task) {
|
||||
@ -161,16 +174,34 @@ class KggTaskQueue {
|
||||
private:
|
||||
bool thread_end_{false};
|
||||
Infra::kgm_ekey_db_t ekey_db_;
|
||||
std::wstring suffix_;
|
||||
lsr::string suffix_;
|
||||
void WorkerThreadBody() {
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||
|
||||
ReduceThreadPriority();
|
||||
std::unique_ptr<KggTask> task{nullptr};
|
||||
while ((task = Pop())) {
|
||||
task->Execute(ekey_db_, suffix_);
|
||||
}
|
||||
}
|
||||
|
||||
static void ReduceThreadPriority() {
|
||||
#ifdef _WIN32
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||
#else
|
||||
pthread_t this_thread = pthread_self();
|
||||
sched_param params;
|
||||
int policy;
|
||||
|
||||
if (pthread_getschedparam(this_thread, &policy, ¶ms) != 0) {
|
||||
perror("pthread_getschedparam");
|
||||
return;
|
||||
}
|
||||
params.sched_priority = std::max(params.sched_priority, sched_get_priority_min(policy));
|
||||
if (pthread_setschedparam(this_thread, policy, ¶ms) != 0) {
|
||||
perror("pthread_setschedparam");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::mutex mutex_{};
|
||||
std::condition_variable signal_;
|
||||
std::queue<std::unique_ptr<KggTask>> tasks_{};
|
||||
|
15
src/main.cpp
15
src/main.cpp
@ -1,6 +1,11 @@
|
||||
#include <sqlite3.h>
|
||||
#include <sqlite3_wrapper.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <queue>
|
||||
|
||||
#include "infra/infra.h"
|
||||
#include "jobs.hpp"
|
||||
#include "str_helper.h"
|
||||
#include "utils/cli.h"
|
||||
|
||||
using Infra::kgm_ekey_db_t;
|
||||
@ -29,7 +34,7 @@ void WalkFileOrDir(KggTaskQueue& queue, const std::filesystem::path& input_path,
|
||||
continue;
|
||||
}
|
||||
|
||||
fwprintf(stderr, L"[WARN] invalid path: %s\n", target_path.c_str());
|
||||
lsr_eprintf("[WARN] invalid path: %s\n", target_path.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,11 +60,11 @@ void print_banner() {
|
||||
print_usage();
|
||||
}
|
||||
|
||||
int main() {
|
||||
int main(int argc, char** argv) {
|
||||
CliParser cli_args;
|
||||
print_banner();
|
||||
|
||||
cli_args.parse_from_cli();
|
||||
cli_args.parse_from_cli(argc, argv);
|
||||
|
||||
bool scan_all_exts = cli_args.get_scan_all_file_ext();
|
||||
|
||||
@ -104,7 +109,7 @@ int main() {
|
||||
|
||||
auto input_files = cli_args.get_input_files();
|
||||
if (input_files.empty()) {
|
||||
input_files.emplace_back(L".");
|
||||
input_files.emplace_back(LSR_STR("."));
|
||||
}
|
||||
for (auto& positional_arg : input_files) {
|
||||
WalkFileOrDir(queue, positional_arg, scan_all_exts);
|
||||
|
@ -1,47 +1,58 @@
|
||||
#include "cli.h"
|
||||
#include <str_helper.h>
|
||||
|
||||
#include <Windows.h>
|
||||
#include <clocale>
|
||||
|
||||
#ifdef _WIN32
|
||||
// clang-format off
|
||||
#include <clocale>
|
||||
#include <Windows.h>
|
||||
#include <shlobj.h>
|
||||
#include <knownfolders.h>
|
||||
// clang-format on
|
||||
#endif
|
||||
|
||||
CliParser::CliParser() {
|
||||
#ifdef _WIN32
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
setlocale(LC_ALL, ".UTF8");
|
||||
#endif
|
||||
}
|
||||
|
||||
void CliParser::parse_from_cli() {
|
||||
int argc;
|
||||
void CliParser::parse_from_cli(int argc_, char** argv_) {
|
||||
int argc{argc_};
|
||||
#ifdef _WIN32
|
||||
wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
#else
|
||||
char**& argv = argv_;
|
||||
#endif
|
||||
|
||||
std::vector<std::wstring> positional_args{};
|
||||
std::unordered_map<std::wstring, std::wstring> named_args{};
|
||||
std::vector<lsr::string> positional_args{};
|
||||
std::unordered_map<lsr::string, lsr::string> named_args{};
|
||||
|
||||
bool positional_only{false};
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::wstring arg{argv[i]};
|
||||
if (arg == L"--") {
|
||||
lsr::string arg{argv[i]};
|
||||
if (arg == LSR_STR("--")) {
|
||||
positional_only = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!positional_only && arg.starts_with(L"--")) {
|
||||
if (!positional_only && arg.starts_with(LSR_STR("--"))) {
|
||||
auto pos = arg.find(L'=');
|
||||
if (pos != std::wstring::npos) {
|
||||
if (pos != lsr::string::npos) {
|
||||
named_args[arg.substr(2, pos - 2)] = arg.substr(pos + 1);
|
||||
} else if (++i < argc) {
|
||||
named_args[arg.substr(2)] = argv[i];
|
||||
} else {
|
||||
named_args[arg.substr(2)] = L"";
|
||||
named_args[arg.substr(2)] = LSR_STR("");
|
||||
}
|
||||
} else {
|
||||
positional_args.push_back(arg);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
LocalFree(argv);
|
||||
#endif
|
||||
|
||||
positional_args_ = positional_args;
|
||||
named_args_ = named_args;
|
||||
@ -49,13 +60,18 @@ void CliParser::parse_from_cli() {
|
||||
|
||||
std::filesystem::path CliParser::get_db_path() const {
|
||||
std::filesystem::path kugou_db{};
|
||||
if (const auto& it = named_args_.find(L"db"); it != named_args_.end()) {
|
||||
|
||||
if (const auto& it = named_args_.find(LSR_STR("db")); it != named_args_.end()) {
|
||||
kugou_db = std::filesystem::path{it->second};
|
||||
} else {
|
||||
#ifdef _WIN32
|
||||
PWSTR pAppDirPath{};
|
||||
SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pAppDirPath);
|
||||
kugou_db = std::filesystem::path{pAppDirPath} / L"Kugou8" / L"KGMusicV3.db";
|
||||
CoTaskMemFree(pAppDirPath);
|
||||
#else
|
||||
kugou_db = std::filesystem::path{"KGMusicV3.db"};
|
||||
#endif
|
||||
}
|
||||
|
||||
return absolute(kugou_db);
|
||||
|
@ -1,30 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <str_helper.h>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
typedef std::pair<std::vector<std::wstring>, std::unordered_map<std::wstring, std::wstring>> parsed_raw_args_t;
|
||||
typedef std::pair<std::vector<lsr::string>, std::unordered_map<lsr::string, lsr::string>> parsed_raw_args_t;
|
||||
|
||||
class CliParser {
|
||||
public:
|
||||
CliParser();
|
||||
void parse_from_cli();
|
||||
void parse_from_cli(int argc, char** argv);
|
||||
|
||||
[[nodiscard]] std::filesystem::path get_infra_dll() const;
|
||||
[[nodiscard]] std::filesystem::path get_db_path() const;
|
||||
[[nodiscard]] std::wstring get_file_suffix() const { return get_with_default(L"suffix", L"_kgg-dec"); }
|
||||
[[nodiscard]] bool get_scan_all_file_ext() const { return get_with_default(L"scan-all-file-ext", L"0") == L"1"; };
|
||||
[[nodiscard]] std::vector<std::wstring> get_input_files() const { return positional_args_; }
|
||||
[[nodiscard]] lsr::string get_file_suffix() const {
|
||||
return get_with_default(LSR_STR("suffix"), LSR_STR("_kgg-dec"));
|
||||
}
|
||||
[[nodiscard]] bool get_scan_all_file_ext() const {
|
||||
return get_with_default(LSR_STR("scan-all-file-ext"), LSR_STR("0")) == LSR_STR("1");
|
||||
};
|
||||
[[nodiscard]] std::vector<lsr::string> get_input_files() const { return positional_args_; }
|
||||
|
||||
private:
|
||||
std::vector<std::wstring> positional_args_{};
|
||||
std::unordered_map<std::wstring, std::wstring> named_args_{};
|
||||
std::vector<lsr::string> positional_args_{};
|
||||
std::unordered_map<lsr::string, lsr::string> named_args_{};
|
||||
|
||||
static parsed_raw_args_t parse();
|
||||
[[nodiscard]] std::wstring get_with_default(const std::wstring& key, const std::wstring& default_value) const {
|
||||
[[nodiscard]] lsr::string get_with_default(const lsr::string& key, const lsr::string& default_value) const {
|
||||
if (const auto& it = named_args_.find(key); it != named_args_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
25
third-party/aes/CMakeLists.txt
vendored
25
third-party/aes/CMakeLists.txt
vendored
@ -5,10 +5,31 @@ 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)
|
||||
message("Using Windows Crypto API for AES-CBC-128")
|
||||
list(APPEND SOURCES aes_win32.cpp)
|
||||
elseif (USE_OPENSSL)
|
||||
message("Using OpenSSL API for AES-CBC-128")
|
||||
find_package(OpenSSL REQUIRED)
|
||||
list(APPEND SOURCES aes_openssl.cpp)
|
||||
else ()
|
||||
# Tiny AES in C (https://github.com/kokke/tiny-AES-c/)
|
||||
# is licensed under the Unlicense license.
|
||||
message("Using included AES-CBC-128 implementation")
|
||||
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)
|
||||
elseif (USE_OPENSSL)
|
||||
target_link_libraries(libaes PRIVATE OpenSSL::Crypto)
|
||||
target_compile_definitions(libaes PRIVATE USE_OPENSSL=1)
|
||||
endif ()
|
||||
|
9
third-party/aes/aes.cpp
vendored
9
third-party/aes/aes.cpp
vendored
@ -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
|
||||
|
31
third-party/aes/aes.h
vendored
31
third-party/aes/aes.h
vendored
@ -1,7 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#if USE_WIN_CRYPTO
|
||||
#include <windows.h>
|
||||
|
||||
#include <bcrypt.h>
|
||||
#elif USE_OPENSSL
|
||||
#include <openssl/evp.h>
|
||||
#endif
|
||||
|
||||
namespace AES {
|
||||
|
||||
constexpr size_t kKeyLen = 16; // Key length in bytes
|
||||
@ -9,17 +18,33 @@ 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];
|
||||
#elif USE_OPENSSL
|
||||
EVP_CIPHER_CTX* cipher_ctx;
|
||||
#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 || USE_OPENSSL
|
||||
bool AES_cleanup(AES_ctx* ctx);
|
||||
#else
|
||||
inline bool AES_cleanup(AES_ctx* ctx) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace AES
|
||||
|
43
third-party/aes/aes_openssl.cpp
vendored
Normal file
43
third-party/aes/aes_openssl.cpp
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
#include "aes.h"
|
||||
|
||||
namespace AES {
|
||||
|
||||
bool AES_init_ctx_iv(AES_ctx* ctx, const uint8_t* key, const uint8_t* iv) {
|
||||
ctx->cipher_ctx = EVP_CIPHER_CTX_new();
|
||||
if (!ctx->cipher_ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (EVP_DecryptInit_ex(ctx->cipher_ctx, EVP_aes_128_cbc(), nullptr, key, iv) != 1) {
|
||||
AES_cleanup(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_set_padding(ctx->cipher_ctx, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
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) {
|
||||
auto buf_len = static_cast<int>(length);
|
||||
EVP_DecryptUpdate(ctx->cipher_ctx, buf, &buf_len, buf, buf_len);
|
||||
assert(buf_len == length && "AES_CBC_decrypt_buffer: buffer length mismatch");
|
||||
return buf_len;
|
||||
}
|
||||
|
||||
bool AES_cleanup(AES_ctx* ctx) {
|
||||
if (ctx->cipher_ctx) {
|
||||
EVP_CIPHER_CTX_free(ctx->cipher_ctx);
|
||||
ctx->cipher_ctx = nullptr;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AES
|
59
third-party/aes/aes_win32.cpp
vendored
Normal file
59
third-party/aes/aes_win32.cpp
vendored
Normal 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
|
27
third-party/md5/CMakeLists.txt
vendored
27
third-party/md5/CMakeLists.txt
vendored
@ -5,11 +5,32 @@ 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)
|
||||
message("Using Windows Crypto API for MD5")
|
||||
list(APPEND SOURCES md5_win32.cpp)
|
||||
elseif (USE_OPENSSL)
|
||||
message("Using OpenSSL API for MD5")
|
||||
find_package(OpenSSL REQUIRED)
|
||||
list(APPEND SOURCES md5_openssl.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
|
||||
message("Using included MD5 implementation")
|
||||
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)
|
||||
elseif (USE_OPENSSL)
|
||||
target_link_libraries(libmd5 PRIVATE OpenSSL::Crypto)
|
||||
target_compile_definitions(libmd5 PRIVATE USE_OPENSSL=1)
|
||||
endif ()
|
||||
|
42
third-party/md5/md5.h
vendored
42
third-party/md5/md5.h
vendored
@ -1,23 +1,36 @@
|
||||
#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
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#define MD5_BLOCK_LENGTH 64
|
||||
#define MD5_DIGEST_LENGTH 16
|
||||
#define MD5_DIGEST_STRING_LENGTH (MD5_DIGEST_LENGTH * 2 + 1)
|
||||
#if USE_WIN_CRYPTO
|
||||
#include <windows.h>
|
||||
|
||||
#include <wincrypt.h>
|
||||
#elif USE_OPENSSL
|
||||
#include <openssl/evp.h>
|
||||
#endif
|
||||
|
||||
#define MD5_DIGEST_LENGTH 16
|
||||
|
||||
/* MD5 context. */
|
||||
struct MD5_CTX {
|
||||
#if USE_WIN_CRYPTO
|
||||
HCRYPTPROV hProv;
|
||||
HCRYPTHASH hHash;
|
||||
#elif USE_OPENSSL
|
||||
EVP_MD_CTX* md_ctx;
|
||||
#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 || USE_OPENSSL
|
||||
bool md5_init(MD5_CTX* ctx);
|
||||
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 +38,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);
|
||||
}
|
||||
|
32
third-party/md5/md5_openssl.cpp
vendored
Normal file
32
third-party/md5/md5_openssl.cpp
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
#include <openssl/evp.h>
|
||||
#include <cstring>
|
||||
#include "md5.h"
|
||||
|
||||
bool md5_init(MD5_CTX* ctx) {
|
||||
memset(ctx, 0, sizeof(*ctx));
|
||||
|
||||
ctx->md_ctx = EVP_MD_CTX_new();
|
||||
if (!ctx->md_ctx) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return EVP_DigestInit_ex(ctx->md_ctx, EVP_md5(), nullptr) == 1;
|
||||
}
|
||||
|
||||
bool md5_cleanup(MD5_CTX* ctx) {
|
||||
if (ctx->md_ctx != nullptr) {
|
||||
EVP_MD_CTX_free(ctx->md_ctx);
|
||||
}
|
||||
memset(ctx, 0xcc, sizeof(*ctx));
|
||||
return true;
|
||||
}
|
||||
|
||||
void md5_update(MD5_CTX* ctx, const uint8_t* in, size_t len) {
|
||||
EVP_DigestUpdate(ctx->md_ctx, in, len);
|
||||
}
|
||||
|
||||
void md5_final(MD5_CTX* ctx, uint8_t* digest) {
|
||||
unsigned int len{MD5_DIGEST_LENGTH};
|
||||
EVP_DigestFinal_ex(ctx->md_ctx, digest, &len);
|
||||
EVP_MD_CTX_reset(ctx->md_ctx);
|
||||
}
|
35
third-party/md5/md5_win32.cpp
vendored
Normal file
35
third-party/md5/md5_win32.cpp
vendored
Normal 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;
|
||||
}
|
Loading…
Reference in New Issue
Block a user