mirror of
				https://github.com/OpenMW/openmw.git
				synced 2025-10-31 22:26:41 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			376 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			376 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| #include "niffile.hpp"
 | |
| 
 | |
| #include <components/debug/debuglog.hpp>
 | |
| #include <components/files/hash.hpp>
 | |
| 
 | |
| #include <algorithm>
 | |
| #include <array>
 | |
| #include <limits>
 | |
| #include <map>
 | |
| #include <sstream>
 | |
| #include <stdexcept>
 | |
| 
 | |
| #include "controlled.hpp"
 | |
| #include "controller.hpp"
 | |
| #include "data.hpp"
 | |
| #include "effect.hpp"
 | |
| #include "exception.hpp"
 | |
| #include "extra.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 <typename NodeType, RecordType recordType>
 | |
|     static std::unique_ptr<Record> construct()
 | |
|     {
 | |
|         auto result = std::make_unique<NodeType>();
 | |
|         result->recType = recordType;
 | |
|         return result;
 | |
|     }
 | |
| 
 | |
|     using CreateRecord = std::unique_ptr<Record> (*)();
 | |
| 
 | |
|     /// These are all the record types we know how to read.
 | |
|     static std::map<std::string, CreateRecord> makeFactory()
 | |
|     {
 | |
|         return {
 | |
|             { "NiNode", &construct<NiNode, RC_NiNode> },
 | |
|             { "NiSwitchNode", &construct<NiSwitchNode, RC_NiSwitchNode> },
 | |
|             { "NiLODNode", &construct<NiLODNode, RC_NiLODNode> },
 | |
|             { "NiFltAnimationNode", &construct<NiFltAnimationNode, RC_NiFltAnimationNode> },
 | |
|             { "AvoidNode", &construct<NiNode, RC_AvoidNode> },
 | |
|             { "NiCollisionSwitch", &construct<NiNode, RC_NiCollisionSwitch> },
 | |
|             { "NiBSParticleNode", &construct<NiNode, RC_NiBSParticleNode> },
 | |
|             { "NiBSAnimationNode", &construct<NiNode, RC_NiBSAnimationNode> },
 | |
|             { "NiBillboardNode", &construct<NiBillboardNode, RC_NiBillboardNode> },
 | |
|             { "NiTriShape", &construct<NiTriShape, RC_NiTriShape> },
 | |
|             { "NiTriStrips", &construct<NiTriStrips, RC_NiTriStrips> },
 | |
|             { "NiLines", &construct<NiLines, RC_NiLines> },
 | |
|             { "NiParticles", &construct<NiParticles, RC_NiParticles> },
 | |
|             { "NiRotatingParticles", &construct<NiParticles, RC_NiParticles> },
 | |
|             { "NiAutoNormalParticles", &construct<NiParticles, RC_NiParticles> },
 | |
|             { "NiCamera", &construct<NiCamera, RC_NiCamera> },
 | |
|             { "RootCollisionNode", &construct<NiNode, RC_RootCollisionNode> },
 | |
|             { "NiTexturingProperty", &construct<NiTexturingProperty, RC_NiTexturingProperty> },
 | |
|             { "NiFogProperty", &construct<NiFogProperty, RC_NiFogProperty> },
 | |
|             { "NiMaterialProperty", &construct<NiMaterialProperty, RC_NiMaterialProperty> },
 | |
|             { "NiZBufferProperty", &construct<NiZBufferProperty, RC_NiZBufferProperty> },
 | |
|             { "NiAlphaProperty", &construct<NiAlphaProperty, RC_NiAlphaProperty> },
 | |
|             { "NiVertexColorProperty", &construct<NiVertexColorProperty, RC_NiVertexColorProperty> },
 | |
|             { "NiShadeProperty", &construct<NiShadeProperty, RC_NiShadeProperty> },
 | |
|             { "NiDitherProperty", &construct<NiDitherProperty, RC_NiDitherProperty> },
 | |
|             { "NiWireframeProperty", &construct<NiWireframeProperty, RC_NiWireframeProperty> },
 | |
|             { "NiSpecularProperty", &construct<NiSpecularProperty, RC_NiSpecularProperty> },
 | |
|             { "NiStencilProperty", &construct<NiStencilProperty, RC_NiStencilProperty> },
 | |
|             { "NiVisController", &construct<NiVisController, RC_NiVisController> },
 | |
|             { "NiGeomMorpherController", &construct<NiGeomMorpherController, RC_NiGeomMorpherController> },
 | |
|             { "NiKeyframeController", &construct<NiKeyframeController, RC_NiKeyframeController> },
 | |
|             { "NiAlphaController", &construct<NiAlphaController, RC_NiAlphaController> },
 | |
|             { "NiRollController", &construct<NiRollController, RC_NiRollController> },
 | |
|             { "NiUVController", &construct<NiUVController, RC_NiUVController> },
 | |
|             { "NiPathController", &construct<NiPathController, RC_NiPathController> },
 | |
|             { "NiMaterialColorController", &construct<NiMaterialColorController, RC_NiMaterialColorController> },
 | |
|             { "NiBSPArrayController", &construct<NiBSPArrayController, RC_NiBSPArrayController> },
 | |
|             { "NiParticleSystemController", &construct<NiParticleSystemController, RC_NiParticleSystemController> },
 | |
|             { "NiFlipController", &construct<NiFlipController, RC_NiFlipController> },
 | |
|             { "NiAmbientLight", &construct<NiLight, RC_NiLight> },
 | |
|             { "NiDirectionalLight", &construct<NiLight, RC_NiLight> },
 | |
|             { "NiPointLight", &construct<NiPointLight, RC_NiLight> },
 | |
|             { "NiSpotLight", &construct<NiSpotLight, RC_NiLight> },
 | |
|             { "NiTextureEffect", &construct<NiTextureEffect, RC_NiTextureEffect> },
 | |
|             { "NiExtraData", &construct<NiExtraData, RC_NiExtraData> },
 | |
|             { "NiVertWeightsExtraData", &construct<NiVertWeightsExtraData, RC_NiVertWeightsExtraData> },
 | |
|             { "NiTextKeyExtraData", &construct<NiTextKeyExtraData, RC_NiTextKeyExtraData> },
 | |
|             { "NiStringExtraData", &construct<NiStringExtraData, RC_NiStringExtraData> },
 | |
|             { "NiGravity", &construct<NiGravity, RC_NiGravity> },
 | |
|             { "NiPlanarCollider", &construct<NiPlanarCollider, RC_NiPlanarCollider> },
 | |
|             { "NiSphericalCollider", &construct<NiSphericalCollider, RC_NiSphericalCollider> },
 | |
|             { "NiParticleGrowFade", &construct<NiParticleGrowFade, RC_NiParticleGrowFade> },
 | |
|             { "NiParticleColorModifier", &construct<NiParticleColorModifier, RC_NiParticleColorModifier> },
 | |
|             { "NiParticleRotation", &construct<NiParticleRotation, RC_NiParticleRotation> },
 | |
|             { "NiFloatData", &construct<NiFloatData, RC_NiFloatData> },
 | |
|             { "NiTriShapeData", &construct<NiTriShapeData, RC_NiTriShapeData> },
 | |
|             { "NiTriStripsData", &construct<NiTriStripsData, RC_NiTriStripsData> },
 | |
|             { "NiLinesData", &construct<NiLinesData, RC_NiLinesData> },
 | |
|             { "NiVisData", &construct<NiVisData, RC_NiVisData> },
 | |
|             { "NiColorData", &construct<NiColorData, RC_NiColorData> },
 | |
|             { "NiPixelData", &construct<NiPixelData, RC_NiPixelData> },
 | |
|             { "NiMorphData", &construct<NiMorphData, RC_NiMorphData> },
 | |
|             { "NiKeyframeData", &construct<NiKeyframeData, RC_NiKeyframeData> },
 | |
|             { "NiSkinData", &construct<NiSkinData, RC_NiSkinData> },
 | |
|             { "NiUVData", &construct<NiUVData, RC_NiUVData> },
 | |
|             { "NiPosData", &construct<NiPosData, RC_NiPosData> },
 | |
|             { "NiParticlesData", &construct<NiParticlesData, RC_NiParticlesData> },
 | |
|             { "NiRotatingParticlesData", &construct<NiRotatingParticlesData, RC_NiParticlesData> },
 | |
|             { "NiAutoNormalParticlesData", &construct<NiParticlesData, RC_NiParticlesData> },
 | |
|             { "NiSequenceStreamHelper", &construct<NiSequenceStreamHelper, RC_NiSequenceStreamHelper> },
 | |
|             { "NiSourceTexture", &construct<NiSourceTexture, RC_NiSourceTexture> },
 | |
|             { "NiSkinInstance", &construct<NiSkinInstance, RC_NiSkinInstance> },
 | |
|             { "NiLookAtController", &construct<NiLookAtController, RC_NiLookAtController> },
 | |
|             { "NiPalette", &construct<NiPalette, RC_NiPalette> },
 | |
|             { "NiIntegerExtraData", &construct<NiIntegerExtraData, RC_NiIntegerExtraData> },
 | |
|             { "NiIntegersExtraData", &construct<NiIntegersExtraData, RC_NiIntegersExtraData> },
 | |
|             { "NiBinaryExtraData", &construct<NiBinaryExtraData, RC_NiBinaryExtraData> },
 | |
|             { "NiBooleanExtraData", &construct<NiBooleanExtraData, RC_NiBooleanExtraData> },
 | |
|             { "NiVectorExtraData", &construct<NiVectorExtraData, RC_NiVectorExtraData> },
 | |
|             { "NiColorExtraData", &construct<NiVectorExtraData, RC_NiColorExtraData> },
 | |
|             { "NiFloatExtraData", &construct<NiFloatExtraData, RC_NiFloatExtraData> },
 | |
|             { "NiFloatsExtraData", &construct<NiFloatsExtraData, RC_NiFloatsExtraData> },
 | |
|             { "NiStringPalette", &construct<NiStringPalette, RC_NiStringPalette> },
 | |
|             { "NiBoolData", &construct<NiBoolData, RC_NiBoolData> },
 | |
|             { "NiSkinPartition", &construct<NiSkinPartition, RC_NiSkinPartition> },
 | |
|             { "BSXFlags", &construct<NiIntegerExtraData, RC_BSXFlags> },
 | |
|             { "BSBound", &construct<BSBound, RC_BSBound> },
 | |
|             { "NiTransformData", &construct<NiKeyframeData, RC_NiKeyframeData> },
 | |
|             { "BSFadeNode", &construct<NiNode, RC_NiNode> },
 | |
|             { "bhkBlendController", &construct<bhkBlendController, RC_bhkBlendController> },
 | |
|             { "NiFloatInterpolator", &construct<NiFloatInterpolator, RC_NiFloatInterpolator> },
 | |
|             { "NiBoolInterpolator", &construct<NiBoolInterpolator, RC_NiBoolInterpolator> },
 | |
|             { "NiPoint3Interpolator", &construct<NiPoint3Interpolator, RC_NiPoint3Interpolator> },
 | |
|             { "NiTransformController", &construct<NiKeyframeController, RC_NiKeyframeController> },
 | |
|             { "NiMultiTargetTransformController",
 | |
|                 &construct<NiMultiTargetTransformController, RC_NiMultiTargetTransformController> },
 | |
|             { "NiTransformInterpolator", &construct<NiTransformInterpolator, RC_NiTransformInterpolator> },
 | |
|             { "NiColorInterpolator", &construct<NiColorInterpolator, RC_NiColorInterpolator> },
 | |
|             { "BSShaderTextureSet", &construct<BSShaderTextureSet, RC_BSShaderTextureSet> },
 | |
|             { "BSLODTriShape", &construct<BSLODTriShape, RC_BSLODTriShape> },
 | |
|             { "BSShaderProperty", &construct<BSShaderProperty, RC_BSShaderProperty> },
 | |
|             { "BSShaderPPLightingProperty", &construct<BSShaderPPLightingProperty, RC_BSShaderPPLightingProperty> },
 | |
|             { "BSShaderNoLightingProperty", &construct<BSShaderNoLightingProperty, RC_BSShaderNoLightingProperty> },
 | |
|             { "BSFurnitureMarker", &construct<BSFurnitureMarker, RC_BSFurnitureMarker> },
 | |
|             { "NiCollisionObject", &construct<NiCollisionObject, RC_NiCollisionObject> },
 | |
|             { "bhkCollisionObject", &construct<bhkCollisionObject, RC_bhkCollisionObject> },
 | |
|             { "BSDismemberSkinInstance", &construct<BSDismemberSkinInstance, RC_BSDismemberSkinInstance> },
 | |
|             { "NiControllerManager", &construct<NiControllerManager, RC_NiControllerManager> },
 | |
|             { "bhkMoppBvTreeShape", &construct<bhkMoppBvTreeShape, RC_bhkMoppBvTreeShape> },
 | |
|             { "bhkNiTriStripsShape", &construct<bhkNiTriStripsShape, RC_bhkNiTriStripsShape> },
 | |
|             { "bhkPackedNiTriStripsShape", &construct<bhkPackedNiTriStripsShape, RC_bhkPackedNiTriStripsShape> },
 | |
|             { "hkPackedNiTriStripsData", &construct<hkPackedNiTriStripsData, RC_hkPackedNiTriStripsData> },
 | |
|             { "bhkConvexVerticesShape", &construct<bhkConvexVerticesShape, RC_bhkConvexVerticesShape> },
 | |
|             { "bhkBoxShape", &construct<bhkBoxShape, RC_bhkBoxShape> },
 | |
|             { "bhkCapsuleShape", &construct<bhkCapsuleShape, RC_bhkCapsuleShape> },
 | |
|             { "bhkSphereShape", &construct<bhkSphereShape, RC_bhkSphereShape> },
 | |
|             { "bhkListShape", &construct<bhkListShape, RC_bhkListShape> },
 | |
|             { "bhkRigidBody", &construct<bhkRigidBody, RC_bhkRigidBody> },
 | |
|             { "bhkRigidBodyT", &construct<bhkRigidBody, RC_bhkRigidBodyT> },
 | |
|             { "BSLightingShaderProperty", &construct<BSLightingShaderProperty, RC_BSLightingShaderProperty> },
 | |
|             { "NiSortAdjustNode", &construct<NiSortAdjustNode, RC_NiSortAdjustNode> },
 | |
|             { "NiClusterAccumulator", &construct<NiClusterAccumulator, RC_NiClusterAccumulator> },
 | |
|             { "NiAlphaAccumulator", &construct<NiAlphaAccumulator, RC_NiAlphaAccumulator> },
 | |
|         };
 | |
|     }
 | |
