Compare commits

..

5 Commits

Author SHA1 Message Date
deaf349d15 chore: bump version to v0.5 2024-11-26 06:26:57 +09:00
f3ed4f969d docs: update readme 2024-11-26 06:26:33 +09:00
b31bce8b1a ci: include license file 2024-11-26 06:22:45 +09:00
ca1162ed28 refactor: refactor cli parser, print license on startup 2024-11-26 06:17:20 +09:00
c680eb5a51 docs: add license 2024-11-26 06:17:03 +09:00
9 changed files with 192 additions and 95 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(kgg-dec VERSION 0.4 LANGUAGES CXX) project(kgg-dec VERSION 0.5 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_STANDARD_REQUIRED ON)
@ -14,6 +14,7 @@ add_executable(kgg-dec
src/qmc2/qmc2_factory.cpp src/qmc2/qmc2_factory.cpp
src/qmc2/qmc2_map.cpp src/qmc2/qmc2_map.cpp
src/qmc2/qmc2_rc4.cpp src/qmc2/qmc2_rc4.cpp
src/utils/cli.cpp
) )
target_include_directories(kgg-dec target_include_directories(kgg-dec

1
Jenkinsfile vendored
View File

@ -23,6 +23,7 @@ pipeline {
cmake --build --preset vs-release --config Release cmake --build --preset vs-release --config Release
copy /y README.MD .\\build\\vs2022\\Release\\ copy /y README.MD .\\build\\vs2022\\Release\\
copy /y Usage*.txt .\\build\\vs2022\\Release\\ copy /y Usage*.txt .\\build\\vs2022\\Release\\
copy /y LICENSE .\\build\\vs2022\\Release\\
''' '''
} }
} }

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 LSR
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -23,8 +23,10 @@ copy /y README.MD .\\build\\vs2022\\
### CLI Options ### CLI Options
* `[--infra-dll </path/to/infra.dll>]`: Specify the path to `infra.dll`, defaults to `infra.dll`. * `--infra-dll` (Optional): Specify the path to `infra.dll`. Defaults to `infra.dll`.
* `[--scan-all-file-ext <0|1>]`: Whether to scan all file extensions, defaults to `0` to scan only `kgg` files. * `--scan-all-file-ext` (Optional, `0` or `1`): Whether to scan all file extensions. Defaults to `0` to scan only `kgg`
* `[--db </path/to/KGMusicV3.db>]`: Specify the path to `KGMusicV3.db`, defaults to `%AppData%/Kugou8/KGMusicV3.db`. files.
* `--db`: Specify the path to `KGMusicV3.db`, defaults to `%AppData%/Kugou8/KGMusicV3.db`.
* `--suffix`: Specify the suffix of the output file, defaults to `_kgg-dec`.
After specifying the parameters, you can specify any number of files or directories. After specifying the parameters, you can specify any number of files or directories.

View File

