From 82914be8b2d86f69f9947c1107f5af1ac22fda8e Mon Sep 17 00:00:00 2001 From: Evil Eye Date: Mon, 8 Dec 2025 17:44:15 +0100 Subject: [PATCH] Treat TES4+ BSA paths as VFS paths --- components/bsa/ba2dx10file.cpp | 29 +++---------- components/bsa/ba2dx10file.hpp | 1 - components/bsa/ba2file.cpp | 13 +++++- components/bsa/ba2file.hpp | 6 ++- components/bsa/ba2gnrlfile.cpp | 32 +++----------- components/bsa/ba2gnrlfile.hpp | 1 - components/bsa/compressedbsafile.cpp | 64 ++++++++++++---------------- components/bsa/compressedbsafile.hpp | 4 +- 8 files changed, 54 insertions(+), 96 deletions(-) diff --git a/components/bsa/ba2dx10file.cpp b/components/bsa/ba2dx10file.cpp index c16a3bacd2..c834ac7692 100644 --- a/components/bsa/ba2dx10file.cpp +++ b/components/bsa/ba2dx10file.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -11,9 +10,8 @@ #include #include -#include #include -#include +#include #include "ba2file.hpp" #include "memorystream.hpp" @@ -148,20 +146,10 @@ namespace Bsa } } -#ifdef _WIN32 - const auto& path = str; -#else - // Force-convert the path into something UNIX can handle first - // to make sure std::filesystem::path doesn't think the entire path is the filename on Linux - // and subsequently purge it to determine the file folder. - std::string path(str); - std::replace(path.begin(), path.end(), '\\', '/'); -#endif + const VFS::Path::Normalized path(str); - const auto p = std::filesystem::path{ path }; // Purposefully damage Unicode strings. - const auto fileName = Misc::StringUtils::lowerCase(p.stem().string()); - const auto ext = Misc::StringUtils::lowerCase(p.extension().string()); // Purposefully damage Unicode strings. - const auto folder = Misc::StringUtils::lowerCase(p.parent_path().string()); + const std::string_view fileName = path.stem(); + const std::string_view folder = path.parent().value(); uint32_t folderHash = generateHash(folder); auto it = mFolders.find(folderHash); @@ -169,7 +157,7 @@ namespace Bsa return std::nullopt; // folder not found uint32_t fileHash = generateHash(fileName); - uint32_t extHash = generateExtensionHash(ext); + uint32_t extHash = generateExtensionHash(path.filename()); auto iter = it->second.find({ fileHash, extHash }); if (iter == it->second.end()) return std::nullopt; // file not found @@ -229,13 +217,6 @@ namespace Bsa fail("Add file is not implemented for compressed BSA: " + filename); } - Files::IStreamPtr BA2DX10File::getFile(const char* file) - { - if (auto fileRec = getFileRecord(file); fileRec) - return getFile(*fileRec); - fail("File not found: " + std::string(file)); - } - constexpr const uint32_t DDSD_CAPS = 0x00000001; constexpr const uint32_t DDSD_HEIGHT = 0x00000002; constexpr const uint32_t DDSD_WIDTH = 0x00000004; diff --git a/components/bsa/ba2dx10file.hpp b/components/bsa/ba2dx10file.hpp index aed727dc1a..f0499f7b67 100644 --- a/components/bsa/ba2dx10file.hpp +++ b/components/bsa/ba2dx10file.hpp @@ -59,7 +59,6 @@ namespace Bsa /// Read header information from the input source void readHeader(std::istream& stream) override; - Files::IStreamPtr getFile(const char* filePath); Files::IStreamPtr getFile(const FileStruct* fileStruct); void addFile(const std::string& filename, std::istream& file); }; diff --git a/components/bsa/ba2file.cpp b/components/bsa/ba2file.cpp index 17cfb03866..55d24ef2d7 100644 --- a/components/bsa/ba2file.cpp +++ b/components/bsa/ba2file.cpp @@ -1,5 +1,7 @@ #include "ba2file.hpp" +#include + namespace Bsa { constexpr const uint32_t crc32table[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, @@ -32,7 +34,7 @@ namespace Bsa 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; - uint32_t generateHash(const std::string& name) + uint32_t generateHash(std::string_view name) { uint32_t result = 0; for (auto c : name) @@ -46,8 +48,15 @@ namespace Bsa return result; } - uint32_t generateExtensionHash(std::string_view extension) + uint32_t generateExtensionHash(VFS::Path::NormalizedView file) { + std::string_view extension; + if (const std::size_t pos = Misc::findExtension(file.value()); pos != std::string_view::npos) + { + // ext including . + extension = file.value(); + extension.remove_prefix(pos); + } uint32_t result = 0; for (size_t i = 0; i < 4 && i < extension.size() - 1; i++) result |= static_cast(extension[i + 1]) << (8 * i); diff --git a/components/bsa/ba2file.hpp b/components/bsa/ba2file.hpp index 0d51be4c0e..cfb2cd11bf 100644 --- a/components/bsa/ba2file.hpp +++ b/components/bsa/ba2file.hpp @@ -4,10 +4,12 @@ #include #include +#include + namespace Bsa { - uint32_t generateHash(const std::string& name); - uint32_t generateExtensionHash(std::string_view extension); + uint32_t generateHash(std::string_view name); + uint32_t generateExtensionHash(VFS::Path::NormalizedView file); enum class BA2Version : std::uint32_t { diff --git a/components/bsa/ba2gnrlfile.cpp b/components/bsa/ba2gnrlfile.cpp index 30e9b1eb0a..47bce25d81 100644 --- a/components/bsa/ba2gnrlfile.cpp +++ b/components/bsa/ba2gnrlfile.cpp @@ -2,7 +2,6 @@ #include #include -#include #include #include @@ -10,9 +9,8 @@ #include #include -#include #include -#include +#include #include "ba2file.hpp" #include "memorystream.hpp" @@ -139,20 +137,10 @@ namespace Bsa } } -#ifdef _WIN32 - const auto& path = str; -#else - // Force-convert the path into something UNIX can handle first - // to make sure std::filesystem::path doesn't think the entire path is the filename on Linux - // and subsequently purge it to determine the file folder. - std::string path(str); - std::replace(path.begin(), path.end(), '\\', '/'); -#endif + const VFS::Path::Normalized path(str); - const auto p = std::filesystem::path{ path }; // Purposefully damage Unicode strings. - const auto fileName = Misc::StringUtils::lowerCase(p.stem().string()); - const auto ext = Misc::StringUtils::lowerCase(p.extension().string()); // Purposefully damage Unicode strings. - const auto folder = Misc::StringUtils::lowerCase(p.parent_path().string()); + const std::string_view fileName = path.stem(); + const std::string_view folder = path.parent().value(); uint32_t folderHash = generateHash(folder); auto it = mFolders.find(folderHash); @@ -160,7 +148,7 @@ namespace Bsa return FileRecord(); // folder not found, return default which has offset of sInvalidOffset uint32_t fileHash = generateHash(fileName); - uint32_t extHash = generateExtensionHash(ext); + uint32_t extHash = generateExtensionHash(path.filename()); auto iter = it->second.find({ fileHash, extHash }); if (iter == it->second.end()) return FileRecord(); // file not found, return default which has offset of sInvalidOffset @@ -183,16 +171,6 @@ namespace Bsa fail("Add file is not implemented for compressed BSA: " + filename); } - Files::IStreamPtr BA2GNRLFile::getFile(const char* file) - { - FileRecord fileRec = getFileRecord(file); - if (!fileRec.isValid()) - { - fail("File not found: " + std::string(file)); - } - return getFile(fileRec); - } - Files::IStreamPtr BA2GNRLFile::getFile(const FileRecord& fileRecord) { const uint32_t inputSize = fileRecord.packedSize ? fileRecord.packedSize : fileRecord.size; diff --git a/components/bsa/ba2gnrlfile.hpp b/components/bsa/ba2gnrlfile.hpp index 371fe3d072..711682033b 100644 --- a/components/bsa/ba2gnrlfile.hpp +++ b/components/bsa/ba2gnrlfile.hpp @@ -47,7 +47,6 @@ namespace Bsa /// Read header information from the input source void readHeader(std::istream& input) override; - Files::IStreamPtr getFile(const char* filePath); Files::IStreamPtr getFile(const FileStruct* fileStruct); void addFile(const std::string& filename, std::istream& file); }; diff --git a/components/bsa/compressedbsafile.cpp b/components/bsa/compressedbsafile.cpp index 8ad7221105..2cfa642b00 100644 --- a/components/bsa/compressedbsafile.cpp +++ b/components/bsa/compressedbsafile.cpp @@ -27,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -38,7 +37,8 @@ #include #include #include -#include +#include +#include #include "memorystream.hpp" @@ -218,26 +218,26 @@ namespace Bsa } } -#ifdef _WIN32 - const auto& path = str; -#else - // Force-convert the path into something UNIX can handle first - // to make sure std::filesystem::path doesn't think the entire path is the filename on Linux - // and subsequently purge it to determine the file folder. - std::string path(str); - std::replace(path.begin(), path.end(), '\\', '/'); -#endif + const VFS::Path::Normalized path(str); - const auto p = std::filesystem::path{ path }; // Purposefully damage Unicode strings. - const auto stem = p.stem(); - const auto ext = p.extension().string(); // Purposefully damage Unicode strings. + const std::string_view stem = path.stem(); + const std::string_view folder = path.parent().value(); - std::uint64_t folderHash = generateHash(p.parent_path(), {}); + std::uint64_t folderHash = generateHash(folder, {}); auto it = mFolders.find(folderHash); if (it == mFolders.end()) return FileRecord(); + const std::string_view file = path.filename().value(); + std::string_view ext; + if (const std::size_t pos = Misc::findExtension(file); pos != std::string_view::npos) + { + // ext including . + ext = file; + ext.remove_prefix(pos); + } + std::uint64_t fileHash = generateHash(stem, ext); auto iter = it->second.mFiles.find(fileHash); if (iter == it->second.mFiles.end()) @@ -262,16 +262,6 @@ namespace Bsa fail("Add file is not implemented for compressed BSA: " + filename); } - Files::IStreamPtr CompressedBSAFile::getFile(const char* file) - { - FileRecord fileRec = getFileRecord(file); - if (fileRec.mOffset == std::numeric_limits::max()) - { - fail("File not found: " + std::string(file)); - } - return getFile(fileRec); - } - Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord) { size_t size = fileRecord.mSize & (~FileSizeFlag_Compression); @@ -337,29 +327,31 @@ namespace Bsa return std::make_unique>(std::move(memoryStreamPtr)); } - std::uint64_t CompressedBSAFile::generateHash(const std::filesystem::path& stem, std::string extension) + std::uint64_t CompressedBSAFile::generateHash(std::string_view str, std::string_view extension) { - auto str = stem.u8string(); - size_t len = str.length(); - if (len == 0) + if (str.empty()) return 0; - std::replace(str.begin(), str.end(), '/', '\\'); - Misc::StringUtils::lowerCaseInPlace(str); - uint64_t result = str[len - 1]; + const auto at = [&](std::size_t i) -> char { + const char c = str[i]; + if (c == '/') + return '\\'; + return c; + }; + const size_t len = str.length(); + uint64_t result = at(len - 1); if (len >= 3) - result |= str[len - 2] << 8; + result |= at(len - 2) << 8; result |= len << 16; - result |= static_cast(str[0] << 24); + result |= static_cast(at(0) << 24); if (len >= 4) { uint32_t hash = 0; for (size_t i = 1; i <= len - 3; ++i) - hash = hash * 0x1003f + str[i]; + hash = hash * 0x1003f + at(i); result += static_cast(hash) << 32; } if (extension.empty()) return result; - Misc::StringUtils::lowerCaseInPlace(extension); if (extension == ".kf") result |= 0x80; else if (extension == ".nif") diff --git a/components/bsa/compressedbsafile.hpp b/components/bsa/compressedbsafile.hpp index 6eae44cec1..11c0e1459b 100644 --- a/components/bsa/compressedbsafile.hpp +++ b/components/bsa/compressedbsafile.hpp @@ -26,7 +26,6 @@ #ifndef OPENMW_COMPONENTS_BSA_COMPRESSEDBSAFILE_HPP #define OPENMW_COMPONENTS_BSA_COMPRESSEDBSAFILE_HPP -#include #include #include @@ -112,7 +111,7 @@ namespace Bsa FileRecord getFileRecord(std::string_view str) const; /// \brief Normalizes given filename or folder and generates format-compatible hash. - static std::uint64_t generateHash(const std::filesystem::path& stem, std::string extension); + static std::uint64_t generateHash(std::string_view stem, std::string_view extension); Files::IStreamPtr getFile(const FileRecord& fileRecord); public: @@ -127,7 +126,6 @@ namespace Bsa /// Read header information from the input source void readHeader(std::istream& input) override; - Files::IStreamPtr getFile(const char* filePath); Files::IStreamPtr getFile(const FileStruct* fileStruct); void addFile(const std::string& filename, std::istream& file); };