Add support for ESL plugins

The heavy lifting is done by esplugin and libloadorder.
This commit is contained in:
Oliver Hamlet
2017-10-13 22:33:41 +01:00
parent 89839fdc74
commit bc3ae6d7f8
7 changed files with 72 additions and 12 deletions
+2 -2
View File
@@ -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}
+6
View File
@@ -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.
+2 -2
View File
@@ -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"));
}
}
+29 -7
View File
@@ -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;
}
}
+4
View File
@@ -32,6 +32,7 @@
#include <vector>
#include <boost/locale.hpp>
#include <boost/algorithm/string.hpp>
#include <esplugin.hpp>
#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<std::remove_pointer<::Plugin>::type> esPlugin;
};
bool hasPluginFileExtension(const std::string& filename, GameType gameType);
}
#endif
+1 -1
View File
@@ -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 {
@@ -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);
}
}
}