@ -1,11 +1,15 @@
# KGG 解密工具 by LSR # KGG 解密工具 by LSR
项目地址https://git.unlock-music.dev/um/kgg-dec 项目地址https://git.unlock-music.dev/um/kgg-dec
项目协议MIT License
注意事项:分发时请附上 `LICENSE` 开源协议文档。
使用方法 (命令行) 使用方法 (命令行)
1. 从酷狗安装目录拷贝 `infra.dll` 文件到 `kgg-dec.exe` 的目录。 1. 从酷狗安装目录拷贝 `infra.dll` 文件到 `kgg-dec.exe` 的目录。
2. 启动 `kgg-dec.exe`,其中第一个参数为含有 `*.kgg` 文件的目录。 2. 启动 `kgg-dec.exe`,其中第一个参数为含有 `*.kgg` 文件的目录。
3. 你也可以使用 `--` 来将参数后的 `-` 开头的参数视为输入文件或目录。
4. 你可以指定多项输入文件或目录。
或者: 或者:
@ -14,13 +18,21 @@
其它参数: 其它参数:
* `[--infra-dll </path/to/infra.dll>]`: 指定 `infra.dll` 的路径,默认为 `infra.dll`。 * `--infra-dll` (可选): 指定 `infra.dll` 的路径,默认为 `infra.dll`。
* `[--scan-all-file-ext <0|1>]`: 是否扫描所有文件后缀名,默认为 `0` 只扫描 `kgg` 文件。 * `--scan-all-file-ext` (可选,`0` 或 `1`): 是否扫描所有文件后缀名。默认为 `0`,只扫描 `kgg` 文件。
* `[--db </path/to/KGMusicV3.db>]`: 指定 `KGMusicV3.db` 的路径,默认为 `%AppData%/Kugou8/KGMusicV3.db`。 * `--db` (可选): 指定 `KGMusicV3.db` 的路径。默认为 `%AppData%/Kugou8/KGMusicV3.db`。
* `--suffix` (可选): 指定解密后文件的后缀。默认为 `_kgg-dec`。
示例:
* `kgg-dec.exe "D:\Music"`: 解密 `D:\Music` 目录下的所有 `*.kgg` 文件。
* `kgg-dec.exe "D:\Music" "D:\Music\1.kgg" "D:\Music\2.kgg"`: 解密 `D:\Music` 目录下的所有 `*.kgg` 文件,以及 `1.kgg` 和 `2.kgg`。
* `kgg-dec.exe "D:\Music" --scan-all-file-ext 1`: 尝试将 `D:\Music` 目录下的所有文件视为 `kgg` 文件来解密。
* `kgg-dec.exe "D:\Music" --suffix ""`: 解密后不添加额外的后缀。
指定完参数后可以指定任意数量的文件或目录。 指定完参数后可以指定任意数量的文件或目录。
错误排查: 错误排查:
1. 需要至少播放一次 KGG 文件,并确保酷狗能正常播放。 1. 需要至少播放一次 KGG 文件,并确保酷狗能正常播放。
2. 文件后缀名嗅探代码只支持:`ogg` / `flac`,其他格式默认回退到 `mp3`。 2. 文件后缀名嗅探代码只支持:`ogg` / `flac`。其他格式会被识别为 `mp3`(大多数情况下能正常工作)

View File

