From b8f1f15eb40783cfee245503e3b2db4183c43205 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Tue, 17 Feb 2026 03:18:01 -0800 Subject: [PATCH] Fix CharSections.dbc field layout for classic/tbc/turtle expansions The binary DBC files for all expansions use the same field ordering (VariationIndex=4, ColorIndex=5, Texture1=6), but classic/tbc/turtle dbc_layouts.json had swapped texture and variation/color fields, causing all skin/face/hair/underwear lookups to fail. Also adds generalized NxN texture scaling and a second video to README. --- Data/expansions/classic/dbc_layouts.json | 5 +- Data/expansions/tbc/dbc_layouts.json | 5 +- Data/expansions/turtle/dbc_layouts.json | 5 +- README.md | 2 + .../Raise the Mug, Sound the Warcry.mp3 | Bin src/rendering/character_renderer.cpp | 104 ++++++++++++++---- 6 files changed, 93 insertions(+), 28 deletions(-) rename assets/{ => Original Music}/Raise the Mug, Sound the Warcry.mp3 (100%) diff --git a/Data/expansions/classic/dbc_layouts.json b/Data/expansions/classic/dbc_layouts.json index 1abe53c9..1b9bf00f 100644 --- a/Data/expansions/classic/dbc_layouts.json +++ b/Data/expansions/classic/dbc_layouts.json @@ -12,8 +12,9 @@ }, "CharSections": { "RaceID": 1, "SexID": 2, "BaseSection": 3, - "Texture1": 4, "Texture2": 5, "Texture3": 6, - "Flags": 7, "VariationIndex": 8, "ColorIndex": 9 + "VariationIndex": 4, "ColorIndex": 5, + "Texture1": 6, "Texture2": 7, "Texture3": 8, + "Flags": 9 }, "SpellIcon": { "ID": 0, "Path": 1 }, "FactionTemplate": { diff --git a/Data/expansions/tbc/dbc_layouts.json b/Data/expansions/tbc/dbc_layouts.json index b302dded..af1bf479 100644 --- a/Data/expansions/tbc/dbc_layouts.json +++ b/Data/expansions/tbc/dbc_layouts.json @@ -12,8 +12,9 @@ }, "CharSections": { "RaceID": 1, "SexID": 2, "BaseSection": 3, - "Texture1": 4, "Texture2": 5, "Texture3": 6, - "Flags": 7, "VariationIndex": 8, "ColorIndex": 9 + "VariationIndex": 4, "ColorIndex": 5, + "Texture1": 6, "Texture2": 7, "Texture3": 8, + "Flags": 9 }, "SpellIcon": { "ID": 0, "Path": 1 }, "FactionTemplate": { diff --git a/Data/expansions/turtle/dbc_layouts.json b/Data/expansions/turtle/dbc_layouts.json index 7daffa66..a3499be5 100644 --- a/Data/expansions/turtle/dbc_layouts.json +++ b/Data/expansions/turtle/dbc_layouts.json @@ -12,8 +12,9 @@ }, "CharSections": { "RaceID": 1, "SexID": 2, "BaseSection": 3, - "Texture1": 4, "Texture2": 5, "Texture3": 6, - "Flags": 7, "VariationIndex": 8, "ColorIndex": 9 + "VariationIndex": 4, "ColorIndex": 5, + "Texture1": 6, "Texture2": 7, "Texture3": 8, + "Flags": 9 }, "SpellIcon": { "ID": 0, "Path": 1 }, "FactionTemplate": { diff --git a/README.md b/README.md index 00b0caf2..7c6cb647 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,8 @@ A native C++ World of Warcraft client with a custom OpenGL renderer. [![Watch the video](https://img.youtube.com/vi/Pd9JuYYxu0o/maxresdefault.jpg)](https://youtu.be/Pd9JuYYxu0o) +[![Watch the video](https://img.youtube.com/vi/J4NXegzqWSQ/maxresdefault.jpg)](https://youtu.be/J4NXegzqWSQ) + Primary target today is **WotLK 3.3.5a**, with active work to broaden compatibility across **Vanilla (Classic) + TBC + WotLK**. > **Legal Disclaimer**: This is an educational/research project. It does not include any Blizzard Entertainment assets, data files, or proprietary code. World of Warcraft and all related assets are the property of Blizzard Entertainment, Inc. This project is not affiliated with or endorsed by Blizzard Entertainment. Users are responsible for supplying their own legally obtained game data files and for ensuring compliance with all applicable laws in their jurisdiction. diff --git a/assets/Raise the Mug, Sound the Warcry.mp3 b/assets/Original Music/Raise the Mug, Sound the Warcry.mp3 similarity index 100% rename from assets/Raise the Mug, Sound the Warcry.mp3 rename to assets/Original Music/Raise the Mug, Sound the Warcry.mp3 diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index 0b170a28..6ef11f86 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include namespace wowee { @@ -431,21 +432,22 @@ static void blitOverlay(std::vector& composite, int compW, int compH, } } -// Nearest-neighbor 2x scale blit of overlay onto composite at (dstX, dstY) -static void blitOverlayScaled2x(std::vector& composite, int compW, int compH, - const pipeline::BLPImage& overlay, int dstX, int dstY) { +// Nearest-neighbor NxN scale blit of overlay onto composite at (dstX, dstY) +static void blitOverlayScaledN(std::vector& composite, int compW, int compH, + const pipeline::BLPImage& overlay, int dstX, int dstY, int scale) { + if (scale < 1) scale = 1; for (int sy = 0; sy < overlay.height; sy++) { for (int sx = 0; sx < overlay.width; sx++) { size_t srcIdx = (static_cast(sy) * overlay.width + sx) * 4; uint8_t srcA = overlay.data[srcIdx + 3]; if (srcA == 0) continue; - // Write to 2x2 block of destination pixels - for (int dy2 = 0; dy2 < 2; dy2++) { - int dy = dstY + sy * 2 + dy2; + // Write to scale×scale block of destination pixels + for (int dy2 = 0; dy2 < scale; dy2++) { + int dy = dstY + sy * scale + dy2; if (dy < 0 || dy >= compH) continue; - for (int dx2 = 0; dx2 < 2; dx2++) { - int dx = dstX + sx * 2 + dx2; + for (int dx2 = 0; dx2 < scale; dx2++) { + int dx = dstX + sx * scale + dx2; if (dx < 0 || dx >= compW) continue; size_t dstIdx = (static_cast(dy) * compW + dx) * 4; @@ -468,6 +470,12 @@ static void blitOverlayScaled2x(std::vector& composite, int compW, int } } +// Legacy 2x wrapper +static void blitOverlayScaled2x(std::vector& composite, int compW, int compH, + const pipeline::BLPImage& overlay, int dstX, int dstY) { + blitOverlayScaledN(composite, compW, compH, overlay, dstX, dstY, 2); +} + GLuint CharacterRenderer::compositeTextures(const std::vector& layerPaths) { if (layerPaths.empty() || !assetManager || !assetManager->isInitialized()) { return whiteTexture; @@ -502,13 +510,22 @@ GLuint CharacterRenderer::compositeTextures(const std::vector& laye // Pelvis Lower 128 160 128 64 // Foot 128 224 128 32 + // Scale factor: base texture may be larger than the 256x256 reference atlas + int coordScale = width / 256; + if (coordScale < 1) coordScale = 1; + + // Atlas region sizes at 256x256 base (w, h) for known regions + struct AtlasRegion { int x, y, w, h; }; + static const AtlasRegion faceLowerRegion256 = {0, 192, 128, 64}; + static const AtlasRegion faceUpperRegion256 = {0, 160, 128, 32}; + // Alpha-blend each overlay onto the composite for (size_t layer = 1; layer < layerPaths.size(); layer++) { if (layerPaths[layer].empty()) continue; auto overlay = assetManager->loadTexture(layerPaths[layer]); if (!overlay.isValid()) { - core::Logger::getInstance().warning("Composite: failed to load overlay: ", layerPaths[layer]); + core::Logger::getInstance().warning("Composite: FAILED to load overlay: ", layerPaths[layer]); continue; } @@ -521,39 +538,82 @@ GLuint CharacterRenderer::compositeTextures(const std::vector& laye } else { // Determine region by filename keywords // Coordinates scale with base texture size (256x256 is reference) - float s = width / 256.0f; int dstX = 0, dstY = 0; + int expectedW256 = 0, expectedH256 = 0; // Expected size at 256-base std::string pathLower = layerPaths[layer]; for (auto& c : pathLower) c = std::tolower(c); if (pathLower.find("faceupper") != std::string::npos) { - dstX = 0; dstY = static_cast(160 * s); + dstX = faceUpperRegion256.x; dstY = faceUpperRegion256.y; + expectedW256 = faceUpperRegion256.w; expectedH256 = faceUpperRegion256.h; } else if (pathLower.find("facelower") != std::string::npos) { - dstX = 0; dstY = static_cast(192 * s); + dstX = faceLowerRegion256.x; dstY = faceLowerRegion256.y; + expectedW256 = faceLowerRegion256.w; expectedH256 = faceLowerRegion256.h; } else if (pathLower.find("pelvis") != std::string::npos) { - dstX = static_cast(128 * s); - dstY = static_cast(96 * s); + dstX = 128; dstY = 96; + expectedW256 = 128; expectedH256 = 64; } else if (pathLower.find("torso") != std::string::npos) { - dstX = static_cast(128 * s); - dstY = 0; + dstX = 128; dstY = 0; + expectedW256 = 128; expectedH256 = 64; } else if (pathLower.find("armupper") != std::string::npos) { dstX = 0; dstY = 0; + expectedW256 = 128; expectedH256 = 64; } else if (pathLower.find("armlower") != std::string::npos) { - dstX = 0; dstY = static_cast(64 * s); + dstX = 0; dstY = 64; + expectedW256 = 128; expectedH256 = 64; } else if (pathLower.find("hand") != std::string::npos) { - dstX = 0; dstY = static_cast(128 * s); + dstX = 0; dstY = 128; + expectedW256 = 128; expectedH256 = 32; } else if (pathLower.find("foot") != std::string::npos || pathLower.find("feet") != std::string::npos) { - dstX = static_cast(128 * s); dstY = static_cast(224 * s); + dstX = 128; dstY = 224; + expectedW256 = 128; expectedH256 = 32; } else if (pathLower.find("legupper") != std::string::npos || pathLower.find("leg") != std::string::npos) { - dstX = static_cast(128 * s); dstY = static_cast(160 * s); + dstX = 128; dstY = 160; + expectedW256 = 128; expectedH256 = 64; } else { // Unknown — center placement as fallback dstX = (width - overlay.width) / 2; dstY = (height - overlay.height) / 2; + core::Logger::getInstance().info("Composite: UNKNOWN region for '", + layerPaths[layer], "', centering at (", dstX, ",", dstY, ")"); + blitOverlay(composite, width, height, overlay, dstX, dstY); + continue; } - core::Logger::getInstance().info("Composite: placing '", layerPaths[layer], "' at (", dstX, ",", dstY, ") on ", width, "x", height); - blitOverlay(composite, width, height, overlay, dstX, dstY); + // Scale coordinates from 256-base to actual canvas + dstX *= coordScale; + dstY *= coordScale; + + // If overlay is 256-base sized but canvas is larger, scale the overlay up + int expectedW = expectedW256 * coordScale; + int expectedH = expectedH256 * coordScale; + bool needsScale = (coordScale > 1 && + overlay.width == expectedW256 && overlay.height == expectedH256); + + core::Logger::getInstance().info("Composite: placing '", layerPaths[layer], + "' (", overlay.width, "x", overlay.height, + ") at (", dstX, ",", dstY, ") on ", width, "x", height, + " expected=", expectedW, "x", expectedH, + needsScale ? " [SCALING]" : ""); + + if (needsScale) { + blitOverlayScaledN(composite, width, height, overlay, dstX, dstY, coordScale); + } else { + blitOverlay(composite, width, height, overlay, dstX, dstY); + } + } + } + + // Debug: dump composite to /tmp for visual inspection + { + std::string dumpPath = "/tmp/wowee_composite_debug_" + + std::to_string(width) + "x" + std::to_string(height) + ".raw"; + std::ofstream dump(dumpPath, std::ios::binary); + if (dump) { + dump.write(reinterpret_cast(composite.data()), + static_cast(composite.size())); + core::Logger::getInstance().info("Composite debug dump: ", dumpPath, + " (", width, "x", height, ", ", composite.size(), " bytes)"); } }