From bc3ae6d7f8ad4ce86bfca7cabde3bbb00f3ee5da Mon Sep 17 00:00:00 2001 From: Oliver Hamlet Date: Fri, 13 Oct 2017 22:03:53 +0100 Subject: [PATCH] Add support for ESL plugins The heavy lifting is done by esplugin and libloadorder. --- CMakeLists.txt | 4 +-- include/loot/plugin_interface.h | 6 ++++ src/api/metadata/condition_evaluator.cpp | 4 +-- src/api/plugin/plugin.cpp | 36 ++++++++++++++++---- src/api/plugin/plugin.h | 4 +++ src/api/plugin/plugin_sorter.cpp | 2 +- src/tests/api/internals/plugin/plugin_test.h | 28 +++++++++++++++ 7 files changed, 72 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 5870f78f..4f6286f7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -74,7 +74,7 @@ set (GTEST_LIBRARIES "${BINARY_DIR}/googlemock/gtest/${CMAKE_CFG_INTDIR}/${CMAKE ExternalProject_Add(esplugin PREFIX "external" - URL "https://github.com/WrinklyNinja/esplugin/archive/1.0.3.tar.gz" + URL "https://github.com/WrinklyNinja/esplugin/archive/1.0.5.tar.gz" CONFIGURE_COMMAND "" BUILD_IN_SOURCE 1 BUILD_COMMAND cargo build --release --all --all-features --target ${RUST_TARGET} @@ -100,7 +100,7 @@ set(LIBGIT2_LIBRARIES "${BINARY_DIR}/${CMAKE_CFG_INTDIR}/${CMAKE_STATIC_LIBRARY_ ExternalProject_Add(libloadorder PREFIX "external" - URL "https://github.com/WrinklyNinja/libloadorder/archive/rust-rewrite.tar.gz" + URL "https://github.com/WrinklyNinja/libloadorder/archive/master.tar.gz" CONFIGURE_COMMAND "" BUILD_IN_SOURCE 1 BUILD_COMMAND cargo build --release --all --all-features --target ${RUST_TARGET} diff --git a/include/loot/plugin_interface.h b/include/loot/plugin_interface.h index 43130496..96b2bb6c 100644 --- a/include/loot/plugin_interface.h +++ b/include/loot/plugin_interface.h @@ -85,6 +85,12 @@ public: */ virtual bool IsMaster() const = 0; + /** + * Check if the plugin is a light master. + * @return True if plugin is a light master, false otherwise. + */ + virtual bool IsLightMaster() const = 0; + /** * Check if the plugin contains any records other than its TES4 header. * @return True if the plugin only contains a TES4 header, false otherwise. diff --git a/src/api/metadata/condition_evaluator.cpp b/src/api/metadata/condition_evaluator.cpp index 402954bd..f9083ca6 100644 --- a/src/api/metadata/condition_evaluator.cpp +++ b/src/api/metadata/condition_evaluator.cpp @@ -174,7 +174,7 @@ bool ConditionEvaluator::fileExists(const std::string& filePath) const { return true; } catch (...) { // Not a loaded plugin, check the filesystem. - if (boost::iends_with(filePath, ".esp") || boost::iends_with(filePath, ".esm")) + if (hasPluginFileExtension(filePath, gameType_)) return boost::filesystem::exists(dataPath_ / filePath) || boost::filesystem::exists(dataPath_ / (filePath + ".ghost")); else @@ -257,7 +257,7 @@ bool ConditionEvaluator::checksumMatches(const std::string& filePath, const uint if (realChecksum == 0) { if (boost::filesystem::exists(dataPath_ / filePath)) realChecksum = GetCrc32(dataPath_ / filePath); - else if ((boost::iends_with(filePath, ".esp") || boost::iends_with(filePath, ".esm")) && boost::filesystem::exists(dataPath_ / (filePath + ".ghost"))) + else if (hasPluginFileExtension(filePath, gameType_) && boost::filesystem::exists(dataPath_ / (filePath + ".ghost"))) realChecksum = GetCrc32(dataPath_ / (filePath + ".ghost")); } } diff --git a/src/api/plugin/plugin.cpp b/src/api/plugin/plugin.cpp index a5262d4a..34f15f93 100644 --- a/src/api/plugin/plugin.cpp +++ b/src/api/plugin/plugin.cpp @@ -155,6 +155,16 @@ bool Plugin::IsMaster() const { return isMaster; } +bool Plugin::IsLightMaster() const { + bool isLightMaster; + auto ret = esp_plugin_is_light_master(esPlugin.get(), &isLightMaster); + if (ret != ESP_OK) { + throw FileAccessError(name_ + " : Libespm error code: " + std::to_string(ret)); + } + + return isLightMaster; +} + bool Plugin::IsEmpty() const { return isEmpty_; } @@ -196,7 +206,7 @@ bool Plugin::IsValid(const std::string& filename, const GameType gameType, const name = filename; // Check that the file has a valid extension. - if (!boost::iends_with(name, ".esm") && !boost::iends_with(name, ".esp")) + if (!hasPluginFileExtension(name, gameType)) return false; bool isValid; @@ -286,15 +296,27 @@ bool Plugin::LoadsArchive(const std::string& pluginName, const GameType gameType } unsigned int Plugin::GetEspluginGameId(GameType gameType) { - if (gameType == GameType::tes4) + switch (gameType) { + case GameType::tes4: return ESP_GAME_OBLIVION; - else if (gameType == GameType::tes5 || gameType == GameType::tes5se) + case GameType::tes5: return ESP_GAME_SKYRIM; - else if (gameType == GameType::fo3) + case GameType::tes5se: + return ESP_GAME_SKYRIMSE; + case GameType::fo3: return ESP_GAME_FALLOUT3; - else if (gameType == GameType::fonv) + case GameType::fonv: return ESP_GAME_FALLOUTNV; - else - return ESP_GAME_SKYRIM; + default: + return ESP_GAME_FALLOUT4; + } +} + +bool hasPluginFileExtension(const std::string& filename, GameType gameType) { + bool espOrEsm = boost::iends_with(filename, ".esp") || boost::iends_with(filename, ".esm"); + bool lightMaster = (gameType == GameType::fo4 || gameType == GameType::tes5se) + && boost::iends_with(filename, ".esl"); + + return espOrEsm || lightMaster; } } diff --git a/src/api/plugin/plugin.h b/src/api/plugin/plugin.h index c9110c92..010eba3e 100644 --- a/src/api/plugin/plugin.h +++ b/src/api/plugin/plugin.h @@ -32,6 +32,7 @@ #include #include +#include #include #include "api/game/load_order_handler.h" @@ -56,6 +57,7 @@ public: uint32_t GetCRC() const; bool IsMaster() const; + bool IsLightMaster() const; bool IsEmpty() const; bool LoadsArchive() const; bool DoFormIDsOverlap(const PluginInterface& plugin) const; @@ -91,6 +93,8 @@ private: std::shared_ptr::type> esPlugin; }; + +bool hasPluginFileExtension(const std::string& filename, GameType gameType); } #endif diff --git a/src/api/plugin/plugin_sorter.cpp b/src/api/plugin/plugin_sorter.cpp index 169d7f0f..96ffdeec 100644 --- a/src/api/plugin/plugin_sorter.cpp +++ b/src/api/plugin/plugin_sorter.cpp @@ -51,7 +51,7 @@ std::string PluginSortingData::GetName() const { } bool PluginSortingData::IsMaster() const { - return plugin_.IsMaster(); + return plugin_.IsMaster() || plugin_.IsLightMaster(); } bool PluginSortingData::LoadsArchive() const { diff --git a/src/tests/api/internals/plugin/plugin_test.h b/src/tests/api/internals/plugin/plugin_test.h index 1379ade0..6c26bca3 100644 --- a/src/tests/api/internals/plugin/plugin_test.h +++ b/src/tests/api/internals/plugin/plugin_test.h @@ -37,6 +37,7 @@ protected: PluginTest() : emptyFile("EmptyFile.esm"), lowercaseBlankEsp("blank.esp"), + blankEsl("blank.esl"), game_(GetParam(), dataPath.parent_path(), localPath), blankArchive("Blank" + GetArchiveFileExtension(game_.Type())), blankSuffixArchive("Blank - Different - suffix" + GetArchiveFileExtension(game_.Type())) {} @@ -55,6 +56,8 @@ protected: ASSERT_NO_THROW(boost::filesystem::copy(dataPath / blankEsp, dataPath / lowercaseBlankEsp)); #endif + ASSERT_NO_THROW(boost::filesystem::copy(dataPath / blankEsp, dataPath / blankEsl)); + // Create dummy archive files. out.open(dataPath / blankArchive); out.close(); @@ -69,6 +72,7 @@ protected: #ifndef _WIN32 boost::filesystem::remove(dataPath / lowercaseBlankEsp); #endif + boost::filesystem::remove(dataPath / blankEsl); boost::filesystem::remove(dataPath / blankArchive); boost::filesystem::remove(dataPath / blankSuffixArchive); } @@ -77,6 +81,7 @@ protected: const std::string emptyFile; const std::string lowercaseBlankEsp; + const std::string blankEsl; const std::string blankArchive; const std::string blankSuffixArchive; private: @@ -98,6 +103,7 @@ public: uint32_t GetCRC() const { return 0; } bool IsMaster() const { return false; } + bool IsLightMaster() const { return false; } bool IsEmpty() const { return false; } bool LoadsArchive() const { return false; } bool DoFormIDsOverlap(const PluginInterface& plugin) const { return true; } @@ -159,6 +165,16 @@ TEST_P(PluginTest, loadingANonMasterPluginShouldReadTheMasterFlagAsFalse) { EXPECT_FALSE(plugin.IsMaster()); } +TEST_P(PluginTest, isLightMasterShouldBeTrueForAPluginWithEslFileExtensionForFallout4AndSkyrimSeAndFalseOtherwise) { + Plugin plugin1(game_.Type(), game_.DataPath(), game_.GetLoadOrderHandler(), blankEsm, true); + Plugin plugin2(game_.Type(), game_.DataPath(), game_.GetLoadOrderHandler(), blankMasterDependentEsp, true); + Plugin plugin3(game_.Type(), game_.DataPath(), game_.GetLoadOrderHandler(), blankEsl, true); + + EXPECT_FALSE(plugin1.IsLightMaster()); + EXPECT_FALSE(plugin2.IsLightMaster()); + EXPECT_EQ(GetParam() == GameType::fo4 || GetParam() == GameType::tes5se, plugin3.IsLightMaster()); +} + TEST_P(PluginTest, loadingAPluginWithMastersShouldReadThemCorrectly) { Plugin plugin(game_.Type(), game_.DataPath(), game_.GetLoadOrderHandler(), blankMasterDependentEsp, true); @@ -271,6 +287,18 @@ TEST_P(PluginTest, doFormIDsOverlapShouldReturnTrueIfOnePluginOverridesTheOthers EXPECT_TRUE(plugin1.DoFormIDsOverlap(plugin2)); EXPECT_TRUE(plugin2.DoFormIDsOverlap(plugin1)); } + +TEST_P(PluginTest, hasPluginFileExtensionShouldBeTrueIfFileEndsInDotEspOrDotEsm) { + EXPECT_TRUE(hasPluginFileExtension("file.esp", GetParam())); + EXPECT_TRUE(hasPluginFileExtension("file.esm", GetParam())); + EXPECT_FALSE(hasPluginFileExtension("file.bsa", GetParam())); +} + +TEST_P(PluginTest, hasPluginFileExtensionShouldBeTrueIfFileEndsInDotEslOnlyForFallout4AndSkyrimSE) { + bool result = hasPluginFileExtension("file.esl", GetParam()); + + EXPECT_EQ(GetParam() == GameType::fo4 || GetParam() == GameType::tes5se, result); +} } }