/** * @file asset_packer.cpp * @brief 资源打包工具 * * 命令行工具,用于创建、查看和提取资源包。 * * 用法: * asset_packer create [options] * asset_packer list * asset_packer extract * * 选项: * -c, --compression 压缩算法 (none, zstd, lz4, zlib),默认 zstd * -l, --level 压缩级别 (1-22),默认 3 * -e, --encrypt 加密密钥 * -t, --encrypt-type 加密类型 (xor, aes),默认 xor * -v, --verbose 详细输出 * -h, --help 显示帮助 */ #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; // --------------------------------------------------------------------------- // 命令行参数解析 // --------------------------------------------------------------------------- struct Options { std::string command; std::string outputFile; std::string packFile; std::string outputDir; std::vector 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 [options] \n"; std::cout << " " << programName << " list \n"; std::cout << " " << programName << " extract \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 压缩算法 (none, zstd, lz4, zlib),默认 zstd\n"; std::cout << " -l, --level 压缩级别 (1-22),默认 3\n"; std::cout << " -e, --encrypt 加密密钥\n"; std::cout << " -t, --encrypt-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(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(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(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 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; } }