1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-12-17 19:43:08 +00:00

Treat TES4+ BSA paths as VFS paths

This commit is contained in:
Evil Eye 2025-12-08 17:44:15 +01:00
parent d45f252f69
commit 82914be8b2
8 changed files with 54 additions and 96 deletions

View file

@ -3,7 +3,6 @@
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <cstring> #include <cstring>
#include <filesystem>
#include <format> #include <format>
#include <istream> #include <istream>
@ -11,9 +10,8 @@
#include <components/esm/fourcc.hpp> #include <components/esm/fourcc.hpp>
#include <components/files/constrainedfilestream.hpp> #include <components/files/constrainedfilestream.hpp>
#include <components/files/conversion.hpp>
#include <components/files/utils.hpp> #include <components/files/utils.hpp>
#include <components/misc/strings/lower.hpp> #include <components/vfs/pathutil.hpp>
#include "ba2file.hpp" #include "ba2file.hpp"
#include "memorystream.hpp" #include "memorystream.hpp"
@ -148,20 +146,10 @@ namespace Bsa
} }
} }
#ifdef _WIN32 const VFS::Path::Normalized path(str);
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 auto p = std::filesystem::path{ path }; // Purposefully damage Unicode strings. const std::string_view fileName = path.stem();
const auto fileName = Misc::StringUtils::lowerCase(p.stem().string()); const std::string_view folder = path.parent().value();
const auto ext = Misc::StringUtils::lowerCase(p.extension().string()); // Purposefully damage Unicode strings.
const auto folder = Misc::StringUtils::lowerCase(p.parent_path().string());
uint32_t folderHash = generateHash(folder); uint32_t folderHash = generateHash(folder);
auto it = mFolders.find(folderHash); auto it = mFolders.find(folderHash);
@ -169,7 +157,7 @@ namespace Bsa
return std::nullopt; // folder not found return std::nullopt; // folder not found
uint32_t fileHash = generateHash(fileName); uint32_t fileHash = generateHash(fileName);
uint32_t extHash = generateExtensionHash(ext); uint32_t extHash = generateExtensionHash(path.filename());
auto iter = it->second.find({ fileHash, extHash }); auto iter = it->second.find({ fileHash, extHash });
if (iter == it->second.end()) if (iter == it->second.end())
return std::nullopt; // file not found return std::nullopt; // file not found
@ -229,13 +217,6 @@ namespace Bsa
fail("Add file is not implemented for compressed BSA: " + filename); 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_CAPS = 0x00000001;
constexpr const uint32_t DDSD_HEIGHT = 0x00000002; constexpr const uint32_t DDSD_HEIGHT = 0x00000002;
constexpr const uint32_t DDSD_WIDTH = 0x00000004; constexpr const uint32_t DDSD_WIDTH = 0x00000004;

View file

@ -59,7 +59,6 @@ namespace Bsa
/// Read header information from the input source /// Read header information from the input source
void readHeader(std::istream& stream) override; void readHeader(std::istream& stream) override;
Files::IStreamPtr getFile(const char* filePath);
Files::IStreamPtr getFile(const FileStruct* fileStruct); Files::IStreamPtr getFile(const FileStruct* fileStruct);
void addFile(const std::string& filename, std::istream& file); void addFile(const std::string& filename, std::istream& file);
}; };

View file

