From 3c48ccd7053ac2c3cea2d0e32d393efddd7097ac Mon Sep 17 00:00:00 2001 From: Amber Burton Date: Sun, 5 Jul 2026 07:17:57 -0400 Subject: [PATCH] 1 Cache + Fix logical errors (#6862) So this fixes some logical errors with loading the resources so its checking they exist before trying to test-load them... It also added a cache for the alt skeleton by tunic type so we arent constantly trying to reassign the tunic (This change in particular may prevent instant model swapping depending how the model swap with custom content is handled if loaded in a menu like NEI does)... Current system was just spamming that check over and over and trying to reload skeletons constantly so I opted for detecting if the tunic actually changes THEN allow a recheck here, otherwise exit out early. The skeleton.cpp and skeleton.h files can be reverted if suspected that will interfere with intended custom model usage. --- soh/soh/Enhancements/customequipment.cpp | 2 +- soh/soh/ResourceManagerHelpers.cpp | 57 +++++++++++++----------- soh/soh/resource/type/Skeleton.cpp | 44 +++++++++++++++--- soh/soh/resource/type/Skeleton.h | 2 + 4 files changed, 70 insertions(+), 35 deletions(-) diff --git a/soh/soh/Enhancements/customequipment.cpp b/soh/soh/Enhancements/customequipment.cpp index e8c2721543..0c14be5a0d 100644 --- a/soh/soh/Enhancements/customequipment.cpp +++ b/soh/soh/Enhancements/customequipment.cpp @@ -54,7 +54,7 @@ static Gfx* LoadCustomGfx(const char* path) { if (!path) return nullptr; path = ResolveCustomFPSHand(path); - if (!ResourceGetIsCustomByName(path) && !ResourceMgr_FileAltExists(path)) + if (!ResourceMgr_FileAltExists(path) || !ResourceGetIsCustomByName(path)) return nullptr; return ResourceMgr_LoadGfxByName(path); } diff --git a/soh/soh/ResourceManagerHelpers.cpp b/soh/soh/ResourceManagerHelpers.cpp index 16c112114e..2818e7e862 100644 --- a/soh/soh/ResourceManagerHelpers.cpp +++ b/soh/soh/ResourceManagerHelpers.cpp @@ -76,8 +76,8 @@ static const char* ResourceMgr_ResolveLinkTunicDListPath(const char* path) { const std::string candidate = fmt::format("__OTR__objects/{}_{}/{}", objectFolder, tunicSuffix, originalPath + objectPrefix.size()); - if (!ResourceGetIsCustomByName(candidate.c_str()) && !ResourceMgr_FileExists(candidate.c_str()) && - !(ResourceMgr_IsAltAssetsEnabled() && ResourceMgr_FileAltExists(candidate.c_str()))) { + if (!ResourceMgr_IsAltAssetsEnabled() || !ResourceMgr_FileAltExists(candidate.c_str()) || + !ResourceGetIsCustomByName(candidate.c_str())) { return path; } @@ -602,33 +602,36 @@ extern "C" AnimationHeaderCommon* ResourceMgr_LoadAnimByName(const char* path) { bool isAlt = ResourceMgr_IsAltAssetsEnabled(); if (isAlt) { - std::string pathStr = std::string(path); - static const std::string sOtr = "__OTR__"; + if (ResourceMgr_FileAltExists(path)) { + std::string pathStr = std::string(path); + static const std::string sOtr = "__OTR__"; - if (pathStr.starts_with(sOtr)) { - pathStr = pathStr.substr(sOtr.length()); - } - - // Try alt/ first - pathStr = Ship::IResource::gAltAssetPrefix + pathStr; - AnimationHeaderCommon* animHeader = (AnimationHeaderCommon*)ResourceGetDataByName(pathStr.c_str()); - - // If alt loaded successfully, verify it has valid data - if (animHeader != NULL) { - // Check for valid frame count (> 0) - if (animHeader->frameCount > 0) { - // For Normal animations: check frameData (comes after frameCount in AnimationHeader) - // For Link animations: check segment (comes after frameCount in LinkAnimationHeader) - // We check both to be safe - if either is valid, the animation is usable - AnimationHeader* normalAnim = (AnimationHeader*)animHeader; - LinkAnimationHeader* linkAnim = (LinkAnimationHeader*)animHeader; - - // Valid if Normal animation has frameData OR Link animation has segment - if (normalAnim->frameData != NULL || linkAnim->segment != NULL) { - return animHeader; - } + if (pathStr.starts_with(sOtr)) { + pathStr = pathStr.substr(sOtr.length()); + } + + // Try alt/ first + pathStr = Ship::IResource::gAltAssetPrefix + pathStr; + + AnimationHeaderCommon* animHeader = (AnimationHeaderCommon*)ResourceGetDataByName(pathStr.c_str()); + + // If alt loaded successfully, verify it has valid data + if (animHeader != NULL) { + // Check for valid frame count (> 0) + if (animHeader->frameCount > 0) { + // For Normal animations: check frameData (comes after frameCount in AnimationHeader) + // For Link animations: check segment (comes after frameCount in LinkAnimationHeader) + // We check both to be safe - if either is valid, the animation is usable + AnimationHeader* normalAnim = (AnimationHeader*)animHeader; + LinkAnimationHeader* linkAnim = (LinkAnimationHeader*)animHeader; + + // Valid if Normal animation has frameData OR Link animation has segment + if (normalAnim->frameData != NULL || linkAnim->segment != NULL) { + return animHeader; + } + } + // Alt loaded but is invalid (broken), fall through to original path } - // Alt loaded but is invalid (broken), fall through to original path } // Fall back to original path diff --git a/soh/soh/resource/type/Skeleton.cpp b/soh/soh/resource/type/Skeleton.cpp index ac83d16615..1871688ff3 100644 --- a/soh/soh/resource/type/Skeleton.cpp +++ b/soh/soh/resource/type/Skeleton.cpp @@ -135,43 +135,73 @@ void SkeletonPatcher::UpdateCustomSkeletons() { void SkeletonPatcher::UpdateTunicSkeletons(SkeletonPatchInfo& skel) { std::string skeletonPath = ""; + s32 tunicID = TUNIC_EQUIP_TO_PLAYER(CUR_EQUIP_VALUE(EQUIP_TYPE_TUNIC)); + s32 ageID = 0; // Check if this is one of Link's skeletons if (sOtr + skel.vanillaSkeletonPath == std::string(gLinkAdultSkel)) { + // Adult skeleton + ageID = 2; + } else if (sOtr + skel.vanillaSkeletonPath == std::string(gLinkChildSkel)) { + // Child skeleton + ageID = 1; + } else { + // Incompatible? + return; + } + + // Check if we even need updating + s32 skelID = ageID << 4 | tunicID; + if (skelID == skel.lastSkeletonId) + return; + skel.lastSkeletonId = skelID; + + // Check if this is one of Link's skeletons + if (ageID == 2) { // Check what Link's current tunic is - switch (TUNIC_EQUIP_TO_PLAYER(CUR_EQUIP_VALUE(EQUIP_TYPE_TUNIC))) { + switch (tunicID) { case PLAYER_TUNIC_KOKIRI: + if (skel.lastSkeletonId == (ageID << 4 | PLAYER_TUNIC_KOKIRI)) + return; skeletonPath = std::string(gLinkAdultKokiriTunicSkel).substr(sOtr.length()); break; case PLAYER_TUNIC_GORON: + if (skel.lastSkeletonId == (ageID << 4 | PLAYER_TUNIC_GORON)) + return; skeletonPath = std::string(gLinkAdultGoronTunicSkel).substr(sOtr.length()); break; case PLAYER_TUNIC_ZORA: + if (skel.lastSkeletonId == (ageID << 4 | PLAYER_TUNIC_ZORA)) + return; skeletonPath = std::string(gLinkAdultZoraTunicSkel).substr(sOtr.length()); break; default: return; } - - UpdateCustomSkeletonFromPath(skeletonPath, skel); - } else if (sOtr + skel.vanillaSkeletonPath == std::string(gLinkChildSkel)) { + } else if (skelID == 1) { // Check what Link's current tunic is - switch (TUNIC_EQUIP_TO_PLAYER(CUR_EQUIP_VALUE(EQUIP_TYPE_TUNIC))) { + switch (tunicID) { case PLAYER_TUNIC_KOKIRI: + if (skel.lastSkeletonId == (ageID << 4 | PLAYER_TUNIC_KOKIRI)) + return; skeletonPath = std::string(gLinkChildKokiriTunicSkel).substr(sOtr.length()); break; case PLAYER_TUNIC_GORON: + if (skel.lastSkeletonId == (ageID << 4 | PLAYER_TUNIC_GORON)) + return; skeletonPath = std::string(gLinkChildGoronTunicSkel).substr(sOtr.length()); break; case PLAYER_TUNIC_ZORA: + if (skel.lastSkeletonId == (ageID << 4 | PLAYER_TUNIC_ZORA)) + return; skeletonPath = std::string(gLinkChildZoraTunicSkel).substr(sOtr.length()); break; default: return; } - - UpdateCustomSkeletonFromPath(skeletonPath, skel); } + + UpdateCustomSkeletonFromPath(skeletonPath, skel); } void SkeletonPatcher::UpdateCustomSkeletonFromPath(const std::string& skeletonPath, SkeletonPatchInfo& skel) { diff --git a/soh/soh/resource/type/Skeleton.h b/soh/soh/resource/type/Skeleton.h index 11e0995c94..8e5805c0ab 100644 --- a/soh/soh/resource/type/Skeleton.h +++ b/soh/soh/resource/type/Skeleton.h @@ -78,6 +78,8 @@ class Skeleton : public Ship::Resource { struct SkeletonPatchInfo { SkelAnime* skelAnime; std::string vanillaSkeletonPath; + + u8 lastSkeletonId; bool isLocalPlayer; };