1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-12-13 11:43:06 +00:00

Merge branch 'bsa_archive' into 'master'

Cleanup and optimize BSA archives code

See merge request OpenMW/openmw!3876
This commit is contained in:
psi29a 2024-02-22 08:02:12 +00:00
commit 518f34c869
8 changed files with 72 additions and 113 deletions

View file

@ -329,17 +329,19 @@ int main(int argc, char** argv)
switch (bsaVersion) switch (bsaVersion)
{ {
case Bsa::BSAVER_COMPRESSED: case Bsa::BsaVersion::Unknown:
return call<Bsa::CompressedBSAFile>(info); break;
case Bsa::BSAVER_BA2_GNRL: case Bsa::BsaVersion::Uncompressed:
return call<Bsa::BA2GNRLFile>(info);
case Bsa::BSAVER_BA2_DX10:
return call<Bsa::BA2DX10File>(info);
case Bsa::BSAVER_UNCOMPRESSED:
return call<Bsa::BSAFile>(info); return call<Bsa::BSAFile>(info);
default: case Bsa::BsaVersion::Compressed:
throw std::runtime_error("Unrecognised BSA archive"); return call<Bsa::CompressedBSAFile>(info);
case Bsa::BsaVersion::BA2GNRL:
return call<Bsa::BA2GNRLFile>(info);
case Bsa::BsaVersion::BA2DX10:
return call<Bsa::BA2DX10File>(info);
} }
throw std::runtime_error("Unrecognised BSA archive");
} }
catch (std::exception& e) catch (std::exception& e)
{ {

View file

@ -819,7 +819,7 @@ void Launcher::DataFilesPage::addArchivesFromDir(const QString& path)
for (const auto& fileinfo : dir.entryInfoList(archiveFilter)) for (const auto& fileinfo : dir.entryInfoList(archiveFilter))
{ {
const auto absPath = fileinfo.absoluteFilePath(); const auto absPath = fileinfo.absoluteFilePath();
if (Bsa::BSAFile::detectVersion(Files::pathFromQString(absPath)) == Bsa::BSAVER_UNKNOWN) if (Bsa::BSAFile::detectVersion(Files::pathFromQString(absPath)) == Bsa::BsaVersion::Unknown)
continue; continue;
const auto fileName = fileinfo.fileName(); const auto fileName = fileinfo.fileName();

View file

@ -42,29 +42,10 @@ bool isBSA(const std::filesystem::path& filename)
return hasExtension(filename, ".bsa") || hasExtension(filename, ".ba2"); return hasExtension(filename, ".bsa") || hasExtension(filename, ".ba2");
} }
std::unique_ptr<VFS::Archive> makeBsaArchive(const std::filesystem::path& path)
{
switch (Bsa::BSAFile::detectVersion(path))
{
case Bsa::BSAVER_COMPRESSED:
return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_COMPRESSED>::type>(path);
case Bsa::BSAVER_BA2_GNRL:
return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_BA2_GNRL>::type>(path);
case Bsa::BSAVER_BA2_DX10:
return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_BA2_DX10>::type>(path);
case Bsa::BSAVER_UNCOMPRESSED:
return std::make_unique<VFS::ArchiveSelector<Bsa::BSAVER_UNCOMPRESSED>::type>(path);
case Bsa::BSAVER_UNKNOWN:
default:
std::cerr << "'" << Files::pathToUnicodeString(path) << "' is not a recognized BSA archive" << std::endl;
return nullptr;
}
}
std::unique_ptr<VFS::Archive> makeArchive(const std::filesystem::path& path) std::unique_ptr<VFS::Archive> makeArchive(const std::filesystem::path& path)
{ {
if (isBSA(path)) if (isBSA(path))
return makeBsaArchive(path); return VFS::makeBsaArchive(path);
if (std::filesystem::is_directory(path)) if (std::filesystem::is_directory(path))
return std::make_unique<VFS::FileSystemArchive>(path); return std::make_unique<VFS::FileSystemArchive>(path);
return nullptr; return nullptr;
@ -124,17 +105,23 @@ void readVFS(std::unique_ptr<VFS::Archive>&& archive, const std::filesystem::pat
if (!archivePath.empty() && !isBSA(archivePath)) if (!archivePath.empty() && !isBSA(archivePath))
{ {
Files::PathContainer dataDirs = { archivePath }; const Files::Collections fileCollections({ archivePath });
const Files::Collections fileCollections = Files::Collections(dataDirs);
const Files::MultiDirCollection& bsaCol = fileCollections.getCollection(".bsa"); const Files::MultiDirCollection& bsaCol = fileCollections.getCollection(".bsa");
const Files::MultiDirCollection& ba2Col = fileCollections.getCollection(".ba2"); const Files::MultiDirCollection& ba2Col = fileCollections.getCollection(".ba2");
for (auto& file : bsaCol) for (const Files::MultiDirCollection& collection : { bsaCol, ba2Col })
{ {
readVFS(makeBsaArchive(file.second), file.second, quiet); for (auto& file : collection)
} {
for (auto& file : ba2Col) try
{ {
readVFS(makeBsaArchive(file.second), file.second, quiet); readVFS(VFS::makeBsaArchive(file.second), file.second, quiet);
}
catch (const std::exception& e)
{
std::cerr << "Failed to read archive file '" << Files::pathToUnicodeString(file.second)
<< "': " << e.what() << std::endl;
}
}
} }
} }
} }

View file

@ -325,43 +325,33 @@ BsaVersion Bsa::BSAFile::detectVersion(const std::filesystem::path& filePath)
{ {
std::ifstream input(filePath, std::ios_base::binary); std::ifstream input(filePath, std::ios_base::binary);
// Total archive size
std::streamoff fsize = 0;
if (input.seekg(0, std::ios_base::end))
{
fsize = input.tellg();
input.seekg(0);
}
if (fsize < 12)
{
return BSAVER_UNKNOWN;
}
// Get essential header numbers // Get essential header numbers
// First 12 bytes // First 12 bytes
uint32_t head[3]; uint32_t head[3];
input.read(reinterpret_cast<char*>(head), 12); input.read(reinterpret_cast<char*>(head), sizeof(head));
if (head[0] == static_cast<uint32_t>(BSAVER_UNCOMPRESSED)) if (input.gcount() != sizeof(head))
return BsaVersion::Unknown;
if (head[0] == static_cast<uint32_t>(BsaVersion::Uncompressed))
{ {
return BSAVER_UNCOMPRESSED; return BsaVersion::Uncompressed;
} }
if (head[0] == static_cast<uint32_t>(BSAVER_COMPRESSED) || head[0] == ESM::fourCC("BTDX")) if (head[0] == static_cast<uint32_t>(BsaVersion::Compressed) || head[0] == ESM::fourCC("BTDX"))
{ {
if (head[1] == static_cast<uint32_t>(0x01)) if (head[1] == static_cast<uint32_t>(0x01))
{ {
if (head[2] == ESM::fourCC("GNRL")) if (head[2] == ESM::fourCC("GNRL"))
return BSAVER_BA2_GNRL; return BsaVersion::BA2GNRL;
if (head[2] == ESM::fourCC("DX10")) if (head[2] == ESM::fourCC("DX10"))
return BSAVER_BA2_DX10; return BsaVersion::BA2DX10;
return BSAVER_UNKNOWN; return BsaVersion::Unknown;
} }
return BSAVER_COMPRESSED; return BsaVersion::Compressed;
} }
return BSAVER_UNKNOWN; return BsaVersion::Unknown;
} }

View file

@ -35,13 +35,13 @@
namespace Bsa namespace Bsa
{ {
enum BsaVersion enum class BsaVersion : std::uint32_t
{ {
BSAVER_UNKNOWN = 0x0, Unknown = 0x0,
BSAVER_UNCOMPRESSED = 0x100, Uncompressed = 0x100,
BSAVER_COMPRESSED = 0x415342, // B, S, A, Compressed = 0x415342, // B, S, A,
BSAVER_BA2_GNRL, // used by FO4, BSA which contains files BA2GNRL, // used by FO4, BSA which contains files
BSAVER_BA2_DX10 // used by FO4, BSA which contains textures BA2DX10 // used by FO4, BSA which contains textures
}; };
/** /**

View file

@ -70,7 +70,7 @@ namespace Bsa
input.read(reinterpret_cast<char*>(&mHeader), sizeof(mHeader)); input.read(reinterpret_cast<char*>(&mHeader), sizeof(mHeader));
if (mHeader.mFormat != BSAVER_COMPRESSED) // BSA if (mHeader.mFormat != static_cast<std::uint32_t>(BsaVersion::Compressed)) // BSA
fail("Unrecognized compressed BSA format"); fail("Unrecognized compressed BSA format");
if (mHeader.mVersion != Version_TES4 && mHeader.mVersion != Version_FO3 && mHeader.mVersion != Version_SSE) if (mHeader.mVersion != Version_TES4 && mHeader.mVersion != Version_FO3 && mHeader.mVersion != Version_SSE)
fail("Unrecognized compressed BSA version"); fail("Unrecognized compressed BSA version");

View file

@ -10,6 +10,10 @@
#include <components/bsa/bsa_file.hpp> #include <components/bsa/bsa_file.hpp>
#include <components/bsa/compressedbsafile.hpp> #include <components/bsa/compressedbsafile.hpp>
#include <algorithm>
#include <memory>
#include <stdexcept>
namespace VFS namespace VFS
{ {
template <typename FileType> template <typename FileType>
@ -44,10 +48,11 @@ namespace VFS
for (Bsa::BSAFile::FileList::const_iterator it = filelist.begin(); it != filelist.end(); ++it) for (Bsa::BSAFile::FileList::const_iterator it = filelist.begin(); it != filelist.end(); ++it)
{ {
mResources.emplace_back(&*it, mFile.get()); mResources.emplace_back(&*it, mFile.get());
mFiles.emplace_back(it->name());
} }
}
virtual ~BsaArchive() {} std::sort(mFiles.begin(), mFiles.end());
}
void listResources(FileMap& out) override void listResources(FileMap& out) override
{ {
@ -57,12 +62,7 @@ namespace VFS
bool contains(Path::NormalizedView file) const override bool contains(Path::NormalizedView file) const override
{ {
for (const auto& it : mResources) return std::binary_search(mFiles.begin(), mFiles.end(), file);
{
if (Path::pathEqual(file.value(), it.mInfo->name()))
return true;
}
return false;
} }
std::string getDescription() const override { return std::string{ "BSA: " } + mFile->getFilename(); } std::string getDescription() const override { return std::string{ "BSA: " } + mFile->getFilename(); }
@ -70,36 +70,27 @@ namespace VFS
private: private:
std::unique_ptr<BSAFileType> mFile; std::unique_ptr<BSAFileType> mFile;
std::vector<BsaArchiveFile<BSAFileType>> mResources; std::vector<BsaArchiveFile<BSAFileType>> mResources;
std::vector<VFS::Path::Normalized> mFiles;
}; };
template <Bsa::BsaVersion> inline std::unique_ptr<VFS::Archive> makeBsaArchive(const std::filesystem::path& path)
struct ArchiveSelector
{ {
}; switch (Bsa::BSAFile::detectVersion(path))
{
case Bsa::BsaVersion::Unknown:
break;
case Bsa::BsaVersion::Uncompressed:
return std::make_unique<BsaArchive<Bsa::BSAFile>>(path);
case Bsa::BsaVersion::Compressed:
return std::make_unique<BsaArchive<Bsa::CompressedBSAFile>>(path);
case Bsa::BsaVersion::BA2GNRL:
return std::make_unique<BsaArchive<Bsa::BA2GNRLFile>>(path);
case Bsa::BsaVersion::BA2DX10:
return std::make_unique<BsaArchive<Bsa::BA2DX10File>>(path);
}
template <> throw std::runtime_error("Unknown archive type '" + Files::pathToUnicodeString(path) + "'");
struct ArchiveSelector<Bsa::BSAVER_UNCOMPRESSED> }
{
using type = BsaArchive<Bsa::BSAFile>;
};
template <>
struct ArchiveSelector<Bsa::BSAVER_COMPRESSED>
{
using type = BsaArchive<Bsa::CompressedBSAFile>;
};
template <>
struct ArchiveSelector<Bsa::BSAVER_BA2_GNRL>
{
using type = BsaArchive<Bsa::BA2GNRLFile>;
};
template <>
struct ArchiveSelector<Bsa::BSAVER_BA2_DX10>
{
using type = BsaArchive<Bsa::BA2DX10File>;
};
} }
#endif #endif

View file

@ -25,18 +25,7 @@ namespace VFS
// Last BSA has the highest priority // Last BSA has the highest priority
const auto archivePath = collections.getPath(*archive); const auto archivePath = collections.getPath(*archive);
Log(Debug::Info) << "Adding BSA archive " << archivePath; Log(Debug::Info) << "Adding BSA archive " << archivePath;
Bsa::BsaVersion bsaVersion = Bsa::BSAFile::detectVersion(archivePath); vfs->addArchive(makeBsaArchive(archivePath));
if (bsaVersion == Bsa::BSAVER_COMPRESSED)
vfs->addArchive(std::make_unique<ArchiveSelector<Bsa::BSAVER_COMPRESSED>::type>(archivePath));
else if (bsaVersion == Bsa::BSAVER_BA2_GNRL)
vfs->addArchive(std::make_unique<ArchiveSelector<Bsa::BSAVER_BA2_GNRL>::type>(archivePath));
else if (bsaVersion == Bsa::BSAVER_BA2_DX10)
vfs->addArchive(std::make_unique<ArchiveSelector<Bsa::BSAVER_BA2_DX10>::type>(archivePath));
else if (bsaVersion == Bsa::BSAVER_UNCOMPRESSED)
vfs->addArchive(std::make_unique<ArchiveSelector<Bsa::BSAVER_UNCOMPRESSED>::type>(archivePath));
else
throw std::runtime_error("Unknown archive type '" + *archive + "'");
} }
else else
{ {