Compare commits

...

2 Commits

Author SHA1 Message Date
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
11 changed files with 166 additions and 62 deletions

View File

@ -35,7 +35,10 @@ target_include_directories(kgg-dec
src/tc_tea src/tc_tea
) )
target_link_libraries(kgg-dec PRIVATE shell32 ole32 libaes libmd5) target_link_libraries(kgg-dec PRIVATE libaes libmd5)
if (WIN32)
target_link_libraries(kgg-dec PRIVATE shell32 ole32)
endif ()
if (WinSQLite3_Found) if (WinSQLite3_Found)
target_link_libraries(kgg-dec PRIVATE WinSQLite3) target_link_libraries(kgg-dec PRIVATE WinSQLite3)
target_include_directories(kgg-dec PRIVATE ${WindowsKitInclude}) target_include_directories(kgg-dec PRIVATE ${WindowsKitInclude})
@ -47,4 +50,4 @@ target_compile_definitions(kgg-dec PRIVATE KGGDEC_PROJECT_VERSION="${PROJECT_VER
if (USE_WIN_CRYPTO) if (USE_WIN_CRYPTO)
target_compile_definitions(kgg-dec PRIVATE USE_WIN_CRYPTO=1) target_compile_definitions(kgg-dec PRIVATE USE_WIN_CRYPTO=1)
endif () endif ()

View File

@ -9,7 +9,7 @@
{ {
"name": "default", "name": "default",
"hidden": true, "hidden": true,
"generator": "Ninja", "generator": "Ninja Multi-Config",
"cacheVariables": { "cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON" "CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
} }
@ -29,6 +29,12 @@
"CMAKE_EXE_LINKER_FLAGS": "-static -static-libgcc -static-libstdc++ -lucrt" "CMAKE_EXE_LINKER_FLAGS": "-static -static-libgcc -static-libstdc++ -lucrt"
} }
}, },
{
"name": "ninja",
"inherits": "default",
"description": "Default Ninja Configuration",
"binaryDir": "${sourceDir}/build/default"
},
{ {
"name": "vs", "name": "vs",
"inherits": "default", "inherits": "default",
@ -54,6 +60,18 @@
"description": "Build using MinGW (Release)", "description": "Build using MinGW (Release)",
"configuration": "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", "name": "vs-debug",
"configurePreset": "vs", "configurePreset": "vs",

View File

@ -50,7 +50,14 @@ cmake --build --preset vs-release --config Release
copy /y README.MD .\\build\\vs2022\\ copy /y README.MD .\\build\\vs2022\\
``` ```
## 第三方软件 ### CMake 参数
CMake 支持以下参数:
- `USE_WIN_SQLITE3` - 使用 Windows 内置的 SQLite3 链接库(仅限 Windows + MSVC 编译环境)。
- `USE_WIN_CRYPTO` - 使用 Windows 内置的加密/哈希实现,而非软件实现(仅限 Windows 目标)。
### 第三方软件
该程序用到了以下第三方软件: 该程序用到了以下第三方软件:
@ -58,3 +65,10 @@ copy /y README.MD .\\build\\vs2022\\
- [Tiny AES in C](https://github.com/kokke/tiny-AES-c) (Public Domain) - [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) - [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". - 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 或以上的版本内置该文件。

View File

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

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

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

View File

@ -1,18 +1,20 @@
#pragma once #pragma once
#include "infra/infra.h"
#include "qmc2/qmc2.h" #include "qmc2/qmc2.h"
#ifdef _WIN32
#include <windows.h> #include <windows.h>
#endif
#include <str_helper.h>
#include <array> #include <array>
#include <condition_variable> #include <condition_variable>
#include <filesystem> #include <filesystem>
#include <format>
#include <fstream> #include <fstream>
#include <mutex> #include <mutex>
#include <queue> #include <queue>
#include <string>
#include <string_view> #include <string_view>
#include <thread>
#include <utility>
#include <vector> #include <vector>
class KggTask { class KggTask {
@ -20,18 +22,14 @@ class KggTask {
explicit KggTask(std::filesystem::path kgg_path, std::filesystem::path out_dir) explicit KggTask(std::filesystem::path kgg_path, std::filesystem::path out_dir)
: kgg_path_(std::move(kgg_path)), out_dir_(std::move(out_dir)) {} : kgg_path_(std::move(kgg_path)), out_dir_(std::move(out_dir)) {}
static void log(const std::wstring& msg) { fputws(msg.c_str(), stderr); } void log(const lsr::string& level, const lsr::string& msg) const {
void warning(const std::wstring& msg) const { lsr_eprintf("[%s] %s (%s)\n", level.c_str(), msg.c_str(), kgg_path_.filename().c_str());
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 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 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, constexpr static std::array<uint8_t, 16> kMagicHeader{0x7C, 0xD5, 0x32, 0xEB, 0x86, 0x02, 0x7F, 0x4B,
0xA8, 0xAF, 0xA6, 0x8E, 0x0F, 0xFF, 0x99, 0x14}; 0xA8, 0xAF, 0xA6, 0x8E, 0x0F, 0xFF, 0x99, 0x14};
@ -39,18 +37,20 @@ class KggTask {
char header[0x100]{}; char header[0x100]{};
kgg_stream_in.read(header, sizeof(header)); kgg_stream_in.read(header, sizeof(header));
if (std::equal(kMagicHeader.cbegin(), kMagicHeader.cend(), header)) { if (std::equal(kMagicHeader.cbegin(), kMagicHeader.cend(), header)) {
warning(L"invalid kgg header"); warning(LSR_STR("invalid kgg header"));
return; return;
} }
const uint32_t offset_to_audio = *reinterpret_cast<uint32_t*>(&header[0x10]); const uint32_t offset_to_audio = *reinterpret_cast<uint32_t*>(&header[0x10]);
const uint32_t encrypt_mode = *reinterpret_cast<uint32_t*>(&header[0x14]); const uint32_t encrypt_mode = *reinterpret_cast<uint32_t*>(&header[0x14]);
if (encrypt_mode != 5) { if (encrypt_mode != 5) {
warning(std::format(L"unsupported enc_version (expect=0x05, got 0x{:02x})", encrypt_mode)); lsr_eprintf("[WARN] unsupported enc_version (expect=0x05, got 0x%02x) (%s)\n", encrypt_mode,
kgg_path_.filename().c_str());
return; return;
} }
uint32_t audio_hash_len = *reinterpret_cast<uint32_t*>(&header[0x44]); uint32_t audio_hash_len = *reinterpret_cast<uint32_t*>(&header[0x44]);
if (audio_hash_len != 0x20) { 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; return;
} }
std::string audio_hash(&header[0x48], &header[0x48 + audio_hash_len]); std::string audio_hash(&header[0x48], &header[0x48 + audio_hash_len]);
@ -58,13 +58,13 @@ class KggTask {
if (auto it = ekey_db.find(audio_hash); it != ekey_db.end()) { if (auto it = ekey_db.find(audio_hash); it != ekey_db.end()) {
ekey = it->second; ekey = it->second;
} else { } else {
warning(L"ekey not found"); warning(LSR_STR("ekey not found"));
return; return;
} }
auto qmc2 = QMC2::Create(ekey); auto qmc2 = QMC2::Create(ekey);
if (!qmc2) { 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()); fprintf(stderr, "%s\n", ekey.c_str());
return; return;
} }
@ -74,17 +74,19 @@ class KggTask {
kgg_stream_in.read(magic.data(), 4); kgg_stream_in.read(magic.data(), 4);
qmc2->Decrypt(std::span(reinterpret_cast<uint8_t*>(magic.data()), 4), 0); qmc2->Decrypt(std::span(reinterpret_cast<uint8_t*>(magic.data()), 4), 0);
auto real_ext = DetectRealExt(magic); 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)) { 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: ")) + out_path.filename().native());
return; return;
} }
kgg_stream_in.seekg(offset_to_audio, std::ios::beg); kgg_stream_in.seekg(offset_to_audio, std::ios::beg);
std::ofstream ofs_decrypted(out_path, std::ios::binary); std::ofstream ofs_decrypted(out_path, std::ios::binary);
if (!ofs_decrypted.is_open()) { if (!ofs_decrypted.is_open()) {
error(L"failed to open output file"); error(LSR_STR("failed to open output file"));
return; return;
} }
@ -101,27 +103,27 @@ class KggTask {
offset += n; offset += n;
} }
info(std::format(L"** OK ** -> {}", out_path.filename().wstring())); info(lsr::string(LSR_STR("** OK ** -> ")) + out_path.filename().native());
} }
private: private:
std::filesystem::path kgg_path_; std::filesystem::path kgg_path_;
std::filesystem::path out_dir_; std::filesystem::path out_dir_;
static const wchar_t* DetectRealExt(const std::string_view magic) { static const lsr::character* DetectRealExt(const std::string_view magic) {
if (magic == "fLaC") { if (magic == "fLaC") {
return L"flac"; return LSR_STR("flac");
} }
if (magic == "OggS") { if (magic == "OggS") {
return L"ogg"; return LSR_STR("ogg");
} }
return L"mp3"; return LSR_STR("mp3");
} }
}; };
class KggTaskQueue { class KggTaskQueue {
public: 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) {} : ekey_db_(std::move(ekey_db)), suffix_(suffix) {}
void Push(std::unique_ptr<KggTask> task) { void Push(std::unique_ptr<KggTask> task) {
@ -165,9 +167,11 @@ class KggTaskQueue {
private: private:
bool thread_end_{false}; bool thread_end_{false};
Infra::kgm_ekey_db_t ekey_db_; Infra::kgm_ekey_db_t ekey_db_;
std::wstring suffix_; lsr::string suffix_;
void WorkerThreadBody() { void WorkerThreadBody() {
#ifdef _WIN32
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
#endif
std::unique_ptr<KggTask> task{nullptr}; std::unique_ptr<KggTask> task{nullptr};
while ((task = Pop())) { while ((task = Pop())) {

View File

@ -1,7 +1,11 @@
#include <sqlite3_wrapper.h> #include <sqlite3_wrapper.h>
#include <filesystem>
#include <queue>
#include "infra/infra.h" #include "infra/infra.h"
#include "jobs.hpp" #include "jobs.hpp"
#include "str_helper.h"
#include "utils/cli.h" #include "utils/cli.h"
using Infra::kgm_ekey_db_t; using Infra::kgm_ekey_db_t;
@ -30,7 +34,7 @@ void WalkFileOrDir(KggTaskQueue& queue, const std::filesystem::path& input_path,
continue; continue;
} }
fputws(std::format(L"[WARN] invalid path: {}\n", target_path.wstring()).c_str(), stderr); lsr_eprintf("[WARN] invalid path: %s\n", target_path.c_str());
} }
} }
@ -56,11 +60,11 @@ void print_banner() {
print_usage(); print_usage();
} }
int main() { int main(int argc, char** argv) {
CliParser cli_args; CliParser cli_args;
print_banner(); 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(); bool scan_all_exts = cli_args.get_scan_all_file_ext();
@ -105,7 +109,7 @@ int main() {
auto input_files = cli_args.get_input_files(); auto input_files = cli_args.get_input_files();
if (input_files.empty()) { if (input_files.empty()) {
input_files.emplace_back(L"."); input_files.emplace_back(LSR_STR("."));
} }
for (auto& positional_arg : input_files) { for (auto& positional_arg : input_files) {
WalkFileOrDir(queue, positional_arg, scan_all_exts); WalkFileOrDir(queue, positional_arg, scan_all_exts);

View File

@ -1,47 +1,58 @@
#include "cli.h" #include "cli.h"
#include <str_helper.h>
#include <Windows.h> #ifdef _WIN32
#include <clocale>
// clang-format off // clang-format off
#include <clocale>
#include <Windows.h>
#include <shlobj.h> #include <shlobj.h>
#include <knownfolders.h> #include <knownfolders.h>
// clang-format on // clang-format on
#endif
CliParser::CliParser() { CliParser::CliParser() {
#ifdef _WIN32
SetConsoleOutputCP(CP_UTF8); SetConsoleOutputCP(CP_UTF8);
setlocale(LC_ALL, ".UTF8"); setlocale(LC_ALL, ".UTF8");
#endif
} }
void CliParser::parse_from_cli() { void CliParser::parse_from_cli(int argc_, char** argv_) {
int argc; int argc{argc_};
#ifdef _WIN32
wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc); wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc);
#else
char**& argv = argv_;
#endif
std::vector<std::wstring> positional_args{}; std::vector<lsr::string> positional_args{};
std::unordered_map<std::wstring, std::wstring> named_args{}; std::unordered_map<lsr::string, lsr::string> named_args{};
bool positional_only{false}; bool positional_only{false};
for (int i = 1; i < argc; i++) { for (int i = 1; i < argc; i++) {
std::wstring arg{argv[i]}; lsr::string arg{argv[i]};
if (arg == L"--") { if (arg == LSR_STR("--")) {
positional_only = true; positional_only = true;
continue; continue;
} }
if (!positional_only && arg.starts_with(L"--")) { if (!positional_only && arg.starts_with(LSR_STR("--"))) {
auto pos = arg.find(L'='); 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); named_args[arg.substr(2, pos - 2)] = arg.substr(pos + 1);
} else if (++i < argc) { } else if (++i < argc) {
named_args[arg.substr(2)] = argv[i]; named_args[arg.substr(2)] = argv[i];
} else { } else {
named_args[arg.substr(2)] = L""; named_args[arg.substr(2)] = LSR_STR("");
} }
} else { } else {
positional_args.push_back(arg); positional_args.push_back(arg);
} }
} }
#ifdef _WIN32
LocalFree(argv); LocalFree(argv);
#endif
positional_args_ = positional_args; positional_args_ = positional_args;
named_args_ = named_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 CliParser::get_db_path() const {
std::filesystem::path kugou_db{}; 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}; kugou_db = std::filesystem::path{it->second};
} else { } else {
#ifdef _WIN32
PWSTR pAppDirPath{}; PWSTR pAppDirPath{};
SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pAppDirPath); SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pAppDirPath);
kugou_db = std::filesystem::path{pAppDirPath} / L"Kugou8" / L"KGMusicV3.db"; kugou_db = std::filesystem::path{pAppDirPath} / L"Kugou8" / L"KGMusicV3.db";
CoTaskMemFree(pAppDirPath); CoTaskMemFree(pAppDirPath);
#else
kugou_db = std::filesystem::path{"KGMusicV3.db"};
#endif
} }
return absolute(kugou_db); return absolute(kugou_db);

