#include "content_pack.hpp" #include "core/logger.hpp" #include #include #include namespace wowee { namespace editor { // WCP file format (simple concatenated archive): // Header: "WCP1" (4 bytes) + fileCount (4) + infoJsonSize (4) // Info JSON block (infoJsonSize bytes) // File table: for each file: pathLen(2) + path(pathLen) + dataSize(4) // File data: concatenated file contents static constexpr uint32_t WCP_MAGIC = 0x31504357; // "WCP1" bool ContentPacker::packZone(const std::string& outputDir, const std::string& mapName, const std::string& destPath, const ContentPackInfo& info) { namespace fs = std::filesystem; std::string srcDir = outputDir + "/" + mapName; if (!fs::exists(srcDir)) { LOG_ERROR("Source directory not found: ", srcDir); return false; } // Collect all files std::vector> files; // relative path, full path for (auto& entry : fs::recursive_directory_iterator(srcDir)) { if (!entry.is_regular_file()) continue; std::string rel = fs::relative(entry.path(), srcDir).string(); files.push_back({rel, entry.path().string()}); } if (files.empty()) { LOG_ERROR("No files to pack in: ", srcDir); return false; } // Build info JSON std::string infoJson = "{\n"; infoJson += " \"format\": \"" + info.format + "\",\n"; infoJson += " \"name\": \"" + info.name + "\",\n"; infoJson += " \"author\": \"" + info.author + "\",\n"; infoJson += " \"description\": \"" + info.description + "\",\n"; infoJson += " \"version\": \"" + info.version + "\",\n"; infoJson += " \"mapId\": " + std::to_string(info.mapId) + ",\n"; infoJson += " \"fileCount\": " + std::to_string(files.size()) + ",\n"; infoJson += " \"files\": [\n"; for (size_t i = 0; i < files.size(); i++) { auto fsize = fs::file_size(files[i].second); infoJson += " {\"path\": \"" + files[i].first + "\", \"size\": " + std::to_string(fsize) + "}"; if (i + 1 < files.size()) infoJson += ","; infoJson += "\n"; } infoJson += " ]\n}\n"; // Write WCP file std::ofstream out(destPath, std::ios::binary); if (!out) { LOG_ERROR("Failed to create pack file: ", destPath); return false; } // Header out.write(reinterpret_cast(&WCP_MAGIC), 4); uint32_t fileCount = static_cast(files.size()); out.write(reinterpret_cast(&fileCount), 4); uint32_t infoSize = static_cast(infoJson.size()); out.write(reinterpret_cast(&infoSize), 4); // Info JSON out.write(infoJson.data(), infoJson.size()); // File table + data for (const auto& [rel, full] : files) { uint16_t pathLen = static_cast(rel.size()); out.write(reinterpret_cast(&pathLen), 2); out.write(rel.data(), pathLen); std::ifstream fin(full, std::ios::binary | std::ios::ate); uint32_t dataSize = static_cast(fin.tellg()); fin.seekg(0); out.write(reinterpret_cast(&dataSize), 4); std::vector buf(dataSize); fin.read(buf.data(), dataSize); out.write(buf.data(), dataSize); } LOG_INFO("Content pack created: ", destPath, " (", files.size(), " files, ", out.tellp(), " bytes)"); return true; } bool ContentPacker::unpackZone(const std::string& wcpPath, const std::string& destDir) { std::ifstream in(wcpPath, std::ios::binary); if (!in) return false; uint32_t magic; in.read(reinterpret_cast(&magic), 4); if (magic != WCP_MAGIC) { LOG_ERROR("Not a WCP file: ", wcpPath); return false; } uint32_t fileCount, infoSize; in.read(reinterpret_cast(&fileCount), 4); in.read(reinterpret_cast(&infoSize), 4); // Skip info JSON in.seekg(infoSize, std::ios::cur); namespace fs = std::filesystem; fs::create_directories(destDir); for (uint32_t i = 0; i < fileCount; i++) { uint16_t pathLen; in.read(reinterpret_cast(&pathLen), 2); std::string path(pathLen, '\0'); in.read(path.data(), pathLen); uint32_t dataSize; in.read(reinterpret_cast(&dataSize), 4); std::vector data(dataSize); in.read(data.data(), dataSize); std::string fullPath = destDir + "/" + path; fs::create_directories(fs::path(fullPath).parent_path()); std::ofstream fout(fullPath, std::ios::binary); fout.write(data.data(), dataSize); } LOG_INFO("Content pack extracted to: ", destDir, " (", fileCount, " files)"); return true; } bool ContentPacker::readInfo(const std::string& wcpPath, ContentPackInfo& info) { std::ifstream in(wcpPath, std::ios::binary); if (!in) return false; uint32_t magic; in.read(reinterpret_cast(&magic), 4); if (magic != WCP_MAGIC) return false; uint32_t fileCount, infoSize; in.read(reinterpret_cast(&fileCount), 4); in.read(reinterpret_cast(&infoSize), 4); std::string json(infoSize, '\0'); in.read(json.data(), infoSize); // Parse basic fields auto findStr = [&](const std::string& key) -> std::string { auto pos = json.find("\"" + key + "\""); if (pos == std::string::npos) return ""; pos = json.find('"', json.find(':', pos) + 1); if (pos == std::string::npos) return ""; auto end = json.find('"', pos + 1); return json.substr(pos + 1, end - pos - 1); }; info.name = findStr("name"); info.author = findStr("author"); info.description = findStr("description"); info.version = findStr("version"); info.format = findStr("format"); return true; } } // namespace editor } // namespace wowee