Compare commits

..

No commits in common. "deaf349d1530f7acfe19be0f231589d9a01ce192" and "ee37f2bd3fc918f67fb250cdfe5943f2235729a9" have entirely different histories.

9 changed files with 95 additions and 192 deletions

View File

@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.10) cmake_minimum_required(VERSION 3.10)
project(kgg-dec VERSION 0.5 LANGUAGES CXX) project(kgg-dec VERSION 0.4 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,7 +14,6 @@ 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,7 +23,6 @@ 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
View File

@ -1,21 +0,0 @@
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,10 +23,8 @@ copy /y README.MD .\\build\\vs2022\\
### CLI Options ### CLI Options
* `--infra-dll` (Optional): Specify the path to `infra.dll`. Defaults to `infra.dll`. * `[--infra-dll </path/to/infra.dll>]`: Specify the path to `infra.dll`, defaults to `infra.dll`.
* `--scan-all-file-ext` (Optional, `0` or `1`): Whether to scan all file extensions. Defaults to `0` to scan only `kgg` * `[--scan-all-file-ext <0|1>]`: Whether to scan all file extensions, defaults to `0` to scan only `kgg` files.
files. * `[--db </path/to/KGMusicV3.db>]`: Specify the path to `KGMusicV3.db`, defaults to `%AppData%/Kugou8/KGMusicV3.db`.
* `--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,15 +1,11 @@
# 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. 你可以指定多项输入文件或目录。
或者: 或者:
@ -18,21 +14,13 @@
其它参数: 其它参数:
* `--infra-dll` (可选): 指定 `infra.dll` 的路径,默认为 `infra.dll`。 * `[--infra-dll </path/to/infra.dll>]`: 指定 `infra.dll` 的路径,默认为 `infra.dll`。
* `--scan-all-file-ext` (可选,`0` 或 `1`): 是否扫描所有文件后缀名。默认为 `0`,只扫描 `kgg` 文件。 * `[--scan-all-file-ext <0|1>]`: 是否扫描所有文件后缀名,默认为 `0` 只扫描 `kgg` 文件。
* `--db` (可选): 指定 `KGMusicV3.db` 的路径。默认为 `%AppData%/Kugou8/KGMusicV3.db`。 * `[--db </path/to/KGMusicV3.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) const { void Execute(const Infra::kgm_ekey_db_t& ekey_db) {
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;
} }
const uint32_t offset_to_audio = *reinterpret_cast<uint32_t*>(&header[0x10]); uint32_t offset_to_audio = *reinterpret_cast<uint32_t*>(&header[0x10]);
const uint32_t encrypt_mode = *reinterpret_cast<uint32_t*>(&header[0x14]); 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(const std::string_view magic) { static const wchar_t* DetectRealExt(std::string_view magic) {
if (magic == "fLaC") { if (magic == "fLaC") {
return L"flac"; return L"flac";
} }

View File

@ -1,7 +1,22 @@
#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 "utils/cli.h" #include "qmc2/qmc2.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;
@ -33,54 +48,83 @@ void WalkFileOrDir(KggTaskQueue& queue, const std::filesystem::path& input_path,
} }
} }
void print_license() { std::pair<std::vector<std::wstring>, std::unordered_map<std::wstring, std::wstring>> ParseCommandLine() {
fputs(" This software is free and open source, licensed under the MIT license.\n", stderr); int argc;
fputs(" For more details, check out: https://git.unlock-music.dev/um/kgg-dec\n", stderr); 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;
} }
void print_usage() { 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() {
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] "
"[--suffix _kgg-dec] " "[--] [kgg-dir... = '.']\n\n",
"[--] "
"[FILE]...\n\n\n",
stderr); stderr);
auto [positional_args, named_args] = ParseCommandLine();
if (positional_args.empty()) {
positional_args.emplace_back(L".");
} }
void print_banner() { std::filesystem::path kgm_db_path{};
fprintf(stderr, "kgg-dec v" KGGDEC_PROJECT_VERSION " by LSR\n"); std::filesystem::path infra_dll_path{L"infra.dll"};
print_license(); if (auto it = named_args.find(L"infra-dll"); it != named_args.end()) {
print_usage(); infra_dll_path = std::filesystem::path{it->second};
} }
infra_dll_path = absolute(infra_dll_path);
int main() {
CliParser cli_args;
print_banner();
cli_args.parse_from_cli();
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);
cli_arg_error = true;
}
if (!exists(kgm_db_path)) {
fputs("[ERR ] KGMusicV3.db not found\n", stderr);
cli_arg_error = true;
}
if (cli_arg_error) {
return 1; return 1;
} }
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)) {
fputs("[ERR ] KGMusicV3.db not found\n", stderr);
return 1;
} }
int error{-1}; int error{-1};
@ -115,11 +159,7 @@ int main() {
queue.AddWorkerThread(); queue.AddWorkerThread();
} }
auto input_files = cli_args.get_input_files(); for (auto& positional_arg : positional_args) {
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();

View File

@ -1,67 +0,0 @@
#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);
}

View File

@ -1,33 +0,0 @@
#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;
}
};