#include "niffile.hpp" #include #include #include #include #include #include #include #include #include #include "controlled.hpp" #include "controller.hpp" #include "data.hpp" #include "effect.hpp" #include "extra.hpp" #include "physics.hpp" #include "property.hpp" namespace Nif { /// Open a NIF stream. The name is used for error messages. NIFFile::NIFFile(Files::IStreamPtr&& stream, const std::filesystem::path &name) : filename(name) { parse(std::move(stream)); } 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 }, {"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 }, {"bhkBlendController" , &construct }, {"NiFloatInterpolator" , &construct }, {"NiBoolInterpolator" , &construct }, {"NiPoint3Interpolator" , &construct }, {"NiTransformController" , &construct }, {"NiMultiTargetTransformController" , &construct }, {"NiTransformInterpolator" , &construct }, {"NiColorInterpolator" , &construct }, {"BSShaderTextureSet" , &construct }, {"BSLODTriShape" , &construct }, {"BSShaderProperty" , &construct }, {"BSShaderPPLightingProperty" , &construct }, {"BSShaderNoLightingProperty" , &construct }, {"BSFurnitureMarker" , &construct }, {"NiCollisionObject" , &construct }, {"bhkCollisionObject" , &construct }, {"BSDismemberSkinInstance" , &construct }, {"NiControllerManager" , &construct }, {"bhkMoppBvTreeShape" , &construct }, {"bhkNiTriStripsShape" , &construct }, {"bhkPackedNiTriStripsShape" , &construct }, {"hkPackedNiTriStripsData" , &construct }, {"bhkConvexVerticesShape" , &construct }, {"bhkBoxShape" , &construct }, {"bhkCapsuleShape" , &construct }, {"bhkSphereShape" , &construct }, {"bhkListShape" , &construct }, {"bhkRigidBody" , &construct }, {"bhkRigidBodyT" , &construct }, {"BSLightingShaderProperty" , &construct }, {"NiSortAdjustNode" , &construct }, {"NiClusterAccumulator" , &construct }, {"NiAlphaAccumulator" , &construct }, }; } ///Make the factory map used for parsing the file static const std::map factories = makeFactory(); std::string NIFFile::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 NIFFile::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.compare(0, verString.size(), verString) == 0; }); if (!supportedHeader) fail("Invalid NIF header: " + head); // 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), VER_MW, }; const bool supportedVersion = std::find(supportedVers.begin(), supportedVers.end(), ver) != supportedVers.end(); if (!supportedVersion) { if (sLoadUnsupportedFiles) warn("Unsupported NIF version: " + printVersion(ver) + ". Proceed with caution!"); else fail("Unsupported NIF version: " + printVersion(ver)); } // NIF data endianness if (ver >= NIFStream::generateVersion(20,0,0,4)) { unsigned char endianness = nif.getChar(); if (endianness == 0) fail("Big endian NIF files are unsupported"); } // 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 == VER_OB_OLD || (userVer >= 3 && ((ver == VER_OB || ver == 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 > BETHVER_FO4) nif.getUInt(); // Unknown nif.getExportString(); // Process script nif.getExportString(); // Export script if (bethVer == 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."; fail(error.str()); } // Record separator. Some Havok records in Oblivion do not have it. if (hasRecordSeparators && rec.compare(0, 3, "bhk")) { if (nif.getInt()) { std::stringstream warning; warning << "Record number " << i << " out of " << recNum << " is preceded by a non-zero separator."; warn(warning.str()); } } const auto entry = factories.find(rec); if (entry == factories.end()) fail("Unknown record type " + rec); r = entry->second(); if (!supportedVersion) 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; warn("Root " + std::to_string(i + 1) + " does not point to a record: index " + std::to_string(idx)); } } // Once parsing is done, do post-processing. for (const auto& record : records) record->post(this); } void NIFFile::setUseSkinning(bool skinning) { mUseSkinning = skinning; } bool NIFFile::getUseSkinning() const { return mUseSkinning; } std::atomic_bool NIFFile::sLoadUnsupportedFiles = false; void NIFFile::setLoadUnsupportedFiles(bool load) { sLoadUnsupportedFiles = load; } void NIFFile::warn(const std::string &msg) const { Log(Debug::Warning) << " NIFFile Warning: " << msg << "\nFile: " << filename; } [[noreturn]] void NIFFile::fail(const std::string &msg) const { throw std::runtime_error(" NIFFile Error: " + msg + "\nFile: " + Files::pathToUnicodeString(filename)); } std::string NIFFile::getString(uint32_t index) const { if (index == std::numeric_limits::max()) return std::string(); return strings.at(index); } }