refactor: refactor cli parser, print license on startup

This commit is contained in:
鲁树人 2024-11-26 06:17:20 +09:00
parent c680eb5a51
commit ca1162ed28
5 changed files with 147 additions and 86 deletions

View File

@ -14,6 +14,7 @@ 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

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 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,
0xA8, 0xAF, 0xA6, 0x8E, 0x0F, 0xFF, 0x99, 0x14};
@ -38,8 +38,8 @@ class KggTask {
warning(L"invalid kgg header");
return;
}
uint32_t offset_to_audio = *reinterpret_cast<uint32_t*>(&header[0x10]);
uint32_t encrypt_mode = *reinterpret_cast<uint32_t*>(&header[0x14]);
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));
return;
@ -104,7 +104,7 @@ class KggTask {
std::filesystem::path kgg_path_;
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") {
return L"flac";
}

View File

@ -1,22 +1,7 @@
#include "infra/infra.h"
#include "infra/sqlite_error.h"
#include "jobs.hpp"
#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>
#include "utils/cli.h"
using Infra::kgm_ekey_db_t;
@ -48,83 +33,54 @@ void WalkFileOrDir(KggTaskQueue& queue, const std::filesystem::path& input_path,
}
}
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_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);
}
int main() {
SetConsoleOutputCP(CP_UTF8);
setlocale(LC_ALL, ".UTF8");
fprintf(stderr, "kgg-dec v" KGGDEC_PROJECT_VERSION " by LSR\n");
void print_usage() {
fputs(
"Usage: kgg-dec "
"[--infra-dll infra.dll] "
"[--scan-all-file-ext 0] "
"[--db /path/to/KGMusicV3.db] "
"[--] [kgg-dir... = '.']\n\n",
"[--suffix _kgg-dec] "
"[--] "
"[FILE]...\n\n\n",
stderr);
}
auto [positional_args, named_args] = ParseCommandLine();
if (positional_args.empty()) {
positional_args.emplace_back(L".");
}
void print_banner() {
fprintf(stderr, "kgg-dec v" KGGDEC_PROJECT_VERSION " by LSR\n");
print_license();
print_usage();
}
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;
}
int main() {
CliParser cli_args;
print_banner();
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";
}
cli_args.parse_from_cli();
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;
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;
}
}
int error{-1};
@ -159,7 +115,11 @@ int main() {
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);
}
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;
}
};