From ca1162ed28443b6eb080f0e7ce252b23dc9a24a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=81=E6=A0=91=E4=BA=BA?= Date: Tue, 26 Nov 2024 06:17:20 +0900 Subject: [PATCH] refactor: refactor cli parser, print license on startup --- CMakeLists.txt | 1 + src/jobs.hpp | 8 +-- src/main.cpp | 124 ++++++++++++++++------------------------------ src/utils/cli.cpp | 67 +++++++++++++++++++++++++ src/utils/cli.h | 33 ++++++++++++ 5 files changed, 147 insertions(+), 86 deletions(-) create mode 100644 src/utils/cli.cpp create mode 100644 src/utils/cli.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 7fc0ce1..2094667 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/src/jobs.hpp b/src/jobs.hpp index cc3d50b..162a7c1 100644 --- a/src/jobs.hpp +++ b/src/jobs.hpp @@ -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 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(&header[0x10]); - uint32_t encrypt_mode = *reinterpret_cast(&header[0x14]); + const uint32_t offset_to_audio = *reinterpret_cast(&header[0x10]); + const uint32_t encrypt_mode = *reinterpret_cast(&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"; } diff --git a/src/main.cpp b/src/main.cpp index 99d4342..150fee4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,22 +1,7 @@ #include "infra/infra.h" #include "infra/sqlite_error.h" #include "jobs.hpp" -#include "qmc2/qmc2.h" - -// clang-format off -#include -#include -#include -#include -// clang-format on - -#include -#include -#include -#include -#include -#include -#include +#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::unordered_map> ParseCommandLine() { - int argc; - wchar_t** argv = CommandLineToArgvW(GetCommandLineW(), &argc); - - std::vector positional_args{}; - std::unordered_map 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(); diff --git a/src/utils/cli.cpp b/src/utils/cli.cpp new file mode 100644 index 0000000..73a4fc1 --- /dev/null +++ b/src/utils/cli.cpp @@ -0,0 +1,67 @@ +#include "cli.h" + +#include +#include + +// clang-format off +#include +#include +// 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 positional_args{}; + std::unordered_map 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); +} diff --git a/src/utils/cli.h b/src/utils/cli.h new file mode 100644 index 0000000..d5b1f68 --- /dev/null +++ b/src/utils/cli.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include +#include + +typedef std::pair, std::unordered_map> 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 get_input_files() const { return positional_args_; } + + private: + std::vector positional_args_{}; + std::unordered_map 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; + } +};