feat: allow sepecify output dir; process dir recursively
use cxxopts library to parse command line options
This commit is contained in:
parent
b9299d8988
commit
0befc5bc93
|
@ -14,7 +14,7 @@ namespace libncmdump_demo_cli
|
||||||
private static extern IntPtr CreateNeteaseCrypt(IntPtr path);
|
private static extern IntPtr CreateNeteaseCrypt(IntPtr path);
|
||||||
|
|
||||||
[DllImport(DLL_PATH, CallingConvention = CallingConvention.Cdecl)]
|
[DllImport(DLL_PATH, CallingConvention = CallingConvention.Cdecl)]
|
||||||
private static extern int Dump(IntPtr NeteaseCrypt);
|
private static extern int Dump(IntPtr NeteaseCrypt, IntPtr outputPath);
|
||||||
|
|
||||||
[DllImport(DLL_PATH, CallingConvention = CallingConvention.Cdecl)]
|
[DllImport(DLL_PATH, CallingConvention = CallingConvention.Cdecl)]
|
||||||
private static extern void FixMetadata(IntPtr NeteaseCrypt);
|
private static extern void FixMetadata(IntPtr NeteaseCrypt);
|
||||||
|
@ -42,10 +42,17 @@ namespace libncmdump_demo_cli
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 启动转换过程。
|
/// 启动转换过程。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="OutputPath">指定一个路径输出,如果为空,则输出到原路径</param>
|
||||||
/// <returns>返回一个整数,指示转储过程的结果。如果成功,返回0;如果失败,返回1。</returns>
|
/// <returns>返回一个整数,指示转储过程的结果。如果成功,返回0;如果失败,返回1。</returns>
|
||||||
public int Dump()
|
public int Dump(string OutputPath)
|
||||||
{
|
{
|
||||||
return Dump(NeteaseCryptClass);
|
byte[] bytes = Encoding.UTF8.GetBytes(OutputPath);
|
||||||
|
|
||||||
|
IntPtr outputPtr = Marshal.AllocHGlobal(bytes.Length + 1);
|
||||||
|
Marshal.Copy(bytes, 0, outputPtr, bytes.Length);
|
||||||
|
Marshal.WriteByte(outputPtr, bytes.Length, 0);
|
||||||
|
|
||||||
|
return Dump(NeteaseCryptClass, outputPtr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
@ -14,7 +14,7 @@ namespace libncmdump_demo_cli
|
||||||
NeteaseCrypt neteaseCrypt = new NeteaseCrypt(filePath);
|
NeteaseCrypt neteaseCrypt = new NeteaseCrypt(filePath);
|
||||||
|
|
||||||
// 启动转换过程
|
// 启动转换过程
|
||||||
int result = neteaseCrypt.Dump();
|
int result = neteaseCrypt.Dump(""); // 为空则输出到源
|
||||||
|
|
||||||
// 修复元数据
|
// 修复元数据
|
||||||
neteaseCrypt.FixMetadata();
|
neteaseCrypt.FixMetadata();
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -66,6 +66,6 @@ public:
|
||||||
~NeteaseCrypt();
|
~NeteaseCrypt();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
void Dump();
|
void Dump(std::string const&);
|
||||||
void FixMetadata();
|
void FixMetadata();
|
||||||
};
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
#define VERSION_MAJOR 1
|
||||||
|
#define VERSION_MINOR 5
|
||||||
|
#define VERSION_PATCH 0
|
|
@ -9,10 +9,10 @@ extern "C" {
|
||||||
return new NeteaseCrypt(fPath.u8string());
|
return new NeteaseCrypt(fPath.u8string());
|
||||||
}
|
}
|
||||||
|
|
||||||
API int Dump(NeteaseCrypt* neteaseCrypt) {
|
API int Dump(NeteaseCrypt* neteaseCrypt, const char* outputPath) {
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
neteaseCrypt->Dump();
|
neteaseCrypt->Dump(outputPath);
|
||||||
}
|
}
|
||||||
catch (const std::invalid_argument& e)
|
catch (const std::invalid_argument& e)
|
||||||
{
|
{
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
API NeteaseCrypt* CreateNeteaseCrypt(const char* path);
|
API NeteaseCrypt* CreateNeteaseCrypt(const char* path);
|
||||||
API int Dump(NeteaseCrypt* neteaseCrypt);
|
API int Dump(NeteaseCrypt* neteaseCrypt, const char* outputPath);
|
||||||
API void FixMetadata(NeteaseCrypt* neteaseCrypt);
|
API void FixMetadata(NeteaseCrypt* neteaseCrypt);
|
||||||
API void DestroyNeteaseCrypt(NeteaseCrypt* neteaseCrypt);
|
API void DestroyNeteaseCrypt(NeteaseCrypt* neteaseCrypt);
|
||||||
}
|
}
|
197
src/main.cpp
197
src/main.cpp
|
@ -10,115 +10,190 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "color.h"
|
#include "color.h"
|
||||||
|
#include "version.h"
|
||||||
|
#include "cxxopts.hpp"
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
void displayHelp()
|
void processFile(const fs::path &filePath, const fs::path &outputFolder)
|
||||||
{
|
|
||||||
std::cout << "Usage: ncmdump [-d] [-h] file1 file2 ..." << std::endl;
|
|
||||||
std::cout << "Options:" << std::endl;
|
|
||||||
std::cout << " -d Process files in a folder (requires folder path)" << std::endl;
|
|
||||||
std::cout << " -h, --help Display this help message" << std::endl;
|
|
||||||
}
|
|
||||||
|
|
||||||
void processFile(const fs::path &filePath)
|
|
||||||
{
|
{
|
||||||
if (fs::exists(filePath) == false)
|
if (fs::exists(filePath) == false)
|
||||||
{
|
{
|
||||||
std::cerr << BOLDRED << "Error: " << RESET << "file '" << filePath.u8string() << "' does not exist." << std::endl;
|
std::cerr << BOLDRED << "[Error] " << RESET << "file '" << filePath.u8string() << "' does not exist." << std::endl;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip if not ending with ".ncm"
|
||||||
|
if (!filePath.has_extension() || filePath.extension().u8string()!= ".ncm")
|
||||||
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
NeteaseCrypt crypt(filePath.u8string());
|
NeteaseCrypt crypt(filePath.u8string());
|
||||||
crypt.Dump();
|
crypt.Dump(outputFolder.u8string());
|
||||||
crypt.FixMetadata();
|
crypt.FixMetadata();
|
||||||
|
|
||||||
std::cout << BOLDGREEN << "[Done] " << RESET << "'" << filePath.u8string() << "' -> '" << crypt.dumpFilepath().u8string() << "'" << std::endl;
|
std::cout << BOLDGREEN << "[Done] " << RESET << "'" << filePath.u8string() << "' -> '" << crypt.dumpFilepath().u8string() << "'" << std::endl;
|
||||||
}
|
}
|
||||||
catch (const std::invalid_argument &e)
|
catch (const std::invalid_argument &e)
|
||||||
{
|
{
|
||||||
std::cerr << BOLDRED << "Exception: " << RESET << RED << e.what() << RESET << " '" << filePath.u8string() << "'" << std::endl;
|
std::cerr << BOLDRED << "[Exception] " << RESET << RED << e.what() << RESET << " '" << filePath.u8string() << "'" << std::endl;
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
std::cerr << BOLDRED << "Unexpected exception while processing file: " << RESET << filePath.u8string() << std::endl;
|
std::cerr << BOLDRED << "[Error] Unexpected exception while processing file: " << RESET << filePath.u8string() << std::endl;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void processFilesInFolder(const fs::path &folderPath)
|
|
||||||
{
|
|
||||||
for (const auto &entry : fs::directory_iterator(folderPath))
|
|
||||||
{
|
|
||||||
if (fs::is_regular_file(entry.status()))
|
|
||||||
{
|
|
||||||
processFile(entry.path());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
#if defined(_WIN32)
|
#if defined(_WIN32)
|
||||||
win32_utf8argv(&argc, &argv);
|
win32_utf8argv(&argc, &argv); // Convert command line arguments to UTF-8 under Windows
|
||||||
#endif
|
#endif
|
||||||
if (argc <= 1)
|
|
||||||
|
cxxopts::Options options("ncmdump");
|
||||||
|
|
||||||
|
options.add_options()
|
||||||
|
("h,help", "Print usage")
|
||||||
|
("d,directory", "Process files in a folder (requires folder path)", cxxopts::value<std::string>())
|
||||||
|
("r,recursive", "Process files recursively (requires -d option)", cxxopts::value<bool>()->default_value("false"))
|
||||||
|
("o,output", "Output folder (default: original file folder)", cxxopts::value<std::string>())
|
||||||
|
("v,version", "Print version information", cxxopts::value<bool>()->default_value("false"))
|
||||||
|
("filenames", "Input files", cxxopts::value<std::vector<std::string>>());
|
||||||
|
|
||||||
|
options.parse_positional({"filenames"});
|
||||||
|
|
||||||
|
// Parse options the usual way
|
||||||
|
auto result = options.parse(argc, argv);
|
||||||
|
|
||||||
|
// display help message
|
||||||
|
if (result.count("help"))
|
||||||
{
|
{
|
||||||
displayHelp();
|
std::cout << options.help() << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// display version information
|
||||||
|
if (result.count("version"))
|
||||||
|
{
|
||||||
|
std::cout << "ncmdump version " << VERSION_MAJOR << "." << VERSION_MINOR << "." << VERSION_PATCH << std::endl;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// no input files or folder provided
|
||||||
|
if (result.count("directory") == 0 && result.count("filenames") == 0)
|
||||||
|
{
|
||||||
|
std::cout << options.help() << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<fs::path> files;
|
// only -r option without -d option
|
||||||
bool processFolders = false;
|
if (result.count("recursive") && result.count("directory") == 0)
|
||||||
|
|
||||||
bool folderProvided = false;
|
|
||||||
|
|
||||||
#define COMPARE_STR(s1, s2) (strcmp(s1, s2) == 0)
|
|
||||||
#define HELP_SHORT "-h"
|
|
||||||
#define HELP_LONG "--help"
|
|
||||||
#define PROCESS_FOLDER "-d"
|
|
||||||
for (int i = 1; i < argc; ++i)
|
|
||||||
{
|
{
|
||||||
if (COMPARE_STR(argv[i], HELP_SHORT) || COMPARE_STR(argv[i], HELP_LONG))
|
std::cerr << BOLDRED << "[Error] " << RESET << "-r option requires -d option." << std::endl;
|
||||||
{
|
return 1;
|
||||||
displayHelp();
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
else if (COMPARE_STR(argv[i], PROCESS_FOLDER))
|
|
||||||
|
// check output folder
|
||||||
|
fs::path outputDir = fs::u8path("");
|
||||||
|
bool outputDirSpecified = result.count("output") > 0;
|
||||||
|
|
||||||
|
if (outputDirSpecified)
|
||||||
{
|
{
|
||||||
processFolders = true;
|
outputDir = fs::u8path(result["output"].as<std::string>());
|
||||||
if (i + 1 < argc && argv[i + 1][0] != '-')
|
if (fs::exists(outputDir))
|
||||||
{
|
{
|
||||||
folderProvided = true;
|
if (!fs::is_directory(outputDir))
|
||||||
processFilesInFolder(fs::u8path(argv[i + 1]));
|
|
||||||
// Skip the folder name
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
std::cerr << "Error: -d option requires a folder path." << std::endl;
|
std::cerr << BOLDRED << "[Error] " << RESET << "'" << outputDir.u8string() << "' is not a valid directory." << std::endl;
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
fs::create_directories(outputDir);
|
||||||
{
|
|
||||||
fs::path path = fs::u8path(argv[i]);
|
|
||||||
files.push_back(path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const auto &file : files)
|
// process files in a folder
|
||||||
|
if (result.count("directory"))
|
||||||
{
|
{
|
||||||
if (processFolders && fs::is_directory(file))
|
fs::path sourceDir = fs::u8path(result["directory"].as<std::string>());
|
||||||
|
if (!fs::is_directory(sourceDir))
|
||||||
{
|
{
|
||||||
processFilesInFolder(file);
|
std::cerr << BOLDRED << "[Error] " << RESET << "'" << sourceDir.u8string() << "' is not a valid directory." << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool recursive = result["recursive"].as<bool>();
|
||||||
|
|
||||||
|
if (recursive)
|
||||||
|
{
|
||||||
|
// 递归遍历源目录
|
||||||
|
for (const auto &entry : fs::recursive_directory_iterator(sourceDir))
|
||||||
|
{
|
||||||
|
// 没有指定输出目录,则使用源目录作为输出目录
|
||||||
|
if (!outputDirSpecified)
|
||||||
|
{
|
||||||
|
outputDir = sourceDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获得递归遍历的相对路径
|
||||||
|
const auto &path = fs::u8path(entry.path().u8string());
|
||||||
|
auto relativePath = fs::relative(path, sourceDir);
|
||||||
|
fs::path destinationPath = outputDir / relativePath;
|
||||||
|
|
||||||
|
if (fs::is_regular_file(path))
|
||||||
|
{
|
||||||
|
// 确保输出文件的目录存在
|
||||||
|
fs::create_directories(destinationPath.parent_path());
|
||||||
|
|
||||||
|
// 处理文件
|
||||||
|
processFile(path, destinationPath.parent_path());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
processFile(file);
|
for (const auto &entry : fs::directory_iterator(sourceDir))
|
||||||
|
{
|
||||||
|
const auto &path = fs::u8path(entry.path().u8string());
|
||||||
|
if (entry.is_regular_file())
|
||||||
|
{
|
||||||
|
if (outputDirSpecified)
|
||||||
|
{
|
||||||
|
processFile(path, outputDir);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processFile(path, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// process individual files
|
||||||
|
if (result.count("filenames"))
|
||||||
|
{
|
||||||
|
for (const auto &filePath : result["filenames"].as<std::vector<std::string>>())
|
||||||
|
{
|
||||||
|
fs::path filePathU8 = fs::u8path(filePath);
|
||||||
|
if (!fs::is_regular_file(filePathU8))
|
||||||
|
{
|
||||||
|
std::cerr << BOLDRED << "[Error] " << RESET << "'" << filePathU8.u8string() << "' is not a valid file." << std::endl;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputDirSpecified)
|
||||||
|
{
|
||||||
|
processFile(filePathU8, outputDir);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
processFile(filePathU8, "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
#pragma warning(disable:4267)
|
||||||
|
#pragma warning(disable:4244)
|
||||||
|
|
||||||
const unsigned char NeteaseCrypt::sCoreKey[17] = {0x68, 0x7A, 0x48, 0x52, 0x41, 0x6D, 0x73, 0x6F, 0x35, 0x6B, 0x49, 0x6E, 0x62, 0x61, 0x78, 0x57, 0};
|
const unsigned char NeteaseCrypt::sCoreKey[17] = {0x68, 0x7A, 0x48, 0x52, 0x41, 0x6D, 0x73, 0x6F, 0x35, 0x6B, 0x49, 0x6E, 0x62, 0x61, 0x78, 0x57, 0};
|
||||||
const unsigned char NeteaseCrypt::sModifyKey[17] = {0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21, 0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28, 0};
|
const unsigned char NeteaseCrypt::sModifyKey[17] = {0x23, 0x31, 0x34, 0x6C, 0x6A, 0x6B, 0x5F, 0x21, 0x5C, 0x5D, 0x26, 0x30, 0x55, 0x3C, 0x27, 0x28, 0};
|
||||||
|
|
||||||
|
@ -260,9 +263,14 @@ void NeteaseCrypt::FixMetadata()
|
||||||
audioFile->~File();
|
audioFile->~File();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeteaseCrypt::Dump()
|
void NeteaseCrypt::Dump(std::string const &outputDir = "")
|
||||||
|
{
|
||||||
|
if (outputDir.empty())
|
||||||
{
|
{
|
||||||
mDumpFilepath = std::filesystem::u8path(mFilepath);
|
mDumpFilepath = std::filesystem::u8path(mFilepath);
|
||||||
|
} else {
|
||||||
|
mDumpFilepath = std::filesystem::u8path(outputDir) / std::filesystem::u8path(mFilepath).filename();
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<unsigned char> buffer(0x8000);
|
std::vector<unsigned char> buffer(0x8000);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue