Extra2D/Tools/asset_packer.cpp

461 lines
14 KiB
C++
Raw Normal View History

/**
* @file asset_packer.cpp
* @brief
*
*
*
* :
* asset_packer create <output.pack> [options] <input files/dirs...>
* asset_packer list <pack file>
* asset_packer extract <pack file> <output dir>
*
* :
* -c, --compression <algo> (none, zstd, lz4, zlib) zstd
* -l, --level <level> (1-22) 3
* -e, --encrypt <key>
* -t, --encrypt-type <type> (xor, aes) xor
* -v, --verbose
* -h, --help
*/
#include <extra2d/asset/asset_pack.h>
#include <extra2d/asset/data_processor.h>
#include <extra2d/core/types.h>
#include <chrono>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
namespace fs = std::filesystem;
// ---------------------------------------------------------------------------
// 命令行参数解析
// ---------------------------------------------------------------------------
struct Options {
std::string command;
std::string outputFile;
std::string packFile;
std::string outputDir;
std::vector<std::string> inputs;
extra2d::Compression compression = extra2d::Compression::Zstd;
int compressionLevel = 3;
std::string encryptKey;
extra2d::Decryptor::Type encryptType = extra2d::Decryptor::Type::XOR;
bool verbose = false;
bool showHelp = false;
};
/**
* @brief
*/
void printHelp(const char* programName) {
std::cout << "Extra2D 资源打包工具 v1.0.0\n\n";
std::cout << "用法:\n";
std::cout << " " << programName << " create <output.pack> [options] <inputs...>\n";
std::cout << " " << programName << " list <pack file>\n";
std::cout << " " << programName << " extract <pack file> <output dir>\n\n";
std::cout << "命令:\n";
std::cout << " create 创建资源包\n";
std::cout << " list 列出资源包内容\n";
std::cout << " extract 提取资源包内容\n\n";
std::cout << "选项:\n";
std::cout << " -c, --compression <algo> 压缩算法 (none, zstd, lz4, zlib),默认 zstd\n";
std::cout << " -l, --level <level> 压缩级别 (1-22),默认 3\n";
std::cout << " -e, --encrypt <key> 加密密钥\n";
std::cout << " -t, --encrypt-type <type> 加密类型 (xor, aes),默认 xor\n";
std::cout << " -v, --verbose 详细输出\n";
std::cout << " -h, --help 显示帮助\n";
}
/**
* @brief
*/
extra2d::Compression parseCompression(const std::string& name) {
if (name == "none" || name == "None" || name == "NONE") {
return extra2d::Compression::None;
} else if (name == "zstd" || name == "Zstd" || name == "ZSTD") {
return extra2d::Compression::Zstd;
} else if (name == "lz4" || name == "LZ4") {
return extra2d::Compression::LZ4;
} else if (name == "zlib" || name == "Zlib" || name == "ZLIB") {
return extra2d::Compression::Zlib;
}
return extra2d::Compression::Zstd;
}
/**
* @brief
*/
extra2d::Decryptor::Type parseEncryptType(const std::string& name) {
if (name == "xor" || name == "XOR") {
return extra2d::Decryptor::Type::XOR;
} else if (name == "aes" || name == "AES" || name == "aes256") {
return extra2d::Decryptor::Type::AES256;
}
return extra2d::Decryptor::Type::XOR;
}
/**
* @brief
*/
bool parseArgs(int argc, char* argv[], Options& opts) {
if (argc < 2) {
opts.showHelp = true;
return false;
}
opts.command = argv[1];
if (opts.command == "-h" || opts.command == "--help") {
opts.showHelp = true;
return true;
}
for (int i = 2; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "-h" || arg == "--help") {
opts.showHelp = true;
return true;
} else if (arg == "-v" || arg == "--verbose") {
opts.verbose = true;
} else if (arg == "-c" || arg == "--compression") {
if (i + 1 < argc) {
opts.compression = parseCompression(argv[++i]);
}
} else if (arg == "-l" || arg == "--level") {
if (i + 1 < argc) {
opts.compressionLevel = std::stoi(argv[++i]);
}
} else if (arg == "-e" || arg == "--encrypt") {
if (i + 1 < argc) {
opts.encryptKey = argv[++i];
}
} else if (arg == "-t" || arg == "--encrypt-type") {
if (i + 1 < argc) {
opts.encryptType = parseEncryptType(argv[++i]);
}
} else if (arg[0] != '-') {
if (opts.command == "create") {
if (opts.outputFile.empty()) {
opts.outputFile = arg;
} else {
opts.inputs.push_back(arg);
}
} else if (opts.command == "list") {
if (opts.packFile.empty()) {
opts.packFile = arg;
}
} else if (opts.command == "extract") {
if (opts.packFile.empty()) {
opts.packFile = arg;
} else if (opts.outputDir.empty()) {
opts.outputDir = arg;
}
}
}
}
return true;
}
// ---------------------------------------------------------------------------
// 创建资源包
// ---------------------------------------------------------------------------
/**
* @brief
*/
std::string formatSize(size_t bytes) {
const char* units[] = {"B", "KB", "MB", "GB"};
int unitIndex = 0;
double size = static_cast<double>(bytes);
while (size >= 1024.0 && unitIndex < 3) {
size /= 1024.0;
++unitIndex;
}
std::ostringstream ss;
ss.precision(2);
ss << size << " " << units[unitIndex];
return ss.str();
}
/**
* @brief
*/
int createPack(const Options& opts) {
if (opts.outputFile.empty()) {
std::cerr << "错误: 未指定输出文件\n";
return 1;
}
if (opts.inputs.empty()) {
std::cerr << "错误: 未指定输入文件或目录\n";
return 1;
}
auto startTime = std::chrono::high_resolution_clock::now();
extra2d::AssetPackBuilder builder(opts.compression, opts.compressionLevel);
if (!opts.encryptKey.empty()) {
builder.setEncryption(opts.encryptKey, opts.encryptType);
}
size_t totalFiles = 0;
size_t totalSize = 0;
for (const auto& input : opts.inputs) {
fs::path inputPath(input);
if (!fs::exists(inputPath)) {
std::cerr << "警告: 跳过不存在的路径: " << input << "\n";
continue;
}
if (fs::is_directory(inputPath)) {
if (opts.verbose) {
std::cout << "扫描目录: " << input << "\n";
}
for (const auto& entry : fs::recursive_directory_iterator(inputPath)) {
if (entry.is_regular_file()) {
fs::path relativePath = fs::relative(entry.path(), inputPath);
std::string packPath = relativePath.generic_string();
if (opts.verbose) {
std::cout << " 添加: " << packPath << "\n";
}
if (builder.addFile(entry.path().string(), packPath)) {
++totalFiles;
totalSize += fs::file_size(entry.path());
}
}
}
} else if (fs::is_regular_file(inputPath)) {
std::string packPath = inputPath.filename().string();
if (opts.verbose) {
std::cout << "添加文件: " << packPath << "\n";
}
if (builder.addFile(inputPath.string(), packPath)) {
++totalFiles;
totalSize += fs::file_size(inputPath);
}
}
}
if (totalFiles == 0) {
std::cerr << "错误: 没有文件被添加到资源包\n";
return 1;
}
if (opts.verbose) {
std::cout << "\n构建资源包: " << opts.outputFile << "\n";
}
if (!builder.build(opts.outputFile)) {
std::cerr << "错误: 构建资源包失败\n";
return 1;
}
auto endTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
size_t packSize = fs::file_size(opts.outputFile);
double ratio = totalSize > 0 ? (100.0 * packSize / totalSize) : 0.0;
std::cout << "\n完成!\n";
std::cout << " 文件数量: " << totalFiles << "\n";
std::cout << " 原始大小: " << formatSize(totalSize) << "\n";
std::cout << " 压缩大小: " << formatSize(packSize) << "\n";
std::cout << " 压缩比率: " << std::fixed << std::setprecision(1) << ratio << "%\n";
std::cout << " 耗时: " << duration.count() << "ms\n";
return 0;
}
// ---------------------------------------------------------------------------
// 列出资源包内容
// ---------------------------------------------------------------------------
/**
* @brief
*/
int listPack(const Options& opts) {
if (opts.packFile.empty()) {
std::cerr << "错误: 未指定资源包文件\n";
return 1;
}
if (!fs::exists(opts.packFile)) {
std::cerr << "错误: 资源包文件不存在: " << opts.packFile << "\n";
return 1;
}
extra2d::AssetPack pack;
if (!pack.open(opts.packFile)) {
std::cerr << "错误: 无法打开资源包\n";
return 1;
}
const auto& header = pack.header();
std::cout << "资源包: " << opts.packFile << "\n";
std::cout << "----------------------------------------\n";
std::cout << "版本: " << header.version << "\n";
std::cout << "条目数: " << pack.count() << "\n";
std::cout << "----------------------------------------\n\n";
auto assets = pack.assets();
std::cout << std::left << std::setw(40) << "路径"
<< std::right << std::setw(12) << "大小" << "\n";
std::cout << std::string(52, '-') << "\n";
size_t totalSize = 0;
for (const auto& id : assets) {
auto* entry = pack.getEntry(id);
if (entry) {
std::cout << std::left << std::setw(40) << id.path
<< std::right << std::setw(12) << formatSize(entry->size) << "\n";
totalSize += entry->size;
}
}
std::cout << std::string(52, '-') << "\n";
std::cout << std::left << std::setw(40) << "总计"
<< std::right << std::setw(12) << formatSize(totalSize) << "\n";
pack.close();
return 0;
}
// ---------------------------------------------------------------------------
// 提取资源包
// ---------------------------------------------------------------------------
/**
* @brief
*/
int extractPack(const Options& opts) {
if (opts.packFile.empty()) {
std::cerr << "错误: 未指定资源包文件\n";
return 1;
}
if (opts.outputDir.empty()) {
std::cerr << "错误: 未指定输出目录\n";
return 1;
}
if (!fs::exists(opts.packFile)) {
std::cerr << "错误: 资源包文件不存在: " << opts.packFile << "\n";
return 1;
}
extra2d::AssetPack pack;
if (!pack.open(opts.packFile)) {
std::cerr << "错误: 无法打开资源包\n";
return 1;
}
fs::path outputDir(opts.outputDir);
if (!fs::exists(outputDir)) {
fs::create_directories(outputDir);
}
auto assets = pack.assets();
size_t extracted = 0;
size_t failed = 0;
std::cout << "提取资源包到: " << opts.outputDir << "\n\n";
for (const auto& id : assets) {
fs::path outputPath = outputDir / id.path;
fs::path parentDir = outputPath.parent_path();
if (!parentDir.empty() && !fs::exists(parentDir)) {
fs::create_directories(parentDir);
}
auto data = pack.read(id);
if (data.empty()) {
std::cerr << "警告: 无法读取: " << id.path << "\n";
++failed;
continue;
}
std::ofstream outFile(outputPath, std::ios::binary);
if (!outFile) {
std::cerr << "警告: 无法创建文件: " << outputPath.string() << "\n";
++failed;
continue;
}
outFile.write(reinterpret_cast<const char*>(data.data()), data.size());
if (opts.verbose) {
std::cout << " 提取: " << id.path << " (" << formatSize(data.size()) << ")\n";
}
++extracted;
}
pack.close();
std::cout << "\n完成! 提取 " << extracted << " 个文件";
if (failed > 0) {
std::cout << ", " << failed << " 个失败";
}
std::cout << "\n";
return failed > 0 ? 1 : 0;
}
// ---------------------------------------------------------------------------
// 主函数
// ---------------------------------------------------------------------------
#include <iomanip>
int main(int argc, char* argv[]) {
Options opts;
if (!parseArgs(argc, argv, opts)) {
printHelp(argv[0]);
return 1;
}
if (opts.showHelp) {
printHelp(argv[0]);
return 0;
}
if (opts.command == "create") {
return createPack(opts);
} else if (opts.command == "list") {
return listPack(opts);
} else if (opts.command == "extract") {
return extractPack(opts);
} else {
std::cerr << "错误: 未知命令: " << opts.command << "\n";
printHelp(argv[0]);
return 1;
}
}