Kelsidavis-WoWee/src/pipeline/dbc_layout.cpp

165 lines
6.1 KiB
C++
Raw Normal View History

#include "pipeline/dbc_layout.hpp"
#include "pipeline/dbc_loader.hpp"
#include "core/logger.hpp"
#include <fstream>
#include <sstream>
#include <algorithm>
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;
}
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;
}
} // namespace pipeline
} // namespace wowee