Compare commits
2 Commits
d19ed2d57f
...
10e0c7446b
Author | SHA1 | Date | |
---|---|---|---|
10e0c7446b | |||
395d5628d8 |
@ -35,7 +35,10 @@ target_include_directories(kgg-dec
|
||||
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)
|
||||
target_link_libraries(kgg-dec PRIVATE WinSQLite3)
|
||||
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)
|
||||
target_compile_definitions(kgg-dec PRIVATE USE_WIN_CRYPTO=1)
|
||||
endif ()
|
||||
endif ()
|
||||
|
@ -9,7 +9,7 @@
|
||||
{
|
||||
"name": "default",
|
||||
"hidden": true,
|
||||
"generator": "Ninja",
|
||||
"generator": "Ninja Multi-Config",
|
||||
"cacheVariables": {
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
}
|
||||
@ -29,6 +29,12 @@
|
||||
"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",
|
||||
@ -54,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",
|
||||
|
16
README.MD
16
README.MD
@ -50,7 +50,14 @@ 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 目标)。
|
||||
|
||||
### 第三方软件
|
||||
|
||||
该程序用到了以下第三方软件:
|
||||
|
||||
@ -58,3 +65,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 或以上的版本内置该文件。
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <bit>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
|
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__)
|
@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace Infra {
|
||||
|
62
src/jobs.hpp
62
src/jobs.hpp
@ -1,18 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "infra/infra.h"
|
||||
#include "qmc2/qmc2.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#include <str_helper.h>
|
||||
#include <array>
|
||||
#include <condition_variable>
|
||||
#include <filesystem>
|
||||
#include <format>
|
||||
#include <fstream>
|
||||
#include <mutex>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
class KggTask {
|
||||
@ -20,18 +22,14 @@ class KggTask {
|
||||
explicit KggTask(std::filesystem::path kgg_path, std::filesystem::path 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 warning(const std::wstring& msg) const {
|
||||
log(std::format(L"[WARN] {} ({})\n", msg, kgg_path_.filename().wstring()));
|
||||
}
|
||||
void error(const std::wstring& msg) const {
|
||||
log(std::format(L"[ERR ] {} ({})\n", msg, kgg_path_.filename().wstring()));
|
||||
}
|
||||
void info(const std::wstring& msg) const {
|
||||
log(std::format(L"[INFO] {} ({})\n", msg, kgg_path_.filename().wstring()));
|
||||
void 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 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};
|
||||
|
||||
@ -39,18 +37,20 @@ class KggTask {
|
||||
char header[0x100]{};
|
||||
kgg_stream_in.read(header, sizeof(header));
|
||||
if (std::equal(kMagicHeader.cbegin(), kMagicHeader.cend(), header)) {
|
||||
warning(L"invalid kgg header");
|
||||
warning(LSR_STR("invalid kgg header"));
|
||||
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));
|
||||
lsr_eprintf("[WARN] unsupported enc_version (expect=0x05, got 0x%02x) (%s)\n", encrypt_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]);
|
||||
@ -58,13 +58,13 @@ 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;
|
||||
}
|
||||
@ -74,17 +74,19 @@ class KggTask {
|
||||
kgg_stream_in.read(magic.data(), 4);
|
||||
qmc2->Decrypt(std::span(reinterpret_cast<uint8_t*>(magic.data()), 4), 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: ")) + out_path.filename().native());
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@ -101,27 +103,27 @@ class KggTask {
|
||||
offset += n;
|
||||
}
|
||||
|
||||
info(std::format(L"** OK ** -> {}", out_path.filename().wstring()));
|
||||
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) {
|
||||
static const lsr::character* DetectRealExt(const std::string_view magic) {
|
||||
if (magic == "fLaC") {
|
||||
return L"flac";
|
||||
return LSR_STR("flac");
|
||||
}
|
||||
if (magic == "OggS") {
|
||||
return L"ogg";
|
||||
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) {
|
||||
@ -165,9 +167,11 @@ class KggTaskQueue {
|
||||
private:
|
||||
bool thread_end_{false};
|
||||
Infra::kgm_ekey_db_t ekey_db_;
|
||||
std::wstring suffix_;
|
||||
lsr::string suffix_;
|
||||
void WorkerThreadBody() {
|
||||
#ifdef _WIN32
|
||||
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL);
|
||||
#endif
|
||||
|
||||
std::unique_ptr<KggTask> task{nullptr};
|
||||
while ((task = Pop())) {
|
||||
|
12
src/main.cpp
12
src/main.cpp
@ -1,7 +1,11 @@
|
||||
#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;
|
||||
@ -30,7 +34,7 @@ void WalkFileOrDir(KggTaskQueue& queue, const std::filesystem::path& input_path,
|
||||
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();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
@ -105,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;
|
||||
}
|
||||
|
7
third-party/aes/aes.h
vendored
7
third-party/aes/aes.h
vendored
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
#if USE_WIN_CRYPTO
|
||||
@ -25,7 +26,7 @@ struct AES_ctx {
|
||||
#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;
|
||||
// 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
|
||||
bool AES_cleanup(AES_ctx* ctx);
|
||||
#else
|
||||
inline bool AES_cleanup(AES_ctx* ctx) {return true;}
|
||||
inline bool AES_cleanup(AES_ctx* ctx) {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace AES
|
||||
|
Loading…
Reference in New Issue
Block a user