@ -27,7 +27,7 @@ class KggTask {
void info(const wchar_t* msg) const { fwprintf(stderr, L"[INFO] %s (%s)\n", msg, kgg_path_.filename().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 info(const std::wstring& msg) const { info(msg.c_str()); }
void Execute(const Infra::kgm_ekey_db_t& ekey_db) { void Execute(const Infra::kgm_ekey_db_t& ekey_db) 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};
@ -38,8 +38,8 @@ class KggTask {
warning(L"invalid kgg header"); warning(L"invalid kgg header");
return; return;
} }
uint32_t offset_to_audio = *reinterpret_cast<uint32_t*>(&header[0x10]); const uint32_t offset_to_audio = *reinterpret_cast<uint32_t*>(&header[0x10]);
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)); warning(std::format(L"unsupported enc_version (expect=0x05, got 0x{:02x})", encrypt_mode));
return; return;
@ -104,7 +104,7 @@ class KggTask {
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(std::string_view magic) { static const wchar_t* DetectRealExt(const std::string_view magic) {
if (magic == "fLaC") { if (magic == "fLaC") {
return L"flac"; return L"flac";
} }

View File

@ -1,22 +1,7 @@
#include "infra/infra.h" #include "infra/infra.h"
#include "infra/sqlite_error.h" #include "infra/sqlite_error.h"
#include "jobs.hpp" #include "jobs.hpp"
#include "qmc2/qmc2.h" #include "utils/cli.h"
// clang-format off
#include <windows.h>
#include <shlobj.h>
#include <initguid.h>
#include <knownfolders.h>
// clang-format on
#include <algorithm>
#include <cstdint>
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <span>
#include <utility>
using Infra::kgm_ekey_db_t; using Infra::kgm_ekey_db_t;
@ -48,84 +33,55 @@ void WalkFileOrDir(KggTaskQueue& queue, const std::filesystem::path& input_path,
} }
} }
std::pair<std::vector<std::wstring>, std::unordered_map<std::wstring, std::wstring>> ParseCommandLine() { void print_license() {
int argc; fputs(" This software is free and open source, licensed under the MIT license.\n", stderr);
wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc); fputs(" For more details, check out: https://git.unlock-music.dev/um/kgg-dec\n", stderr);
std::vector<std::wstring> positional_args{};
std::unordered_map<std::wstring, std::wstring> named_args{};
bool positional_only{false};
for (int i = 1; i < argc; i++) {
std::wstring arg{argv[i]};
if (arg == L"--") {
positional_only = true;
continue;
}
if (!positional_only && arg.starts_with(L"--")) {
auto pos = arg.find(L'=');
if (pos != std::wstring::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"";
}
} else {
positional_args.push_back(arg);
}
}
return std::make_pair(positional_args, named_args);
} }
int main() { void print_usage() {
SetConsoleOutputCP(CP_UTF8);
setlocale(LC_ALL, ".UTF8");
fprintf(stderr, "kgg-dec v" KGGDEC_PROJECT_VERSION " by LSR\n");
fputs( fputs(
"Usage: kgg-dec " "Usage: kgg-dec "
"[--infra-dll infra.dll] " "[--infra-dll infra.dll] "
"[--scan-all-file-ext 0] " "[--scan-all-file-ext 0] "
"[--db /path/to/KGMusicV3.db] " "[--db /path/to/KGMusicV3.db] "
"[--] [kgg-dir... = '.']\n\n", "[--suffix _kgg-dec] "
"[--] "
"[FILE]...\n\n\n",
stderr); stderr);
}
auto [positional_args, named_args] = ParseCommandLine(); void print_banner() {
if (positional_args.empty()) { fprintf(stderr, "kgg-dec v" KGGDEC_PROJECT_VERSION " by LSR\n");
positional_args.emplace_back(L"."); print_license();
} print_usage();
}
std::filesystem::path kgm_db_path{}; int main() {
std::filesystem::path infra_dll_path{L"infra.dll"}; CliParser cli_args;
if (auto it = named_args.find(L"infra-dll"); it != named_args.end()) { print_banner();
infra_dll_path = std::filesystem::path{it->second};
} cli_args.parse_from_cli();
infra_dll_path = absolute(infra_dll_path);
bool scan_all_exts = cli_args.get_scan_all_file_ext();
auto infra_dll_path = cli_args.get_infra_dll();
auto kgm_db_path = cli_args.get_db_path();
auto file_suffix = cli_args.get_file_suffix();
{
bool cli_arg_error{false};
if (!exists(infra_dll_path)) { if (!exists(infra_dll_path)) {
fputs("[ERR ] infra.dll not found\n", stderr); fputs("[ERR ] infra.dll not found\n", stderr);
return 1; cli_arg_error = true;
} }
bool scan_all_exts{false};
if (auto it = named_args.find(L"scan-all-file-ext"); it != named_args.end()) {
scan_all_exts = it->second == L"1";
}
if (auto it = named_args.find(L"db"); it != named_args.end()) {
kgm_db_path = std::filesystem::path{it->second};
} else {
PWSTR pAppDirPath{};
SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, nullptr, &pAppDirPath);
kgm_db_path = std::filesystem::path{pAppDirPath} / L"Kugou8" / L"KGMusicV3.db";
CoTaskMemFree(pAppDirPath);
}
if (!exists(kgm_db_path)) { if (!exists(kgm_db_path)) {
fputs("[ERR ] KGMusicV3.db not found\n", stderr); fputs("[ERR ] KGMusicV3.db not found\n", stderr);
cli_arg_error = true;
}
if (cli_arg_error) {
return 1; return 1;
} }
}
int error{-1}; int error{-1};
Infra::KugouDb db{infra_dll_path, kgm_db_path}; Infra::KugouDb db{infra_dll_path, kgm_db_path};
@ -159,7 +115,11 @@ int main() {
queue.AddWorkerThread(); queue.AddWorkerThread();
} }
for (auto& positional_arg : positional_args) { auto input_files = cli_args.get_input_files();
if (input_files.empty()) {
input_files.emplace_back(L".");
}
for (auto& positional_arg : input_files) {
WalkFileOrDir(queue, positional_arg, scan_all_exts); WalkFileOrDir(queue, positional_arg, scan_all_exts);
} }
queue.Join(); queue.Join();

67
src/utils/cli.cpp Normal file
View File

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

33
src/utils/cli.h Normal file
View File

@ -0,0 +1,33 @@
#pragma once
#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;
class CliParser {
public:
CliParser();
void parse_from_cli();
[[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_; }
private:
std::vector<std::wstring> positional_args_{};
std::unordered_map<std::wstring, std::wstring> named_args_{};
static parsed_raw_args_t parse();
[[nodiscard]] std::wstring get_with_default(const std::wstring& key, const std::wstring& default_value) const {
if (const auto& it = named_args_.find(key); it != named_args_.end()) {
return it->second;
}
return default_value;
}
};