Unify asset system: one asset set, always high-res

Remove HDPackManager, expansion overlay manifests, and BLP size-comparison
logic. Assets now resolve through a single manifest with a simple override
directory (Data/override/) for future HD upgrades.
This commit is contained in:
Kelsi 2026-02-15 04:18:34 -08:00
parent 1bc7b12b20
commit d7e2b26af7
13 changed files with 31 additions and 658 deletions

View file

@ -41,7 +41,7 @@
#include "game/packet_parsers.hpp"
#include "pipeline/asset_manager.hpp"
#include "pipeline/dbc_layout.hpp"
#include "pipeline/hd_pack_manager.hpp"
#include <SDL2/SDL.h>
#include <GL/glew.h>
#include <chrono>
@ -129,9 +129,6 @@ bool Application::initialize() {
// Create DBC layout
dbcLayout_ = std::make_unique<pipeline::DBCLayout>();
// Create HD pack manager
hdPackManager_ = std::make_unique<pipeline::HDPackManager>();
// Create asset manager
assetManager = std::make_unique<pipeline::AssetManager>();
@ -211,37 +208,6 @@ bool Application::initialize() {
gameHandler->getTransportManager()->loadTaxiPathNodeDBC(assetManager.get());
}
// Initialize HD texture packs
if (hdPackManager_) {
std::string hdPath = dataPath + "/hd";
std::string settingsDir;
const char* xdg = std::getenv("XDG_DATA_HOME");
if (xdg && *xdg) {
settingsDir = std::string(xdg) + "/wowee";
} else {
const char* home = std::getenv("HOME");
settingsDir = std::string(home ? home : ".") + "/.local/share/wowee";
}
hdPackManager_->loadSettings(settingsDir + "/settings.cfg");
hdPackManager_->initialize(hdPath);
// Apply enabled packs as overlays
std::string expansionId = "wotlk";
if (expansionRegistry_ && expansionRegistry_->getActive()) {
expansionId = expansionRegistry_->getActive()->id;
}
hdPackManager_->applyToAssetManager(assetManager.get(), expansionId);
}
// Load expansion-specific asset overlay (priority 50, below HD packs at 100+)
if (expansionRegistry_) {
auto* activeProfile = expansionRegistry_->getActive();
if (activeProfile && !activeProfile->assetManifest.empty()) {
if (assetManager->addOverlayManifest(activeProfile->assetManifest, 50, "expansion_overlay")) {
LOG_INFO("Added expansion asset overlay: ", activeProfile->assetManifest);
}
}
}
} else {
LOG_WARNING("Failed to initialize asset manager - asset loading will be unavailable");
LOG_WARNING("Set WOW_DATA_PATH environment variable to your WoW Data directory");
@ -504,14 +470,6 @@ void Application::reloadExpansionData() {
if (assetManager && !profile->dataPath.empty()) {
assetManager->setExpansionDataPath(profile->dataPath);
assetManager->clearDBCCache();
// Swap expansion asset overlay
assetManager->removeOverlay("expansion_overlay");
if (!profile->assetManifest.empty()) {
if (assetManager->addOverlayManifest(profile->assetManifest, 50, "expansion_overlay")) {
LOG_INFO("Swapped expansion asset overlay: ", profile->assetManifest);
}
}
}
// Reset map name cache so it reloads from new expansion's Map.dbc

View file

@ -176,12 +176,6 @@ bool ExpansionRegistry::loadProfile(const std::string& jsonPath, const std::stri
v = jsonValue(json, "locale"); if (!v.empty()) p.locale = v;
p.timezone = static_cast<uint32_t>(jsonInt(json, "timezone", static_cast<int>(p.timezone)));
}
// Expansion-specific asset manifest (overlay for base data)
p.assetManifest = jsonValue(json, "assetManifest");
if (!p.assetManifest.empty() && p.assetManifest[0] != '/') {
p.assetManifest = dirPath + "/" + p.assetManifest;
}
p.maxLevel = static_cast<uint32_t>(jsonInt(json, "maxLevel", 60));
p.races = jsonUintArray(json, "races");
p.classes = jsonUintArray(json, "classes");

View file

@ -42,6 +42,7 @@ bool AssetManager::initialize(const std::string& dataPath_) {
}
dataPath = dataPath_;
overridePath_ = dataPath + "/override";
LOG_INFO("Initializing asset manager with data path: ", dataPath);
setupFileCacheBudget();
@ -58,6 +59,10 @@ bool AssetManager::initialize(const std::string& dataPath_) {
return false;
}
if (std::filesystem::is_directory(overridePath_)) {
LOG_INFO("Override directory found: ", overridePath_);
}
initialized = true;
LOG_INFO("Asset manager initialized: ", manifest_.getEntryCount(),
" files indexed (file cache: ", fileCacheBudget / (1024 * 1024), " MB)");
@ -104,97 +109,18 @@ void AssetManager::shutdown() {
}
clearCache();
overlayLayers_.clear();
initialized = false;
}
bool AssetManager::addOverlayManifest(const std::string& manifestPath, int priority, const std::string& id) {
// Check for duplicate
for (const auto& layer : overlayLayers_) {
if (layer.id == id) {
LOG_WARNING("Overlay '", id, "' already loaded, skipping");
return false;
}
}
ManifestLayer layer;
layer.priority = priority;
layer.id = id;
if (!layer.manifest.load(manifestPath)) {
LOG_ERROR("Failed to load overlay manifest: ", manifestPath);
return false;
}
overlayLayers_.push_back(std::move(layer));
// Sort by priority descending (highest priority first)
std::sort(overlayLayers_.begin(), overlayLayers_.end(),
[](const ManifestLayer& a, const ManifestLayer& b) {
return a.priority > b.priority;
});
LOG_INFO("Added overlay '", id, "' (priority ", priority, ", ",
overlayLayers_.back().manifest.getEntryCount(), " files) from ", manifestPath);
return true;
}
void AssetManager::removeOverlay(const std::string& id) {
auto it = std::remove_if(overlayLayers_.begin(), overlayLayers_.end(),
[&id](const ManifestLayer& layer) { return layer.id == id; });
if (it != overlayLayers_.end()) {
overlayLayers_.erase(it, overlayLayers_.end());
// Clear file cache since overlay removal changes file resolution
{
std::lock_guard<std::mutex> lock(cacheMutex);
fileCache.clear();
fileCacheTotalBytes = 0;
}
LOG_INFO("Removed overlay '", id, "', file cache cleared");
}
}
std::vector<std::string> AssetManager::getOverlayIds() const {
std::vector<std::string> ids;
ids.reserve(overlayLayers_.size());
for (const auto& layer : overlayLayers_) {
ids.push_back(layer.id);
}
return ids;
}
std::string AssetManager::resolveLayeredPath(const std::string& normalizedPath) const {
// For textures (.blp), pick the highest-resolution variant across overlays/base.
// Turtle/classic overlays can include lower-res textures; the base dataset may have higher-res.
auto isBlpTexture = [&]() -> bool {
return normalizedPath.size() >= 4 &&
normalizedPath.compare(normalizedPath.size() - 4, 4, ".blp") == 0;
};
if (isBlpTexture()) {
uint64_t bestSize = 0;
std::string bestFsPath;
auto consider = [&](const AssetManifest& m) {
if (const auto* e = m.lookup(normalizedPath)) {
if (e->size > bestSize) {
bestSize = e->size;
bestFsPath = m.getBasePath() + "/" + e->filesystemPath;
}
std::string AssetManager::resolveFile(const std::string& normalizedPath) const {
// Check override directory first (for HD upgrades, custom textures)
if (!overridePath_.empty()) {
const auto* entry = manifest_.lookup(normalizedPath);
if (entry && !entry->filesystemPath.empty()) {
std::string overrideFsPath = overridePath_ + "/" + entry->filesystemPath;
if (LooseFileReader::fileExists(overrideFsPath)) {
return overrideFsPath;
}
};
for (const auto& layer : overlayLayers_) consider(layer.manifest);
consider(manifest_);
if (!bestFsPath.empty()) return bestFsPath;
}
// Default: check overlay manifests first (sorted by priority desc)
for (const auto& layer : overlayLayers_) {
std::string fsPath = layer.manifest.resolveFilesystemPath(normalizedPath);
if (!fsPath.empty()) {
return fsPath;
}
}
// Fall back to base manifest
@ -239,7 +165,7 @@ BLPImage AssetManager::tryLoadPngOverride(const std::string& normalizedPath) con
std::string ext = normalizedPath.substr(normalizedPath.size() - 4);
if (ext != ".blp") return BLPImage();
std::string fsPath = resolveLayeredPath(normalizedPath);
std::string fsPath = resolveFile(normalizedPath);
if (fsPath.empty()) return BLPImage();
// Replace .blp/.BLP extension with .png
@ -398,12 +324,6 @@ bool AssetManager::fileExists(const std::string& path) const {
return false;
}
std::string normalized = normalizePath(path);
// Check overlay manifests first
for (const auto& layer : overlayLayers_) {
if (layer.manifest.hasEntry(normalized)) {
return true;
}
}
return manifest_.hasEntry(normalized);
}
@ -425,8 +345,8 @@ std::vector<uint8_t> AssetManager::readFile(const std::string& path) const {
}
}
// Read from filesystem using layered resolution (overlays first, then base)
std::string fsPath = resolveLayeredPath(normalized);
// Read from filesystem (override dir first, then base manifest)
std::string fsPath = resolveFile(normalized);
if (fsPath.empty()) {
return {};
}

View file

@ -1,204 +0,0 @@
#include "pipeline/hd_pack_manager.hpp"
#include "pipeline/asset_manager.hpp"
#include "core/logger.hpp"
#include <filesystem>
#include <fstream>
#include <sstream>
#include <algorithm>
namespace wowee {
namespace pipeline {
namespace {
// Minimal JSON string value parser (key must be unique in the flat object)
std::string jsonStringValue(const std::string& json, const std::string& key) {
std::string needle = "\"" + key + "\"";
size_t pos = json.find(needle);
if (pos == std::string::npos) return "";
pos = json.find(':', pos + needle.size());
if (pos == std::string::npos) return "";
pos = json.find('"', pos + 1);
if (pos == std::string::npos) return "";
size_t end = json.find('"', pos + 1);
if (end == std::string::npos) return "";
return json.substr(pos + 1, end - pos - 1);
}
// Parse a JSON number value
uint32_t jsonUintValue(const std::string& json, const std::string& key) {
std::string needle = "\"" + key + "\"";
size_t pos = json.find(needle);
if (pos == std::string::npos) return 0;
pos = json.find(':', pos + needle.size());
if (pos == std::string::npos) return 0;
++pos;
while (pos < json.size() && (json[pos] == ' ' || json[pos] == '\t')) ++pos;
return static_cast<uint32_t>(std::strtoul(json.c_str() + pos, nullptr, 10));
}
// Parse a JSON string array value
std::vector<std::string> jsonStringArray(const std::string& json, const std::string& key) {
std::vector<std::string> result;
std::string needle = "\"" + key + "\"";
size_t pos = json.find(needle);
if (pos == std::string::npos) return result;
pos = json.find('[', pos + needle.size());
if (pos == std::string::npos) return result;
size_t end = json.find(']', pos);
if (end == std::string::npos) return result;
std::string arr = json.substr(pos + 1, end - pos - 1);
size_t p = 0;
while (p < arr.size()) {
size_t qs = arr.find('"', p);
if (qs == std::string::npos) break;
size_t qe = arr.find('"', qs + 1);
if (qe == std::string::npos) break;
result.push_back(arr.substr(qs + 1, qe - qs - 1));
p = qe + 1;
}
return result;
}
} // namespace
void HDPackManager::initialize(const std::string& hdRootPath) {
packs_.clear();
if (!std::filesystem::exists(hdRootPath) || !std::filesystem::is_directory(hdRootPath)) {
LOG_DEBUG("HD pack directory not found: ", hdRootPath);
return;
}
for (const auto& entry : std::filesystem::directory_iterator(hdRootPath)) {
if (!entry.is_directory()) continue;
std::string packJsonPath = entry.path().string() + "/pack.json";
if (!std::filesystem::exists(packJsonPath)) continue;
std::ifstream f(packJsonPath);
if (!f.is_open()) continue;
std::string json((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
HDPack pack;
pack.id = jsonStringValue(json, "id");
pack.name = jsonStringValue(json, "name");
pack.group = jsonStringValue(json, "group");
pack.totalSizeMB = jsonUintValue(json, "totalSizeMB");
pack.expansions = jsonStringArray(json, "expansions");
pack.packDir = entry.path().string();
pack.manifestPath = entry.path().string() + "/manifest.json";
if (pack.id.empty()) {
LOG_WARNING("HD pack in ", entry.path().string(), " has no id, skipping");
continue;
}
if (!std::filesystem::exists(pack.manifestPath)) {
LOG_WARNING("HD pack '", pack.id, "' missing manifest.json, skipping");
continue;
}
// Apply saved enabled state if available
auto it = enabledState_.find(pack.id);
if (it != enabledState_.end()) {
pack.enabled = it->second;
}
LOG_INFO("Discovered HD pack: '", pack.id, "' (", pack.name, ") ",
pack.totalSizeMB, " MB, ", pack.expansions.size(), " expansions");
packs_.push_back(std::move(pack));
}
LOG_INFO("HDPackManager: found ", packs_.size(), " packs in ", hdRootPath);
}
std::vector<const HDPack*> HDPackManager::getPacksForExpansion(const std::string& expansionId) const {
std::vector<const HDPack*> result;
for (const auto& pack : packs_) {
if (pack.expansions.empty()) {
// No expansion filter = compatible with all
result.push_back(&pack);
} else {
for (const auto& exp : pack.expansions) {
if (exp == expansionId) {
result.push_back(&pack);
break;
}
}
}
}
return result;
}
void HDPackManager::setPackEnabled(const std::string& packId, bool enabled) {
enabledState_[packId] = enabled;
for (auto& pack : packs_) {
if (pack.id == packId) {
pack.enabled = enabled;
break;
}
}
}
bool HDPackManager::isPackEnabled(const std::string& packId) const {
auto it = enabledState_.find(packId);
return it != enabledState_.end() && it->second;
}
void HDPackManager::applyToAssetManager(AssetManager* assetManager, const std::string& expansionId) {
if (!assetManager) return;
// Remove previously applied overlays
for (const auto& overlayId : appliedOverlayIds_) {
assetManager->removeOverlay(overlayId);
}
appliedOverlayIds_.clear();
// Get packs compatible with current expansion
auto compatiblePacks = getPacksForExpansion(expansionId);
int priorityOffset = 0;
for (const auto* pack : compatiblePacks) {
if (!pack->enabled) continue;
std::string overlayId = "hd_" + pack->id;
int priority = HD_OVERLAY_PRIORITY_BASE + priorityOffset;
if (assetManager->addOverlayManifest(pack->manifestPath, priority, overlayId)) {
appliedOverlayIds_.push_back(overlayId);
LOG_INFO("Applied HD pack '", pack->id, "' as overlay (priority ", priority, ")");
}
++priorityOffset;
}
if (!appliedOverlayIds_.empty()) {
LOG_INFO("Applied ", appliedOverlayIds_.size(), " HD pack overlays");
}
}
void HDPackManager::saveSettings(const std::string& settingsPath) const {
std::ofstream f(settingsPath, std::ios::app);
if (!f.is_open()) return;
for (const auto& [packId, enabled] : enabledState_) {
f << "hd_pack_" << packId << "=" << (enabled ? "1" : "0") << "\n";
}
}
void HDPackManager::loadSettings(const std::string& settingsPath) {
std::ifstream f(settingsPath);
if (!f.is_open()) return;
std::string line;
while (std::getline(f, line)) {
if (line.substr(0, 8) != "hd_pack_") continue;
size_t eq = line.find('=');
if (eq == std::string::npos) continue;
std::string packId = line.substr(8, eq - 8);
bool enabled = (line.substr(eq + 1) == "1");
enabledState_[packId] = enabled;
}
}
} // namespace pipeline
} // namespace wowee

View file

@ -23,7 +23,7 @@
#include "pipeline/dbc_loader.hpp"
#include "pipeline/blp_loader.hpp"
#include "pipeline/dbc_layout.hpp"
#include "pipeline/hd_pack_manager.hpp"
#include "game/expansion_profile.hpp"
#include "core/logger.hpp"
#include <imgui.h>
@ -5449,79 +5449,6 @@ void GameScreen::renderSettingsWindow() {
ImGui::EndTabItem();
}
// ============================================================
// HD TEXTURES TAB
// ============================================================
if (ImGui::BeginTabItem("HD Textures")) {
ImGui::Spacing();
auto& app = core::Application::getInstance();
auto* hdMgr = app.getHDPackManager();
if (hdMgr) {
const auto& packs = hdMgr->getAllPacks();
if (packs.empty()) {
ImGui::TextWrapped("No HD texture packs found.");
ImGui::Spacing();
ImGui::TextWrapped("Place packs in Data/hd/<pack_name>/ with a pack.json and manifest.json.");
} else {
ImGui::Text("Available HD Texture Packs:");
ImGui::Spacing();
bool changed = false;
for (const auto& pack : packs) {
bool enabled = pack.enabled;
if (ImGui::Checkbox(pack.name.c_str(), &enabled)) {
hdMgr->setPackEnabled(pack.id, enabled);
changed = true;
}
ImGui::SameLine(0, 10);
ImGui::TextDisabled("(%u MB)", pack.totalSizeMB);
if (!pack.group.empty()) {
ImGui::SameLine(0, 10);
ImGui::TextDisabled("[%s]", pack.group.c_str());
}
if (!pack.expansions.empty()) {
std::string expList;
for (const auto& e : pack.expansions) {
if (!expList.empty()) expList += ", ";
expList += e;
}
ImGui::TextDisabled(" Compatible: %s", expList.c_str());
}
}
if (changed) {
ImGui::Spacing();
ImGui::Separator();
ImGui::Spacing();
if (ImGui::Button("Apply HD Packs", ImVec2(-1, 0))) {
std::string expansionId = "wotlk";
if (app.getExpansionRegistry() && app.getExpansionRegistry()->getActive()) {
expansionId = app.getExpansionRegistry()->getActive()->id;
}
hdMgr->applyToAssetManager(app.getAssetManager(), expansionId);
// Save settings
std::string settingsDir;
const char* xdg = std::getenv("XDG_DATA_HOME");
if (xdg && *xdg) {
settingsDir = std::string(xdg) + "/wowee";
} else {
const char* home = std::getenv("HOME");
settingsDir = std::string(home ? home : ".") + "/.local/share/wowee";
}
hdMgr->saveSettings(settingsDir + "/settings.cfg");
}
}
}
} else {
ImGui::Text("HD Pack Manager not available.");
}
ImGui::EndTabItem();
}
// ============================================================
// CHAT TAB
// ============================================================