mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
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:
parent
1bc7b12b20
commit
d7e2b26af7
13 changed files with 31 additions and 658 deletions
|
|
@ -141,7 +141,7 @@ set(WOWEE_SOURCES
|
|||
src/pipeline/wmo_loader.cpp
|
||||
src/pipeline/adt_loader.cpp
|
||||
src/pipeline/dbc_layout.cpp
|
||||
src/pipeline/hd_pack_manager.cpp
|
||||
|
||||
src/pipeline/terrain_mesh.cpp
|
||||
|
||||
# Rendering
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ namespace rendering { class Renderer; }
|
|||
namespace ui { class UIManager; }
|
||||
namespace auth { class AuthHandler; }
|
||||
namespace game { class GameHandler; class World; class ExpansionRegistry; }
|
||||
namespace pipeline { class AssetManager; class DBCLayout; class HDPackManager; }
|
||||
namespace pipeline { class AssetManager; class DBCLayout; }
|
||||
namespace audio { enum class VoiceType; }
|
||||
|
||||
namespace core {
|
||||
|
|
@ -57,7 +57,6 @@ public:
|
|||
pipeline::AssetManager* getAssetManager() { return assetManager.get(); }
|
||||
game::ExpansionRegistry* getExpansionRegistry() { return expansionRegistry_.get(); }
|
||||
pipeline::DBCLayout* getDBCLayout() { return dbcLayout_.get(); }
|
||||
pipeline::HDPackManager* getHDPackManager() { return hdPackManager_.get(); }
|
||||
void reloadExpansionData(); // Reload DBC layouts, opcodes, etc. after expansion change
|
||||
|
||||
// Singleton access
|
||||
|
|
@ -121,7 +120,6 @@ private:
|
|||
std::unique_ptr<pipeline::AssetManager> assetManager;
|
||||
std::unique_ptr<game::ExpansionRegistry> expansionRegistry_;
|
||||
std::unique_ptr<pipeline::DBCLayout> dbcLayout_;
|
||||
std::unique_ptr<pipeline::HDPackManager> hdPackManager_;
|
||||
|
||||
AppState state = AppState::AUTHENTICATION;
|
||||
bool running = false;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ struct ExpansionProfile {
|
|||
std::string locale = "enUS";
|
||||
uint32_t timezone = 0;
|
||||
std::string dataPath; // Absolute path to expansion data dir
|
||||
std::string assetManifest; // Path to expansion-specific asset manifest (absolute, resolved from dataPath)
|
||||
uint32_t maxLevel = 60;
|
||||
std::vector<uint32_t> races;
|
||||
std::vector<uint32_t> classes;
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ namespace pipeline {
|
|||
* AssetManager - Unified interface for loading WoW assets
|
||||
*
|
||||
* Reads pre-extracted loose files indexed by manifest.json.
|
||||
* Supports layered manifests: overlay manifests (HD packs, mods)
|
||||
* are checked before the base manifest, with higher priority first.
|
||||
* Supports an override directory (Data/override/) checked before the manifest
|
||||
* for HD textures, custom content, or mod overrides.
|
||||
* Use the asset_extract tool to extract MPQ archives first.
|
||||
* All reads are fully parallel (no serialization mutex needed).
|
||||
*/
|
||||
|
|
@ -44,26 +44,6 @@ public:
|
|||
*/
|
||||
bool isInitialized() const { return initialized; }
|
||||
|
||||
/**
|
||||
* Add an overlay manifest (HD packs, mods) checked before the base manifest.
|
||||
* Higher priority overlays are checked first.
|
||||
* @param manifestPath Full path to the overlay's manifest.json
|
||||
* @param priority Priority level (higher = checked first)
|
||||
* @param id Unique identifier for this overlay (e.g. "hd_character")
|
||||
* @return true if overlay loaded successfully
|
||||
*/
|
||||
bool addOverlayManifest(const std::string& manifestPath, int priority, const std::string& id);
|
||||
|
||||
/**
|
||||
* Remove a previously added overlay manifest by id.
|
||||
*/
|
||||
void removeOverlay(const std::string& id);
|
||||
|
||||
/**
|
||||
* Get list of active overlay IDs.
|
||||
*/
|
||||
std::vector<std::string> getOverlayIds() const;
|
||||
|
||||
/**
|
||||
* Load a BLP texture
|
||||
* @param path Virtual path to BLP file (e.g., "Textures\\Minimap\\Background.blp")
|
||||
|
|
@ -140,24 +120,17 @@ private:
|
|||
bool initialized = false;
|
||||
std::string dataPath;
|
||||
std::string expansionDataPath_; // e.g. "Data/expansions/wotlk"
|
||||
std::string overridePath_; // e.g. "Data/override"
|
||||
|
||||
// Base manifest (loaded from dataPath/manifest.json)
|
||||
AssetManifest manifest_;
|
||||
LooseFileReader looseReader_;
|
||||
|
||||
// Overlay manifests (HD packs, mods) - sorted by priority descending
|
||||
struct ManifestLayer {
|
||||
AssetManifest manifest;
|
||||
int priority;
|
||||
std::string id;
|
||||
};
|
||||
std::vector<ManifestLayer> overlayLayers_; // Sorted by priority desc
|
||||
|
||||
/**
|
||||
* Resolve filesystem path checking overlays first, then base manifest.
|
||||
* Returns empty string if not found in any layer.
|
||||
* Resolve filesystem path: check override dir first, then base manifest.
|
||||
* Returns empty string if not found.
|
||||
*/
|
||||
std::string resolveLayeredPath(const std::string& normalizedPath) const;
|
||||
std::string resolveFile(const std::string& normalizedPath) const;
|
||||
|
||||
mutable std::mutex cacheMutex;
|
||||
std::map<std::string, std::shared_ptr<DBCFile>> dbcCache;
|
||||
|
|
|
|||
|
|
@ -1,97 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace wowee {
|
||||
namespace pipeline {
|
||||
|
||||
class AssetManager;
|
||||
|
||||
/**
|
||||
* Metadata for a single HD texture pack on disk.
|
||||
*
|
||||
* Each pack lives in Data/hd/<packDir>/ and contains:
|
||||
* pack.json - metadata (id, name, group, compatible expansions, size)
|
||||
* manifest.json - standard asset manifest with HD override textures
|
||||
* assets/ - the actual HD files
|
||||
*/
|
||||
struct HDPack {
|
||||
std::string id; // Unique identifier (e.g. "character_hd")
|
||||
std::string name; // Human-readable name
|
||||
std::string group; // Grouping label (e.g. "Character", "Terrain")
|
||||
std::vector<std::string> expansions; // Compatible expansion IDs
|
||||
uint32_t totalSizeMB = 0; // Approximate total size on disk
|
||||
std::string manifestPath; // Full path to manifest.json
|
||||
std::string packDir; // Full path to pack directory
|
||||
bool enabled = false; // User-toggled enable state
|
||||
};
|
||||
|
||||
/**
|
||||
* HDPackManager - discovers, manages, and wires HD texture packs.
|
||||
*
|
||||
* Scans Data/hd/ subdirectories for pack.json files. Each pack can be
|
||||
* enabled/disabled via setPackEnabled(). Enabled packs are wired into
|
||||
* AssetManager as high-priority overlay manifests so that HD textures
|
||||
* override the base expansion assets transparently.
|
||||
*/
|
||||
class HDPackManager {
|
||||
public:
|
||||
HDPackManager() = default;
|
||||
|
||||
/**
|
||||
* Scan the HD root directory for available packs.
|
||||
* @param hdRootPath Path to Data/hd/ directory
|
||||
*/
|
||||
void initialize(const std::string& hdRootPath);
|
||||
|
||||
/**
|
||||
* Get all discovered packs.
|
||||
*/
|
||||
const std::vector<HDPack>& getAllPacks() const { return packs_; }
|
||||
|
||||
/**
|
||||
* Get packs compatible with a specific expansion.
|
||||
*/
|
||||
std::vector<const HDPack*> getPacksForExpansion(const std::string& expansionId) const;
|
||||
|
||||
/**
|
||||
* Enable or disable a pack. Persists state in enabledPacks_ map.
|
||||
*/
|
||||
void setPackEnabled(const std::string& packId, bool enabled);
|
||||
|
||||
/**
|
||||
* Check if a pack is enabled.
|
||||
*/
|
||||
bool isPackEnabled(const std::string& packId) const;
|
||||
|
||||
/**
|
||||
* Apply enabled packs as overlays to the asset manager.
|
||||
* Removes previously applied overlays and re-adds enabled ones.
|
||||
*/
|
||||
void applyToAssetManager(AssetManager* assetManager, const std::string& expansionId);
|
||||
|
||||
/**
|
||||
* Save enabled pack state to a settings file.
|
||||
*/
|
||||
void saveSettings(const std::string& settingsPath) const;
|
||||
|
||||
/**
|
||||
* Load enabled pack state from a settings file.
|
||||
*/
|
||||
void loadSettings(const std::string& settingsPath);
|
||||
|
||||
private:
|
||||
std::vector<HDPack> packs_;
|
||||
std::unordered_map<std::string, bool> enabledState_; // packId → enabled
|
||||
|
||||
// Overlay IDs currently applied to AssetManager (for removal on re-apply)
|
||||
std::vector<std::string> appliedOverlayIds_;
|
||||
|
||||
static constexpr int HD_OVERLAY_PRIORITY_BASE = 100; // High priority, above expansion base
|
||||
};
|
||||
|
||||
} // namespace pipeline
|
||||
} // namespace wowee
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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 {};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
// ============================================================
|
||||
|
|
|
|||
|
|
@ -570,28 +570,7 @@ bool Extractor::enumerateFiles(const Options& opts,
|
|||
bool Extractor::run(const Options& opts) {
|
||||
auto startTime = std::chrono::steady_clock::now();
|
||||
|
||||
const bool overlayMode = !opts.asOverlay.empty();
|
||||
|
||||
// Overlay mode writes files to expansions/<id>/overlay/ under the output dir
|
||||
const std::string effectiveOutputDir = overlayMode
|
||||
? opts.outputDir + "/expansions/" + opts.asOverlay + "/overlay"
|
||||
: opts.outputDir;
|
||||
|
||||
// Load base manifest CRCs for overlay deduplication
|
||||
std::unordered_map<std::string, uint32_t> baseCRCs;
|
||||
if (overlayMode) {
|
||||
std::string baseManifestPath = opts.outputDir + "/manifest.json";
|
||||
auto baseEntries = loadManifestEntries(baseManifestPath);
|
||||
if (baseEntries.empty()) {
|
||||
std::cerr << "Warning: base manifest empty or missing at " << baseManifestPath << "\n"
|
||||
<< " Extract the base expansion first, then use --as-overlay for others.\n";
|
||||
} else {
|
||||
for (auto& [k, v] : baseEntries) {
|
||||
baseCRCs[k] = v.crc32;
|
||||
}
|
||||
std::cout << "Loaded " << baseCRCs.size() << " base manifest entries for CRC comparison\n";
|
||||
}
|
||||
}
|
||||
const std::string effectiveOutputDir = opts.outputDir;
|
||||
|
||||
// Enumerate all unique files across all archives
|
||||
std::vector<std::string> files;
|
||||
|
|
@ -708,15 +687,6 @@ bool Extractor::run(const Options& opts) {
|
|||
// Compute CRC32
|
||||
uint32_t crc = ManifestWriter::computeCRC32(data.data(), data.size());
|
||||
|
||||
// In overlay mode, skip files identical to base
|
||||
if (!baseCRCs.empty()) {
|
||||
auto it = baseCRCs.find(normalized);
|
||||
if (it != baseCRCs.end() && it->second == crc) {
|
||||
stats.filesSkipped++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Create output directory and write file
|
||||
fs::path outPath(fullOutputPath);
|
||||
fs::create_directories(outPath.parent_path(), ec);
|
||||
|
|
@ -773,9 +743,8 @@ bool Extractor::run(const Options& opts) {
|
|||
<< stats.filesFailed.load() << " failed\n";
|
||||
|
||||
// Merge with existing manifest so partial extractions don't nuke prior entries
|
||||
// (skip merge for overlay manifests — they're standalone)
|
||||
std::string manifestPath = effectiveOutputDir + "/manifest.json";
|
||||
if (!overlayMode && fs::exists(manifestPath)) {
|
||||
if (fs::exists(manifestPath)) {
|
||||
auto existing = loadManifestEntries(manifestPath);
|
||||
if (!existing.empty()) {
|
||||
// New entries override existing ones with same key
|
||||
|
|
@ -853,7 +822,7 @@ bool Extractor::run(const Options& opts) {
|
|||
if (opts.generateDbcCsv) {
|
||||
std::cout << "Converting selected DBCs to CSV for committing...\n";
|
||||
const std::string dbcDir = effectiveOutputDir + "/db";
|
||||
const std::string csvExpansion = overlayMode ? opts.asOverlay : opts.expansion;
|
||||
const std::string csvExpansion = opts.expansion;
|
||||
const std::string csvDir = !opts.dbcCsvOutputDir.empty()
|
||||
? opts.dbcCsvOutputDir
|
||||
: (opts.outputDir + "/expansions/" + csvExpansion + "/db");
|
||||
|
|
@ -885,8 +854,8 @@ bool Extractor::run(const Options& opts) {
|
|||
}
|
||||
}
|
||||
|
||||
// Cache WoW.exe for Warden MEM_CHECK responses (base extraction only)
|
||||
if (!overlayMode) {
|
||||
// Cache WoW.exe for Warden MEM_CHECK responses
|
||||
{
|
||||
const char* exeNames[] = { "WoW.exe", "TurtleWoW.exe", "Wow.exe" };
|
||||
std::vector<std::string> searchDirs = {
|
||||
fs::path(opts.mpqDir).parent_path().string(), // WoW.exe is typically next to Data/
|
||||
|
|
@ -910,41 +879,6 @@ bool Extractor::run(const Options& opts) {
|
|||
}
|
||||
}
|
||||
|
||||
// Auto-update expansion.json with assetManifest field
|
||||
if (overlayMode) {
|
||||
std::string expJsonPath = opts.outputDir + "/expansions/" + opts.asOverlay + "/expansion.json";
|
||||
if (fs::exists(expJsonPath)) {
|
||||
std::ifstream fin(expJsonPath);
|
||||
std::string content((std::istreambuf_iterator<char>(fin)),
|
||||
std::istreambuf_iterator<char>());
|
||||
fin.close();
|
||||
|
||||
if (content.find("\"assetManifest\"") == std::string::npos) {
|
||||
// Insert assetManifest before the closing brace
|
||||
size_t lastBrace = content.rfind('}');
|
||||
if (lastBrace != std::string::npos) {
|
||||
// Find the last non-whitespace before the closing brace to add comma
|
||||
size_t pos = lastBrace;
|
||||
while (pos > 0 && (content[pos - 1] == ' ' || content[pos - 1] == '\n' ||
|
||||
content[pos - 1] == '\r' || content[pos - 1] == '\t')) {
|
||||
pos--;
|
||||
}
|
||||
std::string insert = ",\n \"assetManifest\": \"overlay/manifest.json\"\n";
|
||||
content.insert(pos, insert);
|
||||
|
||||
std::ofstream fout(expJsonPath);
|
||||
fout << content;
|
||||
fout.close();
|
||||
std::cout << "Updated " << expJsonPath << " with assetManifest\n";
|
||||
}
|
||||
} else {
|
||||
std::cout << "expansion.json already has assetManifest field\n";
|
||||
}
|
||||
} else {
|
||||
std::cerr << "Warning: " << expJsonPath << " not found — create it manually\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "Done in " << secs / 60 << "m " << secs % 60 << "s\n";
|
||||
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ public:
|
|||
bool onlyUsedDbcs = false; // Extract only the DBC files wowee uses (implies DBFilesClient/*.dbc filter)
|
||||
std::string dbcCsvOutputDir; // When set, write CSVs into this directory instead of outputDir/expansions/<exp>/db
|
||||
std::string referenceManifest; // If set, only extract files NOT in this manifest (delta extraction)
|
||||
std::string asOverlay; // If set, extract as overlay for this expansion ID (only files differing from base)
|
||||
};
|
||||
|
||||
struct Stats {
|
||||
|
|
|
|||
|
|
@ -20,11 +20,6 @@ static void printUsage(const char* prog) {
|
|||
<< " --skip-dbc Do not extract DBFilesClient/*.dbc (visual assets only)\n"
|
||||
<< " --dbc-csv Convert selected DBFilesClient/*.dbc to CSV under\n"
|
||||
<< " <output>/expansions/<expansion>/db/*.csv (for committing)\n"
|
||||
<< " --as-overlay <id> Extract as expansion overlay (only files differing from base\n"
|
||||
<< " manifest at <output>/manifest.json). Stores overlay assets in\n"
|
||||
<< " <output>/expansions/<id>/overlay/ and implies --dbc-csv.\n"
|
||||
<< " Auto-detected when base manifest already exists.\n"
|
||||
<< " --full-base Force full base extraction even if manifest exists\n"
|
||||
<< " --reference-manifest <path>\n"
|
||||
<< " Only extract files NOT in this manifest (delta extraction)\n"
|
||||
<< " --dbc-csv-out <dir> Write CSV DBCs into <dir> (overrides default output path)\n"
|
||||
|
|
@ -38,7 +33,6 @@ int main(int argc, char** argv) {
|
|||
wowee::tools::Extractor::Options opts;
|
||||
std::string expansion;
|
||||
std::string locale;
|
||||
bool forceBase = false;
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (std::strcmp(argv[i], "--mpq-dir") == 0 && i + 1 < argc) {
|
||||
|
|
@ -59,11 +53,6 @@ int main(int argc, char** argv) {
|
|||
opts.generateDbcCsv = true;
|
||||
} else if (std::strcmp(argv[i], "--dbc-csv-out") == 0 && i + 1 < argc) {
|
||||
opts.dbcCsvOutputDir = argv[++i];
|
||||
} else if (std::strcmp(argv[i], "--as-overlay") == 0 && i + 1 < argc) {
|
||||
opts.asOverlay = argv[++i];
|
||||
opts.generateDbcCsv = true; // Overlay mode always generates per-expansion CSVs
|
||||
} else if (std::strcmp(argv[i], "--full-base") == 0) {
|
||||
forceBase = true;
|
||||
} else if (std::strcmp(argv[i], "--reference-manifest") == 0 && i + 1 < argc) {
|
||||
opts.referenceManifest = argv[++i];
|
||||
} else if (std::strcmp(argv[i], "--verify") == 0) {
|
||||
|
|
@ -110,20 +99,6 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
opts.locale = locale;
|
||||
|
||||
// Auto-detect overlay mode: if a base manifest already exists and this expansion
|
||||
// has a profile directory, automatically use overlay mode so the user doesn't have
|
||||
// to think about extraction order.
|
||||
if (opts.asOverlay.empty() && !forceBase && !opts.onlyUsedDbcs) {
|
||||
namespace fs = std::filesystem;
|
||||
std::string baseManifest = opts.outputDir + "/manifest.json";
|
||||
std::string expJson = opts.outputDir + "/expansions/" + expansion + "/expansion.json";
|
||||
if (fs::exists(baseManifest) && fs::exists(expJson)) {
|
||||
opts.asOverlay = expansion;
|
||||
opts.generateDbcCsv = true;
|
||||
std::cout << "Base manifest found — auto-overlay mode for " << expansion << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
std::cout << "=== Wowee Asset Extractor ===\n";
|
||||
std::cout << "MPQ directory: " << opts.mpqDir << "\n";
|
||||
std::cout << "Output: " << opts.outputDir << "\n";
|
||||
|
|
@ -144,9 +119,6 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!opts.asOverlay.empty()) {
|
||||
std::cout << "Overlay: " << opts.asOverlay << " (only files differing from base)\n";
|
||||
}
|
||||
if (!opts.referenceManifest.empty()) {
|
||||
std::cout << "Reference: " << opts.referenceManifest << " (delta mode)\n";
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue