From b523a58e7b1c5510959e2052ee47f325f50329dc Mon Sep 17 00:00:00 2001 From: DJLegends Date: Mon, 11 May 2026 14:44:55 -0500 Subject: [PATCH] Fix BSGeometry missing virtual overrides and add SkinAttach bone name fallback BSGeometry had skinInstanceRef, shaderPropertyRef, and alphaPropertyRef members and read them in Sync(), but never overrode the virtual accessors inherited from NiShape. This caused SkinInstanceRef(), IsSkinned(), HasShaderProperty(), etc. to return null/false for all Starfield BSGeometry shapes, breaking skinning, bone loading, and shader/alpha property access. GetShapeBoneList now falls back to SkinAttach extra data when boneRefs yield no bone NiNodes. Starfield NIFs store bone names as strings in SkinAttach instead of NiNode block references (all boneRefs are 0xFFFFFFFF). Co-Authored-By: Claude Opus 4.6 --- include/Geometry.hpp | 14 ++++++++++++++ src/NifFile.cpp | 23 +++++++++++++++++------ 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/include/Geometry.hpp b/include/Geometry.hpp index e7e94ed..dbf184e 100644 --- a/include/Geometry.hpp +++ b/include/Geometry.hpp @@ -652,6 +652,20 @@ class BSGeometry : public NiCloneableStreamable { bool GetTriangles(std::vector& tris) const override; void SetTriangles(const std::vector& tris) override; + bool IsSkinned() const override { return !skinInstanceRef.IsEmpty(); } + + bool HasSkinInstance() const override { return !skinInstanceRef.IsEmpty(); } + NiBlockRef* SkinInstanceRef() override { return &skinInstanceRef; } + const NiBlockRef* SkinInstanceRef() const override { return &skinInstanceRef; } + + bool HasShaderProperty() const override { return !shaderPropertyRef.IsEmpty(); } + NiBlockRef* ShaderPropertyRef() override { return &shaderPropertyRef; } + const NiBlockRef* ShaderPropertyRef() const override { return &shaderPropertyRef; } + + bool HasAlphaProperty() const override { return !alphaPropertyRef.IsEmpty(); } + NiBlockRef* AlphaPropertyRef() override { return &alphaPropertyRef; } + const NiBlockRef* AlphaPropertyRef() const override { return &alphaPropertyRef; } + uint8_t MeshCount() { return (uint8_t) meshes.size(); } // Flag 0x200 (512) on BSGeometry controls whether mesh data is embedded inline diff --git a/src/NifFile.cpp b/src/NifFile.cpp index 59ecb0c..5959190 100644 --- a/src/NifFile.cpp +++ b/src/NifFile.cpp @@ -2445,13 +2445,24 @@ uint32_t NifFile::GetShapeBoneList(NiShape* shape, std::vector& out return 0; auto skinInst = hdr.GetBlock(shape->SkinInstanceRef()); - if (!skinInst) - return 0; + if (skinInst) { + for (auto& bone : skinInst->boneRefs) { + auto node = hdr.GetBlock(bone); + if (node) + outList.push_back(node->name.get()); + } + } - for (auto& bone : skinInst->boneRefs) { - auto node = hdr.GetBlock(bone); - if (node) - outList.push_back(node->name.get()); + // SF NIFs store bone names in SkinAttach extra data instead of NiNode refs + if (outList.empty()) { + for (auto& extraDataRef : shape->extraDataRefs) { + auto skinAttach = hdr.GetBlock(extraDataRef); + if (skinAttach) { + for (auto& bone : skinAttach->bones) + outList.push_back(bone.get()); + break; + } + } } return static_cast(outList.size());