diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp index b37a8cd6c0..92aece9075 100644 --- a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -335,6 +335,7 @@ namespace MOCK_METHOD(void, setUseSkinning, (bool), (override)); MOCK_METHOD(bool, getUseSkinning, (), (const, override)); MOCK_METHOD(std::string, getFilename, (), (const, override)); + MOCK_METHOD(std::uint64_t, getHash, (), (const, override)); MOCK_METHOD(unsigned int, getVersion, (), (const, override)); MOCK_METHOD(unsigned int, getUserVersion, (), (const, override)); MOCK_METHOD(unsigned int, getBethVersion, (), (const, override)); @@ -381,6 +382,7 @@ namespace ), btVector3(4, 8, 12) }; + const std::uint64_t mHash = 42; TestBulletNifLoader() { @@ -411,6 +413,8 @@ namespace mNiTriStripsData.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0), osg::Vec3f(0, 1, 0)}; mNiTriStripsData.strips = {{0, 1, 2, 3}}; mNiTriStrips.data = Nif::NiGeometryDataPtr(&mNiTriStripsData); + + EXPECT_CALL(mNifFile, getHash()).WillOnce(Return(mHash)); } }; @@ -423,6 +427,8 @@ namespace Resource::BulletShape expected; EXPECT_EQ(*result, expected); + EXPECT_EQ(result->mFileName, "test.nif"); + EXPECT_EQ(result->mFileHash, mHash); } TEST_F(TestBulletNifLoader, should_ignore_nullptr_root) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 5c0ead4ad0..71a96716ec 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -93,7 +93,7 @@ add_component_dir (esmterrain add_component_dir (misc constants utf8stream stringops resourcehelpers rng messageformatparser weakcache thread - compression + compression osguservalues ) add_component_dir (debug @@ -106,7 +106,7 @@ IF(NOT WIN32 AND NOT APPLE) ENDIF() add_component_dir (files linuxpath androidpath windowspath macospath fixedpath multidircollection collections configurationmanager escape - lowlevelfile constrainedfilestream memorystream + lowlevelfile constrainedfilestream memorystream hash ) add_component_dir (compiler diff --git a/components/files/hash.cpp b/components/files/hash.cpp new file mode 100644 index 0000000000..91af1e9283 --- /dev/null +++ b/components/files/hash.cpp @@ -0,0 +1,36 @@ +#include "hash.hpp" + +#include + +#include +#include +#include +#include + +namespace Files +{ + std::uint64_t getHash(const std::string& fileName, std::istream& stream) + { + std::uint64_t hash = std::hash {}(fileName); + try + { + const auto start = stream.tellg(); + const auto exceptions = stream.exceptions(); + stream.exceptions(std::ios_base::badbit); + while (stream) + { + std::uint64_t value = 0; + stream.read(reinterpret_cast(&value), sizeof(value)); + Misc::hashCombine(hash, value); + } + stream.exceptions(exceptions); + stream.clear(); + stream.seekg(start); + } + catch (const std::exception& e) + { + throw std::runtime_error("Error while reading \"" + fileName + "\" to get hash: " + std::string(e.what())); + } + return hash; + } +} diff --git a/components/files/hash.hpp b/components/files/hash.hpp new file mode 100644 index 0000000000..46784ee9be --- /dev/null +++ b/components/files/hash.hpp @@ -0,0 +1,13 @@ +#ifndef COMPONENTS_FILES_HASH_H +#define COMPONENTS_FILES_HASH_H + +#include +#include +#include + +namespace Files +{ + std::uint64_t getHash(const std::string& fileName, std::istream& stream); +} + +#endif diff --git a/components/misc/osguservalues.cpp b/components/misc/osguservalues.cpp new file mode 100644 index 0000000000..3bdd0d1848 --- /dev/null +++ b/components/misc/osguservalues.cpp @@ -0,0 +1,6 @@ +#include "osguservalues.hpp" + +namespace Misc +{ + const std::string OsgUserValues::sFileHash = "fileHash"; +} diff --git a/components/misc/osguservalues.hpp b/components/misc/osguservalues.hpp new file mode 100644 index 0000000000..022e81764f --- /dev/null +++ b/components/misc/osguservalues.hpp @@ -0,0 +1,14 @@ +#ifndef OPENMW_COMPONENTS_MISC_OSGUSERVALUES_H +#define OPENMW_COMPONENTS_MISC_OSGUSERVALUES_H + +#include + +namespace Misc +{ + struct OsgUserValues + { + static const std::string sFileHash; + }; +} + +#endif diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index 24b9bbceed..dbe455fbd4 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -1,6 +1,8 @@ #include "niffile.hpp" #include "effect.hpp" +#include + #include #include #include @@ -161,6 +163,8 @@ std::string NIFFile::printVersion(unsigned int version) void NIFFile::parse(Files::IStreamPtr stream) { + hash = Files::getHash(filename, *stream); + NIFStream nif (this, stream); // Check the header string diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 1ed7cbd5d8..eb851a74ff 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -34,6 +34,8 @@ struct File virtual std::string getFilename() const = 0; + virtual std::uint64_t getHash() const = 0; + virtual unsigned int getVersion() const = 0; virtual unsigned int getUserVersion() const = 0; @@ -50,6 +52,7 @@ class NIFFile final : public File /// File name, used for error messages and opening the file std::string filename; + std::uint64_t hash = 0; /// Record list std::vector records; @@ -141,6 +144,8 @@ public: /// Get the name of the file std::string getFilename() const override { return filename; } + std::uint64_t getHash() const override { return hash; } + /// Get the version of the NIF format used unsigned int getVersion() const override { return ver; } diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 6678d8ff74..4be07525a6 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -166,6 +166,8 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) mStaticMesh.reset(); mAvoidStaticMesh.reset(); + mShape->mFileHash = nif.getHash(); + const size_t numRoots = nif.numRoots(); std::vector roots; for (size_t i = 0; i < numRoots; ++i) @@ -178,6 +180,7 @@ osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) roots.emplace_back(node); } const std::string filename = nif.getFilename(); + mShape->mFileName = filename; if (roots.empty()) { warn("Found no root nodes in NIF file " + filename); diff --git a/components/nifosg/nifloader.cpp b/components/nifosg/nifloader.cpp index e46e2d934b..dad5feb041 100644 --- a/components/nifosg/nifloader.cpp +++ b/components/nifosg/nifloader.cpp @@ -17,6 +17,7 @@ #include #include #include +#include // particle #include @@ -325,6 +326,10 @@ namespace NifOsg if (!textkeys->mTextKeys.empty()) created->getOrCreateUserDataContainer()->addUserObject(textkeys); + const std::uint64_t nifHash = nif->getHash(); + created->setUserValue(Misc::OsgUserValues::sFileHash, + std::string(reinterpret_cast(&nifHash), sizeof(nifHash))); + return created; } diff --git a/components/resource/bulletshape.cpp b/components/resource/bulletshape.cpp index 52d639d272..74515691c6 100644 --- a/components/resource/bulletshape.cpp +++ b/components/resource/bulletshape.cpp @@ -74,6 +74,8 @@ BulletShape::BulletShape(const BulletShape ©, const osg::CopyOp ©op) , mAvoidCollisionShape(duplicateCollisionShape(copy.mAvoidCollisionShape.get())) , mCollisionBox(copy.mCollisionBox) , mAnimatedShapes(copy.mAnimatedShapes) + , mFileName(copy.mFileName) + , mFileHash(copy.mFileHash) { } diff --git a/components/resource/bulletshape.hpp b/components/resource/bulletshape.hpp index 7188165045..6dfa37aeda 100644 --- a/components/resource/bulletshape.hpp +++ b/components/resource/bulletshape.hpp @@ -12,6 +12,11 @@ class btCollisionShape; +namespace NifBullet +{ + class BulletNifLoader; +} + namespace Resource { struct DeleteCollisionShape @@ -47,6 +52,9 @@ namespace Resource // we store the node's record index mapped to the child index of the shape in the btCompoundShape. std::map mAnimatedShapes; + std::string mFileName; + std::uint64_t mFileHash = 0; + void setLocalScaling(const btVector3& scale); bool isAnimated() const { return !mAnimatedShapes.empty(); } @@ -60,6 +68,8 @@ namespace Resource public: BulletShapeInstance(osg::ref_ptr source); + const osg::ref_ptr& getSource() const { return mSource; } + private: osg::ref_ptr mSource; }; diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index 39ceb4fe7e..3803fbf669 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -1,5 +1,7 @@ #include "bulletshapemanager.hpp" +#include + #include #include #include @@ -10,6 +12,7 @@ #include #include #include +#include #include @@ -162,6 +165,15 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & if (!shape) return osg::ref_ptr(); } + + if (shape != nullptr) + { + shape->mFileName = normalized; + std::string fileHash; + constNode->getUserValue(Misc::OsgUserValues::sFileHash, fileHash); + if (!fileHash.empty()) + std::memcpy(&shape->mFileHash, fileHash.data(), std::min(fileHash.size(), sizeof(shape->mFileHash))); + } } mCache->addEntryToObjectCache(normalized, shape); diff --git a/components/resource/scenemanager.cpp b/components/resource/scenemanager.cpp index f6c041a879..23581a09b8 100644 --- a/components/resource/scenemanager.cpp +++ b/components/resource/scenemanager.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include @@ -34,6 +35,8 @@ #include #include +#include + #include "imagemanager.hpp" #include "niffilemanager.hpp" #include "objectcache.hpp" @@ -502,7 +505,10 @@ namespace Resource options->setReadFileCallback(new ImageReadCallback(imageManager)); if (ext == "dae") options->setOptionString("daeUseSequencedTextureUnits"); - osgDB::ReaderWriter::ReadResult result = reader->readNode(*vfs->get(normalizedFilename), options); + Files::IStreamPtr stream = vfs->get(normalizedFilename); + const std::uint64_t fileHash = Files::getHash(normalizedFilename, *stream); + + osgDB::ReaderWriter::ReadResult result = reader->readNode(*stream, options); if (!result.success()) { std::stringstream errormsg; @@ -529,8 +535,12 @@ namespace Resource result.getNode()->getOrCreateStateSet()->addUniform(new osg::Uniform("useFalloff", false)); } + auto node = result.getNode(); - return result.getNode(); + node->setUserValue(Misc::OsgUserValues::sFileHash, + std::string(reinterpret_cast(&fileHash), sizeof(fileHash))); + + return node; } }