Compare commits
No commits in common. "deaf349d1530f7acfe19be0f231589d9a01ce192" and "ee37f2bd3fc918f67fb250cdfe5943f2235729a9" have entirely different histories.
deaf349d15
...
ee37f2bd3f
@ -1,6 +1,6 @@
|
||||
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_REQUIRED ON)
|
||||
@ -14,7 +14,6 @@ add_executable(kgg-dec
|
||||
src/qmc2/qmc2_factory.cpp
|
||||
src/qmc2/qmc2_map.cpp
|
||||
src/qmc2/qmc2_rc4.cpp
|
||||
src/utils/cli.cpp
|
||||
)
|
||||
|
||||
target_include_directories(kgg-dec
|
||||
|
1
Jenkinsfile
vendored
1
Jenkinsfile
vendored
@ -23,7 +23,6 @@ pipeline {
|
||||
cmake --build --preset vs-release --config Release
|
||||
copy /y README.MD .\\build\\vs2022\\Release\\
|
||||
copy /y Usage*.txt .\\build\\vs2022\\Release\\
|
||||
copy /y LICENSE .\\build\\vs2022\\Release\\
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
21
LICENSE
21
LICENSE
@ -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.
|
10
README.MD
10
README.MD
@ -23,10 +23,8 @@ copy /y README.MD .\\build\\vs2022\\
|
||||
|
||||
### CLI Options
|
||||
|
||||
* `--infra-dll` (Optional): 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`
|
||||
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`.
|
||||
* `[--infra-dll </path/to/infra.dll>]`: 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.
|
||||
* `[--db </path/to/KGMusicV3.db>]`: Specify the path to `KGMusicV3.db`, defaults to `%AppData%/Kugou8/KGMusicV3.db`.
|
||||
|
||||
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.
|
20
Usage.zh.txt
20
Usage.zh.txt
@ -1,15 +1,11 @@
|
||||
# KGG 解密工具 by LSR
|
||||
|
||||
项目地址:https://git.unlock-music.dev/um/kgg-dec
|
||||
项目协议:MIT License
|
||||
注意事项:分发时请附上 `LICENSE` 开源协议文档。
|
||||
|
||||
使用方法 (命令行):
|
||||
|
||||
1. 从酷狗安装目录拷贝 `infra.dll` 文件到 `kgg-dec.exe` 的目录。
|
||||
2. 启动 `kgg-dec.exe`,其中第一个参数为含有 `*.kgg` 文件的目录。
|
||||
3. 你也可以使用 `--` 来将参数后的 `-` 开头的参数视为输入文件或目录。
|
||||
4. 你可以指定多项输入文件或目录。
|
||||
|
||||
或者:
|
||||
|
||||
@ -18,21 +14,13 @@
|
||||
|
||||
其它参数:
|
||||
|
||||
* `--infra-dll` (可选): 指定 `infra.dll` 的路径,默认为 `infra.dll`。
|
||||
* `--scan-all-file-ext` (可选,`0` 或 `1`): 是否扫描所有文件后缀名。默认为 `0`,只扫描 `kgg` 文件。
|
||||
* `--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 ""`: 解密后不添加额外的后缀。
|
||||
* `[--infra-dll </path/to/infra.dll>]`: 指定 `infra.dll` 的路径,默认为 `infra.dll`。
|
||||
* `[--scan-all-file-ext <0|1>]`: 是否扫描所有文件后缀名,默认为 `0` 只扫描 `kgg` 文件。
|
||||
* `[--db </path/to/KGMusicV3.db>]`: 指定 `KGMusicV3.db` 的路径,默认为 `%AppData%/Kugou8/KGMusicV3.db`。
|
||||
|
||||
指定完参数后可以指定任意数量的文件或目录。
|
||||
|
||||
错误排查:
|
||||
|
||||
1. 需要至少播放一次 KGG 文件,并确保酷狗能正常播放。
|
||||
2. 文件后缀名嗅探代码只支持:`ogg` / `flac`。其他格式会被识别为 `mp3`(大多数情况下能正常工作)。
|
||||
2. 文件后缀名嗅探代码只支持:`ogg` / `flac`,其他格式默认回退到 `mp3`。
|
||||
|
@ -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 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,
|
||||
0xA8, 0xAF, 0xA6, 0x8E, 0x0F, 0xFF, 0x99, 0x14};
|
||||
|
||||
@ -38,8 +38,8 @@ class KggTask {
|
||||
warning(L"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]);
|
||||
uint32_t offset_to_audio = *reinterpret_cast<uint32_t*>(&header[0x10]);
|
||||
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));
|
||||
return;
|
||||
@ -104,7 +104,7 @@ class KggTask {
|
||||
std::filesystem::path kgg_path_;
|
||||
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") {
|
||||
return L"flac";
|
||||
}
|
||||
|
124
src/main.cpp
124
src/main.cpp
@ -1,7 +1,22 @@
|
||||
#include "infra/infra.h"
|
||||
#include "infra/sqlite_error.h"
|
||||
#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;
|
||||
|
||||
@ -33,54 +48,83 @@ void WalkFileOrDir(KggTaskQueue& queue, const std::filesystem::path& input_path,
|
||||
}
|
||||
}
|
||||
|
||||
void print_license() {
|
||||
fputs(" This software is free and open source, licensed under the MIT license.\n", stderr);
|
||||
fputs(" For more details, check out: https://git.unlock-music.dev/um/kgg-dec\n", stderr);
|
||||
std::pair<std::vector<std::wstring>, std::unordered_map<std::wstring, std::wstring>> ParseCommandLine() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_pair(positional_args, named_args);
|
||||
}
|
||||
|
||||
void print_usage() {
|
||||
int main() {
|
||||
SetConsoleOutputCP(CP_UTF8);
|
||||
setlocale(LC_ALL, ".UTF8");
|
||||
|
||||
fprintf(stderr, "kgg-dec v" KGGDEC_PROJECT_VERSION " by LSR\n");
|
||||
fputs(
|
||||
"Usage: kgg-dec "
|
||||
"[--infra-dll infra.dll] "
|
||||
"[--scan-all-file-ext 0] "
|
||||
"[--db /path/to/KGMusicV3.db] "
|
||||
"[--suffix _kgg-dec] "
|
||||
"[--] "
|
||||
"[FILE]...\n\n\n",
|
||||
"[--] [kgg-dir... = '.']\n\n",
|
||||
stderr);
|
||||
}
|
||||
|
||||
void print_banner() {
|
||||
fprintf(stderr, "kgg-dec v" KGGDEC_PROJECT_VERSION " by LSR\n");
|
||||
print_license();
|
||||
print_usage();
|
||||
}
|
||||
auto [positional_args, named_args] = ParseCommandLine();
|
||||
if (positional_args.empty()) {
|
||||
positional_args.emplace_back(L".");
|
||||
}
|
||||
|
||||
int main() {
|
||||
CliParser cli_args;
|
||||
print_banner();
|
||||
std::filesystem::path kgm_db_path{};
|
||||
std::filesystem::path infra_dll_path{L"infra.dll"};
|
||||
if (auto it = named_args.find(L"infra-dll"); it != named_args.end()) {
|
||||
infra_dll_path = std::filesystem::path{it->second};
|
||||
}
|
||||
infra_dll_path = absolute(infra_dll_path);
|
||||
if (!exists(infra_dll_path)) {
|
||||
fputs("[ERR ] infra.dll not found\n", stderr);
|
||||
return 1;
|
||||
}
|
||||
|
||||
cli_args.parse_from_cli();
|
||||
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";
|
||||
}
|
||||
|
||||
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)) {
|
||||
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;
|
||||
}
|
||||
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};
|
||||
@ -115,11 +159,7 @@ int main() {
|
||||
queue.AddWorkerThread();
|
||||
}
|
||||
|
||||
auto input_files = cli_args.get_input_files();
|
||||
if (input_files.empty()) {
|
||||
input_files.emplace_back(L".");
|
||||
}
|
||||
for (auto& positional_arg : input_files) {
|
||||
for (auto& positional_arg : positional_args) {
|
||||
WalkFileOrDir(queue, positional_arg, scan_all_exts);
|
||||
}
|
||||
queue.Join();
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
}
|
||||
};
|
Loading…
Reference in New Issue
Block a user