View File

@ -1,30 +1,34 @@
#pragma once #pragma once
#include <str_helper.h>
#include <filesystem> #include <filesystem>
#include <string>
#include <unordered_map> #include <unordered_map>
#include <utility> #include <utility>
#include <vector> #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 { class CliParser {
public: public:
CliParser(); 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_infra_dll() const;
[[nodiscard]] std::filesystem::path get_db_path() 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]] lsr::string get_file_suffix() const {
[[nodiscard]] bool get_scan_all_file_ext() const { return get_with_default(L"scan-all-file-ext", L"0") == L"1"; }; return get_with_default(LSR_STR("suffix"), LSR_STR("_kgg-dec"));
[[nodiscard]] std::vector<std::wstring> get_input_files() const { return positional_args_; } }
[[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: private:
std::vector<std::wstring> positional_args_{}; std::vector<lsr::string> positional_args_{};
std::unordered_map<std::wstring, std::wstring> named_args_{}; std::unordered_map<lsr::string, lsr::string> named_args_{};
static parsed_raw_args_t parse(); 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()) { if (const auto& it = named_args_.find(key); it != named_args_.end()) {
return it->second; return it->second;
} }

View File

@ -1,5 +1,6 @@
#pragma once #pragma once
#include <cstddef>
#include <cstdint> #include <cstdint>
#if USE_WIN_CRYPTO #if USE_WIN_CRYPTO
@ -25,7 +26,7 @@ struct AES_ctx {
#endif #endif
}; };
bool 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; // buffer size MUST be mutile of AES_BLOCKLEN;
// Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme // Suggest https://en.wikipedia.org/wiki/Padding_(cryptography)#PKCS7 for padding scheme
@ -37,7 +38,9 @@ size_t AES_CBC_decrypt_buffer(AES_ctx* ctx, uint8_t* buf, size_t length);
#if USE_WIN_CRYPTO #if USE_WIN_CRYPTO
bool AES_cleanup(AES_ctx* ctx); bool AES_cleanup(AES_ctx* ctx);
#else #else
inline bool AES_cleanup(AES_ctx* ctx) {return true;} inline bool AES_cleanup(AES_ctx* ctx) {
return true;
}
#endif #endif
} // namespace AES } // namespace AES