From ca3150e43dc93c66e47be550f644b212f08652e3 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Feb 2026 13:57:54 -0800 Subject: [PATCH] Fix mesh artifacts on vanilla/TBC M2 models when using expansion overlays MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vanilla (v256) and TBC (v263) M2 files embed skin data directly, parsed during M2Loader::load(). The code unconditionally loaded external .skin files afterwards, which resolved to WotLK-format .skin files (48-byte submeshes) from the base manifest — overwriting the correctly parsed embedded skin (32-byte submeshes) and causing mesh corruption on all character models. Guard all 13 loadSkin() call sites with version >= 264 so external .skin files are only loaded for WotLK M2s that need them. --- src/core/application.cpp | 26 +++++++++++++------------- src/rendering/character_preview.cpp | 4 ++-- src/rendering/terrain_manager.cpp | 8 ++++---- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/core/application.cpp b/src/core/application.cpp index e8db5e66..7d34f473 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -1790,7 +1790,7 @@ void Application::spawnPlayerCharacter() { // Load skin file for submesh/batch data std::string skinPath = modelDir + baseName + "00.skin"; auto skinData = assetManager->readFile(skinPath); - if (!skinData.empty()) { + if (!skinData.empty() && model.version >= 264) { pipeline::M2Loader::loadSkin(skinData, model); } @@ -2241,7 +2241,7 @@ void Application::loadEquippedWeapons() { // Try same directory as m2 std::string skinDir = m2Path.substr(0, m2Path.rfind('\\') + 1); auto skinData = assetManager->readFile(skinDir + skinFile); - if (!skinData.empty()) { + if (!skinData.empty() && weaponModel.version >= 264) { pipeline::M2Loader::loadSkin(skinData, weaponModel); } @@ -3039,10 +3039,10 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x return; } - // Load skin file + // Load skin file (only for WotLK M2s - vanilla has embedded skin) std::string skinPath = m2Path.substr(0, m2Path.size() - 3) + "00.skin"; auto skinData = assetManager->readFile(skinPath); - if (!skinData.empty()) { + if (!skinData.empty() && model.version >= 264) { pipeline::M2Loader::loadSkin(skinData, model); } @@ -3429,10 +3429,10 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x if (!helmData.empty()) { auto helmModel = pipeline::M2Loader::load(helmData); - // Load skin + // Load skin (only for WotLK M2s) std::string skinPath = helmPath.substr(0, helmPath.size() - 3) + "00.skin"; auto skinData = assetManager->readFile(skinPath); - if (!skinData.empty()) { + if (!skinData.empty() && helmModel.version >= 264) { pipeline::M2Loader::loadSkin(skinData, helmModel); } @@ -3512,7 +3512,7 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x auto helmModel = pipeline::M2Loader::load(helmData); std::string skinPath = helmPath.substr(0, helmPath.size() - 3) + "00.skin"; auto skinData = assetManager->readFile(skinPath); - if (!skinData.empty()) { + if (!skinData.empty() && helmModel.version >= 264) { pipeline::M2Loader::loadSkin(skinData, helmModel); } @@ -3607,10 +3607,10 @@ void Application::spawnOnlinePlayer(uint64_t guid, return; } - // Skin file + // Skin file (only for WotLK M2s - vanilla has embedded skin) std::string skinPath = modelDir + baseName + "00.skin"; auto skinData = assetManager->readFile(skinPath); - if (!skinData.empty()) { + if (!skinData.empty() && model.version >= 264) { pipeline::M2Loader::loadSkin(skinData, model); } @@ -4145,7 +4145,7 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t pipeline::M2Model m2Model = pipeline::M2Loader::load(m2Data); std::string skinPath = doodadTemplate.m2Path.substr(0, doodadTemplate.m2Path.size() - 3) + "00.skin"; std::vector skinData = assetManager->readFile(skinPath); - if (!skinData.empty()) { + if (!skinData.empty() && m2Model.version >= 264) { pipeline::M2Loader::loadSkin(skinData, m2Model); } if (!m2Model.isValid()) continue; @@ -4217,7 +4217,7 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t std::string skinPath = modelPath.substr(0, modelPath.size() - 3) + "00.skin"; auto skinData = assetManager->readFile(skinPath); - if (!skinData.empty()) { + if (!skinData.empty() && model.version >= 264) { pipeline::M2Loader::loadSkin(skinData, model); } @@ -4359,10 +4359,10 @@ void Application::processPendingMount() { return; } - // Load skin file + // Load skin file (only for WotLK M2s - vanilla has embedded skin) std::string skinPath = m2Path.substr(0, m2Path.size() - 3) + "00.skin"; auto skinData = assetManager->readFile(skinPath); - if (!skinData.empty()) { + if (!skinData.empty() && model.version >= 264) { pipeline::M2Loader::loadSkin(skinData, model); } diff --git a/src/rendering/character_preview.cpp b/src/rendering/character_preview.cpp index f40c3149..d8275480 100644 --- a/src/rendering/character_preview.cpp +++ b/src/rendering/character_preview.cpp @@ -135,10 +135,10 @@ bool CharacterPreview::loadCharacter(game::Race race, game::Gender gender, auto model = pipeline::M2Loader::load(m2Data); - // Load skin file + // Load skin file (only for WotLK M2s - vanilla has embedded skin) std::string skinPath = modelDir + baseName + "00.skin"; auto skinData = assetManager_->readFile(skinPath); - if (!skinData.empty()) { + if (!skinData.empty() && model.version >= 264) { pipeline::M2Loader::loadSkin(skinData, model); } diff --git a/src/rendering/terrain_manager.cpp b/src/rendering/terrain_manager.cpp index 22f68d8b..94bbc444 100644 --- a/src/rendering/terrain_manager.cpp +++ b/src/rendering/terrain_manager.cpp @@ -303,12 +303,12 @@ std::shared_ptr TerrainManager::prepareTile(int x, int y) { if (!m2Data.empty()) { pipeline::M2Model m2Model = pipeline::M2Loader::load(m2Data); - // Try to load skin file + // Try to load skin file (only for WotLK M2s - vanilla has embedded skin) std::string skinPath = m2Path.substr(0, m2Path.size() - 3) + "00.skin"; std::vector skinData = assetManager->readFile(skinPath); - if (!skinData.empty()) { + if (!skinData.empty() && m2Model.version >= 264) { pipeline::M2Loader::loadSkin(skinData, m2Model); - } else { + } else if (skinData.empty() && m2Model.version >= 264) { skippedSkinNotFound++; LOG_WARNING("M2 skin not found: ", skinPath); } @@ -447,7 +447,7 @@ std::shared_ptr TerrainManager::prepareTile(int x, int y) { pipeline::M2Model m2Model = pipeline::M2Loader::load(m2Data); std::string skinPath = m2Path.substr(0, m2Path.size() - 3) + "00.skin"; std::vector skinData = assetManager->readFile(skinPath); - if (!skinData.empty()) { + if (!skinData.empty() && m2Model.version >= 264) { pipeline::M2Loader::loadSkin(skinData, m2Model); } if (!m2Model.isValid()) continue;