Compare commits

...

9 Commits
v0.6.0 ... main

Author SHA1 Message Date
2554fc8e41 chore: bump version to v0.6.1 2024-12-22 01:53:34 +09:00
aeaa8933cd docs: update build command examples 2024-12-22 01:53:34 +09:00
b258cf41d0 build: use existing sqlite3 / openssl when enabled. 2024-12-22 01:53:17 +09:00
6f9d4c6aa6 refactor: various fixes
- Drop thread priority by 1
- Reduce signed/unsigned conversions
2024-12-22 01:36:14 +09:00
10e0c7446b refactor: get linux build 2024-12-21 13:23:44 +09:00
395d5628d8 docs: update readme about build 2024-12-21 13:19:57 +09:00
d19ed2d57f refactor: use winapi for aes/md5 procedure 2024-12-21 06:02:40 +09:00
f458c053c8 build: use WinSQLite3 when possible 2024-12-21 04:42:16 +09:00
f364acaec3 refactor: make code work with mingw 2024-12-20 04:56:17 -09:00
23 changed files with 606 additions and 121 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
build/
.cache/
cmake-build-*
.vscode/

View File

@ -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)
# 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}")

View File

@ -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",

View File

@ -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
View 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()

View File

@ -1,6 +1,7 @@
#pragma once
#include <bit>
#include <cstddef>
#include <cstdint>
#include <type_traits>

View 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
View 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__)

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,7 +1,6 @@
#pragma once
#include <filesystem>
#include <optional>
#include <unordered_map>
namespace Infra {

View File

@ -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, &params) != 0) {
perror("pthread_getschedparam");
return;
}
params.sched_priority = std::max(params.sched_priority, sched_get_priority_min(policy));
if (pthread_setschedparam(this_thread, policy, &params) != 0) {
perror("pthread_setschedparam");
}
#endif
}
std::mutex mutex_{};
std::condition_variable signal_;
std::queue<std::unique_ptr<KggTask>> tasks_{};

View File

@ -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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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 ()

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

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

@ -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
View 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
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,32 @@ project(md5 VERSION 0.0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
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
add_library(libmd5 STATIC md5.cpp)
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
View File

@ -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
View 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
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;
}