@ -1,5 +1,7 @@
#include "ba2file.hpp" #include "ba2file.hpp"
#include <components/misc/pathhelpers.hpp>
namespace Bsa namespace Bsa
{ {
constexpr const uint32_t crc32table[256] = { 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 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, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e,
0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; 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; uint32_t result = 0;
for (auto c : name) for (auto c : name)
@ -46,8 +48,15 @@ namespace Bsa
return result; 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; uint32_t result = 0;
for (size_t i = 0; i < 4 && i < extension.size() - 1; i++) for (size_t i = 0; i < 4 && i < extension.size() - 1; i++)
result |= static_cast<uint8_t>(extension[i + 1]) << (8 * i); result |= static_cast<uint8_t>(extension[i + 1]) << (8 * i);

View file

@ -4,10 +4,12 @@
#include <cstdint> #include <cstdint>
#include <string> #include <string>
#include <components/vfs/pathutil.hpp>
namespace Bsa namespace Bsa
{ {
uint32_t generateHash(const std::string& name); uint32_t generateHash(std::string_view name);
uint32_t generateExtensionHash(std::string_view extension); uint32_t generateExtensionHash(VFS::Path::NormalizedView file);
enum class BA2Version : std::uint32_t enum class BA2Version : std::uint32_t
{ {

View file

@ -2,7 +2,6 @@
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <filesystem>
#include <format> #include <format>
#include <fstream> #include <fstream>
@ -10,9 +9,8 @@
#include <components/esm/fourcc.hpp> #include <components/esm/fourcc.hpp>
#include <components/files/constrainedfilestream.hpp> #include <components/files/constrainedfilestream.hpp>
#include <components/files/conversion.hpp>
#include <components/files/utils.hpp> #include <components/files/utils.hpp>
#include <components/misc/strings/lower.hpp> #include <components/vfs/pathutil.hpp>
#include "ba2file.hpp" #include "ba2file.hpp"
#include "memorystream.hpp" #include "memorystream.hpp"
@ -139,20 +137,10 @@ namespace Bsa
} }
} }
#ifdef _WIN32 const VFS::Path::Normalized path(str);
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 auto p = std::filesystem::path{ path }; // Purposefully damage Unicode strings. const std::string_view fileName = path.stem();
const auto fileName = Misc::StringUtils::lowerCase(p.stem().string()); const std::string_view folder = path.parent().value();
const auto ext = Misc::StringUtils::lowerCase(p.extension().string()); // Purposefully damage Unicode strings.
const auto folder = Misc::StringUtils::lowerCase(p.parent_path().string());
uint32_t folderHash = generateHash(folder); uint32_t folderHash = generateHash(folder);
auto it = mFolders.find(folderHash); auto it = mFolders.find(folderHash);
@ -160,7 +148,7 @@ namespace Bsa
return FileRecord(); // folder not found, return default which has offset of sInvalidOffset return FileRecord(); // folder not found, return default which has offset of sInvalidOffset
uint32_t fileHash = generateHash(fileName); uint32_t fileHash = generateHash(fileName);
uint32_t extHash = generateExtensionHash(ext); uint32_t extHash = generateExtensionHash(path.filename());
auto iter = it->second.find({ fileHash, extHash }); auto iter = it->second.find({ fileHash, extHash });
if (iter == it->second.end()) if (iter == it->second.end())
return FileRecord(); // file not found, return default which has offset of sInvalidOffset 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); 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) Files::IStreamPtr BA2GNRLFile::getFile(const FileRecord& fileRecord)
{ {
const uint32_t inputSize = fileRecord.packedSize ? fileRecord.packedSize : fileRecord.size; const uint32_t inputSize = fileRecord.packedSize ? fileRecord.packedSize : fileRecord.size;

View file

@ -47,7 +47,6 @@ namespace Bsa
/// Read header information from the input source /// Read header information from the input source
void readHeader(std::istream& input) override; void readHeader(std::istream& input) override;
Files::IStreamPtr getFile(const char* filePath);
Files::IStreamPtr getFile(const FileStruct* fileStruct); Files::IStreamPtr getFile(const FileStruct* fileStruct);
void addFile(const std::string& filename, std::istream& file); void addFile(const std::string& filename, std::istream& file);
}; };

View file

@ -27,7 +27,6 @@
#include <algorithm> #include <algorithm>
#include <cassert> #include <cassert>
#include <cerrno> #include <cerrno>
#include <filesystem>
#include <format> #include <format>
#include <istream> #include <istream>
#include <system_error> #include <system_error>
@ -38,7 +37,8 @@
#include <components/files/constrainedfilestream.hpp> #include <components/files/constrainedfilestream.hpp>
#include <components/files/conversion.hpp> #include <components/files/conversion.hpp>
#include <components/files/utils.hpp> #include <components/files/utils.hpp>
#include <components/misc/strings/lower.hpp> #include <components/misc/pathhelpers.hpp>
#include <components/vfs/pathutil.hpp>
#include "memorystream.hpp" #include "memorystream.hpp"
@ -218,26 +218,26 @@ namespace Bsa
} }
} }
#ifdef _WIN32 const VFS::Path::Normalized path(str);
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 auto p = std::filesystem::path{ path }; // Purposefully damage Unicode strings. const std::string_view stem = path.stem();
const auto stem = p.stem(); const std::string_view folder = path.parent().value();
const auto ext = p.extension().string(); // Purposefully damage Unicode strings.
std::uint64_t folderHash = generateHash(p.parent_path(), {}); std::uint64_t folderHash = generateHash(folder, {});
auto it = mFolders.find(folderHash); auto it = mFolders.find(folderHash);
if (it == mFolders.end()) if (it == mFolders.end())
return FileRecord(); 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); std::uint64_t fileHash = generateHash(stem, ext);
auto iter = it->second.mFiles.find(fileHash); auto iter = it->second.mFiles.find(fileHash);
if (iter == it->second.mFiles.end()) if (iter == it->second.mFiles.end())
@ -262,16 +262,6 @@ namespace Bsa
fail("Add file is not implemented for compressed BSA: " + filename); 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<uint32_t>::max())
{
fail("File not found: " + std::string(file));
}
return getFile(fileRec);
}
Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord) Files::IStreamPtr CompressedBSAFile::getFile(const FileRecord& fileRecord)
{ {
size_t size = fileRecord.mSize & (~FileSizeFlag_Compression); size_t size = fileRecord.mSize & (~FileSizeFlag_Compression);
@ -337,29 +327,31 @@ namespace Bsa
return std::make_unique<Files::StreamWithBuffer<MemoryInputStream>>(std::move(memoryStreamPtr)); return std::make_unique<Files::StreamWithBuffer<MemoryInputStream>>(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(); if (str.empty())
size_t len = str.length();
if (len == 0)
return 0; return 0;
std::replace(str.begin(), str.end(), '/', '\\'); const auto at = [&](std::size_t i) -> char {
Misc::StringUtils::lowerCaseInPlace(str); const char c = str[i];
uint64_t result = str[len - 1]; if (c == '/')
return '\\';
return c;
};
const size_t len = str.length();
uint64_t result = at(len - 1);
if (len >= 3) if (len >= 3)
result |= str[len - 2] << 8; result |= at(len - 2) << 8;
result |= len << 16; result |= len << 16;
result |= static_cast<uint32_t>(str[0] << 24); result |= static_cast<uint32_t>(at(0) << 24);
if (len >= 4) if (len >= 4)
{ {
uint32_t hash = 0; uint32_t hash = 0;
for (size_t i = 1; i <= len - 3; ++i) for (size_t i = 1; i <= len - 3; ++i)
hash = hash * 0x1003f + str[i]; hash = hash * 0x1003f + at(i);
result += static_cast<uint64_t>(hash) << 32; result += static_cast<uint64_t>(hash) << 32;
} }
if (extension.empty()) if (extension.empty())
return result; return result;
Misc::StringUtils::lowerCaseInPlace(extension);
if (extension == ".kf") if (extension == ".kf")
result |= 0x80; result |= 0x80;
else if (extension == ".nif") else if (extension == ".nif")

View file

@ -26,7 +26,6 @@
#ifndef OPENMW_COMPONENTS_BSA_COMPRESSEDBSAFILE_HPP #ifndef OPENMW_COMPONENTS_BSA_COMPRESSEDBSAFILE_HPP
#define OPENMW_COMPONENTS_BSA_COMPRESSEDBSAFILE_HPP #define OPENMW_COMPONENTS_BSA_COMPRESSEDBSAFILE_HPP
#include <filesystem>
#include <limits> #include <limits>
#include <map> #include <map>
@ -112,7 +111,7 @@ namespace Bsa
FileRecord getFileRecord(std::string_view str) const; FileRecord getFileRecord(std::string_view str) const;
/// \brief Normalizes given filename or folder and generates format-compatible hash. /// \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); Files::IStreamPtr getFile(const FileRecord& fileRecord);
public: public:
@ -127,7 +126,6 @@ namespace Bsa
/// Read header information from the input source /// Read header information from the input source
void readHeader(std::istream& input) override; void readHeader(std::istream& input) override;
Files::IStreamPtr getFile(const char* filePath);
Files::IStreamPtr getFile(const FileStruct* fileStruct); Files::IStreamPtr getFile(const FileStruct* fileStruct);
void addFile(const std::string& filename, std::istream& file); void addFile(const std::string& filename, std::istream& file);
}; };