2026-02-12 22:56:36 -08:00
|
|
|
#include "pipeline/dbc_layout.hpp"
|
2026-03-23 11:00:49 -07:00
|
|
|
#include "pipeline/dbc_loader.hpp"
|
2026-02-12 22:56:36 -08:00
|
|
|
#include "core/logger.hpp"
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <sstream>
|
2026-03-23 11:00:49 -07:00
|
|
|
#include <algorithm>
|
2026-02-12 22:56:36 -08:00
|
|
|
|
|
|
|
|
namespace wowee {
|
|
|
|
|
namespace pipeline {
|
|
|
|
|
|
|
|
|
|
static const DBCLayout* g_activeDBCLayout = nullptr;
|
|
|
|
|
|
|
|
|
|
void setActiveDBCLayout(const DBCLayout* layout) { g_activeDBCLayout = layout; }
|
|
|
|
|
const DBCLayout* getActiveDBCLayout() { return g_activeDBCLayout; }
|
|
|
|
|
|
|
|
|
|
bool DBCLayout::loadFromJson(const std::string& path) {
|
|
|
|
|
std::ifstream f(path);
|
|
|
|
|
if (!f.is_open()) {
|
|
|
|
|
LOG_WARNING("DBCLayout: cannot open ", path);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::string json((std::istreambuf_iterator<char>(f)), std::istreambuf_iterator<char>());
|
|
|
|
|
|
|
|
|
|
layouts_.clear();
|
|
|
|
|
size_t loaded = 0;
|
|
|
|
|
size_t pos = 0;
|
|
|
|
|
|
|
|
|
|
// Parse top-level object: { "DbcName": { "FieldName": index, ... }, ... }
|
|
|
|
|
// Find the first '{'
|
|
|
|
|
pos = json.find('{', pos);
|
|
|
|
|
if (pos == std::string::npos) return false;
|
|
|
|
|
++pos;
|
|
|
|
|
|
|
|
|
|
while (pos < json.size()) {
|
|
|
|
|
// Find DBC name key
|
|
|
|
|
size_t dbcKeyStart = json.find('"', pos);
|
|
|
|
|
if (dbcKeyStart == std::string::npos) break;
|
|
|
|
|
size_t dbcKeyEnd = json.find('"', dbcKeyStart + 1);
|
|
|
|
|
if (dbcKeyEnd == std::string::npos) break;
|
|
|
|
|
std::string dbcName = json.substr(dbcKeyStart + 1, dbcKeyEnd - dbcKeyStart - 1);
|
|
|
|
|
|
|
|
|
|
// Find the nested object '{'
|
|
|
|
|
size_t objStart = json.find('{', dbcKeyEnd);
|
|
|
|
|
if (objStart == std::string::npos) break;
|
|
|
|
|
|
|
|
|
|
// Find the matching '}'
|
|
|
|
|
size_t objEnd = json.find('}', objStart);
|
|
|
|
|
if (objEnd == std::string::npos) break;
|
|
|
|
|
|
|
|
|
|
// Parse the inner object
|
|
|
|
|
std::string inner = json.substr(objStart + 1, objEnd - objStart - 1);
|
|
|
|
|
DBCFieldMap fieldMap;
|
|
|
|
|
size_t ipos = 0;
|
|
|
|
|
while (ipos < inner.size()) {
|
|
|
|
|
size_t fkStart = inner.find('"', ipos);
|
|
|
|
|
if (fkStart == std::string::npos) break;
|
|
|
|
|
size_t fkEnd = inner.find('"', fkStart + 1);
|
|
|
|
|
if (fkEnd == std::string::npos) break;
|
|
|
|
|
std::string fieldName = inner.substr(fkStart + 1, fkEnd - fkStart - 1);
|
|
|
|
|
|
|
|
|
|
size_t colon = inner.find(':', fkEnd);
|
|
|
|
|
if (colon == std::string::npos) break;
|
|
|
|
|
size_t valStart = colon + 1;
|
|
|
|
|
while (valStart < inner.size() && (inner[valStart] == ' ' || inner[valStart] == '\t' ||
|
|
|
|
|
inner[valStart] == '\r' || inner[valStart] == '\n'))
|
|
|
|
|
++valStart;
|
|
|
|
|
size_t valEnd = inner.find_first_of(",}\r\n", valStart);
|
|
|
|
|
if (valEnd == std::string::npos) valEnd = inner.size();
|
|
|
|
|
std::string valStr = inner.substr(valStart, valEnd - valStart);
|
|
|
|
|
while (!valStr.empty() && (valStr.back() == ' ' || valStr.back() == '\t'))
|
|
|
|
|
valStr.pop_back();
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
uint32_t idx = static_cast<uint32_t>(std::stoul(valStr));
|
|
|
|
|
fieldMap.fields[fieldName] = idx;
|
|
|
|
|
} catch (...) {}
|
|
|
|
|
|
|
|
|
|
ipos = valEnd + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!fieldMap.fields.empty()) {
|
|
|
|
|
layouts_[dbcName] = std::move(fieldMap);
|
|
|
|
|
++loaded;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pos = objEnd + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LOG_INFO("DBCLayout: loaded ", loaded, " layouts from ", path);
|
|
|
|
|
return loaded > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const DBCFieldMap* DBCLayout::getLayout(const std::string& dbcName) const {
|
|
|
|
|
auto it = layouts_.find(dbcName);
|
|
|
|
|
return (it != layouts_.end()) ? &it->second : nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-23 11:00:49 -07:00
|
|
|
CharSectionsFields detectCharSectionsFields(const DBCFile* dbc, const DBCFieldMap* csL) {
|
|
|
|
|
// Cache: avoid re-probing the same DBC on every call.
|
|
|
|
|
static const DBCFile* s_cachedDbc = nullptr;
|
|
|
|
|
static CharSectionsFields s_cachedResult;
|
|
|
|
|
if (dbc && dbc == s_cachedDbc) return s_cachedResult;
|
|
|
|
|
|
|
|
|
|
CharSectionsFields f;
|
|
|
|
|
if (!dbc || dbc->getRecordCount() == 0) return f;
|
|
|
|
|
|
|
|
|
|
// Start from the JSON layout (or defaults matching Classic-style: variation-first)
|
|
|
|
|
f.raceId = csL ? (*csL)["RaceID"] : 1;
|
|
|
|
|
f.sexId = csL ? (*csL)["SexID"] : 2;
|
|
|
|
|
f.baseSection = csL ? (*csL)["BaseSection"] : 3;
|
|
|
|
|
f.variationIndex = csL ? (*csL)["VariationIndex"] : 4;
|
|
|
|
|
f.colorIndex = csL ? (*csL)["ColorIndex"] : 5;
|
|
|
|
|
f.texture1 = csL ? (*csL)["Texture1"] : 6;
|
|
|
|
|
f.texture2 = csL ? (*csL)["Texture2"] : 7;
|
|
|
|
|
f.texture3 = csL ? (*csL)["Texture3"] : 8;
|
|
|
|
|
f.flags = csL ? (*csL)["Flags"] : 9;
|
|
|
|
|
|
|
|
|
|
// Auto-detect: probe the field that the JSON layout says is VariationIndex.
|
|
|
|
|
// In Classic-style layout, VariationIndex (field 4) holds small integers 0-15.
|
|
|
|
|
// In stock WotLK layout, field 4 is actually Texture1 (a string block offset, typically > 100).
|
|
|
|
|
// Sample up to 20 records and check if all field-4 values are small integers.
|
|
|
|
|
uint32_t probeField = f.variationIndex;
|
|
|
|
|
if (probeField >= dbc->getFieldCount()) {
|
|
|
|
|
s_cachedDbc = dbc;
|
|
|
|
|
s_cachedResult = f;
|
|
|
|
|
return f; // safety
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t sampleCount = std::min(dbc->getRecordCount(), 20u);
|
|
|
|
|
uint32_t largeCount = 0;
|
|
|
|
|
uint32_t smallCount = 0;
|
|
|
|
|
for (uint32_t r = 0; r < sampleCount; r++) {
|
|
|
|
|
uint32_t val = dbc->getUInt32(r, probeField);
|
|
|
|
|
if (val > 50) {
|
|
|
|
|
++largeCount;
|
|
|
|
|
} else {
|
|
|
|
|
++smallCount;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If most sampled values are large, the JSON layout's VariationIndex field
|
|
|
|
|
// actually contains string offsets => this is stock WotLK (texture-first).
|
|
|
|
|
// Swap to texture-first layout: Tex1=4, Tex2=5, Tex3=6, Flags=7, Var=8, Color=9.
|
|
|
|
|
if (largeCount > smallCount) {
|
|
|
|
|
uint32_t base = probeField; // the field index the JSON calls VariationIndex (typically 4)
|
|
|
|
|
f.texture1 = base;
|
|
|
|
|
f.texture2 = base + 1;
|
|
|
|
|
f.texture3 = base + 2;
|
|
|
|
|
f.flags = base + 3;
|
|
|
|
|
f.variationIndex = base + 4;
|
|
|
|
|
f.colorIndex = base + 5;
|
|
|
|
|
LOG_INFO("CharSections.dbc: detected stock WotLK layout (textures-first at field ", base, ")");
|
|
|
|
|
} else {
|
|
|
|
|
LOG_INFO("CharSections.dbc: detected Classic-style layout (variation-first at field ", probeField, ")");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s_cachedDbc = dbc;
|
|
|
|
|
s_cachedResult = f;
|
|
|
|
|
return f;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-12 22:56:36 -08:00
|
|
|
} // namespace pipeline
|
|
|
|
|
} // namespace wowee
|