Kelsidavis-WoWee/src/pipeline/asset_manifest.cpp
Kelsi aa16a687c2 Replace MPQ runtime with loose file asset system
Extract assets from MPQ archives into organized loose files indexed by
manifest.json, enabling fully parallel reads without StormLib serialization.
Add asset_extract and blp_convert tools, PNG texture override support.
2026-02-12 20:32:14 -08:00

97 lines
2.8 KiB
C++

#include "pipeline/asset_manifest.hpp"
#include "core/logger.hpp"
#include <nlohmann/json.hpp>
#include <fstream>
#include <filesystem>
#include <chrono>
namespace wowee {
namespace pipeline {
bool AssetManifest::load(const std::string& manifestPath) {
auto startTime = std::chrono::steady_clock::now();
std::ifstream file(manifestPath);
if (!file.is_open()) {
LOG_ERROR("Failed to open manifest: ", manifestPath);
return false;
}
nlohmann::json doc;
try {
doc = nlohmann::json::parse(file);
} catch (const nlohmann::json::parse_error& e) {
LOG_ERROR("Failed to parse manifest JSON: ", e.what());
return false;
}
// Read header
int version = doc.value("version", 0);
if (version != 1) {
LOG_ERROR("Unsupported manifest version: ", version);
return false;
}
basePath_ = doc.value("basePath", "assets");
manifestDir_ = std::filesystem::path(manifestPath).parent_path().string();
// If basePath is relative, resolve against manifest directory
if (!basePath_.empty() && basePath_[0] != '/') {
basePath_ = manifestDir_ + "/" + basePath_;
}
// Parse entries
auto& entriesObj = doc["entries"];
if (!entriesObj.is_object()) {
LOG_ERROR("Manifest missing 'entries' object");
return false;
}
entries_.reserve(entriesObj.size());
for (auto& [key, val] : entriesObj.items()) {
Entry entry;
entry.filesystemPath = val.value("p", "");
entry.size = val.value("s", uint64_t(0));
// Parse CRC32 from hex string
std::string hexHash = val.value("h", "");
if (!hexHash.empty()) {
entry.crc32 = static_cast<uint32_t>(std::strtoul(hexHash.c_str(), nullptr, 16));
} else {
entry.crc32 = 0;
}
entries_[key] = std::move(entry);
}
loaded_ = true;
auto elapsed = std::chrono::steady_clock::now() - startTime;
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
LOG_INFO("Loaded asset manifest: ", entries_.size(), " entries in ", ms, "ms (base: ", basePath_, ")");
return true;
}
const AssetManifest::Entry* AssetManifest::lookup(const std::string& normalizedWowPath) const {
auto it = entries_.find(normalizedWowPath);
if (it != entries_.end()) {
return &it->second;
}
return nullptr;
}
std::string AssetManifest::resolveFilesystemPath(const std::string& normalizedWowPath) const {
auto it = entries_.find(normalizedWowPath);
if (it == entries_.end()) {
return {};
}
return basePath_ + "/" + it->second.filesystemPath;
}
bool AssetManifest::hasEntry(const std::string& normalizedWowPath) const {
return entries_.find(normalizedWowPath) != entries_.end();
}
} // namespace pipeline
} // namespace wowee