| 
 | |
|     /// Make the factory map used for parsing the file
 | |
|     static const std::map<std::string, CreateRecord> 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<std::uint64_t, 2> fileHash = Files::getHash(filename, *stream);
 | |
|         hash.append(reinterpret_cast<const char*>(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<std::string, 2> 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)
 | |
|             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<uint32_t, 2> supportedVers = {
 | |
|             NIFStream::generateVersion(4, 0, 0, 0),
 | |
|             NIFFile::VER_MW,
 | |
|         };
 | |
|         const bool supportedVersion = std::find(supportedVers.begin(), supportedVers.end(), ver) != supportedVers.end();
 | |
|         if (!supportedVersion)
 | |
|         {
 | |
|             if (sLoadUnsupportedFiles)
 | |
|                 Log(Debug::Warning) << " NIFFile Warning: Unsupported NIF version: " << printVersion(ver)
 | |
|                                     << ". Proceed with caution! File: " << filename;
 | |
|             else
 | |
|                 throw Nif::Exception("Unsupported NIF version: " + printVersion(ver), 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<std::string> recTypes;
 | |
|         std::vector<unsigned short> 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<unsigned int> 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<unsigned int> 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<Record> 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.compare(0, 3, "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)
 | |
|                 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<std::size_t>(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;
 | |
| 
 | |
|     void Reader::setLoadUnsupportedFiles(bool load)
 | |
|     {
 | |
|         sLoadUnsupportedFiles = load;
 | |
|     }
 | |
| 
 | |
|     std::string Reader::getString(std::uint32_t index) const
 | |
|     {
 | |
|         if (index == std::numeric_limits<std::uint32_t>::max())
 | |
|             return std::string();
 | |
|         return strings.at(index);
 | |
|     }
 | |
| 
 | |
| }
 |