mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Fix case-insensitive detection of archives starting with a non-ASCII string
This commit is contained in:
@@ -253,7 +253,7 @@ void Game::CacheArchives() {
|
||||
// file extension with the archive extension resolves to the same file.
|
||||
// Could use boost::iends_with, but it's less obvious here that the
|
||||
// test string is ASCII-only.
|
||||
if (boost::iequals(it->path().extension().u8string(), archiveFileExtension)) {
|
||||
if (loot::equivalent(it->path(), replaceExtension(it->path(), archiveFileExtension))) {
|
||||
cache_->CacheArchivePath(it->path());
|
||||
}
|
||||
}
|
||||
|
||||
+31
-8
@@ -264,7 +264,6 @@ uintmax_t Plugin::GetFileSize(std::filesystem::path pluginPath) {
|
||||
|
||||
bool Plugin::operator<(const Plugin& rhs) const {
|
||||
return boost::ilexicographical_compare(name_, rhs.name_);
|
||||
;
|
||||
}
|
||||
|
||||
bool Plugin::IsActive() const { return isActive_; }
|
||||
@@ -314,24 +313,48 @@ std::string GetArchiveFileExtension(const GameType gameType) {
|
||||
return ".bsa";
|
||||
}
|
||||
|
||||
std::filesystem::path replaceExtension(std::filesystem::path path, const std::string& newExtension) {
|
||||
return path.replace_extension(std::filesystem::u8path(newExtension));
|
||||
}
|
||||
|
||||
bool equivalent(const std::filesystem::path& path1, const std::filesystem::path& path2) {
|
||||
try {
|
||||
return std::filesystem::equivalent(path1, path2);
|
||||
} catch (std::filesystem::filesystem_error) {
|
||||
// One of the paths checked for equivalence doesn't exist,
|
||||
// so they can't be equivalent.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Get whether the plugin loads an archive (BSA/BA2) or not.
|
||||
bool Plugin::LoadsArchive(const GameType gameType,
|
||||
const std::shared_ptr<GameCache> gameCache,
|
||||
const std::filesystem::path& pluginPath) {
|
||||
// Get whether the plugin loads an archive (BSA/BA2) or not.
|
||||
const string archiveExtension = GetArchiveFileExtension(gameType);
|
||||
auto pluginName = pluginPath.filename().u8string();
|
||||
|
||||
if (gameType == GameType::tes5) {
|
||||
// Skyrim plugins only load BSAs that exactly match their basename.
|
||||
auto filename = pluginName.substr(0, pluginName.length() - 4) + archiveExtension;
|
||||
return std::filesystem::exists(pluginPath.parent_path() / std::filesystem::u8path(filename));
|
||||
return std::filesystem::exists(replaceExtension(pluginPath, archiveExtension));
|
||||
} else if (gameType != GameType::tes4 ||
|
||||
boost::iends_with(pluginName, ".esp")) {
|
||||
boost::iends_with(pluginPath.filename().u8string(), ".esp")) {
|
||||
// Oblivion .esp files and FO3, FNV, FO4 plugins can load archives which
|
||||
// begin with the plugin basename.
|
||||
string basename = pluginName.substr(0, pluginName.length() - 4);
|
||||
|
||||
auto basenameLength = pluginPath.stem().native().length();
|
||||
auto pluginExtension = pluginPath.extension().native();
|
||||
|
||||
for (const auto& archivePath : gameCache->GetArchivePaths()) {
|
||||
if (boost::istarts_with(archivePath.filename().u8string(), basename)) {
|
||||
// Need to check if it starts with the given plugin's basename,
|
||||
// but case insensitively. This is hard to do accurately, so
|
||||
// instead check if the plugin with the same length basename and
|
||||
// and the given plugin's file extension is equivalent.
|
||||
auto bsaPluginFilename =
|
||||
archivePath.filename().native().substr(0, basenameLength) +
|
||||
pluginExtension;
|
||||
auto bsaPluginPath =
|
||||
pluginPath.parent_path() / bsaPluginFilename;
|
||||
if (loot::equivalent(pluginPath, bsaPluginPath)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,10 @@ private:
|
||||
std::string GetArchiveFileExtension(const GameType gameType);
|
||||
|
||||
bool hasPluginFileExtension(const std::string& filename, GameType gameType);
|
||||
|
||||
bool equivalent(const std::filesystem::path& path1, const std::filesystem::path& path2);
|
||||
|
||||
std::filesystem::path replaceExtension(std::filesystem::path path, const std::string& newExtension);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -38,6 +38,7 @@ protected:
|
||||
emptyFile("EmptyFile.esm"),
|
||||
lowercaseBlankEsp("blank.esp"),
|
||||
nonAsciiEsp(u8"non\u00C1scii.esp"),
|
||||
otherNonAsciiEsp(u8"other non\u00C1scii.esp"),
|
||||
game_(GetParam(), dataPath.parent_path(), localPath),
|
||||
blankArchive("Blank" + GetArchiveFileExtension(game_.Type())),
|
||||
blankSuffixArchive("Blank - Different - suffix" +
|
||||
@@ -58,9 +59,11 @@ protected:
|
||||
dataPath / lowercaseBlankEsp));
|
||||
#endif
|
||||
|
||||
// Make sure the plugin with a non-ASCII filename exists.
|
||||
// Make sure the plugins with non-ASCII filenames exists.
|
||||
ASSERT_NO_THROW(std::filesystem::copy_file(dataPath / blankEsp,
|
||||
dataPath / std::filesystem::u8path(nonAsciiEsp)));
|
||||
ASSERT_NO_THROW(std::filesystem::copy_file(dataPath / blankEsp,
|
||||
dataPath / std::filesystem::u8path(otherNonAsciiEsp)));
|
||||
|
||||
if (GetParam() != GameType::fo4 && GetParam() != GameType::tes5se) {
|
||||
ASSERT_NO_THROW(
|
||||
@@ -73,13 +76,18 @@ protected:
|
||||
out.open(dataPath / blankSuffixArchive);
|
||||
out.close();
|
||||
|
||||
auto nonAsciiArchivePath = dataPath / std::filesystem::u8path(u8"non\u00C1scii" + GetArchiveFileExtension(game_.Type()));
|
||||
auto nonAsciiArchivePath = dataPath / std::filesystem::u8path(u8"non\u00E1scii" + GetArchiveFileExtension(game_.Type()));
|
||||
out.open(nonAsciiArchivePath);
|
||||
out.close();
|
||||
|
||||
auto nonAsciiPrefixArchivePath = dataPath / std::filesystem::u8path(u8"other non\u00E1scii2 - suffix" + GetArchiveFileExtension(game_.Type()));
|
||||
out.open(nonAsciiPrefixArchivePath);
|
||||
out.close();
|
||||
|
||||
game_.GetCache()->CacheArchivePath(dataPath / blankArchive);
|
||||
game_.GetCache()->CacheArchivePath(dataPath / blankSuffixArchive);
|
||||
game_.GetCache()->CacheArchivePath(dataPath / nonAsciiArchivePath);
|
||||
game_.GetCache()->CacheArchivePath(dataPath / nonAsciiPrefixArchivePath);
|
||||
}
|
||||
|
||||
uintmax_t getGhostedPluginFileSize() {
|
||||
@@ -101,6 +109,7 @@ protected:
|
||||
const std::string emptyFile;
|
||||
const std::string lowercaseBlankEsp;
|
||||
const std::string nonAsciiEsp;
|
||||
const std::string otherNonAsciiEsp;
|
||||
const std::string blankArchive;
|
||||
const std::string blankSuffixArchive;
|
||||
|
||||
@@ -281,6 +290,7 @@ TEST_P(
|
||||
EXPECT_TRUE(loadsArchive);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
loadsArchiveForAnArchiveThatExactlyMatchesANonAsciiEspFileBasenameShouldReturnTrue) {
|
||||
@@ -291,6 +301,7 @@ TEST_P(
|
||||
true)
|
||||
.LoadsArchive());
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
@@ -335,6 +346,24 @@ TEST_P(
|
||||
EXPECT_TRUE(loadsArchive);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheNonAsciiEspFileBasenameShouldReturnTrueForAllGamesExceptSkyrim) {
|
||||
bool loadsArchive = Plugin(game_.Type(),
|
||||
game_.GetCache(),
|
||||
game_.GetLoadOrderHandler(),
|
||||
game_.DataPath() / std::filesystem::u8path(otherNonAsciiEsp),
|
||||
true)
|
||||
.LoadsArchive();
|
||||
|
||||
if (GetParam() == GameType::tes5)
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
else
|
||||
EXPECT_TRUE(loadsArchive);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_P(PluginTest,
|
||||
loadsArchiveShouldReturnFalseForAPluginThatDoesNotLoadAnArchive) {
|
||||
EXPECT_FALSE(Plugin(game_.Type(),
|
||||
|
||||
Reference in New Issue
Block a user