Fix case-insensitive detection of archives starting with a non-ASCII string

This commit is contained in:
Oliver Hamlet
2018-10-20 12:48:21 +01:00
parent b142dd1a8f
commit 1b53ae4ff4
4 changed files with 67 additions and 11 deletions
+1 -1
View File
@@ -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
View File
@@ -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;
}
}
+4
View File
@@ -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
+31 -2
View File
@@ -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(),