mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Implement support for OpenMW
Most of the complexity is handled by libloadorder, but it's worth noting that: - The game path is OpenMW's install path, not Morrowind's - OpenMW doesn't force master-flagged plugins to load before others - OpenMW doesn't provide a way to record the load order of inactive plugins - .omwgame and .omwaddon plugins are equivalent to .esm and .esp respectively, while .omwscripts plugins have a completely different format with none of the metadata that libloot uses. - OpenMW effectively relies on additional data paths to load Morrowind's (and mods') files, and the last directory listed that contains a given filename is used to load a file with that filename, with the main data path effectively being the first listed. - I've disabled support for ghosted plugins for OpenMW because it makes the multi-path stuff more confusing and may not provide any benefit.
This commit is contained in:
@@ -54,6 +54,8 @@ const char* DescribeGameType(GameType gameType) {
|
||||
return "The Elder Scrolls III: Morrowind";
|
||||
case GameType::starfield:
|
||||
return "Starfield";
|
||||
case GameType::openmw:
|
||||
return "OpenMW";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
+50
-34
@@ -89,6 +89,7 @@ bool IsMicrosoftStoreInstall(const GameType gameType,
|
||||
case GameType::tes5:
|
||||
case GameType::tes5vr:
|
||||
case GameType::fo4vr:
|
||||
case GameType::openmw:
|
||||
return false;
|
||||
default:
|
||||
throw std::logic_error("Unrecognised game type");
|
||||
@@ -115,42 +116,20 @@ std::filesystem::path GetUserDocumentsPath(
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> GetAdditionalDataPaths(
|
||||
const GameType gameType,
|
||||
const std::filesystem::path& dataPath,
|
||||
const std::filesystem::path& gameLocalPath) {
|
||||
const auto gamePath = dataPath.parent_path();
|
||||
|
||||
if (gameType == GameType::fo4 &&
|
||||
IsMicrosoftStoreInstall(gameType, gamePath)) {
|
||||
return {gamePath / MS_FO4_AUTOMATRON_DATA_PATH,
|
||||
gamePath / MS_FO4_NUKA_WORLD_DATA_PATH,
|
||||
gamePath / MS_FO4_WASTELAND_DATA_PATH,
|
||||
gamePath / MS_FO4_TEXTURE_PACK_DATA_PATH,
|
||||
gamePath / MS_FO4_VAULT_TEC_DATA_PATH,
|
||||
gamePath / MS_FO4_FAR_HARBOR_DATA_PATH,
|
||||
gamePath / MS_FO4_CONTRAPTIONS_DATA_PATH};
|
||||
}
|
||||
|
||||
if (gameType == GameType::starfield) {
|
||||
return {GetUserDocumentsPath(gameLocalPath) / "My Games" / "Starfield" /
|
||||
"Data"};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
std::filesystem::path ResolvePluginPath(
|
||||
GameType gameType,
|
||||
const std::filesystem::path& dataPath,
|
||||
const std::filesystem::path& pluginPath) {
|
||||
auto absolutePath =
|
||||
pluginPath.is_absolute() ? pluginPath : dataPath / pluginPath;
|
||||
|
||||
// In case the plugin is ghosted.
|
||||
if (!std::filesystem::exists(absolutePath)) {
|
||||
if (gameType != GameType::openmw && !std::filesystem::exists(absolutePath)) {
|
||||
const auto logger = loot::getLogger();
|
||||
if (logger) {
|
||||
logger->debug("Could not find plugin at {}, adding {} file extension", absolutePath.u8string(), loot::GHOST_FILE_EXTENSION);
|
||||
logger->debug("Could not find plugin at {}, adding {} file extension",
|
||||
absolutePath.u8string(),
|
||||
loot::GHOST_FILE_EXTENSION);
|
||||
}
|
||||
absolutePath += loot::GHOST_FILE_EXTENSION;
|
||||
}
|
||||
@@ -182,6 +161,35 @@ std::vector<std::filesystem::path> FindArchives(
|
||||
|
||||
return archivePaths;
|
||||
}
|
||||
|
||||
std::filesystem::path FindPlugin(
|
||||
GameType gameType,
|
||||
const std::filesystem::path& dataPath,
|
||||
const std::vector<std::filesystem::path>& additionalDataPaths,
|
||||
const std::string& pluginName) {
|
||||
const auto relativePath = std::filesystem::u8path(pluginName);
|
||||
|
||||
const auto finder = [&](const auto& path) {
|
||||
const auto resolvedPath = ResolvePluginPath(gameType, path, relativePath);
|
||||
return std::filesystem::exists(resolvedPath);
|
||||
};
|
||||
|
||||
if (gameType == GameType::openmw) {
|
||||
const auto it = std::find_if(
|
||||
additionalDataPaths.rbegin(), additionalDataPaths.rend(), finder);
|
||||
if (it != additionalDataPaths.rend()) {
|
||||
return *it;
|
||||
}
|
||||
} else {
|
||||
const auto it = std::find_if(
|
||||
additionalDataPaths.begin(), additionalDataPaths.end(), finder);
|
||||
if (it != additionalDataPaths.end()) {
|
||||
return *it;
|
||||
}
|
||||
}
|
||||
|
||||
return dataPath / relativePath;
|
||||
}
|
||||
}
|
||||
|
||||
namespace loot {
|
||||
@@ -193,9 +201,8 @@ Game::Game(const GameType gameType,
|
||||
loadOrderHandler_(type_, gamePath_, localDataPath),
|
||||
conditionEvaluator_(
|
||||
std::make_shared<ConditionEvaluator>(GetType(), DataPath())),
|
||||
database_(ApiDatabase(conditionEvaluator_)),
|
||||
additionalDataPaths_(
|
||||
::GetAdditionalDataPaths(GetType(), DataPath(), localDataPath)) {
|
||||
database_(ApiDatabase(conditionEvaluator_)) {
|
||||
additionalDataPaths_ = loadOrderHandler_.GetAdditionalDataPaths();
|
||||
conditionEvaluator_->SetAdditionalDataPaths(additionalDataPaths_);
|
||||
}
|
||||
|
||||
@@ -204,6 +211,8 @@ GameType Game::GetType() const { return type_; }
|
||||
std::filesystem::path Game::DataPath() const {
|
||||
if (type_ == GameType::tes3) {
|
||||
return gamePath_ / "Data Files";
|
||||
} else if (type_ == GameType::openmw) {
|
||||
return gamePath_ / "resources" / "vfs";
|
||||
} else {
|
||||
return gamePath_ / "Data";
|
||||
}
|
||||
@@ -237,7 +246,8 @@ void Game::SetAdditionalDataPaths(
|
||||
}
|
||||
|
||||
bool Game::IsValidPlugin(const std::filesystem::path& pluginPath) const {
|
||||
return Plugin::IsValid(GetType(), ResolvePluginPath(DataPath(), pluginPath));
|
||||
return Plugin::IsValid(GetType(),
|
||||
ResolvePluginPath(GetType(), DataPath(), pluginPath));
|
||||
}
|
||||
|
||||
void Game::LoadPlugins(const std::vector<std::filesystem::path>& pluginPaths,
|
||||
@@ -285,7 +295,12 @@ void Game::LoadPlugins(const std::vector<std::filesystem::path>& pluginPaths,
|
||||
logger->trace("Starting plugin loading.");
|
||||
}
|
||||
|
||||
const auto masterPath = DataPath() / u8path(masterFilename_);
|
||||
const auto masterPath = GetType() == GameType::openmw
|
||||
? FindPlugin(GetType(),
|
||||
DataPath(),
|
||||
GetAdditionalDataPaths(),
|
||||
masterFilename_)
|
||||
: DataPath() / u8path(masterFilename_);
|
||||
std::for_each(
|
||||
std::execution::par_unseq,
|
||||
pluginPaths.begin(),
|
||||
@@ -293,7 +308,7 @@ void Game::LoadPlugins(const std::vector<std::filesystem::path>& pluginPaths,
|
||||
[&](const std::filesystem::path& pluginPath) {
|
||||
try {
|
||||
const auto resolvedPluginPath =
|
||||
ResolvePluginPath(DataPath(), pluginPath);
|
||||
ResolvePluginPath(GetType(), DataPath(), pluginPath);
|
||||
|
||||
const bool loadHeader =
|
||||
loadHeadersOnly ||
|
||||
@@ -312,7 +327,8 @@ void Game::LoadPlugins(const std::vector<std::filesystem::path>& pluginPaths,
|
||||
});
|
||||
|
||||
if (!loadHeadersOnly &&
|
||||
(GetType() == GameType::tes3 || GetType() == GameType::starfield)) {
|
||||
(GetType() == GameType::tes3 || GetType() == GameType::openmw ||
|
||||
GetType() == GameType::starfield)) {
|
||||
auto plugins = cache_.GetPlugins();
|
||||
const auto pluginsMetadata = Plugin::GetPluginsMetadata(plugins);
|
||||
for (auto& plugin : plugins) {
|
||||
|
||||
@@ -50,6 +50,8 @@ unsigned int mapGameId(GameType gameType) {
|
||||
return LIBLO_GAME_FO4VR;
|
||||
case GameType::starfield:
|
||||
return LIBLO_GAME_STARFIELD;
|
||||
case GameType::openmw:
|
||||
return LIBLO_GAME_OPENMW;
|
||||
default:
|
||||
throw std::logic_error("Unexpected game type");
|
||||
}
|
||||
@@ -208,6 +210,28 @@ std::filesystem::path LoadOrderHandler::GetActivePluginsFilePath() const {
|
||||
return filePath;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> LoadOrderHandler::GetAdditionalDataPaths() const {
|
||||
const auto logger = getLogger();
|
||||
if (logger) {
|
||||
logger->trace("Getting additional data paths.");
|
||||
}
|
||||
|
||||
char** pathArr = nullptr;
|
||||
size_t pathArrSize = 0;
|
||||
|
||||
const unsigned int ret = lo_get_additional_plugins_directories(gh_.get(), &pathArr, &pathArrSize);
|
||||
|
||||
HandleError("get additional data paths", ret);
|
||||
|
||||
std::vector<std::filesystem::path> loadOrder;
|
||||
for (size_t i = 0; i < pathArrSize; i += 1) {
|
||||
loadOrder.push_back(std::filesystem::u8path(std::string(pathArr[i])));
|
||||
}
|
||||
lo_free_string_array(pathArr, pathArrSize);
|
||||
|
||||
return loadOrder;
|
||||
}
|
||||
|
||||
void LoadOrderHandler::SetLoadOrder(
|
||||
const std::vector<std::string>& loadOrder) const {
|
||||
auto logger = getLogger();
|
||||
|
||||
@@ -51,6 +51,8 @@ public:
|
||||
|
||||
std::filesystem::path GetActivePluginsFilePath() const;
|
||||
|
||||
std::vector<std::filesystem::path> GetAdditionalDataPaths() const;
|
||||
|
||||
bool IsPluginActive(const std::string& pluginName) const;
|
||||
|
||||
void SetLoadOrder(const std::vector<std::string>& loadOrder) const;
|
||||
|
||||
@@ -51,7 +51,8 @@ void HandleError(const std::string operation, int returnCode) {
|
||||
logger->error(err);
|
||||
}
|
||||
|
||||
throw ConditionSyntaxError(returnCode, loot_condition_interpreter_category(), err);
|
||||
throw ConditionSyntaxError(
|
||||
returnCode, loot_condition_interpreter_category(), err);
|
||||
}
|
||||
|
||||
int mapGameType(GameType gameType) {
|
||||
@@ -76,6 +77,8 @@ int mapGameType(GameType gameType) {
|
||||
return LCI_GAME_FALLOUT_4_VR;
|
||||
case GameType::starfield:
|
||||
return LCI_GAME_STARFIELD;
|
||||
case GameType::openmw:
|
||||
return LCI_GAME_OPENMW;
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"Unrecognised game type encountered while mapping for condition "
|
||||
@@ -90,9 +93,8 @@ ConditionEvaluator::ConditionEvaluator(const GameType gameType,
|
||||
lci_state_destroy)) {
|
||||
lci_state* state = nullptr;
|
||||
|
||||
int result = lci_state_create(&state,
|
||||
mapGameType(gameType),
|
||||
dataPath.u8string().c_str());
|
||||
int result = lci_state_create(
|
||||
&state, mapGameType(gameType), dataPath.u8string().c_str());
|
||||
HandleError("create state object for condition evaluation", result);
|
||||
|
||||
lciState_ = std::unique_ptr<lci_state, decltype(&lci_state_destroy)>(
|
||||
|
||||
+102
-14
@@ -135,6 +135,7 @@ std::vector<std::filesystem::path> FindAssociatedArchives(
|
||||
const std::filesystem::path& pluginPath) {
|
||||
switch (gameType) {
|
||||
case GameType::tes3:
|
||||
case GameType::openmw:
|
||||
return {};
|
||||
case GameType::tes5:
|
||||
// Skyrim (non-SE) plugins can only load BSAs that have exactly the same
|
||||
@@ -229,18 +230,24 @@ Plugin::Plugin(const GameType gameType,
|
||||
const GameCache& gameCache,
|
||||
std::filesystem::path pluginPath,
|
||||
const bool headerOnly) :
|
||||
name_(TrimDotGhostExtension(pluginPath.filename().u8string())),
|
||||
name_(gameType == GameType::openmw
|
||||
? pluginPath.filename().u8string()
|
||||
: TrimDotGhostExtension(pluginPath.filename().u8string())),
|
||||
esPlugin(
|
||||
std::unique_ptr<::Plugin, decltype(&esp_plugin_free)>(nullptr,
|
||||
esp_plugin_free)),
|
||||
ignoreMasterFlag_(gameType == GameType::openmw),
|
||||
isEmpty_(true) {
|
||||
auto logger = getLogger();
|
||||
|
||||
try {
|
||||
Load(pluginPath, gameType, headerOnly);
|
||||
if (gameType != GameType::openmw ||
|
||||
pluginPath.extension() != ".omwscripts") {
|
||||
Load(pluginPath, gameType, headerOnly);
|
||||
|
||||
auto ret = esp_plugin_is_empty(esPlugin.get(), &isEmpty_);
|
||||
HandleEspluginError(ret, "check if \"{}\" is empty", name_);
|
||||
auto ret = esp_plugin_is_empty(esPlugin.get(), &isEmpty_);
|
||||
HandleEspluginError(ret, "check if \"{}\" is empty", name_);
|
||||
}
|
||||
|
||||
archivePaths_ = FindAssociatedArchives(gameType, gameCache, pluginPath);
|
||||
|
||||
@@ -284,6 +291,10 @@ Plugin::Plugin(const GameType gameType,
|
||||
}
|
||||
|
||||
void Plugin::ResolveRecordIds(Vec_PluginMetadata* pluginsMetadata) const {
|
||||
if (esPlugin == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto ret = esp_plugin_resolve_record_ids(esPlugin.get(), pluginsMetadata);
|
||||
HandleEspluginError(ret, "resolve the record IDs of \"{}\"", name_);
|
||||
}
|
||||
@@ -291,6 +302,10 @@ void Plugin::ResolveRecordIds(Vec_PluginMetadata* pluginsMetadata) const {
|
||||
std::string Plugin::GetName() const { return name_; }
|
||||
|
||||
std::optional<float> Plugin::GetHeaderVersion() const {
|
||||
if (esPlugin == nullptr) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
float version = 0.0f;
|
||||
|
||||
const auto ret = esp_plugin_header_version(esPlugin.get(), &version);
|
||||
@@ -308,6 +323,10 @@ std::optional<std::string> Plugin::GetVersion() const {
|
||||
}
|
||||
|
||||
std::vector<std::string> Plugin::GetMasters() const {
|
||||
if (esPlugin == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
char** masters = nullptr;
|
||||
size_t numMasters = 0;
|
||||
const auto ret = esp_plugin_masters(esPlugin.get(), &masters, &numMasters);
|
||||
@@ -325,6 +344,10 @@ std::vector<Tag> Plugin::GetBashTags() const { return tags_; }
|
||||
std::optional<uint32_t> Plugin::GetCRC() const { return crc_; }
|
||||
|
||||
bool Plugin::IsMaster() const {
|
||||
if (ignoreMasterFlag_ || esPlugin == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isMaster = false;
|
||||
const auto ret = esp_plugin_is_master(esPlugin.get(), &isMaster);
|
||||
HandleEspluginError(ret, "check if \"{}\" is a master", name_);
|
||||
@@ -333,6 +356,10 @@ bool Plugin::IsMaster() const {
|
||||
}
|
||||
|
||||
bool Plugin::IsLightPlugin() const {
|
||||
if (esPlugin == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isLightPlugin = false;
|
||||
const auto ret = esp_plugin_is_light_plugin(esPlugin.get(), &isLightPlugin);
|
||||
HandleEspluginError(ret, "check if \"{}\" is a light plugin", name_);
|
||||
@@ -341,6 +368,10 @@ bool Plugin::IsLightPlugin() const {
|
||||
}
|
||||
|
||||
bool Plugin::IsMediumPlugin() const {
|
||||
if (esPlugin == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isMediumPlugin = false;
|
||||
const auto ret = esp_plugin_is_medium_plugin(esPlugin.get(), &isMediumPlugin);
|
||||
HandleEspluginError(ret, "check if \"{}\" is a medium plugin", name_);
|
||||
@@ -349,6 +380,10 @@ bool Plugin::IsMediumPlugin() const {
|
||||
}
|
||||
|
||||
bool Plugin::IsUpdatePlugin() const {
|
||||
if (esPlugin == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isUpdatePlugin = false;
|
||||
const auto ret = esp_plugin_is_update_plugin(esPlugin.get(), &isUpdatePlugin);
|
||||
HandleEspluginError(ret, "check if \"{}\" is an update plugin", name_);
|
||||
@@ -357,6 +392,10 @@ bool Plugin::IsUpdatePlugin() const {
|
||||
}
|
||||
|
||||
bool Plugin::IsBlueprintPlugin() const {
|
||||
if (esPlugin == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isBlueprintPlugin = false;
|
||||
const auto ret =
|
||||
esp_plugin_is_blueprint_plugin(esPlugin.get(), &isBlueprintPlugin);
|
||||
@@ -366,6 +405,10 @@ bool Plugin::IsBlueprintPlugin() const {
|
||||
}
|
||||
|
||||
bool Plugin::IsValidAsLightPlugin() const {
|
||||
if (esPlugin == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isValid = false;
|
||||
const auto ret =
|
||||
esp_plugin_is_valid_as_light_plugin(esPlugin.get(), &isValid);
|
||||
@@ -375,6 +418,10 @@ bool Plugin::IsValidAsLightPlugin() const {
|
||||
}
|
||||
|
||||
bool Plugin::IsValidAsMediumPlugin() const {
|
||||
if (esPlugin == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isValid = false;
|
||||
const auto ret =
|
||||
esp_plugin_is_valid_as_medium_plugin(esPlugin.get(), &isValid);
|
||||
@@ -385,6 +432,10 @@ bool Plugin::IsValidAsMediumPlugin() const {
|
||||
}
|
||||
|
||||
bool Plugin::IsValidAsUpdatePlugin() const {
|
||||
if (esPlugin == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool isValid = false;
|
||||
const auto ret =
|
||||
esp_plugin_is_valid_as_update_plugin(esPlugin.get(), &isValid);
|
||||
@@ -399,9 +450,17 @@ bool Plugin::IsEmpty() const { return isEmpty_; }
|
||||
bool Plugin::LoadsArchive() const { return !archivePaths_.empty(); }
|
||||
|
||||
bool Plugin::DoRecordsOverlap(const PluginInterface& plugin) const {
|
||||
if (esPlugin == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
auto& otherPlugin = dynamic_cast<const Plugin&>(plugin);
|
||||
|
||||
if (otherPlugin.esPlugin == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool doPluginsOverlap = false;
|
||||
const auto ret = esp_plugin_do_records_overlap(
|
||||
esPlugin.get(), otherPlugin.esPlugin.get(), &doPluginsOverlap);
|
||||
@@ -424,6 +483,10 @@ bool Plugin::DoRecordsOverlap(const PluginInterface& plugin) const {
|
||||
}
|
||||
|
||||
size_t Plugin::GetOverrideRecordCount() const {
|
||||
if (esPlugin == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
size_t overrideRecordCount;
|
||||
const auto ret =
|
||||
esp_plugin_count_override_records(esPlugin.get(), &overrideRecordCount);
|
||||
@@ -433,6 +496,10 @@ size_t Plugin::GetOverrideRecordCount() const {
|
||||
}
|
||||
|
||||
uint32_t Plugin::GetRecordAndGroupCount() const {
|
||||
if (esPlugin == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t recordAndGroupCount = 0;
|
||||
const auto ret =
|
||||
esp_plugin_record_and_group_count(esPlugin.get(), &recordAndGroupCount);
|
||||
@@ -515,6 +582,10 @@ void Plugin::Load(const std::filesystem::path& path,
|
||||
}
|
||||
|
||||
std::string Plugin::GetDescription() const {
|
||||
if (esPlugin == nullptr) {
|
||||
return "";
|
||||
}
|
||||
|
||||
char* description = nullptr;
|
||||
const auto ret = esp_plugin_description(esPlugin.get(), &description);
|
||||
HandleEspluginError(ret, "read the description of \"{}\"", name_);
|
||||
@@ -530,7 +601,7 @@ std::string Plugin::GetDescription() const {
|
||||
}
|
||||
|
||||
std::unique_ptr<Vec_PluginMetadata, decltype(&esp_plugins_metadata_free)>
|
||||
Plugin::GetPluginsMetadata(std::vector<const Plugin*> plugins) {
|
||||
Plugin::GetPluginsMetadata(const std::vector<const Plugin*>& plugins) {
|
||||
if (plugins.empty()) {
|
||||
return std::unique_ptr<Vec_PluginMetadata,
|
||||
decltype(&esp_plugins_metadata_free)>(
|
||||
@@ -540,7 +611,10 @@ Plugin::GetPluginsMetadata(std::vector<const Plugin*> plugins) {
|
||||
std::vector<const ::Plugin*> esPlugins;
|
||||
esPlugins.reserve(plugins.size());
|
||||
for (const auto& plugin : plugins) {
|
||||
esPlugins.push_back(plugin->esPlugin.get());
|
||||
const auto esPlugin = plugin->esPlugin.get();
|
||||
if (esPlugin != nullptr) {
|
||||
esPlugins.push_back(plugin->esPlugin.get());
|
||||
}
|
||||
}
|
||||
|
||||
Vec_PluginMetadata* pluginsMetadata = nullptr;
|
||||
@@ -566,6 +640,7 @@ std::string GetArchiveFileExtension(const GameType gameType) {
|
||||
unsigned int Plugin::GetEspluginGameId(GameType gameType) {
|
||||
switch (gameType) {
|
||||
case GameType::tes3:
|
||||
case GameType::openmw:
|
||||
return ESP_GAME_MORROWIND;
|
||||
case GameType::tes4:
|
||||
return ESP_GAME_OBLIVION;
|
||||
@@ -589,18 +664,31 @@ unsigned int Plugin::GetEspluginGameId(GameType gameType) {
|
||||
}
|
||||
|
||||
bool hasPluginFileExtension(std::string filename, GameType gameType) {
|
||||
if (boost::iends_with(filename, GHOST_FILE_EXTENSION)) {
|
||||
if (gameType != GameType::openmw &&
|
||||
boost::iends_with(filename, GHOST_FILE_EXTENSION)) {
|
||||
filename =
|
||||
filename.substr(0, filename.length() - GHOST_FILE_EXTENSION_LENGTH);
|
||||
}
|
||||
|
||||
bool isEspOrEsm = boost::iends_with(filename, ".esp") ||
|
||||
boost::iends_with(filename, ".esm");
|
||||
bool isEsl = (gameType == GameType::fo4 || gameType == GameType::fo4vr ||
|
||||
gameType == GameType::tes5se || gameType == GameType::tes5vr ||
|
||||
gameType == GameType::starfield) &&
|
||||
boost::iends_with(filename, ".esl");
|
||||
if (boost::iends_with(filename, ".esp") ||
|
||||
boost::iends_with(filename, ".esm")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return isEspOrEsm || isEsl;
|
||||
if (gameType == GameType::openmw &&
|
||||
(boost::iends_with(filename, ".omwaddon") ||
|
||||
boost::iends_with(filename, ".omwgame") ||
|
||||
boost::iends_with(filename, ".omwscripts"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ((gameType == GameType::fo4 || gameType == GameType::fo4vr ||
|
||||
gameType == GameType::tes5se || gameType == GameType::tes5vr ||
|
||||
gameType == GameType::starfield) &&
|
||||
boost::iends_with(filename, ".esl")) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -93,8 +93,7 @@ public:
|
||||
|
||||
static std::unique_ptr<Vec_PluginMetadata,
|
||||
decltype(&esp_plugins_metadata_free)>
|
||||
GetPluginsMetadata(
|
||||
std::vector<const Plugin*>);
|
||||
GetPluginsMetadata(const std::vector<const Plugin*>& plugins);
|
||||
|
||||
private:
|
||||
void Load(const std::filesystem::path& path,
|
||||
@@ -106,8 +105,10 @@ private:
|
||||
|
||||
std::string name_;
|
||||
std::unique_ptr<::Plugin, decltype(&esp_plugin_free)> esPlugin;
|
||||
bool isEmpty_; // Does the plugin contain any records other than the TES4
|
||||
// header?
|
||||
bool ignoreMasterFlag_{false};
|
||||
bool isEmpty_{false}; // Does the plugin contain any records other than the
|
||||
// TES4
|
||||
// header?
|
||||
std::optional<std::string> version_; // Obtained from description field.
|
||||
std::optional<uint32_t> crc_;
|
||||
std::vector<Tag> tags_;
|
||||
|
||||
@@ -95,7 +95,8 @@ INSTANTIATE_TEST_SUITE_P(,
|
||||
GameType::fo4vr,
|
||||
GameType::tes5vr,
|
||||
GameType::tes3,
|
||||
GameType::starfield));
|
||||
GameType::starfield,
|
||||
GameType::openmw));
|
||||
|
||||
TEST_P(CreateGameHandleTest,
|
||||
shouldSucceedIfPassedValidParametersWithRelativePaths) {
|
||||
@@ -124,7 +125,7 @@ TEST_P(CreateGameHandleTest, shouldSucceedIfPassedALocalPathThatDoesNotExist) {
|
||||
|
||||
TEST_P(CreateGameHandleTest, shouldThrowIfPassedALocalPathThatIsNotADirectory) {
|
||||
EXPECT_THROW(CreateGameHandle(GetParam(), gamePath, dataPath / blankEsm),
|
||||
std::invalid_argument);
|
||||
std::invalid_argument);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
@@ -83,7 +83,8 @@ INSTANTIATE_TEST_SUITE_P(,
|
||||
GameType::fo4vr,
|
||||
GameType::tes5vr,
|
||||
GameType::tes3,
|
||||
GameType::starfield));
|
||||
GameType::starfield,
|
||||
GameType::openmw));
|
||||
|
||||
TEST_P(GameInterfaceTest, setAdditionalDataPathsShouldDoThat) {
|
||||
const auto paths = std::vector<std::filesystem::path>{
|
||||
@@ -135,7 +136,16 @@ TEST_P(
|
||||
}
|
||||
|
||||
TEST_P(GameInterfaceTest, loadPluginsShouldTrimDotGhostFileExtensions) {
|
||||
handle_->LoadPlugins({blankMasterDependentEsm + ".ghost"}, true);
|
||||
if (GetParam() == GameType::openmw) {
|
||||
// Ghosting is not supported for OpenMW.
|
||||
EXPECT_THROW(
|
||||
handle_->LoadPlugins({blankMasterDependentEsm + ".ghost"}, true),
|
||||
std::invalid_argument);
|
||||
return;
|
||||
} else {
|
||||
handle_->LoadPlugins({blankMasterDependentEsm + ".ghost"}, true);
|
||||
}
|
||||
|
||||
EXPECT_EQ(1, handle_->GetLoadedPlugins().size());
|
||||
|
||||
ASSERT_NO_THROW(handle_->GetPlugin(blankMasterDependentEsm));
|
||||
@@ -245,11 +255,32 @@ TEST_P(GameInterfaceTest, getLoadOrderShouldReturnTheCurrentLoadOrder) {
|
||||
std::filesystem::remove(dataPath / std::filesystem::u8path(nonAsciiEsm));
|
||||
|
||||
// Set no additional data paths to avoid picking up non-test plugins on PCs
|
||||
// which have Starfield or Fallout 4 installed.
|
||||
handle_->SetAdditionalDataPaths({});
|
||||
// which have Starfield or Fallout 4 installed. Don't clear the additional
|
||||
// data paths for OpenMW because they come from test config.
|
||||
if (GetParam() != GameType::openmw) {
|
||||
handle_->SetAdditionalDataPaths({});
|
||||
}
|
||||
|
||||
handle_->LoadCurrentLoadOrderState();
|
||||
ASSERT_EQ(getLoadOrder(), handle_->GetLoadOrder());
|
||||
|
||||
if (GetParam() == GameType::openmw) {
|
||||
ASSERT_EQ(std::vector<std::string>({
|
||||
blankDifferentEsm,
|
||||
blankDifferentMasterDependentEsm,
|
||||
blankDifferentEsp,
|
||||
blankDifferentPluginDependentEsp,
|
||||
blankMasterDependentEsm,
|
||||
blankMasterDependentEsp,
|
||||
blankEsp,
|
||||
blankPluginDependentEsp,
|
||||
masterFile,
|
||||
blankEsm,
|
||||
blankDifferentMasterDependentEsp,
|
||||
}),
|
||||
handle_->GetLoadOrder());
|
||||
} else {
|
||||
ASSERT_EQ(getLoadOrder(), handle_->GetLoadOrder());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(GameInterfaceTest, setLoadOrderShouldSetTheLoadOrder) {
|
||||
@@ -257,8 +288,11 @@ TEST_P(GameInterfaceTest, setLoadOrderShouldSetTheLoadOrder) {
|
||||
std::filesystem::remove(dataPath / std::filesystem::u8path(nonAsciiEsm));
|
||||
|
||||
// Set no additional data paths to avoid picking up non-test plugins on PCs
|
||||
// which have Starfield or Fallout 4 installed.
|
||||
handle_->SetAdditionalDataPaths({});
|
||||
// which have Starfield or Fallout 4 installed. Don't clear the additional
|
||||
// data paths for OpenMW because they come from test config.
|
||||
if (GetParam() != GameType::openmw) {
|
||||
handle_->SetAdditionalDataPaths({});
|
||||
}
|
||||
|
||||
handle_->LoadCurrentLoadOrderState();
|
||||
|
||||
@@ -278,6 +312,20 @@ TEST_P(GameInterfaceTest, setLoadOrderShouldSetTheLoadOrder) {
|
||||
blankEsp,
|
||||
blankMasterDependentEsp,
|
||||
};
|
||||
} else if (GetParam() == GameType::openmw) {
|
||||
loadOrder = {
|
||||
blankDifferentMasterDependentEsm,
|
||||
blankDifferentPluginDependentEsp,
|
||||
blankDifferentEsm,
|
||||
blankDifferentEsp,
|
||||
blankMasterDependentEsm,
|
||||
blankMasterDependentEsp,
|
||||
blankPluginDependentEsp,
|
||||
blankEsp,
|
||||
masterFile,
|
||||
blankDifferentMasterDependentEsp,
|
||||
blankEsm,
|
||||
};
|
||||
} else {
|
||||
loadOrder = {
|
||||
masterFile,
|
||||
@@ -302,11 +350,15 @@ TEST_P(GameInterfaceTest, setLoadOrderShouldSetTheLoadOrder) {
|
||||
|
||||
EXPECT_EQ(loadOrder, handle_->GetLoadOrder());
|
||||
|
||||
if (gameSupportsEsl) {
|
||||
loadOrder.erase(std::begin(loadOrder));
|
||||
}
|
||||
// It's not possible to persist the load order of inactive plugins for
|
||||
// OpenMW.
|
||||
if (GetParam() != GameType::openmw) {
|
||||
if (gameSupportsEsl) {
|
||||
loadOrder.erase(std::begin(loadOrder));
|
||||
}
|
||||
|
||||
EXPECT_EQ(loadOrder, getLoadOrder());
|
||||
EXPECT_EQ(loadOrder, getLoadOrder());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,8 @@ INSTANTIATE_TEST_SUITE_P(,
|
||||
GameType::fo4vr,
|
||||
GameType::tes5vr,
|
||||
GameType::tes3,
|
||||
GameType::starfield));
|
||||
GameType::starfield,
|
||||
GameType::openmw));
|
||||
|
||||
TEST_P(GameTest, constructingShouldStoreTheGivenValues) {
|
||||
Game game = Game(GetParam(), gamePath, localPath);
|
||||
@@ -70,7 +71,7 @@ TEST_P(GameTest, constructingShouldStoreTheGivenValues) {
|
||||
#ifndef _WIN32
|
||||
TEST_P(GameTest,
|
||||
constructingShouldThrowOnLinuxIfLocalPathIsNotGivenExceptForMorrowind) {
|
||||
if (GetParam() == GameType::tes3) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
EXPECT_NO_THROW(Game(GetParam(), gamePath));
|
||||
} else {
|
||||
EXPECT_THROW(Game(GetParam(), gamePath), std::system_error);
|
||||
@@ -118,6 +119,9 @@ TEST_P(
|
||||
"My Games" / "Starfield" / "Data";
|
||||
EXPECT_TRUE(boost::ends_with(game.GetAdditionalDataPaths()[0].u8string(),
|
||||
expectedSuffix.u8string()));
|
||||
} else if (GetParam() == GameType::openmw) {
|
||||
EXPECT_EQ(std::vector<std::filesystem::path>{localPath / "data"},
|
||||
game.GetAdditionalDataPaths());
|
||||
} else {
|
||||
EXPECT_TRUE(game.GetAdditionalDataPaths().empty());
|
||||
}
|
||||
@@ -195,6 +199,23 @@ TEST_P(GameTest, isValidPluginShouldUseAbsolutePathsAsGiven) {
|
||||
EXPECT_TRUE(game.IsValidPlugin(path));
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
GameTest,
|
||||
isValidPluginShouldTryGhostedPathIfGivenPluginDoesNotExistExceptForOpenMW) {
|
||||
const Game game(GetParam(), gamePath, localPath);
|
||||
|
||||
if (GetParam() == GameType::openmw) {
|
||||
// This wasn't done for OpenMW during common setup.
|
||||
const auto pluginPath =
|
||||
game.DataPath() / (blankMasterDependentEsm + ".ghost");
|
||||
std::filesystem::rename(dataPath / blankMasterDependentEsm, pluginPath);
|
||||
|
||||
EXPECT_FALSE(game.IsValidPlugin(blankMasterDependentEsm));
|
||||
} else {
|
||||
EXPECT_TRUE(game.IsValidPlugin(blankMasterDependentEsm));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
GameTest,
|
||||
loadPluginsWithHeadersOnlyTrueShouldLoadTheHeadersOfAllInstalledPlugins) {
|
||||
@@ -289,8 +310,8 @@ TEST_P(GameTest, loadPluginsShouldFindArchivesInAdditionalDataPaths) {
|
||||
archiveFileExtension);
|
||||
const auto ba2Path2 =
|
||||
gamePath / ("../../Fallout 4- Nuka-World (PC)/Content/Data/DLCNukaWorld "
|
||||
"- Voices_it" +
|
||||
archiveFileExtension);
|
||||
"- Voices_it" +
|
||||
archiveFileExtension);
|
||||
touch(ba2Path1);
|
||||
touch(ba2Path2);
|
||||
|
||||
@@ -371,10 +392,11 @@ TEST_P(
|
||||
|
||||
std::filesystem::remove(dataPath / pluginName);
|
||||
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::starfield) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw ||
|
||||
GetParam() == GameType::starfield) {
|
||||
try {
|
||||
game.LoadPlugins({blankMasterDependentEsm}, false);
|
||||
FAIL();
|
||||
FAIL();
|
||||
} catch (const std::system_error& e) {
|
||||
EXPECT_EQ(ESP_ERROR_PLUGIN_METADATA_NOT_FOUND, e.code().value());
|
||||
EXPECT_EQ(esplugin_category(), e.code().category());
|
||||
|
||||
@@ -57,6 +57,8 @@ protected:
|
||||
|
||||
std::vector<std::string> getEarlyLoadingPlugins() {
|
||||
switch (GetParam()) {
|
||||
case GameType::openmw:
|
||||
return {"builtin.omwscripts"};
|
||||
case GameType::tes5:
|
||||
return {"Skyrim.esm"};
|
||||
case GameType::tes5se:
|
||||
@@ -111,7 +113,8 @@ INSTANTIATE_TEST_SUITE_P(,
|
||||
GameType::fo3,
|
||||
GameType::fonv,
|
||||
GameType::fo4,
|
||||
GameType::tes5se));
|
||||
GameType::tes5se,
|
||||
GameType::openmw));
|
||||
|
||||
TEST_P(LoadOrderHandlerTest, constructorShouldThrowIfNoGamePathIsSet) {
|
||||
EXPECT_THROW(LoadOrderHandler(GetParam(), ""), std::invalid_argument);
|
||||
@@ -129,7 +132,7 @@ TEST_P(LoadOrderHandlerTest, constructorShouldNotThrowIfNoLocalPathIsSet) {
|
||||
#else
|
||||
TEST_P(LoadOrderHandlerTest,
|
||||
constructorShouldNotThrowIfNoLocalPathIsSetAndGameTypeIsMorrowind) {
|
||||
if (GetParam() == GameType::tes3) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
EXPECT_NO_THROW(LoadOrderHandler(GetParam(), gamePath));
|
||||
} else {
|
||||
EXPECT_THROW(LoadOrderHandler(GetParam(), gamePath), std::system_error);
|
||||
@@ -179,7 +182,24 @@ TEST_P(LoadOrderHandlerTest, getLoadOrderShouldReturnTheCurrentLoadOrder) {
|
||||
auto loadOrderHandler = createHandler();
|
||||
loadOrderHandler.LoadCurrentState();
|
||||
|
||||
ASSERT_EQ(getLoadOrder(), loadOrderHandler.GetLoadOrder());
|
||||
if (GetParam() == GameType::openmw) {
|
||||
EXPECT_EQ(std::vector<std::string>({
|
||||
blankDifferentEsm,
|
||||
blankDifferentMasterDependentEsm,
|
||||
blankDifferentEsp,
|
||||
blankDifferentPluginDependentEsp,
|
||||
blankMasterDependentEsm,
|
||||
blankMasterDependentEsp,
|
||||
blankEsp,
|
||||
blankPluginDependentEsp,
|
||||
masterFile,
|
||||
blankEsm,
|
||||
blankDifferentMasterDependentEsp,
|
||||
}),
|
||||
loadOrderHandler.GetLoadOrder());
|
||||
} else {
|
||||
ASSERT_EQ(getLoadOrder(), loadOrderHandler.GetLoadOrder());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(LoadOrderHandlerTest,
|
||||
@@ -209,6 +229,45 @@ TEST_P(LoadOrderHandlerTest,
|
||||
loadOrderHandler.GetEarlyLoadingPlugins());
|
||||
}
|
||||
|
||||
TEST_P(LoadOrderHandlerTest, getAdditionalDataPathsShouldReturnValidData) {
|
||||
if (GetParam() == GameType::fo4) {
|
||||
// Create the file that indicates it's a Microsoft Store install.
|
||||
touch(gamePath / "appxmanifest.xml");
|
||||
}
|
||||
|
||||
auto loadOrderHandler = createHandler();
|
||||
|
||||
if (GetParam() == GameType::fo4) {
|
||||
const auto basePath = gamePath / ".." / "..";
|
||||
EXPECT_EQ(std::vector<std::filesystem::path>(
|
||||
{basePath / "Fallout 4- Automatron (PC)" / "Content" / "Data",
|
||||
basePath / "Fallout 4- Nuka-World (PC)" / "Content" / "Data",
|
||||
basePath / "Fallout 4- Wasteland Workshop (PC)" / "Content" /
|
||||
"Data",
|
||||
basePath / "Fallout 4- High Resolution Texture Pack" /
|
||||
"Content" / "Data",
|
||||
basePath / "Fallout 4- Vault-Tec Workshop (PC)" / "Content" /
|
||||
"Data",
|
||||
basePath / "Fallout 4- Far Harbor (PC)" / "Content" / "Data",
|
||||
basePath / "Fallout 4- Contraptions Workshop (PC)" /
|
||||
"Content" / "Data"}),
|
||||
loadOrderHandler.GetAdditionalDataPaths());
|
||||
} else if (GetParam() == GameType::starfield) {
|
||||
ASSERT_EQ(1, loadOrderHandler.GetAdditionalDataPaths().size());
|
||||
|
||||
const auto expectedSuffix = std::filesystem::u8path("Documents") /
|
||||
"My Games" / "Starfield" / "Data";
|
||||
EXPECT_TRUE(boost::ends_with(
|
||||
loadOrderHandler.GetAdditionalDataPaths()[0].u8string(),
|
||||
expectedSuffix.u8string()));
|
||||
} else if (GetParam() == GameType::openmw) {
|
||||
EXPECT_EQ(std::vector<std::filesystem::path>{localPath / "data"},
|
||||
loadOrderHandler.GetAdditionalDataPaths());
|
||||
} else {
|
||||
EXPECT_TRUE(loadOrderHandler.GetAdditionalDataPaths().empty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(LoadOrderHandlerTest, setLoadOrderShouldSetTheLoadOrder) {
|
||||
auto loadOrderHandler = createHandler();
|
||||
loadOrderHandler.LoadCurrentState();
|
||||
@@ -218,7 +277,14 @@ TEST_P(LoadOrderHandlerTest, setLoadOrderShouldSetTheLoadOrder) {
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se)
|
||||
loadOrderToSet_.erase(begin(loadOrderToSet_));
|
||||
|
||||
EXPECT_EQ(loadOrderToSet_, getLoadOrder());
|
||||
if (GetParam() == GameType::openmw) {
|
||||
// Can't set the load order positions of inactive plugins,
|
||||
// this reads what libloadorder has cached in memory instead of
|
||||
// what was actually saved.
|
||||
EXPECT_EQ(loadOrderToSet_, loadOrderHandler.GetLoadOrder());
|
||||
} else {
|
||||
EXPECT_EQ(loadOrderToSet_, getLoadOrder());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(LoadOrderHandlerTest, setExternalPluginPathsShouldAcceptAnEmptyVector) {
|
||||
|
||||
@@ -91,7 +91,8 @@ INSTANTIATE_TEST_SUITE_P(,
|
||||
GameType::fo3,
|
||||
GameType::fonv,
|
||||
GameType::fo4,
|
||||
GameType::tes5se));
|
||||
GameType::tes5se,
|
||||
GameType::openmw));
|
||||
|
||||
TEST_P(ConditionEvaluatorTest,
|
||||
evaluateShouldReturnTrueForAnEmptyConditionString) {
|
||||
|
||||
@@ -85,7 +85,7 @@ protected:
|
||||
dataPath / blankMasterDependentArchive);
|
||||
ASSERT_TRUE(
|
||||
std::filesystem::exists(dataPath / blankMasterDependentArchive));
|
||||
} else if (GetParam() == GameType::tes3) {
|
||||
} else if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
touch(dataPath / blankArchive);
|
||||
|
||||
blankMasterDependentArchive = "Blank - Master Dependent.bsa";
|
||||
@@ -191,7 +191,26 @@ INSTANTIATE_TEST_SUITE_P(,
|
||||
GameType::fo4vr,
|
||||
GameType::tes5vr,
|
||||
GameType::tes3,
|
||||
GameType::starfield));
|
||||
GameType::starfield,
|
||||
GameType::openmw));
|
||||
|
||||
TEST_P(PluginTest, constructorShouldTrimGhostExtensionExceptForOpenMW) {
|
||||
const auto pluginPath =
|
||||
game_.DataPath() / (blankMasterDependentEsm + ".ghost");
|
||||
|
||||
if (GetParam() == GameType::openmw) {
|
||||
// This wasn't done for OpenMW during common setup.
|
||||
std::filesystem::rename(dataPath / blankMasterDependentEsm, pluginPath);
|
||||
}
|
||||
|
||||
Plugin plugin(game_.GetType(), game_.GetCache(), pluginPath, true);
|
||||
|
||||
if (GetParam() == GameType::openmw) {
|
||||
EXPECT_EQ(pluginPath.filename().u8string(), plugin.GetName());
|
||||
} else {
|
||||
EXPECT_EQ(blankMasterDependentEsm, plugin.GetName());
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PluginTest, loadingShouldHandleNonAsciiFilenamesCorrectly) {
|
||||
Plugin plugin(game_.GetType(),
|
||||
@@ -209,11 +228,15 @@ TEST_P(PluginTest, loadingHeaderOnlyShouldReadHeaderData) {
|
||||
|
||||
EXPECT_EQ(blankEsm, plugin.GetName());
|
||||
EXPECT_TRUE(plugin.GetMasters().empty());
|
||||
EXPECT_TRUE(plugin.IsMaster());
|
||||
if (GetParam() == GameType::openmw) {
|
||||
EXPECT_FALSE(plugin.IsMaster());
|
||||
} else {
|
||||
EXPECT_TRUE(plugin.IsMaster());
|
||||
}
|
||||
EXPECT_FALSE(plugin.IsEmpty());
|
||||
EXPECT_EQ("5.0", plugin.GetVersion());
|
||||
|
||||
if (GetParam() == GameType::tes3) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
EXPECT_FLOAT_EQ(1.2f, plugin.GetHeaderVersion().value());
|
||||
} else if (GetParam() == GameType::tes4) {
|
||||
EXPECT_FLOAT_EQ(0.8f, plugin.GetHeaderVersion().value());
|
||||
@@ -237,11 +260,15 @@ TEST_P(PluginTest, loadingWholePluginShouldReadHeaderData) {
|
||||
|
||||
EXPECT_EQ(blankEsm, plugin.GetName());
|
||||
EXPECT_TRUE(plugin.GetMasters().empty());
|
||||
EXPECT_TRUE(plugin.IsMaster());
|
||||
if (GetParam() == GameType::openmw) {
|
||||
EXPECT_FALSE(plugin.IsMaster());
|
||||
} else {
|
||||
EXPECT_TRUE(plugin.IsMaster());
|
||||
}
|
||||
EXPECT_FALSE(plugin.IsEmpty());
|
||||
EXPECT_EQ("5.0", plugin.GetVersion());
|
||||
|
||||
if (GetParam() == GameType::tes3) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
EXPECT_FLOAT_EQ(1.2f, plugin.GetHeaderVersion().value());
|
||||
} else if (GetParam() == GameType::tes4) {
|
||||
EXPECT_FLOAT_EQ(0.8f, plugin.GetHeaderVersion().value());
|
||||
@@ -253,12 +280,13 @@ TEST_P(PluginTest, loadingWholePluginShouldReadHeaderData) {
|
||||
}
|
||||
|
||||
TEST_P(PluginTest, loadingWholePluginShouldReadFields) {
|
||||
Plugin plugin(game_.GetType(),
|
||||
game_.GetCache(),
|
||||
game_.DataPath() / (blankMasterDependentEsm + ".ghost"),
|
||||
false);
|
||||
const auto pluginName = GetParam() == GameType::openmw
|
||||
? blankMasterDependentEsm
|
||||
: blankMasterDependentEsm + ".ghost";
|
||||
Plugin plugin(
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / pluginName, false);
|
||||
|
||||
if (GetParam() == GameType::tes3) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
Plugin master(
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsm, false);
|
||||
const auto pluginsMetadata = Plugin::GetPluginsMetadata({&master});
|
||||
@@ -297,6 +325,30 @@ TEST_P(PluginTest, loadingANonMasterPluginShouldReadTheMasterFlagAsFalse) {
|
||||
EXPECT_FALSE(plugin.IsMaster());
|
||||
}
|
||||
|
||||
TEST_P(PluginTest, loadingWholePluginShouldSucceedForOpenMWPlugins) {
|
||||
const auto omwgame = "Blank.omwgame";
|
||||
const auto omwaddon = "Blank.omwaddon";
|
||||
const auto omwscripts = "Blank.omwscripts";
|
||||
|
||||
std::filesystem::rename(dataPath / blankEsm, dataPath / omwgame);
|
||||
std::filesystem::rename(dataPath / blankEsp, dataPath / omwaddon);
|
||||
std::ofstream out(dataPath / omwscripts);
|
||||
out.close();
|
||||
|
||||
EXPECT_NO_THROW(
|
||||
Plugin(game_.GetType(), game_.GetCache(), dataPath / omwgame, false));
|
||||
EXPECT_NO_THROW(
|
||||
Plugin(game_.GetType(), game_.GetCache(), dataPath / omwaddon, false));
|
||||
if (GetParam() == GameType::openmw) {
|
||||
EXPECT_NO_THROW(Plugin(
|
||||
game_.GetType(), game_.GetCache(), dataPath / omwscripts, false));
|
||||
} else {
|
||||
EXPECT_THROW(
|
||||
Plugin(game_.GetType(), game_.GetCache(), dataPath / omwscripts, false),
|
||||
std::system_error);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
isLightPluginShouldBeTrueForAPluginWithEslFileExtensionForFallout4AndSkyrimSeAndFalseOtherwise) {
|
||||
@@ -399,7 +451,8 @@ TEST_P(
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsm, true)
|
||||
.LoadsArchive();
|
||||
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::tes4)
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw ||
|
||||
GetParam() == GameType::tes4)
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
else
|
||||
EXPECT_TRUE(loadsArchive);
|
||||
@@ -416,7 +469,8 @@ TEST_P(
|
||||
true)
|
||||
.LoadsArchive();
|
||||
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::starfield)
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw ||
|
||||
GetParam() == GameType::starfield)
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
else
|
||||
EXPECT_TRUE(loadsArchive);
|
||||
@@ -431,7 +485,7 @@ TEST_P(
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsp, true)
|
||||
.LoadsArchive();
|
||||
|
||||
if (GetParam() == GameType::tes3)
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw)
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
else
|
||||
EXPECT_TRUE(loadsArchive);
|
||||
@@ -597,10 +651,12 @@ TEST_P(PluginTest,
|
||||
doRecordsOverlapShouldReturnFalseForTwoPluginsWithOnlyHeadersLoaded) {
|
||||
Plugin plugin1(
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsm, true);
|
||||
Plugin plugin2(game_.GetType(),
|
||||
game_.GetCache(),
|
||||
game_.DataPath() / (blankMasterDependentEsm + ".ghost"),
|
||||
true);
|
||||
|
||||
const auto pluginName = GetParam() == GameType::openmw
|
||||
? blankMasterDependentEsm
|
||||
: blankMasterDependentEsm + ".ghost";
|
||||
Plugin plugin2(
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / pluginName, true);
|
||||
|
||||
EXPECT_FALSE(plugin1.DoRecordsOverlap(plugin2));
|
||||
EXPECT_FALSE(plugin2.DoRecordsOverlap(plugin1));
|
||||
@@ -626,13 +682,14 @@ TEST_P(PluginTest,
|
||||
doRecordsOverlapShouldReturnTrueIfOnePluginOverridesTheOthersRecords) {
|
||||
const auto plugin1Name =
|
||||
GetParam() == GameType::starfield ? blankFullEsm : blankEsm;
|
||||
const auto plugin2Name = GetParam() == GameType::openmw
|
||||
? blankMasterDependentEsm
|
||||
: blankMasterDependentEsm + ".ghost";
|
||||
|
||||
Plugin plugin1(
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / plugin1Name, false);
|
||||
Plugin plugin2(game_.GetType(),
|
||||
game_.GetCache(),
|
||||
game_.DataPath() / (blankMasterDependentEsm + ".ghost"),
|
||||
false);
|
||||
Plugin plugin2(
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / plugin2Name, false);
|
||||
|
||||
if (GetParam() == GameType::starfield) {
|
||||
plugin1.ResolveRecordIds(nullptr);
|
||||
@@ -649,7 +706,7 @@ TEST_P(PluginTest, getRecordAndGroupCountShouldReturnTheHeaderFieldValue) {
|
||||
Plugin plugin(
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsm, true);
|
||||
|
||||
if (GetParam() == GameType::tes3) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
EXPECT_EQ(10u, plugin.GetRecordAndGroupCount());
|
||||
} else if (GetParam() == GameType::tes4) {
|
||||
EXPECT_EQ(14u, plugin.GetRecordAndGroupCount());
|
||||
@@ -665,7 +722,7 @@ TEST_P(PluginTest,
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsp, false)
|
||||
.GetAssetCount();
|
||||
|
||||
if (GetParam() == GameType::tes3) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
EXPECT_EQ(0, assetCount);
|
||||
} else if (GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr ||
|
||||
GetParam() == GameType::starfield) {
|
||||
@@ -690,7 +747,7 @@ TEST_P(PluginTest,
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsp, false);
|
||||
OtherPluginType plugin2;
|
||||
|
||||
if (GetParam() == GameType::tes3) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
EXPECT_FALSE(plugin1.DoAssetsOverlap(plugin2));
|
||||
} else {
|
||||
EXPECT_THROW(plugin1.DoAssetsOverlap(plugin2), std::invalid_argument);
|
||||
@@ -734,7 +791,7 @@ TEST_P(PluginTest,
|
||||
game_.DataPath() / blankMasterDependentEsp,
|
||||
false);
|
||||
|
||||
if (GetParam() == GameType::tes3) {
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw) {
|
||||
// Morrowind plugins can't load assets.
|
||||
EXPECT_FALSE(plugin1.DoAssetsOverlap(plugin2));
|
||||
EXPECT_FALSE(plugin2.DoAssetsOverlap(plugin1));
|
||||
@@ -744,16 +801,20 @@ TEST_P(PluginTest,
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(PluginTest,
|
||||
hasPluginFileExtensionShouldBeTrueIfFileEndsInDotEspOrDotEsm) {
|
||||
class HasPluginFileExtensionTest : public ::testing::TestWithParam<GameType> {};
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(,
|
||||
HasPluginFileExtensionTest,
|
||||
::testing::ValuesIn(ALL_GAME_TYPES));
|
||||
|
||||
TEST_P(HasPluginFileExtensionTest, shouldBeTrueIfFileEndsInDotEspOrDotEsm) {
|
||||
EXPECT_TRUE(hasPluginFileExtension("file.esp", GetParam()));
|
||||
EXPECT_TRUE(hasPluginFileExtension("file.esm", GetParam()));
|
||||
EXPECT_FALSE(hasPluginFileExtension("file.bsa", GetParam()));
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
hasPluginFileExtensionShouldBeTrueIfFileEndsInDotEslOnlyForFallout4AndLater) {
|
||||
TEST_P(HasPluginFileExtensionTest,
|
||||
shouldBeTrueIfFileEndsInDotEslOnlyForFallout4AndLater) {
|
||||
bool result = hasPluginFileExtension("file.esl", GetParam());
|
||||
|
||||
EXPECT_EQ(GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr ||
|
||||
@@ -763,6 +824,26 @@ TEST_P(
|
||||
result);
|
||||
}
|
||||
|
||||
TEST_P(HasPluginFileExtensionTest, shouldTrimGhostExtensionExceptForOpenMW) {
|
||||
if (GetParam() == GameType::openmw) {
|
||||
EXPECT_FALSE(hasPluginFileExtension("file.esp.ghost", GetParam()));
|
||||
EXPECT_FALSE(hasPluginFileExtension("file.esm.ghost", GetParam()));
|
||||
} else {
|
||||
EXPECT_TRUE(hasPluginFileExtension("file.esp.ghost", GetParam()));
|
||||
EXPECT_TRUE(hasPluginFileExtension("file.esm.ghost", GetParam()));
|
||||
}
|
||||
EXPECT_FALSE(hasPluginFileExtension("file.bsa.ghost", GetParam()));
|
||||
}
|
||||
|
||||
TEST_P(HasPluginFileExtensionTest, shouldRecogniseOpenMWPluginExtensions) {
|
||||
EXPECT_EQ(GetParam() == GameType::openmw,
|
||||
hasPluginFileExtension("file.omwgame", GetParam()));
|
||||
EXPECT_EQ(GetParam() == GameType::openmw,
|
||||
hasPluginFileExtension("file.omwaddon", GetParam()));
|
||||
EXPECT_EQ(GetParam() == GameType::openmw,
|
||||
hasPluginFileExtension("file.omwscripts", GetParam()));
|
||||
}
|
||||
|
||||
TEST(equivalent, shouldReturnTrueIfGivenEqualPathsThatExist) {
|
||||
auto path1 = std::filesystem::path("./testing-plugins/LICENSE");
|
||||
auto path2 = std::filesystem::path("./testing-plugins/LICENSE");
|
||||
|
||||
@@ -119,7 +119,8 @@ INSTANTIATE_TEST_SUITE_P(,
|
||||
::testing::Values(GameType::tes3,
|
||||
GameType::tes4,
|
||||
GameType::fo4,
|
||||
GameType::starfield));
|
||||
GameType::starfield,
|
||||
GameType::openmw));
|
||||
|
||||
TEST_P(PluginSortTest, sortingWithNoLoadedPluginsShouldReturnAnEmptyList) {
|
||||
std::vector<std::string> sorted = SortPlugins(game_, game_.GetLoadOrder());
|
||||
@@ -131,11 +132,31 @@ TEST_P(PluginSortTest,
|
||||
sortingShouldNotMakeUnnecessaryChangesToAnExistingLoadOrder) {
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
std::vector<std::string> expectedSortedOrder = getLoadOrder();
|
||||
std::vector<std::string> expectedSortedOrder;
|
||||
if (GetParam() == GameType::openmw) {
|
||||
// The existing load order for OpenMW doesn't have plugins loading after
|
||||
// their masters, because the game doesn't enforce that, and the test
|
||||
// setup cannot enforce the positions of inactive plugins.
|
||||
expectedSortedOrder = {
|
||||
blankDifferentEsm,
|
||||
blankDifferentMasterDependentEsm,
|
||||
blankDifferentEsp,
|
||||
blankDifferentPluginDependentEsp,
|
||||
blankEsm,
|
||||
blankMasterDependentEsm,
|
||||
blankMasterDependentEsp,
|
||||
blankEsp,
|
||||
blankPluginDependentEsp,
|
||||
masterFile,
|
||||
blankDifferentMasterDependentEsp,
|
||||
};
|
||||
} else {
|
||||
expectedSortedOrder = getLoadOrder();
|
||||
}
|
||||
|
||||
// Check stability by running the sort 100 times.
|
||||
for (int i = 0; i < 100; i++) {
|
||||
std::vector<std::string> sorted = SortPlugins(game_, game_.GetLoadOrder());
|
||||
auto sorted = SortPlugins(game_, game_.GetLoadOrder());
|
||||
ASSERT_EQ(expectedSortedOrder, sorted) << " for sort " << i;
|
||||
}
|
||||
}
|
||||
@@ -218,6 +239,26 @@ TEST_P(PluginSortTest,
|
||||
blankDifferentEsp,
|
||||
blankMasterDependentEsp,
|
||||
};
|
||||
} else if (GetParam() == GameType::openmw) {
|
||||
// OpenMW's starting order is different, so more metadata is needed to see
|
||||
// a change.
|
||||
plugin = PluginMetadata(blankEsp);
|
||||
plugin.SetGroup("A");
|
||||
game_.GetDatabase().SetPluginUserMetadata(plugin);
|
||||
|
||||
expectedSortedOrder = {
|
||||
blankDifferentEsm,
|
||||
blankDifferentMasterDependentEsm,
|
||||
blankDifferentEsp,
|
||||
blankDifferentPluginDependentEsp,
|
||||
blankEsp,
|
||||
blankEsm,
|
||||
blankMasterDependentEsm,
|
||||
blankMasterDependentEsp,
|
||||
blankPluginDependentEsp,
|
||||
masterFile,
|
||||
blankDifferentMasterDependentEsp,
|
||||
};
|
||||
} else {
|
||||
expectedSortedOrder = {
|
||||
masterFile,
|
||||
@@ -288,6 +329,20 @@ TEST_P(PluginSortTest,
|
||||
blankDifferentEsp,
|
||||
blankMasterDependentEsp,
|
||||
};
|
||||
} else if (GetParam() == GameType::openmw) {
|
||||
expectedSortedOrder = {
|
||||
blankDifferentEsp,
|
||||
blankDifferentPluginDependentEsp,
|
||||
blankEsp,
|
||||
blankPluginDependentEsp,
|
||||
masterFile,
|
||||
blankDifferentEsm,
|
||||
blankDifferentMasterDependentEsm,
|
||||
blankDifferentMasterDependentEsp,
|
||||
blankEsm,
|
||||
blankMasterDependentEsm,
|
||||
blankMasterDependentEsp,
|
||||
};
|
||||
} else {
|
||||
expectedSortedOrder = {
|
||||
masterFile,
|
||||
@@ -346,6 +401,26 @@ TEST_P(PluginSortTest,
|
||||
blankEsp,
|
||||
blankMasterDependentEsp,
|
||||
};
|
||||
} else if (GetParam() == GameType::openmw) {
|
||||
// OpenMW's starting order is different, so more metadata is needed to see
|
||||
// a change.
|
||||
plugin = PluginMetadata(blankEsp);
|
||||
plugin.SetLoadAfterFiles({File(blankDifferentMasterDependentEsp)});
|
||||
game_.GetDatabase().SetPluginUserMetadata(plugin);
|
||||
|
||||
expectedSortedOrder = {
|
||||
blankDifferentEsm,
|
||||
blankDifferentMasterDependentEsm,
|
||||
blankDifferentEsp,
|
||||
blankDifferentPluginDependentEsp,
|
||||
blankEsm,
|
||||
blankMasterDependentEsm,
|
||||
blankMasterDependentEsp,
|
||||
blankDifferentMasterDependentEsp,
|
||||
blankEsp,
|
||||
blankPluginDependentEsp,
|
||||
masterFile,
|
||||
};
|
||||
} else {
|
||||
expectedSortedOrder = {
|
||||
masterFile,
|
||||
@@ -394,6 +469,26 @@ TEST_P(PluginSortTest,
|
||||
blankEsp,
|
||||
blankMasterDependentEsp,
|
||||
};
|
||||
} else if (GetParam() == GameType::openmw) {
|
||||
// OpenMW's starting order is different, so more metadata is needed to see
|
||||
// a change.
|
||||
plugin = PluginMetadata(blankEsp);
|
||||
plugin.SetRequirements({File(blankDifferentMasterDependentEsp)});
|
||||
game_.GetDatabase().SetPluginUserMetadata(plugin);
|
||||
|
||||
expectedSortedOrder = {
|
||||
blankDifferentEsm,
|
||||
blankDifferentMasterDependentEsm,
|
||||
blankDifferentEsp,
|
||||
blankDifferentPluginDependentEsp,
|
||||
blankEsm,
|
||||
blankMasterDependentEsm,
|
||||
blankMasterDependentEsp,
|
||||
blankDifferentMasterDependentEsp,
|
||||
blankEsp,
|
||||
blankPluginDependentEsp,
|
||||
masterFile,
|
||||
};
|
||||
} else {
|
||||
expectedSortedOrder = {
|
||||
masterFile,
|
||||
@@ -520,6 +615,11 @@ TEST_P(PluginSortTest,
|
||||
TEST_P(
|
||||
PluginSortTest,
|
||||
sortingShouldThrowIfMasterlistRequirementEdgeWouldContradictMasterFlags) {
|
||||
if (GetParam() == GameType::openmw) {
|
||||
// OpenMW doesn't require master-flagged plugins to load before others.
|
||||
return;
|
||||
}
|
||||
|
||||
using std::endl;
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
@@ -550,6 +650,11 @@ TEST_P(
|
||||
|
||||
TEST_P(PluginSortTest,
|
||||
sortingShouldThrowIfUserRequirementEdgeWouldContradictMasterFlags) {
|
||||
if (GetParam() == GameType::openmw) {
|
||||
// OpenMW doesn't require master-flagged plugins to load before others.
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
PluginMetadata plugin(blankEsm);
|
||||
@@ -573,6 +678,11 @@ TEST_P(PluginSortTest,
|
||||
|
||||
TEST_P(PluginSortTest,
|
||||
sortingShouldThrowIfMasterlistLoadAfterEdgeWouldContradictMasterFlags) {
|
||||
if (GetParam() == GameType::openmw) {
|
||||
// OpenMW doesn't require master-flagged plugins to load before others.
|
||||
return;
|
||||
}
|
||||
|
||||
using std::endl;
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
@@ -603,6 +713,11 @@ TEST_P(PluginSortTest,
|
||||
|
||||
TEST_P(PluginSortTest,
|
||||
sortingShouldThrowIfUserLoadAfterEdgeWouldContradictMasterFlags) {
|
||||
if (GetParam() == GameType::openmw) {
|
||||
// OpenMW doesn't require master-flagged plugins to load before others.
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
|
||||
PluginMetadata plugin(blankEsm);
|
||||
|
||||
@@ -62,15 +62,6 @@ protected:
|
||||
return loadedPluginInterfaces;
|
||||
}
|
||||
|
||||
std::vector<ComparableFilename> getNativeLoadOrder() {
|
||||
std::vector<ComparableFilename> wideLoadOrder;
|
||||
for (const auto &pluginName : getLoadOrder()) {
|
||||
wideLoadOrder.push_back(ToComparableFilename(pluginName));
|
||||
}
|
||||
|
||||
return wideLoadOrder;
|
||||
}
|
||||
|
||||
Game game_;
|
||||
const std::string blankEslEsp;
|
||||
};
|
||||
@@ -82,7 +73,8 @@ INSTANTIATE_TEST_SUITE_P(,
|
||||
::testing::Values(GameType::tes3,
|
||||
GameType::tes4,
|
||||
GameType::fo4,
|
||||
GameType::starfield));
|
||||
GameType::starfield,
|
||||
GameType::openmw));
|
||||
|
||||
TEST_P(PluginSortingDataTest, lightFlaggedEspFilesShouldNotBeTreatedAsMasters) {
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) {
|
||||
@@ -97,22 +89,26 @@ TEST_P(PluginSortingDataTest, lightFlaggedEspFilesShouldNotBeTreatedAsMasters) {
|
||||
dynamic_cast<const PluginSortingInterface *>(game_.GetPlugin(blankEsp)),
|
||||
PluginMetadata(),
|
||||
PluginMetadata(),
|
||||
getNativeLoadOrder());
|
||||
{});
|
||||
EXPECT_FALSE(esp.IsMaster());
|
||||
|
||||
auto master = PluginSortingData(
|
||||
dynamic_cast<const PluginSortingInterface *>(game_.GetPlugin(blankEsm)),
|
||||
PluginMetadata(),
|
||||
PluginMetadata(),
|
||||
getNativeLoadOrder());
|
||||
EXPECT_TRUE(master.IsMaster());
|
||||
{});
|
||||
if (GetParam() == GameType::openmw) {
|
||||
EXPECT_FALSE(master.IsMaster());
|
||||
} else {
|
||||
EXPECT_TRUE(master.IsMaster());
|
||||
}
|
||||
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) {
|
||||
auto lightMaster = PluginSortingData(
|
||||
dynamic_cast<const PluginSortingInterface *>(game_.GetPlugin(blankEsl)),
|
||||
PluginMetadata(),
|
||||
PluginMetadata(),
|
||||
getNativeLoadOrder());
|
||||
{});
|
||||
EXPECT_TRUE(lightMaster.IsMaster());
|
||||
|
||||
auto lightPlugin =
|
||||
@@ -120,7 +116,7 @@ TEST_P(PluginSortingDataTest, lightFlaggedEspFilesShouldNotBeTreatedAsMasters) {
|
||||
game_.GetPlugin(blankEslEsp)),
|
||||
PluginMetadata(),
|
||||
PluginMetadata(),
|
||||
getNativeLoadOrder());
|
||||
{});
|
||||
EXPECT_FALSE(lightPlugin.IsMaster());
|
||||
}
|
||||
}
|
||||
@@ -133,7 +129,7 @@ TEST_P(PluginSortingDataTest,
|
||||
dynamic_cast<const Plugin *>(game_.GetPlugin(blankMasterDependentEsm)),
|
||||
PluginMetadata(),
|
||||
PluginMetadata(),
|
||||
getNativeLoadOrder());
|
||||
{});
|
||||
if (GetParam() == GameType::starfield) {
|
||||
EXPECT_EQ(1, plugin.GetOverrideRecordCount());
|
||||
} else {
|
||||
@@ -152,7 +148,7 @@ TEST_P(PluginSortingDataTest,
|
||||
PluginSortingData(dynamic_cast<const Plugin *>(game_.GetPlugin(blankEsm)),
|
||||
PluginMetadata(),
|
||||
PluginMetadata(),
|
||||
getNativeLoadOrder());
|
||||
{});
|
||||
if (GetParam() == GameType::starfield) {
|
||||
EXPECT_TRUE(plugin.IsBlueprintMaster());
|
||||
} else {
|
||||
@@ -163,21 +159,21 @@ TEST_P(PluginSortingDataTest,
|
||||
dynamic_cast<const Plugin *>(game_.GetPlugin(blankDifferentEsm)),
|
||||
PluginMetadata(),
|
||||
PluginMetadata(),
|
||||
getNativeLoadOrder());
|
||||
{});
|
||||
EXPECT_FALSE(plugin.IsBlueprintMaster());
|
||||
|
||||
plugin =
|
||||
PluginSortingData(dynamic_cast<const Plugin *>(game_.GetPlugin(blankEsp)),
|
||||
PluginMetadata(),
|
||||
PluginMetadata(),
|
||||
getNativeLoadOrder());
|
||||
{});
|
||||
EXPECT_FALSE(plugin.IsBlueprintMaster());
|
||||
|
||||
plugin = PluginSortingData(
|
||||
dynamic_cast<const Plugin *>(game_.GetPlugin(blankDifferentEsp)),
|
||||
PluginMetadata(),
|
||||
PluginMetadata(),
|
||||
getNativeLoadOrder());
|
||||
{});
|
||||
EXPECT_FALSE(plugin.IsBlueprintMaster());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,17 +27,33 @@ along with LOOT. If not, see
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "loot/enum/game_type.h"
|
||||
#include "tests/test_helpers.h"
|
||||
|
||||
namespace loot {
|
||||
namespace test {
|
||||
static const std::array<GameType, 11> ALL_GAME_TYPES = {
|
||||
GameType::tes3,
|
||||
GameType::tes4,
|
||||
GameType::tes5,
|
||||
GameType::tes5se,
|
||||
GameType::tes5vr,
|
||||
GameType::fo3,
|
||||
GameType::fonv,
|
||||
GameType::fo4,
|
||||
GameType::fo4vr,
|
||||
GameType::starfield,
|
||||
GameType::openmw,
|
||||
};
|
||||
|
||||
class CommonGameTestFixture : public ::testing::TestWithParam<GameType> {
|
||||
protected:
|
||||
CommonGameTestFixture() :
|
||||
@@ -46,7 +62,7 @@ protected:
|
||||
german("de"),
|
||||
missingPath(rootTestPath / "missing"),
|
||||
gamePath(rootTestPath / "games" / "game"),
|
||||
dataPath(gamePath / getPluginsFolder()),
|
||||
dataPath(gamePath / getPluginsFolder()),
|
||||
localPath(rootTestPath / "local" / "game"),
|
||||
metadataFilesPath(rootTestPath / "metadata"),
|
||||
masterFile(getMasterFile()),
|
||||
@@ -147,12 +163,16 @@ protected:
|
||||
// Set initial load order and active plugins.
|
||||
setLoadOrder(getInitialLoadOrder());
|
||||
|
||||
// Ghost a plugin.
|
||||
ASSERT_NO_THROW(std::filesystem::rename(
|
||||
dataPath / blankMasterDependentEsm,
|
||||
dataPath / (blankMasterDependentEsm + ".ghost")));
|
||||
ASSERT_FALSE(exists(dataPath / blankMasterDependentEsm));
|
||||
ASSERT_TRUE(exists(dataPath / (blankMasterDependentEsm + ".ghost")));
|
||||
// Ghost a plugin, except for OpenMW.
|
||||
if (GetParam() != GameType::openmw) {
|
||||
ASSERT_NO_THROW(std::filesystem::rename(
|
||||
dataPath / blankMasterDependentEsm,
|
||||
dataPath / (blankMasterDependentEsm + ".ghost")));
|
||||
ASSERT_FALSE(exists(dataPath / blankMasterDependentEsm));
|
||||
ASSERT_TRUE(exists(dataPath / (blankMasterDependentEsm + ".ghost")));
|
||||
} else {
|
||||
touch(gamePath / "openmw.cfg");
|
||||
}
|
||||
|
||||
// Write out an non-empty, non-plugin file.
|
||||
std::ofstream out(dataPath / nonPluginFile);
|
||||
@@ -226,6 +246,10 @@ protected:
|
||||
if (!line.empty())
|
||||
actual.push_back(line);
|
||||
}
|
||||
} else if (GetParam() == GameType::openmw) {
|
||||
throw std::runtime_error(
|
||||
"OpenMW's load order derivation is too complicated to replicate "
|
||||
"accurately just for a test.");
|
||||
} else {
|
||||
actual = readFileLines(localPath / "Plugins.txt");
|
||||
for (auto& line : actual) {
|
||||
@@ -377,7 +401,7 @@ protected:
|
||||
|
||||
private:
|
||||
std::string getMasterFile() const {
|
||||
if (GetParam() == GameType::tes3)
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::openmw)
|
||||
return "Morrowind.esm";
|
||||
else if (GetParam() == GameType::tes4)
|
||||
return "Oblivion.esm";
|
||||
@@ -397,7 +421,9 @@ private:
|
||||
}
|
||||
|
||||
std::string getPluginsFolder() const {
|
||||
if (GetParam() == GameType::tes3) {
|
||||
if (GetParam() == GameType::openmw) {
|
||||
return "resources/vfs";
|
||||
} else if (GetParam() == GameType::tes3) {
|
||||
return "Data Files";
|
||||
} else {
|
||||
return "Data";
|
||||
@@ -407,6 +433,7 @@ private:
|
||||
uint32_t getBlankEsmCrc() const {
|
||||
switch (GetParam()) {
|
||||
case GameType::tes3:
|
||||
case GameType::openmw:
|
||||
return 0x790DC6FB;
|
||||
case GameType::tes4:
|
||||
return 0x374E2A6F;
|
||||
@@ -426,6 +453,14 @@ private:
|
||||
out << "GameFile0=" << plugin.first << std::endl;
|
||||
}
|
||||
}
|
||||
} else if (GetParam() == GameType::openmw) {
|
||||
std::ofstream out(localPath / "openmw.cfg");
|
||||
|
||||
for (const auto& plugin : loadOrder) {
|
||||
if (plugin.second) {
|
||||
out << "content=" << plugin.first << std::endl;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::ofstream out(localPath / "Plugins.txt");
|
||||
for (const auto& plugin : loadOrder) {
|
||||
|
||||
@@ -40,7 +40,7 @@ bool supportsLightPlugins(GameType gameType) {
|
||||
|
||||
std::filesystem::path getSourcePluginsPath(GameType gameType) {
|
||||
using std::filesystem::absolute;
|
||||
if (gameType == GameType::tes3) {
|
||||
if (gameType == GameType::tes3 || gameType == GameType::openmw) {
|
||||
return absolute("./testing-plugins/Morrowind/Data Files");
|
||||
} else if (gameType == GameType::tes4) {
|
||||
return absolute("./testing-plugins/Oblivion/Data");
|
||||
|
||||
Reference in New Issue
Block a user