#include "niffile.hpp" #include #include #include #include #include #include #include #include #include "controlled.hpp" #include "controller.hpp" #include "data.hpp" #include "effect.hpp" #include "exception.hpp" #include "extra.hpp" #include "node.hpp" #include "physics.hpp" #include "property.hpp" namespace Nif { Reader::Reader(NIFFile& file) : ver(file.mVersion) , userVer(file.mUserVersion) , bethVer(file.mBethVersion) , filename(file.mPath) , hash(file.mHash) , records(file.mRecords) , roots(file.mRoots) , mUseSkinning(file.mUseSkinning) { } template static std::unique_ptr construct() { auto result = std::make_unique(); result->recType = recordType; return result; } using CreateRecord = std::unique_ptr (*)(); /// These are all the record types we know how to read. static std::map makeFactory() { return { { "NiNode", &construct }, { "NiSwitchNode", &construct }, { "NiLODNode", &construct }, { "NiFltAnimationNode", &construct }, { "AvoidNode", &construct }, { "NiCollisionSwitch", &construct }, { "NiBSParticleNode", &construct }, { "NiBSAnimationNode", &construct }, { "NiBillboardNode", &construct }, { "NiTriShape", &construct }, { "NiTriStrips", &construct }, { "NiLines", &construct }, { "NiParticles", &construct }, { "NiRotatingParticles", &construct }, { "NiAutoNormalParticles", &construct }, { "NiCamera", &construct }, { "RootCollisionNode", &construct }, { "NiTexturingProperty", &construct }, { "NiFogProperty", &construct }, { "NiMaterialProperty", &construct }, { "NiZBufferProperty", &construct }, { "NiAlphaProperty", &construct }, { "NiVertexColorProperty", &construct }, { "NiShadeProperty", &construct }, { "NiDitherProperty", &construct }, { "NiWireframeProperty", &construct }, { "NiSpecularProperty", &construct }, { "NiStencilProperty", &construct }, { "NiVisController", &construct }, { "NiGeomMorpherController", &construct }, { "NiKeyframeController", &construct }, { "NiAlphaController", &construct }, { "NiRollController", &construct }, { "NiUVController", &construct }, { "NiPathController", &construct }, { "NiMaterialColorController", &construct }, { "NiBSPArrayController", &construct }, { "NiParticleSystemController", &construct }, { "NiFlipController", &construct }, { "NiTextureTransformController", &construct }, { "NiAmbientLight", &construct }, { "NiDirectionalLight", &construct }, { "NiPointLight", &construct }, { "NiSpotLight", &construct }, { "NiTextureEffect", &construct }, { "NiExtraData", &construct }, { "NiVertWeightsExtraData", &construct }, { "NiTextKeyExtraData", &construct }, { "NiStringExtraData", &construct }, { "NiGravity", &construct }, { "NiPlanarCollider", &construct }, { "NiSphericalCollider", &construct }, { "NiParticleGrowFade", &construct }, { "NiParticleColorModifier", &construct }, { "NiParticleRotation", &construct }, { "NiFloatData", &construct }, { "NiTriShapeData", &construct }, { "NiTriStripsData", &construct }, { "NiLinesData", &construct }, { "NiVisData", &construct }, { "NiColorData", &construct }, { "NiPixelData", &construct }, { "NiMorphData", &construct }, { "NiKeyframeData", &construct }, { "NiSkinData", &construct }, { "NiUVData", &construct }, { "NiPosData", &construct }, { "NiParticlesData", &construct }, { "NiRotatingParticlesData", &construct }, { "NiAutoNormalParticlesData", &construct }, { "NiSequenceStreamHelper", &construct }, { "NiSourceTexture", &construct }, { "NiSkinInstance", &construct }, { "NiLookAtController", &construct }, { "NiPalette", &construct }, { "NiIntegerExtraData", &construct }, { "NiIntegersExtraData", &construct }, { "NiBinaryExtraData", &construct }, { "NiBooleanExtraData", &construct }, { "NiVectorExtraData", &construct }, { "NiColorExtraData", &construct }, { "NiFloatExtraData", &construct }, { "NiFloatsExtraData", &construct }, { "NiStringPalette", &construct }, { "NiBoolData", &construct }, { "NiSkinPartition", &construct }, { "BSXFlags", &construct }, { "BSBound", &construct }, { "NiTransformData", &construct }, { "BSFadeNode", &construct }, { "BSLeafAnimNode", &construct }, { "BSTreeNode", &construct }, { "BSValueNode", &construct }, { "BSOrderedNode", &construct }, { "BSMultiBoundNode", &construct }, { "BSRangeNode", &construct }, { "BSBlastNode", &construct }, { "BSDamageStage", &construct }, { "bhkBlendController", &construct }, { "NiFloatInterpolator", &construct }, { "NiBoolInterpolator", &construct }, { "NiBoolTimelineInterpolator", &construct }, { "NiPoint3Interpolator", &construct }, { "NiTransformController", &construct }, { "NiMultiTargetTransformController", &construct }, { "NiTransformInterpolator", &construct }, { "NiColorInterpolator", &construct }, { "BSShaderTextureSet", &construct }, { "BSLODTriShape", &construct }, { "BSShaderProperty", &construct }, { "BSShaderPPLightingProperty", &construct }, { "BSShaderNoLightingProperty", &construct }, { "BSFurnitureMarker", &construct }, { "BSFurnitureMarkerNode", &construct }, { "NiCollisionObject", &construct }, { "bhkCollisionObject", &construct }, { "bhkSPCollisionObject", &construct }, { "bhkPCollisionObject", &construct }, { "BSDismemberSkinInstance", &construct }, { "NiControllerManager", &construct }, { "bhkMoppBvTreeShape", &construct }, { "bhkNiTriStripsShape", &construct }, { "bhkPackedNiTriStripsShape", &construct }, { "hkPackedNiTriStripsData", &construct }, { "bhkConvexVerticesShape", &construct }, { "bhkConvexTransformShape", &construct }, { "bhkTransformShape", &construct }, { "bhkSimpleShapePhantom", &construct }, { "bhkBoxShape", &construct }, { "bhkCapsuleShape", &construct }, { "bhkSphereShape", &construct }, { "bhkListShape", &construct }, { "bhkRigidBody", &construct }, { "bhkRigidBodyT", &construct }, { "bhkRagdollConstraint", &construct }, { "bhkHingeConstraint", &construct }, { "bhkLimitedHingeConstraint", &construct }, { "BSLightingShaderProperty", &construct }, { "BSEffectShaderProperty", &construct }, { "NiSortAdjustNode", &construct }, { "NiClusterAccumulator", &construct }, { "NiAlphaAccumulator", &construct }, { "NiSequence", &construct }, { "NiControllerSequence", &construct }, { "NiDefaultAVObjectPalette", &construct }, { "NiBlendBoolInterpolator", &construct }, { "NiBlendFloatInterpolator", &construct }, { "NiBlendPoint3Interpolator", &construct }, { "NiBlendTransformInterpolator", &construct }, { "bhkCompressedMeshShape", &construct }, { "bhkCompressedMeshShapeData", &construct }, { "BSMultiBound", &construct }, { "BSMultiBoundOBB", &construct }, { "BSMultiBoundSphere", &construct }, { "BSInvMarker", &construct }, { "BSTriShape", &construct }, { "BSEffectShaderPropertyFloatController", &construct }, { "BSLightingShaderPropertyFloatController", &construct }, { "BSEffectShaderPropertyColorController", &construct }, { "BSLightingShaderPropertyColorController", &construct }, { "BSBehaviorGraphExtraData", &construct }, }; } /// Make the factory map used for parsing the file static const std::map factories = makeFactory(); std::string Reader::printVersion(unsigned int version) { int major = (version >> 24) & 0xFF; int minor = (version >> 16) & 0xFF; int patch = (version >> 8) & 0xFF; int rev = version & 0xFF; std::stringstream stream; stream << major << "." << minor << "." << patch << "." << rev; return stream.str(); } void Reader::parse(Files::IStreamPtr&& stream) { const std::array fileHash = Files::getHash(filename, *stream); hash.append(reinterpret_cast(fileHash.data()), fileHash.size() * sizeof(std::uint64_t)); NIFStream nif(*this, std::move(stream)); // Check the header string std::string head = nif.getVersionString(); static const std::array verStrings = { "NetImmerse File Format", "Gamebryo File Format", }; const bool supportedHeader = std::any_of(verStrings.begin(), verStrings.end(), [&](const std::string& verString) { return head.starts_with(verString); }); if (!supportedHeader) throw Nif::Exception("Invalid NIF header: " + head, filename); // Get BCD version ver = nif.getUInt(); // 4.0.0.0 is an older, practically identical version of the format. // It's not used by Morrowind assets but Morrowind supports it. static const std::array supportedVers = { NIFStream::generateVersion(4, 0, 0, 0), NIFFile::VER_MW, }; const bool supportedVersion = std::find(supportedVers.begin(), supportedVers.end(), ver) != supportedVers.end(); const bool writeDebugLog = sWriteNifDebugLog; if (!supportedVersion) { if (!sLoadUnsupportedFiles) throw Nif::Exception("Unsupported NIF version: " + printVersion(ver), filename); if (writeDebugLog) Log(Debug::Warning) << " NIFFile Warning: Unsupported NIF version: " << printVersion(ver) << ". Proceed with caution! File: " << filename; } // NIF data endianness if (ver >= NIFStream::generateVersion(20, 0, 0, 4)) { unsigned char endianness = nif.getChar(); if (endianness == 0) throw Nif::Exception("Big endian NIF files are unsupported", filename); } // User version if (ver > NIFStream::generateVersion(10, 0, 1, 8)) userVer = nif.getUInt(); // Number of records const std::size_t recNum = nif.getUInt(); records.resize(recNum); // Bethesda stream header // It contains Bethesda format version and (useless) export information if (ver == NIFFile::VER_OB_OLD || (userVer >= 3 && ((ver == NIFFile::VER_OB || ver == NIFFile::VER_BGS) || (ver >= NIFStream::generateVersion(10, 1, 0, 0) && ver <= NIFStream::generateVersion(20, 0, 0, 4) && userVer <= 11)))) { bethVer = nif.getUInt(); nif.getExportString(); // Author if (bethVer > NIFFile::BETHVER_FO4) nif.getUInt(); // Unknown nif.getExportString(); // Process script nif.getExportString(); // Export script if (bethVer == NIFFile::BETHVER_FO4) nif.getExportString(); // Max file path } std::vector recTypes; std::vector recTypeIndices; const bool hasRecTypeListings = ver >= NIFStream::generateVersion(5, 0, 0, 1); if (hasRecTypeListings) { unsigned short recTypeNum = nif.getUShort(); if (recTypeNum) // Record type list nif.getSizedStrings(recTypes, recTypeNum); if (recNum) // Record type mapping for each record nif.getUShorts(recTypeIndices, recNum); if (ver >= NIFStream::generateVersion(5, 0, 0, 6)) // Groups { if (ver >= NIFStream::generateVersion(20, 1, 0, 1)) // String table { if (ver >= NIFStream::generateVersion(20, 2, 0, 5) && recNum) // Record sizes { std::vector recSizes; // Currently unused nif.getUInts(recSizes, recNum); } const std::size_t stringNum = nif.getUInt(); nif.getUInt(); // Max string length if (stringNum) nif.getSizedStrings(strings, stringNum); } std::vector groups; // Currently unused unsigned int groupNum = nif.getUInt(); if (groupNum) nif.getUInts(groups, groupNum); } } const bool hasRecordSeparators = ver >= NIFStream::generateVersion(10, 0, 0, 0) && ver < NIFStream::generateVersion(10, 2, 0, 0); for (std::size_t i = 0; i < recNum; i++) { std::unique_ptr r; std::string rec = hasRecTypeListings ? recTypes[recTypeIndices[i]] : nif.getString(); if (rec.empty()) { std::stringstream error; error << "Record number " << i << " out of " << recNum << " is blank."; throw Nif::Exception(error.str(), filename); } // Record separator. Some Havok records in Oblivion do not have it. if (hasRecordSeparators && !rec.starts_with("bhk")) if (nif.getInt()) Log(Debug::Warning) << "NIFFile Warning: Record number " << i << " out of " << recNum << " is preceded by a non-zero separator. File: " << filename; const auto entry = factories.find(rec); if (entry == factories.end()) throw Nif::Exception("Unknown record type " + rec, filename); r = entry->second(); if (!supportedVersion && writeDebugLog) Log(Debug::Verbose) << "NIF Debug: Reading record of type " << rec << ", index " << i << " (" << filename << ")"; assert(r != nullptr); assert(r->recType != RC_MISSING); r->recName = rec; r->recIndex = i; r->read(&nif); records[i] = std::move(r); } const std::size_t rootNum = nif.getUInt(); roots.resize(rootNum); // Determine which records are roots for (std::size_t i = 0; i < rootNum; i++) { int idx = nif.getInt(); if (idx >= 0 && static_cast(idx) < records.size()) { roots[i] = records[idx].get(); } else { roots[i] = nullptr; Log(Debug::Warning) << "NIFFile Warning: Root " << i + 1 << " does not point to a record: index " << idx << ". File: " << filename; } } // Once parsing is done, do post-processing. for (const auto& record : records) record->post(*this); } void Reader::setUseSkinning(bool skinning) { mUseSkinning = skinning; } std::atomic_bool Reader::sLoadUnsupportedFiles = false; std::atomic_bool Reader::sWriteNifDebugLog = false; void Reader::setLoadUnsupportedFiles(bool load) { sLoadUnsupportedFiles = load; } void Reader::setWriteNifDebugLog(bool value) { sWriteNifDebugLog = value; } std::string Reader::getString(std::uint32_t index) const { if (index == std::numeric_limits::max()) return std::string(); return strings.at(index); } }