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

@ -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;

View file

@ -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 {

View file

@ -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";
}