diff --git a/docs/api/changelog.rst b/docs/api/changelog.rst index 3ba43038..e248cd55 100644 --- a/docs/api/changelog.rst +++ b/docs/api/changelog.rst @@ -451,7 +451,7 @@ Changed Fixed ----- -- ``.ghost`` file extensionms are no longer recursively trimmed when checking if +- ``.ghost`` file extensions are no longer recursively trimmed when checking if a file has a valid plugin file extension during metadata condition evaluation. Via loot-condition-interpreter. - When looking for a plugin file matching a path during metadata condition diff --git a/include/loot/plugin_interface.h b/include/loot/plugin_interface.h index 73bb2943..e3055cd9 100644 --- a/include/loot/plugin_interface.h +++ b/include/loot/plugin_interface.h @@ -43,7 +43,8 @@ public: /** * Get the plugin's filename. - * @return The plugin filename. + * @return The plugin filename. If the plugin was ghosted when it was loaded, + * this filename will be without the .ghost suffix. */ virtual std::string GetName() const = 0; diff --git a/src/api/game/game.cpp b/src/api/game/game.cpp index eacde10e..1f4e90e2 100644 --- a/src/api/game/game.cpp +++ b/src/api/game/game.cpp @@ -115,7 +115,15 @@ std::vector GetAdditionalDataPaths( std::filesystem::path ResolvePluginPath( const std::filesystem::path& dataPath, const std::filesystem::path& pluginPath) { - return pluginPath.is_absolute() ? pluginPath : dataPath / pluginPath; + auto absolutePath = + pluginPath.is_absolute() ? pluginPath : dataPath / pluginPath; + + // In case the plugin is ghosted. + if (!std::filesystem::exists(absolutePath)) { + absolutePath += loot::GHOST_FILE_EXTENSION; + } + + return absolutePath; } std::vector FindArchives( @@ -252,12 +260,7 @@ void Game::LoadPlugins(const std::vector& pluginPaths, [&](const std::filesystem::path& pluginPath) { try { const auto resolvedPluginPath = - boost::iequals(pluginPath.extension().u8string(), - GHOST_FILE_EXTENSION) - ? ResolvePluginPath( - DataPath(), - std::filesystem::path(pluginPath).replace_extension()) - : ResolvePluginPath(DataPath(), pluginPath); + ResolvePluginPath(DataPath(), pluginPath); const bool loadHeader = loadHeadersOnly || diff --git a/src/api/helpers/text.cpp b/src/api/helpers/text.cpp index fec7263b..b42ea107 100644 --- a/src/api/helpers/text.cpp +++ b/src/api/helpers/text.cpp @@ -223,4 +223,13 @@ std::string NormalizeFilename(const std::string& filename) { return normalizedFilename; #endif } + +std::string TrimDotGhostExtension(const std::string& filename) { + // If the name passed ends in '.ghost', that should be trimmed. + if (boost::iends_with(filename, GHOST_FILE_EXTENSION)) { + return filename.substr(0, filename.length() - GHOST_FILE_EXTENSION_LENGTH); + } + + return filename; +} } diff --git a/src/api/helpers/text.h b/src/api/helpers/text.h index 26703d52..a8ab27a2 100644 --- a/src/api/helpers/text.h +++ b/src/api/helpers/text.h @@ -53,6 +53,8 @@ int CompareFilenames(const std::string& lhs, const std::string& rhs); // that the normalized filenames distinguish characters in a similar way to the // Windows filesystem. std::string NormalizeFilename(const std::string& filename); + +std::string TrimDotGhostExtension(const std::string& filename); } #endif diff --git a/src/api/metadata/plugin_metadata.cpp b/src/api/metadata/plugin_metadata.cpp index 8e837940..21e63b09 100644 --- a/src/api/metadata/plugin_metadata.cpp +++ b/src/api/metadata/plugin_metadata.cpp @@ -36,9 +36,7 @@ namespace loot { PluginMetadata::PluginMetadata(const std::string& n) : name_(n) { // If the name passed ends in '.ghost', that should be trimmed. - if (boost::iends_with(name_, GHOST_FILE_EXTENSION)) { - name_ = name_.substr(0, name_.length() - GHOST_FILE_EXTENSION_LENGTH); - } + name_ = TrimDotGhostExtension(n); if (IsRegexPlugin()) { nameRegex_ = std::regex(name_, std::regex::ECMAScript | std::regex::icase); diff --git a/src/api/plugin.cpp b/src/api/plugin.cpp index f75447da..e98cab9d 100644 --- a/src/api/plugin.cpp +++ b/src/api/plugin.cpp @@ -141,7 +141,7 @@ Plugin::Plugin(const GameType gameType, const GameCache& gameCache, std::filesystem::path pluginPath, const bool headerOnly) : - name_(pluginPath.filename().u8string()), + name_(TrimDotGhostExtension(pluginPath.filename().u8string())), esPlugin( std::unique_ptr<::Plugin, decltype(&esp_plugin_free)>(nullptr, esp_plugin_free)), @@ -150,17 +150,12 @@ Plugin::Plugin(const GameType gameType, auto logger = getLogger(); try { - // In case the plugin is ghosted. - if (!std::filesystem::exists(pluginPath)) { - pluginPath += GHOST_FILE_EXTENSION; - } - Load(pluginPath, gameType, headerOnly); auto ret = esp_plugin_is_empty(esPlugin.get(), &isEmpty_); if (ret != ESP_OK) { throw FileAccessError( - "Error checking if \"" + name_ + + "Error checking if \"" + pluginPath.u8string() + "\" is empty. esplugin error code: " + std::to_string(ret)); } @@ -173,7 +168,7 @@ Plugin::Plugin(const GameType gameType, &overrideRecordCount_); if (ret != ESP_OK) { throw FileAccessError( - "Error counting override records in \"" + name_ + + "Error counting override records in \"" + pluginPath.u8string() + "\". esplugin error code: " + std::to_string(ret)); } @@ -184,7 +179,7 @@ Plugin::Plugin(const GameType gameType, if (logger) { logger->debug( "Plugin file \"{}\" loads {} assets from Bethesda archives", - name_, + pluginPath.u8string(), GetAssetCount()); } } @@ -192,10 +187,11 @@ Plugin::Plugin(const GameType gameType, tags_ = ExtractBashTags(GetDescription()); } catch (const std::exception& e) { if (logger) { - logger->error( - "Cannot read plugin file \"{}\". Details: {}", name_, e.what()); + logger->error("Cannot read plugin file \"{}\". Details: {}", + pluginPath.u8string(), + e.what()); } - throw FileAccessError("Cannot read \"" + name_ + + throw FileAccessError("Cannot read \"" + pluginPath.u8string() + "\". Details: " + e.what()); } } @@ -396,13 +392,6 @@ bool Plugin::IsValid(const GameType gameType, true, &isValid); - if (returnCode != ESP_OK || !isValid) { - // Try adding .ghost extension. - auto ghostedFilename = pluginPath.u8string() + GHOST_FILE_EXTENSION; - returnCode = esp_plugin_is_valid( - GetEspluginGameId(gameType), ghostedFilename.c_str(), true, &isValid); - } - if (returnCode == ESP_OK && isValid) { return true; } @@ -411,7 +400,7 @@ bool Plugin::IsValid(const GameType gameType, auto logger = getLogger(); if (logger) { logger->debug("The file \"{}\" is not a valid plugin.", - pluginPath.filename().u8string()); + pluginPath.u8string()); } return false; diff --git a/src/tests/api/internals/game/game_cache_test.h b/src/tests/api/internals/game/game_cache_test.h index a39085f2..5729c186 100644 --- a/src/tests/api/internals/game/game_cache_test.h +++ b/src/tests/api/internals/game/game_cache_test.h @@ -87,10 +87,11 @@ TEST_P(GameCacheTest, gettingPluginsShouldReturnASetOfCachedPluginsIfPluginsHaveBeenCached) { cache_.AddPlugin( Plugin(game_.GetType(), GameCache(), game_.DataPath() / blankEsm, true)); - cache_.AddPlugin(Plugin(game_.GetType(), - GameCache(), - game_.DataPath() / blankMasterDependentEsm, - true)); + cache_.AddPlugin( + Plugin(game_.GetType(), + GameCache(), + game_.DataPath() / (blankMasterDependentEsm + ".ghost"), + true)); EXPECT_FALSE(cache_.GetPlugins().empty()); } diff --git a/src/tests/api/internals/plugin_test.h b/src/tests/api/internals/plugin_test.h index 33f2f6ea..81be3b7e 100644 --- a/src/tests/api/internals/plugin_test.h +++ b/src/tests/api/internals/plugin_test.h @@ -265,7 +265,7 @@ TEST_P(PluginTest, loadingWholePluginShouldReadHeaderData) { TEST_P(PluginTest, loadingWholePluginShouldReadFields) { Plugin plugin(game_.GetType(), game_.GetCache(), - game_.DataPath() / blankMasterDependentEsm, + game_.DataPath() / (blankMasterDependentEsm + ".ghost"), false); if (GetParam() == GameType::tes3) { @@ -481,7 +481,7 @@ TEST_P(PluginTest, game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsm, true); Plugin plugin2(game_.GetType(), game_.GetCache(), - game_.DataPath() / blankMasterDependentEsm, + game_.DataPath() / (blankMasterDependentEsm + ".ghost"), true); EXPECT_FALSE(plugin1.DoRecordsOverlap(plugin2)); @@ -505,7 +505,7 @@ TEST_P(PluginTest, game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsm, false); Plugin plugin2(game_.GetType(), game_.GetCache(), - game_.DataPath() / blankMasterDependentEsm, + game_.DataPath() / (blankMasterDependentEsm + ".ghost"), false); EXPECT_TRUE(plugin1.DoRecordsOverlap(plugin2)); @@ -528,7 +528,7 @@ TEST_P(PluginTest, getOverlapSizeShouldCountEachRecordOnce) { game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsm, false); Plugin plugin2(game_.GetType(), game_.GetCache(), - game_.DataPath() / blankMasterDependentEsm, + game_.DataPath() / (blankMasterDependentEsm + ".ghost"), false); EXPECT_EQ(4, plugin1.GetOverlapSize({&plugin2, &plugin2})); @@ -541,7 +541,7 @@ TEST_P(PluginTest, getOverlapSizeShouldCheckAgainstAllGivenPlugins) { game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsp, false); Plugin plugin3(game_.GetType(), game_.GetCache(), - game_.DataPath() / blankMasterDependentEsm, + game_.DataPath() / (blankMasterDependentEsm + ".ghost"), false); EXPECT_EQ(4, plugin1.GetOverlapSize({&plugin2, &plugin3})); @@ -553,7 +553,7 @@ TEST_P(PluginTest, game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsm, true); Plugin plugin2(game_.GetType(), game_.GetCache(), - game_.DataPath() / blankMasterDependentEsm, + game_.DataPath() / (blankMasterDependentEsm + ".ghost"), true); EXPECT_EQ(0, plugin1.GetOverlapSize({&plugin2}));