fix: restore correct CharSections.dbc field indices for character textures

PR #19 (572bb4ef) swapped CharSections.dbc field indices, placing
Texture1-3 at fields 4-6 and VariationIndex/ColorIndex at 8-9. Binary
analysis of the actual DBC files (Classic, TBC, Turtle — all identical
layout, no WotLK-specific override) confirms the correct order is:

  Field 4 = VariationIndex
  Field 5 = ColorIndex
  Field 6 = Texture1 (string)
  Field 7 = Texture2 (string)
  Field 8 = Texture3 (string)
  Field 9 = Flags

With the wrong indices, VariationIndex/ColorIndex reads returned string
offsets (garbage values that never matched), so all CharSections lookups
failed silently — producing white untextured character models at the
login screen and in-world.

Fixes all 4 expansion JSON layouts, hardcoded fallbacks in
character_preview.cpp, application.cpp, and character_create_screen.cpp.
Also handles the single-layer edge case (body skin only, no face/underwear)
by loading the texture directly instead of skipping compositing.
This commit is contained in:
Kelsi 2026-03-22 15:22:25 -07:00
parent 329a1f4b12
commit e9ce062112
7 changed files with 49 additions and 38 deletions

View file

@ -37,12 +37,12 @@
"RaceID": 1, "RaceID": 1,
"SexID": 2, "SexID": 2,
"BaseSection": 3, "BaseSection": 3,
"Texture1": 4, "VariationIndex": 4,
"Texture2": 5, "ColorIndex": 5,
"Texture3": 6, "Texture1": 6,
"Flags": 7, "Texture2": 7,
"VariationIndex": 8, "Texture3": 8,
"ColorIndex": 9 "Flags": 9
}, },
"SpellIcon": { "SpellIcon": {
"ID": 0, "ID": 0,

View file

@ -37,12 +37,12 @@
"RaceID": 1, "RaceID": 1,
"SexID": 2, "SexID": 2,
"BaseSection": 3, "BaseSection": 3,
"Texture1": 4, "VariationIndex": 4,
"Texture2": 5, "ColorIndex": 5,
"Texture3": 6, "Texture1": 6,
"Flags": 7, "Texture2": 7,
"VariationIndex": 8, "Texture3": 8,
"ColorIndex": 9 "Flags": 9
}, },
"SpellIcon": { "SpellIcon": {
"ID": 0, "ID": 0,

View file

@ -37,12 +37,12 @@
"RaceID": 1, "RaceID": 1,
"SexID": 2, "SexID": 2,
"BaseSection": 3, "BaseSection": 3,
"Texture1": 4, "VariationIndex": 4,
"Texture2": 5, "ColorIndex": 5,
"Texture3": 6, "Texture1": 6,
"Flags": 7, "Texture2": 7,
"VariationIndex": 8, "Texture3": 8,
"ColorIndex": 9 "Flags": 9
}, },
"SpellIcon": { "SpellIcon": {
"ID": 0, "ID": 0,

View file

@ -37,12 +37,12 @@
"RaceID": 1, "RaceID": 1,
"SexID": 2, "SexID": 2,
"BaseSection": 3, "BaseSection": 3,
"Texture1": 4, "VariationIndex": 4,
"Texture2": 5, "ColorIndex": 5,
"Texture3": 6, "Texture1": 6,
"Flags": 7, "Texture2": 7,
"VariationIndex": 8, "Texture3": 8,
"ColorIndex": 9 "Flags": 9
}, },
"SpellIcon": { "SpellIcon": {
"ID": 0, "ID": 0,

View file

@ -3671,13 +3671,13 @@ void Application::spawnPlayerCharacter() {
uint32_t raceId = charSectionsDbc->getUInt32(r, csL ? (*csL)["RaceID"] : 1); uint32_t raceId = charSectionsDbc->getUInt32(r, csL ? (*csL)["RaceID"] : 1);
uint32_t sexId = charSectionsDbc->getUInt32(r, csL ? (*csL)["SexID"] : 2); uint32_t sexId = charSectionsDbc->getUInt32(r, csL ? (*csL)["SexID"] : 2);
uint32_t baseSection = charSectionsDbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3); uint32_t baseSection = charSectionsDbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
uint32_t variationIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8); uint32_t variationIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
uint32_t colorIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9); uint32_t colorIndex = charSectionsDbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
if (raceId != targetRaceId || sexId != targetSexId) continue; if (raceId != targetRaceId || sexId != targetSexId) continue;
// Section 0 = skin: match by colorIndex = skin byte // Section 0 = skin: match by colorIndex = skin byte
const uint32_t csTex1 = csL ? (*csL)["Texture1"] : 4; const uint32_t csTex1 = csL ? (*csL)["Texture1"] : 6;
if (baseSection == 0 && !foundSkin && colorIndex == charSkinId) { if (baseSection == 0 && !foundSkin && colorIndex == charSkinId) {
std::string tex1 = charSectionsDbc->getString(r, csTex1); std::string tex1 = charSectionsDbc->getString(r, csTex1);
if (!tex1.empty()) { if (!tex1.empty()) {

View file

@ -336,8 +336,8 @@ bool CharacterPreview::loadCharacter(game::Race race, game::Gender gender,
uint32_t fRace = csL ? (*csL)["RaceID"] : 1; uint32_t fRace = csL ? (*csL)["RaceID"] : 1;
uint32_t fSex = csL ? (*csL)["SexID"] : 2; uint32_t fSex = csL ? (*csL)["SexID"] : 2;
uint32_t fBase = csL ? (*csL)["BaseSection"] : 3; uint32_t fBase = csL ? (*csL)["BaseSection"] : 3;
uint32_t fVar = csL ? (*csL)["VariationIndex"] : 8; uint32_t fVar = csL ? (*csL)["VariationIndex"] : 4;
uint32_t fColor = csL ? (*csL)["ColorIndex"] : 9; uint32_t fColor = csL ? (*csL)["ColorIndex"] : 5;
for (uint32_t r = 0; r < charSectionsDbc->getRecordCount(); r++) { for (uint32_t r = 0; r < charSectionsDbc->getRecordCount(); r++) {
uint32_t raceId = charSectionsDbc->getUInt32(r, fRace); uint32_t raceId = charSectionsDbc->getUInt32(r, fRace);
uint32_t sexId = charSectionsDbc->getUInt32(r, fSex); uint32_t sexId = charSectionsDbc->getUInt32(r, fSex);
@ -350,7 +350,7 @@ bool CharacterPreview::loadCharacter(game::Race race, game::Gender gender,
// Section 0: Body skin (variation=0, colorIndex = skin color) // Section 0: Body skin (variation=0, colorIndex = skin color)
if (baseSection == 0 && !foundSkin && if (baseSection == 0 && !foundSkin &&
variationIndex == 0 && colorIndex == static_cast<uint32_t>(skin)) { variationIndex == 0 && colorIndex == static_cast<uint32_t>(skin)) {
std::string tex1 = charSectionsDbc->getString(r, csL ? (*csL)["Texture1"] : 4); std::string tex1 = charSectionsDbc->getString(r, csL ? (*csL)["Texture1"] : 6);
if (!tex1.empty()) { if (!tex1.empty()) {
bodySkinPath_ = tex1; bodySkinPath_ = tex1;
foundSkin = true; foundSkin = true;
@ -360,8 +360,8 @@ bool CharacterPreview::loadCharacter(game::Race race, game::Gender gender,
else if (baseSection == 1 && !foundFace && else if (baseSection == 1 && !foundFace &&
variationIndex == static_cast<uint32_t>(face) && variationIndex == static_cast<uint32_t>(face) &&
colorIndex == static_cast<uint32_t>(skin)) { colorIndex == static_cast<uint32_t>(skin)) {
std::string tex1 = charSectionsDbc->getString(r, csL ? (*csL)["Texture1"] : 4); std::string tex1 = charSectionsDbc->getString(r, csL ? (*csL)["Texture1"] : 6);
std::string tex2 = charSectionsDbc->getString(r, csL ? (*csL)["Texture2"] : 5); std::string tex2 = charSectionsDbc->getString(r, csL ? (*csL)["Texture2"] : 7);
if (!tex1.empty()) faceLowerPath = tex1; if (!tex1.empty()) faceLowerPath = tex1;
if (!tex2.empty()) faceUpperPath = tex2; if (!tex2.empty()) faceUpperPath = tex2;
foundFace = true; foundFace = true;
@ -370,7 +370,7 @@ bool CharacterPreview::loadCharacter(game::Race race, game::Gender gender,
else if (baseSection == 3 && !foundHair && else if (baseSection == 3 && !foundHair &&
variationIndex == static_cast<uint32_t>(hairStyle) && variationIndex == static_cast<uint32_t>(hairStyle) &&
colorIndex == static_cast<uint32_t>(hairColor)) { colorIndex == static_cast<uint32_t>(hairColor)) {
std::string tex1 = charSectionsDbc->getString(r, csL ? (*csL)["Texture1"] : 4); std::string tex1 = charSectionsDbc->getString(r, csL ? (*csL)["Texture1"] : 6);
if (!tex1.empty()) { if (!tex1.empty()) {
hairScalpPath = tex1; hairScalpPath = tex1;
foundHair = true; foundHair = true;
@ -379,7 +379,7 @@ bool CharacterPreview::loadCharacter(game::Race race, game::Gender gender,
// Section 4: Underwear (variation=0, colorIndex = skin color) // Section 4: Underwear (variation=0, colorIndex = skin color)
else if (baseSection == 4 && !foundUnderwear && else if (baseSection == 4 && !foundUnderwear &&
variationIndex == 0 && colorIndex == static_cast<uint32_t>(skin)) { variationIndex == 0 && colorIndex == static_cast<uint32_t>(skin)) {
uint32_t texBase = csL ? (*csL)["Texture1"] : 4; uint32_t texBase = csL ? (*csL)["Texture1"] : 6;
for (uint32_t f = texBase; f <= texBase + 2; f++) { for (uint32_t f = texBase; f <= texBase + 2; f++) {
std::string tex = charSectionsDbc->getString(r, f); std::string tex = charSectionsDbc->getString(r, f);
if (!tex.empty()) { if (!tex.empty()) {
@ -462,6 +462,17 @@ bool CharacterPreview::loadCharacter(game::Race race, game::Gender gender,
} }
} }
} }
} else {
// Single layer (body skin only, no face/underwear overlays) — load directly
VkTexture* skinTex = charRenderer_->loadTexture(bodySkinPath_);
if (skinTex != nullptr) {
for (size_t ti = 0; ti < model.textures.size(); ti++) {
if (model.textures[ti].type == 1) {
charRenderer_->setModelTexture(PREVIEW_MODEL_ID, static_cast<uint32_t>(ti), skinTex);
break;
}
}
}
} }
} }

View file

@ -257,8 +257,8 @@ void CharacterCreateScreen::updateAppearanceRanges() {
if (raceId != targetRaceId || sexId != targetSexId) continue; if (raceId != targetRaceId || sexId != targetSexId) continue;
uint32_t baseSection = dbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3); uint32_t baseSection = dbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
uint32_t variationIndex = dbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8); uint32_t variationIndex = dbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
uint32_t colorIndex = dbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9); uint32_t colorIndex = dbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
if (baseSection == 0 && variationIndex == 0) { if (baseSection == 0 && variationIndex == 0) {
skinMax = std::max(skinMax, static_cast<int>(colorIndex)); skinMax = std::max(skinMax, static_cast<int>(colorIndex));
@ -284,8 +284,8 @@ void CharacterCreateScreen::updateAppearanceRanges() {
if (raceId != targetRaceId || sexId != targetSexId) continue; if (raceId != targetRaceId || sexId != targetSexId) continue;
uint32_t baseSection = dbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3); uint32_t baseSection = dbc->getUInt32(r, csL ? (*csL)["BaseSection"] : 3);
uint32_t variationIndex = dbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 8); uint32_t variationIndex = dbc->getUInt32(r, csL ? (*csL)["VariationIndex"] : 4);
uint32_t colorIndex = dbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 9); uint32_t colorIndex = dbc->getUInt32(r, csL ? (*csL)["ColorIndex"] : 5);
if (baseSection == 1 && colorIndex == static_cast<uint32_t>(skin)) { if (baseSection == 1 && colorIndex == static_cast<uint32_t>(skin)) {
faceMax = std::max(faceMax, static_cast<int>(variationIndex)); faceMax = std::max(faceMax, static_cast<int>(variationIndex));