Index: source/lib/file/vfs/vfs.h =================================================================== --- source/lib/file/vfs/vfs.h +++ source/lib/file/vfs/vfs.h @@ -169,6 +169,11 @@ **/ virtual std::wstring TextRepresentation() const = 0; + /** + * Return a hash of the files in the following paths. + */ + virtual std::string HashRepresentation(const std::vector& paths) const = 0; + /** * retrieve the real (POSIX) pathname underlying a VFS file. * Index: source/lib/file/vfs/vfs.cpp =================================================================== --- source/lib/file/vfs/vfs.cpp +++ source/lib/file/vfs/vfs.cpp @@ -32,6 +32,7 @@ #include "lib/file/vfs/vfs_tree.h" #include "lib/file/vfs/vfs_lookup.h" #include "lib/file/vfs/vfs_populate.h" +#include "ps/Util.h" #include #include @@ -203,6 +204,19 @@ return textRepresentation; } + virtual std::string HashRepresentation(const std::vector& paths) const + { + std::lock_guard lock(vfs_mutex); + unsigned long crc; + for (const std::string& path : paths) + { + VfsDirectory* directory; + WARN_IF_ERR(vfs_Lookup(path, &m_rootDirectory, directory, NULL)); + RecursiveHash(crc, *directory); + } + return Hexify(reinterpret_cast(&crc), sizeof(decltype(crc))); + } + virtual Status GetRealPath(const VfsPath& pathname, OsPath& realPathname) { std::lock_guard lock(vfs_mutex); Index: source/lib/file/vfs/vfs_tree.h =================================================================== --- source/lib/file/vfs/vfs_tree.h +++ source/lib/file/vfs/vfs_tree.h @@ -191,4 +191,9 @@ **/ void DirectoryDescriptionR(std::wstring& descriptions, const VfsDirectory& directory, size_t indentLevel); +/** + * Recursively hash file contents. + **/ +void RecursiveHash(unsigned long& hash, const VfsDirectory& directory); + #endif // #ifndef INCLUDED_VFS_TREE Index: source/lib/file/vfs/vfs_tree.cpp =================================================================== --- source/lib/file/vfs/vfs_tree.cpp +++ source/lib/file/vfs/vfs_tree.cpp @@ -33,6 +33,8 @@ #include "lib/file/common/file_stats.h" #include "lib/sysdep/cpu.h" +#include "lib/external_libraries/zlib.h" + //----------------------------------------------------------------------------- @@ -250,3 +252,17 @@ DirectoryDescriptionR(descriptions, subdirectory, indentLevel+1); } } + +void RecursiveHash(unsigned long& hash, const VfsDirectory& directory) +{ + VfsDirectory::VfsFiles files = directory.Files(); + for(const std::pair& file : files) + { + hash = crc32(hash, (u8*)file.first.string().c_str(), file.first.string().size()*sizeof(decltype(*file.first.string().data()))); + time_t timestamp = file.second.MTime(); + hash = crc32(hash, (u8*)×tamp, sizeof(time_t)); + } + + for (const auto& subdirectory : directory.Subdirectories()) + RecursiveHash(hash, subdirectory.second); +} Index: source/ps/scripting/JSInterface_VFS.h =================================================================== --- source/ps/scripting/JSInterface_VFS.h +++ source/ps/scripting/JSInterface_VFS.h @@ -50,6 +50,9 @@ // Tests whether the current script context is allowed to read from the given directory bool PathRestrictionMet(ScriptInterface::CmptPrivate* pCmptPrivate, const std::vector& validPaths, const CStrW& filePath); + // Return a hash of the files in paths. + std::string Hash(ScriptInterface::CmptPrivate* pCmptPrivate, const std::vector& paths); + void RegisterScriptFunctions_GUI(const ScriptInterface& scriptInterface); void RegisterScriptFunctions_Simulation(const ScriptInterface& scriptInterface); void RegisterScriptFunctions_Maps(const ScriptInterface& scriptInterface); Index: source/ps/scripting/JSInterface_VFS.cpp =================================================================== --- source/ps/scripting/JSInterface_VFS.cpp +++ source/ps/scripting/JSInterface_VFS.cpp @@ -230,6 +230,12 @@ return false; } +std::string JSI_VFS::Hash(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), const std::vector& paths) +{ + PROFILE2("VFSHash"); + return g_VFS->HashRepresentation(paths); +} + #define VFS_ScriptFunctions(context)\ JS::Value Script_ReadJSONFile_##context(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& filePath)\ {\ @@ -259,6 +265,7 @@ scriptInterface.RegisterFunction("ReadFileLines"); scriptInterface.RegisterFunction("ReadJSONFile"); scriptInterface.RegisterFunction("WriteJSONFile"); + scriptInterface.RegisterFunction, &JSI_VFS::Hash>("Hash"); } void JSI_VFS::RegisterScriptFunctions_Simulation(const ScriptInterface& scriptInterface)