mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Add support for Starfield
Starfield adds a new type of plugin that I'm calling an override plugin, which does not use up a load order slot when active, and a plugin cannot be light and override at the same time. It also increases the BA2 version number, though the differences are not relevant to libloot's usage, and loads BA2s from plugins differently. The latest libloadorder distinguishes between plugins that are implicitly active (i.e. they're active despite not being listed in plugins.txt) and those that are made to load in certain positions by the game.
This commit is contained in:
+6
-6
@@ -80,8 +80,8 @@ set(GTEST_LIBRARIES "${BINARY_DIR}/lib/${CMAKE_CFG_INTDIR}/${CMAKE_STATIC_LIBRAR
|
||||
|
||||
ExternalProject_Add(esplugin
|
||||
PREFIX "external"
|
||||
URL "https://github.com/Ortham/esplugin/archive/4.0.0.tar.gz"
|
||||
URL_HASH "SHA256=e3aa21ffbfd8ce55e3398fbd789adb2aa0d2cceefc99bcc789f02ff95cc98b86"
|
||||
URL "https://github.com/Ortham/esplugin/archive/4.1.0.tar.gz"
|
||||
URL_HASH "SHA256=c0958051f99b1a3a1ea2db1d68f2521567563238b9d624dde1c5c075baddfa41"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_IN_SOURCE 1
|
||||
BUILD_COMMAND cargo build --release --manifest-path ffi/Cargo.toml --target ${RUST_TARGET} &&
|
||||
@@ -98,8 +98,8 @@ endif()
|
||||
|
||||
ExternalProject_Add(libloadorder
|
||||
PREFIX "external"
|
||||
URL "https://github.com/Ortham/libloadorder/archive/14.2.1.tar.gz"
|
||||
URL_HASH "SHA256=4341844407f55ce9d96826a933ac35dfb22bbad14906e5dff742f910182eca93"
|
||||
URL "https://github.com/Ortham/libloadorder/archive/15.0.0.tar.gz"
|
||||
URL_HASH "SHA256=3196e497a285f069acdb0ba68c03df88361fcc9928646c88267c052111f31a90"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_IN_SOURCE 1
|
||||
BUILD_COMMAND cargo build --release --manifest-path ffi/Cargo.toml --target ${RUST_TARGET} &&
|
||||
@@ -116,8 +116,8 @@ endif()
|
||||
|
||||
ExternalProject_Add(loot-condition-interpreter
|
||||
PREFIX "external"
|
||||
URL "https://github.com/loot/loot-condition-interpreter/archive/3.0.0.tar.gz"
|
||||
URL_HASH "SHA256=6492c19848f01f297537da6a561e8d0c0a79e7c15e3ddf958910dec7cdfe0abf"
|
||||
URL "https://github.com/loot/loot-condition-interpreter/archive/3.1.0.tar.gz"
|
||||
URL_HASH "SHA256=a2dcadff0e6f9fd4fdc12785d296bf5ee24cc556f4b20a357233e2a2e79d025c"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_IN_SOURCE 1
|
||||
BUILD_COMMAND cargo build --release --manifest-path ffi/Cargo.toml --target ${RUST_TARGET} &&
|
||||
|
||||
@@ -49,6 +49,8 @@ enum struct GameType : unsigned int {
|
||||
tes5vr,
|
||||
/** The Elder Scrolls III: Morrowind */
|
||||
tes3,
|
||||
/** Starfield */
|
||||
starfield
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -99,6 +99,12 @@ public:
|
||||
*/
|
||||
virtual bool IsLightPlugin() const = 0;
|
||||
|
||||
/**
|
||||
* Check if the plugin is an override plugin.
|
||||
* @return True if plugin is an override plugin, false otherwise.
|
||||
*/
|
||||
virtual bool IsOverridePlugin() const = 0;
|
||||
|
||||
/**
|
||||
* Check if the plugin is or would be valid as a light plugin.
|
||||
* @return True if the plugin is a valid light plugin or would be a valid
|
||||
@@ -106,6 +112,13 @@ public:
|
||||
*/
|
||||
virtual bool IsValidAsLightPlugin() const = 0;
|
||||
|
||||
/**
|
||||
* Check if the plugin is or would be valid as an override plugin.
|
||||
* @return True if the plugin is a valid override plugin or would be a valid
|
||||
* override plugin, false otherwise.
|
||||
*/
|
||||
virtual bool IsValidAsOverridePlugin() 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.
|
||||
|
||||
@@ -52,6 +52,8 @@ const char* DescribeGameType(GameType gameType) {
|
||||
return "The Elder Scrolls V: Skyrim VR";
|
||||
case GameType::tes3:
|
||||
return "The Elder Scrolls III: Morrowind";
|
||||
case GameType::starfield:
|
||||
return "Starfield";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
+4
-3
@@ -142,7 +142,7 @@ void StoreHashes(std::map<uint64_t, std::set<uint64_t>>& folderFileHashes,
|
||||
}
|
||||
}
|
||||
|
||||
// Normalise the path the same way that BA2 hashes do (it's thet same as for
|
||||
// Normalise the path the same way that BA2 hashes do (it's the same as for
|
||||
// BSAs).
|
||||
void NormalisePath(std::string& filePath) {
|
||||
for (size_t i = 0; i < filePath.size(); ++i) {
|
||||
@@ -217,7 +217,8 @@ std::map<uint64_t, std::set<uint64_t>> GetAssetsInBA2(std::istream& in,
|
||||
throw std::runtime_error("BA2 file header type ID is invalid");
|
||||
}
|
||||
|
||||
if (header.version != 1) {
|
||||
// The header version is 1 for Fallout 4 and 2 or 3 for Starfield.
|
||||
if (header.version != 1 && header.version != 2 && header.version != 3) {
|
||||
throw std::runtime_error("BA2 file header version is invalid");
|
||||
}
|
||||
|
||||
@@ -237,7 +238,7 @@ std::map<uint64_t, std::set<uint64_t>> GetAssetsInBA2(std::istream& in,
|
||||
bool ShouldWarnAboutHashCollisions(const std::filesystem::path& archivePath) {
|
||||
const auto filename = archivePath.filename().u8string();
|
||||
|
||||
return !boost::iends_with(filename, ".ba2") ||
|
||||
return !boost::iends_with(filename, BA2_FILE_EXTENSION) ||
|
||||
(!boost::istarts_with(filename, "Fallout4 - ") &&
|
||||
!boost::istarts_with(filename, "DLCUltraHighResolution - "));
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@
|
||||
#include <vector>
|
||||
|
||||
namespace loot {
|
||||
inline constexpr const char* BSA_FILE_EXTENSION = ".bsa";
|
||||
inline constexpr const char* BA2_FILE_EXTENSION = ".ba2";
|
||||
|
||||
std::map<uint64_t, std::set<uint64_t>> GetAssetsInBethesdaArchive(
|
||||
const std::filesystem::path& archivePath);
|
||||
|
||||
|
||||
@@ -84,9 +84,14 @@ bool IsMicrosoftStoreInstall(const GameType gameType,
|
||||
"appxmanifest.xml");
|
||||
case GameType::tes5se:
|
||||
case GameType::fo4:
|
||||
case GameType::starfield:
|
||||
return std::filesystem::exists(gamePath / "appxmanifest.xml");
|
||||
default:
|
||||
case GameType::tes5:
|
||||
case GameType::tes5vr:
|
||||
case GameType::fo4vr:
|
||||
return false;
|
||||
default:
|
||||
throw std::logic_error("Unrecognised game type");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,8 @@ unsigned int mapGameId(GameType gameType) {
|
||||
return LIBLO_GAME_FO4;
|
||||
case GameType::fo4vr:
|
||||
return LIBLO_GAME_FO4VR;
|
||||
case GameType::starfield:
|
||||
return LIBLO_GAME_STARFIELD;
|
||||
default:
|
||||
throw std::logic_error("Unexpected game type");
|
||||
}
|
||||
@@ -165,7 +167,7 @@ std::vector<std::string> LoadOrderHandler::GetActivePlugins() const {
|
||||
return loadOrder;
|
||||
}
|
||||
|
||||
std::vector<std::string> LoadOrderHandler::GetImplicitlyActivePlugins() const {
|
||||
std::vector<std::string> LoadOrderHandler::GetEarlyLoadingPlugins() const {
|
||||
auto logger = getLogger();
|
||||
if (logger) {
|
||||
logger->trace("Getting implicitly active plugins.");
|
||||
@@ -175,7 +177,7 @@ std::vector<std::string> LoadOrderHandler::GetImplicitlyActivePlugins() const {
|
||||
size_t pluginArrSize = 0;
|
||||
|
||||
const unsigned int ret =
|
||||
lo_get_implicitly_active_plugins(gh_.get(), &pluginArr, &pluginArrSize);
|
||||
lo_get_early_loading_plugins(gh_.get(), &pluginArr, &pluginArrSize);
|
||||
|
||||
HandleError("get implicitly active plugins", ret);
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ public:
|
||||
|
||||
std::vector<std::string> GetActivePlugins() const;
|
||||
|
||||
std::vector<std::string> GetImplicitlyActivePlugins() const;
|
||||
std::vector<std::string> GetEarlyLoadingPlugins() const;
|
||||
|
||||
std::filesystem::path GetActivePluginsFilePath() const;
|
||||
|
||||
|
||||
@@ -73,6 +73,8 @@ int mapGameType(GameType gameType) {
|
||||
return LCI_GAME_FALLOUT_4;
|
||||
case GameType::fo4vr:
|
||||
return LCI_GAME_FALLOUT_4_VR;
|
||||
case GameType::starfield:
|
||||
return LCI_GAME_STARFIELD;
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"Unrecognised game type encountered while mapping for condition "
|
||||
|
||||
+131
-57
@@ -41,11 +41,12 @@ std::filesystem::path ReplaceExtension(std::filesystem::path path,
|
||||
return path.replace_extension(std::filesystem::u8path(newExtension));
|
||||
}
|
||||
|
||||
std::filesystem::path GetTexturesArchivePath(std::filesystem::path pluginPath,
|
||||
std::filesystem::path GetSuffixedArchivePath(std::filesystem::path pluginPath,
|
||||
const std::string& suffix,
|
||||
const std::string& newExtension) {
|
||||
// replace_extension() with no argument just removes the existing extension.
|
||||
pluginPath.replace_extension();
|
||||
pluginPath += " - Textures" + newExtension;
|
||||
pluginPath += suffix + newExtension;
|
||||
return pluginPath;
|
||||
}
|
||||
|
||||
@@ -74,69 +75,110 @@ bool equivalent(const std::filesystem::path& path1,
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> FindAssociatedArchives(
|
||||
const GameType gameType,
|
||||
const GameCache& gameCache,
|
||||
std::vector<std::filesystem::path> FindAssociatedArchive(
|
||||
const std::filesystem::path& pluginPath) {
|
||||
std::vector<std::filesystem::path> paths;
|
||||
const auto archiveFilename = ReplaceExtension(pluginPath, BSA_FILE_EXTENSION);
|
||||
|
||||
if (gameType == GameType::tes3) {
|
||||
return paths;
|
||||
if (std::filesystem::exists(archiveFilename)) {
|
||||
return {archiveFilename};
|
||||
}
|
||||
|
||||
const auto archiveExtension = GetArchiveFileExtension(gameType);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (gameType == GameType::tes5) {
|
||||
// Skyrim (non-SE) plugins can only load BSAs that have exactly the same
|
||||
// basename, ignoring file extensions.
|
||||
const auto archiveFilename = ReplaceExtension(pluginPath, archiveExtension);
|
||||
std::vector<std::filesystem::path> FindAssociatedArchivesWithSuffixes(
|
||||
const std::filesystem::path& pluginPath,
|
||||
const std::string& archiveExtension,
|
||||
const std::vector<std::string>& supportedSuffixes) {
|
||||
std::vector<std::filesystem::path> paths;
|
||||
|
||||
if (std::filesystem::exists(archiveFilename)) {
|
||||
paths.push_back(archiveFilename);
|
||||
}
|
||||
} else if (gameType == GameType::tes5se || gameType == GameType::tes5vr) {
|
||||
// Skyrim SE can load BSAs that have exactly the same
|
||||
// basename, ignoring file extensions, and also BSAs with filenames of
|
||||
// the form "<basename> - Textures.bsa" (case-insensitively).
|
||||
// This assumes that Skyrim VR works the same way as Skyrim SE.
|
||||
const auto archiveFilename = ReplaceExtension(pluginPath, archiveExtension);
|
||||
const auto texturesArchiveFilename =
|
||||
GetTexturesArchivePath(pluginPath, archiveExtension);
|
||||
for (const auto& suffix : supportedSuffixes) {
|
||||
const auto archivePath =
|
||||
GetSuffixedArchivePath(pluginPath, suffix, archiveExtension);
|
||||
|
||||
if (std::filesystem::exists(archiveFilename)) {
|
||||
paths.push_back(archiveFilename);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(texturesArchiveFilename)) {
|
||||
paths.push_back(texturesArchiveFilename);
|
||||
}
|
||||
} else if (gameType != GameType::tes4 ||
|
||||
boost::iends_with(pluginPath.filename().u8string(), ".esp")) {
|
||||
// Oblivion .esp files and FO3, FNV, FO4 plugins can load archives which
|
||||
// begin with the plugin basename.
|
||||
// This assumes that FO4 VR works the same way as FO4.
|
||||
|
||||
const auto basenameLength = pluginPath.stem().native().length();
|
||||
const auto pluginExtension = pluginPath.extension().native();
|
||||
|
||||
for (const auto& archivePath : gameCache.GetArchivePaths()) {
|
||||
// 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.
|
||||
const auto bsaPluginFilename =
|
||||
archivePath.filename().native().substr(0, basenameLength) +
|
||||
pluginExtension;
|
||||
const auto bsaPluginPath = pluginPath.parent_path() / bsaPluginFilename;
|
||||
if (loot::equivalent(pluginPath, bsaPluginPath)) {
|
||||
paths.push_back(archivePath);
|
||||
}
|
||||
if (std::filesystem::exists(archivePath)) {
|
||||
paths.push_back(archivePath);
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> FindAssociatedArchivesWithArbitrarySuffixes(
|
||||
const GameCache& gameCache,
|
||||
const std::filesystem::path& pluginPath) {
|
||||
const auto basenameLength = pluginPath.stem().native().length();
|
||||
const auto pluginExtension = pluginPath.extension().native();
|
||||
|
||||
std::vector<std::filesystem::path> paths;
|
||||
for (const auto& archivePath : gameCache.GetArchivePaths()) {
|
||||
// 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.
|
||||
const auto bsaPluginFilename =
|
||||
archivePath.filename().native().substr(0, basenameLength) +
|
||||
pluginExtension;
|
||||
const auto bsaPluginPath = pluginPath.parent_path() / bsaPluginFilename;
|
||||
if (loot::equivalent(pluginPath, bsaPluginPath)) {
|
||||
paths.push_back(archivePath);
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
std::vector<std::filesystem::path> FindAssociatedArchives(
|
||||
const GameType gameType,
|
||||
const GameCache& gameCache,
|
||||
const std::filesystem::path& pluginPath) {
|
||||
switch (gameType) {
|
||||
case GameType::tes3:
|
||||
return {};
|
||||
case GameType::tes5:
|
||||
// Skyrim (non-SE) plugins can only load BSAs that have exactly the same
|
||||
// basename, ignoring file extensions.
|
||||
return FindAssociatedArchive(pluginPath);
|
||||
case GameType::tes5se:
|
||||
case GameType::tes5vr:
|
||||
// Skyrim SE can load BSAs that have exactly the same
|
||||
// basename, ignoring file extensions, and also BSAs with filenames of
|
||||
// the form "<basename> - Textures.bsa" (case-insensitively).
|
||||
// This assumes that Skyrim VR works the same way as Skyrim SE.
|
||||
return FindAssociatedArchivesWithSuffixes(
|
||||
pluginPath, BSA_FILE_EXTENSION, {"", " - Textures"});
|
||||
case GameType::tes4: {
|
||||
// Oblivion .esp files can load archives which begin with the plugin
|
||||
// basename.
|
||||
if (!boost::iends_with(pluginPath.filename().u8string(), ".esp")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return FindAssociatedArchivesWithArbitrarySuffixes(gameCache, pluginPath);
|
||||
}
|
||||
case GameType::fo3:
|
||||
case GameType::fonv:
|
||||
case GameType::fo4:
|
||||
case GameType::fo4vr: {
|
||||
// FO3, FNV, FO4 plugins can load archives which begin with the plugin
|
||||
// basename. This assumes that FO4 VR works the same way as FO4.
|
||||
return FindAssociatedArchivesWithArbitrarySuffixes(gameCache, pluginPath);
|
||||
}
|
||||
case GameType::starfield:
|
||||
// The game will load a BA2 that's suffixed with " - Voices_<language>"
|
||||
// where <language> is whatever language Starfield is configured to use
|
||||
// (sLanguage in the ini), so this isn't exactly correct but will work
|
||||
// so long as a plugin with voices has voices for English, which seems
|
||||
// likely.
|
||||
return FindAssociatedArchivesWithSuffixes(
|
||||
pluginPath,
|
||||
BA2_FILE_EXTENSION,
|
||||
{" - Main", " - Textures", " - Localization", " - Voices_en"});
|
||||
default:
|
||||
throw std::logic_error("Unrecognised game type");
|
||||
}
|
||||
}
|
||||
|
||||
Plugin::Plugin(const GameType gameType,
|
||||
const GameCache& gameCache,
|
||||
std::filesystem::path pluginPath,
|
||||
@@ -260,6 +302,18 @@ bool Plugin::IsLightPlugin() const {
|
||||
return isLightPlugin;
|
||||
}
|
||||
|
||||
bool Plugin::IsOverridePlugin() const {
|
||||
bool isOverridePlugin = false;
|
||||
const auto ret =
|
||||
esp_plugin_is_override_plugin(esPlugin.get(), &isOverridePlugin);
|
||||
if (ret != ESP_OK) {
|
||||
throw FileAccessError(name_ +
|
||||
" : esplugin error code: " + std::to_string(ret));
|
||||
}
|
||||
|
||||
return isOverridePlugin;
|
||||
}
|
||||
|
||||
bool Plugin::IsValidAsLightPlugin() const {
|
||||
bool isValid = false;
|
||||
const auto ret =
|
||||
@@ -272,6 +326,18 @@ bool Plugin::IsValidAsLightPlugin() const {
|
||||
return isValid;
|
||||
}
|
||||
|
||||
bool Plugin::IsValidAsOverridePlugin() const {
|
||||
bool isValid = false;
|
||||
const auto ret =
|
||||
esp_plugin_is_valid_as_override_plugin(esPlugin.get(), &isValid);
|
||||
if (ret != ESP_OK) {
|
||||
throw FileAccessError(name_ +
|
||||
" : esplugin error code: " + std::to_string(ret));
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
bool Plugin::IsEmpty() const { return isEmpty_; }
|
||||
|
||||
bool Plugin::LoadsArchive() const { return !archivePaths_.empty(); }
|
||||
@@ -445,10 +511,11 @@ std::string Plugin::GetDescription() const {
|
||||
}
|
||||
|
||||
std::string GetArchiveFileExtension(const GameType gameType) {
|
||||
if (gameType == GameType::fo4 || gameType == GameType::fo4vr)
|
||||
return ".ba2";
|
||||
if (gameType == GameType::fo4 || gameType == GameType::fo4vr ||
|
||||
gameType == GameType::starfield)
|
||||
return BA2_FILE_EXTENSION;
|
||||
else
|
||||
return ".bsa";
|
||||
return BSA_FILE_EXTENSION;
|
||||
}
|
||||
|
||||
unsigned int Plugin::GetEspluginGameId(GameType gameType) {
|
||||
@@ -460,13 +527,19 @@ unsigned int Plugin::GetEspluginGameId(GameType gameType) {
|
||||
case GameType::tes5:
|
||||
return ESP_GAME_SKYRIM;
|
||||
case GameType::tes5se:
|
||||
case GameType::tes5vr:
|
||||
return ESP_GAME_SKYRIMSE;
|
||||
case GameType::fo3:
|
||||
return ESP_GAME_FALLOUT3;
|
||||
case GameType::fonv:
|
||||
return ESP_GAME_FALLOUTNV;
|
||||
default:
|
||||
case GameType::fo4:
|
||||
case GameType::fo4vr:
|
||||
return ESP_GAME_FALLOUT4;
|
||||
case GameType::starfield:
|
||||
return ESP_GAME_STARFIELD;
|
||||
default:
|
||||
throw std::logic_error("Unrecognised game type");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -479,7 +552,8 @@ bool hasPluginFileExtension(std::string filename, GameType gameType) {
|
||||
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::tes5se || gameType == GameType::tes5vr ||
|
||||
gameType == GameType::starfield) &&
|
||||
boost::iends_with(filename, ".esl");
|
||||
|
||||
return isEspOrEsm || isEsl;
|
||||
|
||||
@@ -70,8 +70,10 @@ public:
|
||||
bool IsMaster() const override;
|
||||
|
||||
bool IsLightPlugin() const override;
|
||||
bool IsOverridePlugin() const override;
|
||||
|
||||
bool IsValidAsLightPlugin() const override;
|
||||
bool IsValidAsOverridePlugin() const override;
|
||||
bool IsEmpty() const override;
|
||||
bool LoadsArchive() const override;
|
||||
bool DoRecordsOverlap(const PluginInterface& plugin) const override;
|
||||
|
||||
@@ -70,23 +70,6 @@ std::vector<PluginSortingData> GetPluginsSortingData(
|
||||
return pluginsSortingData;
|
||||
}
|
||||
|
||||
std::vector<std::string> GetPluginsWithHardcodedPositions(
|
||||
const GameType gameType,
|
||||
std::vector<std::string> implicitlyActivePlugins) {
|
||||
if (gameType == GameType::tes5) {
|
||||
auto newEndIt =
|
||||
std::remove_if(implicitlyActivePlugins.begin(),
|
||||
implicitlyActivePlugins.end(),
|
||||
[](const std::string& plugin) {
|
||||
return boost::iequals(plugin, "update.esm");
|
||||
});
|
||||
|
||||
implicitlyActivePlugins.erase(newEndIt, implicitlyActivePlugins.end());
|
||||
}
|
||||
|
||||
return implicitlyActivePlugins;
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, Group> GetGroupsMap(
|
||||
const std::vector<Group> masterlistGroups,
|
||||
const std::vector<Group> userGroups) {
|
||||
@@ -223,7 +206,7 @@ std::vector<std::string> SortPlugins(
|
||||
const GameType gameType,
|
||||
const std::vector<Group> masterlistGroups,
|
||||
const std::vector<Group> userGroups,
|
||||
const std::vector<std::string>& implicitlyActivePlugins) {
|
||||
const std::vector<std::string>& earlyLoadingPlugins) {
|
||||
// If there aren't any plugins, exit early, because sorting assumes
|
||||
// there is at least one plugin.
|
||||
if (pluginsSortingData.empty()) {
|
||||
@@ -245,9 +228,6 @@ std::vector<std::string> SortPlugins(
|
||||
});
|
||||
|
||||
// Create some shared data structures.
|
||||
const auto hardcodedPlugins =
|
||||
GetPluginsWithHardcodedPositions(gameType, implicitlyActivePlugins);
|
||||
|
||||
const auto groupsMap = GetGroupsMap(masterlistGroups, userGroups);
|
||||
const auto predecessorGroupsMap =
|
||||
GetPredecessorGroups(masterlistGroups, userGroups);
|
||||
@@ -270,17 +250,17 @@ std::vector<std::string> SortPlugins(
|
||||
ValidateSpecificAndHardcodedEdges(pluginsSortingData.begin(),
|
||||
firstNonMasterIt,
|
||||
pluginsSortingData.end(),
|
||||
hardcodedPlugins);
|
||||
earlyLoadingPlugins);
|
||||
|
||||
auto newLoadOrder = SortPlugins(pluginsSortingData.begin(),
|
||||
firstNonMasterIt,
|
||||
hardcodedPlugins,
|
||||
earlyLoadingPlugins,
|
||||
groupsMap,
|
||||
predecessorGroupsMap);
|
||||
|
||||
const auto newNonMastersLoadOrder = SortPlugins(firstNonMasterIt,
|
||||
pluginsSortingData.end(),
|
||||
hardcodedPlugins,
|
||||
earlyLoadingPlugins,
|
||||
groupsMap,
|
||||
predecessorGroupsMap);
|
||||
|
||||
@@ -310,7 +290,7 @@ std::vector<std::string> SortPlugins(
|
||||
game.GetType(),
|
||||
game.GetDatabase().GetGroups(false),
|
||||
game.GetDatabase().GetUserGroups(),
|
||||
game.GetLoadOrderHandler().GetImplicitlyActivePlugins());
|
||||
game.GetLoadOrderHandler().GetEarlyLoadingPlugins());
|
||||
|
||||
if (logger) {
|
||||
logger->debug("Calculated order:");
|
||||
|
||||
@@ -92,7 +92,11 @@ INSTANTIATE_TEST_SUITE_P(,
|
||||
GameType::fo3,
|
||||
GameType::fonv,
|
||||
GameType::fo4,
|
||||
GameType::tes5se));
|
||||
GameType::tes5se,
|
||||
GameType::fo4vr,
|
||||
GameType::tes5vr,
|
||||
GameType::tes3,
|
||||
GameType::starfield));
|
||||
|
||||
TEST_P(CreateGameHandleTest,
|
||||
shouldSucceedIfPassedValidParametersWithRelativePaths) {
|
||||
|
||||
@@ -68,7 +68,11 @@ INSTANTIATE_TEST_SUITE_P(,
|
||||
GameType::fo3,
|
||||
GameType::fonv,
|
||||
GameType::fo4,
|
||||
GameType::tes5se));
|
||||
GameType::tes5se,
|
||||
GameType::fo4vr,
|
||||
GameType::tes5vr,
|
||||
GameType::tes3,
|
||||
GameType::starfield));
|
||||
|
||||
TEST_P(GameInterfaceTest, setAdditionalDataPathsShouldDoThat) {
|
||||
const auto paths = std::vector<std::filesystem::path>{
|
||||
@@ -211,6 +215,7 @@ TEST_P(GameInterfaceTest, sortPluginsShouldSucceedIfPassedValidArguments) {
|
||||
TEST_P(GameInterfaceTest,
|
||||
isPluginActiveShouldReturnFalseIfTheGivenPluginIsNotActive) {
|
||||
handle_->LoadCurrentLoadOrderState();
|
||||
|
||||
EXPECT_TRUE(handle_->IsPluginActive(blankEsm));
|
||||
}
|
||||
|
||||
@@ -224,6 +229,10 @@ TEST_P(GameInterfaceTest, getLoadOrderShouldReturnTheCurrentLoadOrder) {
|
||||
// Remove the non-ASCII duplicate plugin.
|
||||
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({});
|
||||
|
||||
handle_->LoadCurrentLoadOrderState();
|
||||
ASSERT_EQ(getLoadOrder(), handle_->GetLoadOrder());
|
||||
}
|
||||
@@ -232,6 +241,10 @@ TEST_P(GameInterfaceTest, setLoadOrderShouldSetTheLoadOrder) {
|
||||
// Remove the non-ASCII duplicate plugin.
|
||||
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({});
|
||||
|
||||
handle_->LoadCurrentLoadOrderState();
|
||||
std::vector<std::string> loadOrder({
|
||||
masterFile,
|
||||
@@ -247,7 +260,12 @@ TEST_P(GameInterfaceTest, setLoadOrderShouldSetTheLoadOrder) {
|
||||
blankPluginDependentEsp,
|
||||
});
|
||||
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) {
|
||||
const auto gameSupportsEsl =
|
||||
GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr ||
|
||||
GetParam() == GameType::tes5se || GetParam() == GameType::tes5vr ||
|
||||
GetParam() == GameType::starfield;
|
||||
|
||||
if (gameSupportsEsl) {
|
||||
loadOrder.insert(loadOrder.begin() + 5, blankEsl);
|
||||
}
|
||||
|
||||
@@ -255,8 +273,9 @@ TEST_P(GameInterfaceTest, setLoadOrderShouldSetTheLoadOrder) {
|
||||
|
||||
EXPECT_EQ(loadOrder, handle_->GetLoadOrder());
|
||||
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se)
|
||||
if (gameSupportsEsl) {
|
||||
loadOrder.erase(std::begin(loadOrder));
|
||||
}
|
||||
|
||||
EXPECT_EQ(loadOrder, getLoadOrder());
|
||||
}
|
||||
|
||||
@@ -67,7 +67,11 @@ INSTANTIATE_TEST_SUITE_P(,
|
||||
GameType::fo3,
|
||||
GameType::fonv,
|
||||
GameType::fo4,
|
||||
GameType::tes5se));
|
||||
GameType::tes5se,
|
||||
GameType::fo4vr,
|
||||
GameType::tes5vr,
|
||||
GameType::tes3,
|
||||
GameType::starfield));
|
||||
|
||||
TEST_P(GameTest, constructingShouldStoreTheGivenValues) {
|
||||
Game game = Game(GetParam(), dataPath.parent_path(), localPath);
|
||||
@@ -77,18 +81,17 @@ TEST_P(GameTest, constructingShouldStoreTheGivenValues) {
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
// Testing on Windows will find real game installs in the Registry, so cannot
|
||||
// test autodetection fully unless on Linux.
|
||||
TEST_P(GameTest, constructingShouldThrowOnLinuxIfGamePathIsNotGiven) {
|
||||
EXPECT_THROW(Game(GetParam(), "", localPath), std::invalid_argument);
|
||||
}
|
||||
|
||||
TEST_P(GameTest, constructingShouldThrowOnLinuxIfLocalPathIsNotGiven) {
|
||||
EXPECT_THROW(Game(GetParam(), dataPath.parent_path()), std::system_error);
|
||||
TEST_P(GameTest,
|
||||
constructingShouldThrowOnLinuxIfLocalPathIsNotGivenExceptForMorrowind) {
|
||||
if (GetParam() == GameType::tes3) {
|
||||
EXPECT_NO_THROW(Game(GetParam(), dataPath.parent_path()));
|
||||
} else {
|
||||
EXPECT_THROW(Game(GetParam(), dataPath.parent_path()), std::system_error);
|
||||
}
|
||||
}
|
||||
#else
|
||||
TEST_P(GameTest, constructingShouldNotThrowOnWindowsIfLocalPathIsNotGiven) {
|
||||
EXPECT_NO_THROW(Game(GetParam(), dataPath.parent_path(), localPath));
|
||||
EXPECT_NO_THROW(Game(GetParam(), dataPath.parent_path()));
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -246,8 +249,10 @@ TEST_P(GameTest, loadPluginsShouldFindArchivesInExternalDataPaths) {
|
||||
|
||||
// Create a couple of external archive files.
|
||||
const std::string archiveFileExtension =
|
||||
GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr ? ".ba2"
|
||||
: ".bsa";
|
||||
GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr ||
|
||||
GetParam() == GameType::starfield
|
||||
? ".ba2"
|
||||
: ".bsa";
|
||||
|
||||
const auto ba2Path1 =
|
||||
dataPath.parent_path() /
|
||||
|
||||
@@ -55,10 +55,10 @@ protected:
|
||||
return LoadOrderHandler(GetParam(), dataPath.parent_path(), localPath);
|
||||
}
|
||||
|
||||
std::vector<std::string> getImplicitlyActivePlugins() {
|
||||
std::vector<std::string> getEarlyLoadingPlugins() {
|
||||
switch (GetParam()) {
|
||||
case GameType::tes5:
|
||||
return {"Skyrim.esm", "Update.esm"};
|
||||
return {"Skyrim.esm"};
|
||||
case GameType::tes5se:
|
||||
return {"Skyrim.esm",
|
||||
"Update.esm",
|
||||
@@ -198,18 +198,17 @@ TEST_P(LoadOrderHandlerTest, getActivePluginsShouldReturnOnlyActivePlugins) {
|
||||
ASSERT_EQ(getActivePlugins(), loadOrderHandler.GetActivePlugins());
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
LoadOrderHandlerTest,
|
||||
getImplicitlyActivePluginsShouldReturnValidDataEvenIfStateHasNotBeenLoaded) {
|
||||
TEST_P(LoadOrderHandlerTest,
|
||||
getEarlyLoadingPluginsShouldReturnValidDataEvenIfStateHasNotBeenLoaded) {
|
||||
auto loadOrderHandler = createHandler();
|
||||
|
||||
ASSERT_EQ(getImplicitlyActivePlugins(),
|
||||
loadOrderHandler.GetImplicitlyActivePlugins());
|
||||
ASSERT_EQ(getEarlyLoadingPlugins(),
|
||||
loadOrderHandler.GetEarlyLoadingPlugins());
|
||||
|
||||
loadOrderHandler.LoadCurrentState();
|
||||
|
||||
ASSERT_EQ(getImplicitlyActivePlugins(),
|
||||
loadOrderHandler.GetImplicitlyActivePlugins());
|
||||
ASSERT_EQ(getEarlyLoadingPlugins(),
|
||||
loadOrderHandler.GetEarlyLoadingPlugins());
|
||||
}
|
||||
|
||||
TEST_P(LoadOrderHandlerTest, setLoadOrderShouldSetTheLoadOrder) {
|
||||
|
||||
@@ -65,18 +65,29 @@ protected:
|
||||
dataPath / blankEsp,
|
||||
dataPath / std::filesystem::u8path(otherNonAsciiEsp)));
|
||||
|
||||
if (GetParam() != GameType::fo4 && GetParam() != GameType::tes5se) {
|
||||
if (GetParam() != GameType::fo4 && GetParam() != GameType::fo4vr &&
|
||||
GetParam() != GameType::tes5se && GetParam() != GameType::tes5vr &&
|
||||
GetParam() != GameType::starfield) {
|
||||
ASSERT_NO_THROW(
|
||||
std::filesystem::copy(dataPath / blankEsp, dataPath / blankEsl));
|
||||
}
|
||||
|
||||
if (GetParam() == GameType::starfield) {
|
||||
// The ESL flag is not the same as in Skyrim SE, so modify the file
|
||||
// accordingly.
|
||||
auto bytes = ReadFile(dataPath / blankEsl);
|
||||
bytes[9] = 0x1;
|
||||
WriteFile(dataPath / blankEsl, bytes);
|
||||
}
|
||||
|
||||
// Copy across archive files.
|
||||
const auto blankMasterDependentArchive =
|
||||
"Blank - Master Dependent" + GetArchiveFileExtension(GetParam());
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr) {
|
||||
std::filesystem::path blankMasterDependentArchive;
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr ||
|
||||
GetParam() == GameType::starfield) {
|
||||
copyPlugin("./Fallout 4/Data", "Blank - Main.ba2");
|
||||
copyPlugin("./Fallout 4/Data", "Blank - Textures.ba2");
|
||||
|
||||
blankMasterDependentArchive = "Blank - Master Dependent - Main.ba2";
|
||||
std::filesystem::copy_file("./Fallout 4/Data/Blank - Main.ba2",
|
||||
dataPath / blankMasterDependentArchive);
|
||||
ASSERT_TRUE(
|
||||
@@ -84,10 +95,15 @@ protected:
|
||||
} else if (GetParam() == GameType::tes3) {
|
||||
out.open(dataPath / blankArchive);
|
||||
out.close();
|
||||
|
||||
blankMasterDependentArchive = "Blank - Master Dependent.bsa";
|
||||
out.open(dataPath / blankMasterDependentArchive);
|
||||
out.close();
|
||||
} else {
|
||||
copyPlugin(getSourcePluginsPath(), blankArchive);
|
||||
|
||||
// Also create a copy for Blank - Master Dependent.esp to test overlap.
|
||||
blankMasterDependentArchive = "Blank - Master Dependent.bsa";
|
||||
std::filesystem::copy_file(getSourcePluginsPath() / blankArchive,
|
||||
dataPath / blankMasterDependentArchive);
|
||||
ASSERT_TRUE(
|
||||
@@ -143,6 +159,24 @@ protected:
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<char> ReadFile(const std::filesystem::path& path) {
|
||||
std::vector<char> bytes;
|
||||
std::ifstream in(path, std::ios::binary);
|
||||
|
||||
std::copy(std::istreambuf_iterator<char>(in),
|
||||
std::istreambuf_iterator<char>(),
|
||||
std::back_inserter(bytes));
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void WriteFile(const std::filesystem::path& path,
|
||||
const std::vector<char>& content) {
|
||||
std::ofstream out(path, std::ios::binary);
|
||||
|
||||
out.write(content.data(), content.size());
|
||||
}
|
||||
|
||||
const std::string emptyFile;
|
||||
const std::string lowercaseBlankEsp;
|
||||
const std::string nonAsciiEsp;
|
||||
@@ -154,7 +188,8 @@ protected:
|
||||
|
||||
private:
|
||||
static std::string GetArchiveFileExtension(const GameType gameType) {
|
||||
if (gameType == GameType::fo4)
|
||||
if (gameType == GameType::fo4 || gameType == GameType::fo4vr ||
|
||||
gameType == GameType::starfield)
|
||||
return ".ba2";
|
||||
else
|
||||
return ".bsa";
|
||||
@@ -176,7 +211,9 @@ public:
|
||||
|
||||
bool IsMaster() const override { return false; }
|
||||
bool IsLightPlugin() const override { return false; }
|
||||
bool IsOverridePlugin() const override { return false; }
|
||||
bool IsValidAsLightPlugin() const override { return false; }
|
||||
bool IsValidAsOverridePlugin() const override { return false; }
|
||||
bool IsEmpty() const override { return false; }
|
||||
bool LoadsArchive() const override { return false; }
|
||||
bool DoRecordsOverlap(const PluginInterface&) const override { return true; }
|
||||
@@ -199,13 +236,16 @@ public:
|
||||
// but we only have the one so no prefix is necessary.
|
||||
INSTANTIATE_TEST_SUITE_P(,
|
||||
PluginTest,
|
||||
::testing::Values(GameType::tes3,
|
||||
GameType::tes4,
|
||||
::testing::Values(GameType::tes4,
|
||||
GameType::tes5,
|
||||
GameType::fo3,
|
||||
GameType::fonv,
|
||||
GameType::fo4,
|
||||
GameType::tes5se));
|
||||
GameType::tes5se,
|
||||
GameType::fo4vr,
|
||||
GameType::tes5vr,
|
||||
GameType::tes3,
|
||||
GameType::starfield));
|
||||
|
||||
TEST_P(PluginTest, loadingShouldHandleNonAsciiFilenamesCorrectly) {
|
||||
Plugin plugin(game_.GetType(),
|
||||
@@ -305,10 +345,30 @@ TEST_P(
|
||||
|
||||
EXPECT_FALSE(plugin1.IsLightPlugin());
|
||||
EXPECT_FALSE(plugin2.IsLightPlugin());
|
||||
EXPECT_EQ(GetParam() == GameType::fo4 || GetParam() == GameType::tes5se,
|
||||
EXPECT_EQ(GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr ||
|
||||
GetParam() == GameType::tes5se ||
|
||||
GetParam() == GameType::tes5vr ||
|
||||
GetParam() == GameType::starfield,
|
||||
plugin3.IsLightPlugin());
|
||||
}
|
||||
|
||||
TEST_P(PluginTest,
|
||||
isOverridePluginShouldOnlyBeTrueForAStarfieldOverridePlugin) {
|
||||
auto bytes = ReadFile(dataPath / blankDifferentPluginDependentEsp);
|
||||
bytes[9] = 0x2;
|
||||
WriteFile(dataPath / blankDifferentPluginDependentEsp, bytes);
|
||||
|
||||
Plugin plugin1(
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsp, true);
|
||||
Plugin plugin2(game_.GetType(),
|
||||
game_.GetCache(),
|
||||
game_.DataPath() / blankDifferentPluginDependentEsp,
|
||||
true);
|
||||
|
||||
EXPECT_FALSE(plugin1.IsOverridePlugin());
|
||||
EXPECT_EQ(GetParam() == GameType::starfield, plugin2.IsOverridePlugin());
|
||||
}
|
||||
|
||||
TEST_P(PluginTest, loadingAPluginWithMastersShouldReadThemCorrectly) {
|
||||
Plugin plugin(game_.GetType(),
|
||||
game_.GetCache(),
|
||||
@@ -343,7 +403,7 @@ TEST_P(
|
||||
#ifdef _WIN32
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
loadsArchiveForAnArchiveThatExactlyMatchesANonAsciiEspFileBasenameShouldReturnTrueForAllGamesExceptMorrowind) {
|
||||
loadsArchiveForAnArchiveThatExactlyMatchesANonAsciiEspFileBasenameShouldReturnTrueForAllGamesExceptMorrowindAndStarfield) {
|
||||
bool loadsArchive =
|
||||
Plugin(game_.GetType(),
|
||||
game_.GetCache(),
|
||||
@@ -351,7 +411,7 @@ TEST_P(
|
||||
true)
|
||||
.LoadsArchive();
|
||||
|
||||
if (GetParam() == GameType::tes3)
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::starfield)
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
else
|
||||
EXPECT_TRUE(loadsArchive);
|
||||
@@ -374,40 +434,43 @@ TEST_P(
|
||||
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheEsmFileBasenameShouldReturnTrueForAllGamesExceptMorrowindOblivionAndSkyrim) {
|
||||
loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheEsmFileBasenameShouldReturnTrueForOnlyTheFalloutGames) {
|
||||
bool loadsArchive = Plugin(game_.GetType(),
|
||||
game_.GetCache(),
|
||||
game_.DataPath() / blankDifferentEsm,
|
||||
true)
|
||||
.LoadsArchive();
|
||||
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::tes4 ||
|
||||
GetParam() == GameType::tes5 || GetParam() == GameType::tes5se)
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
else
|
||||
if (GetParam() == GameType::fo3 || GetParam() == GameType::fonv ||
|
||||
GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr) {
|
||||
EXPECT_TRUE(loadsArchive);
|
||||
} else {
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheEspFileBasenameShouldReturnTrueForAllGamesExceptMorrowindAndSkyrim) {
|
||||
loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheEspFileBasenameShouldReturnTrueForOnlyOblivionAndTheFalloutGames) {
|
||||
bool loadsArchive = Plugin(game_.GetType(),
|
||||
game_.GetCache(),
|
||||
game_.DataPath() / blankDifferentEsp,
|
||||
true)
|
||||
.LoadsArchive();
|
||||
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::tes5 ||
|
||||
GetParam() == GameType::tes5se)
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
else
|
||||
if (GetParam() == GameType::tes4 || GetParam() == GameType::fo3 ||
|
||||
GetParam() == GameType::fonv || GetParam() == GameType::fo4 ||
|
||||
GetParam() == GameType::fo4vr) {
|
||||
EXPECT_TRUE(loadsArchive);
|
||||
} else {
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheNonAsciiEspFileBasenameShouldReturnTrueForAllGamesExceptMorrowindAndSkyrim) {
|
||||
loadsArchiveForAnArchiveWithAFilenameWhichStartsWithTheNonAsciiEspFileBasenameShouldReturnTrueForOnlyOblivionAndTheFalloutGames) {
|
||||
bool loadsArchive =
|
||||
Plugin(game_.GetType(),
|
||||
game_.GetCache(),
|
||||
@@ -415,11 +478,13 @@ TEST_P(
|
||||
true)
|
||||
.LoadsArchive();
|
||||
|
||||
if (GetParam() == GameType::tes3 || GetParam() == GameType::tes5 ||
|
||||
GetParam() == GameType::tes5se)
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
else
|
||||
if (GetParam() == GameType::tes4 || GetParam() == GameType::fo3 ||
|
||||
GetParam() == GameType::fonv || GetParam() == GameType::fo4 ||
|
||||
GetParam() == GameType::fo4vr) {
|
||||
EXPECT_TRUE(loadsArchive);
|
||||
} else {
|
||||
EXPECT_FALSE(loadsArchive);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -458,13 +523,30 @@ TEST_P(
|
||||
Plugin(
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsm, true)
|
||||
.IsValidAsLightPlugin();
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) {
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr ||
|
||||
GetParam() == GameType::tes5se || GetParam() == GameType::tes5vr ||
|
||||
GetParam() == GameType::starfield) {
|
||||
EXPECT_TRUE(valid);
|
||||
} else {
|
||||
EXPECT_FALSE(valid);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
IsValidAsOverridePluginShouldOnlyReturnTrueForAStarfieldPluginWithNoNewRecords) {
|
||||
Plugin plugin1(
|
||||
game_.GetType(), game_.GetCache(), game_.DataPath() / blankEsp, false);
|
||||
Plugin plugin2(game_.GetType(),
|
||||
game_.GetCache(),
|
||||
game_.DataPath() / blankDifferentPluginDependentEsp,
|
||||
false);
|
||||
|
||||
EXPECT_FALSE(plugin1.IsValidAsOverridePlugin());
|
||||
EXPECT_EQ(GetParam() == GameType::starfield,
|
||||
plugin2.IsValidAsOverridePlugin());
|
||||
}
|
||||
|
||||
TEST_P(PluginTest,
|
||||
doRecordsOverlapShouldReturnFalseIfTheArgumentIsNotAPluginObject) {
|
||||
Plugin plugin1(
|
||||
@@ -590,7 +672,8 @@ TEST_P(PluginTest,
|
||||
|
||||
if (GetParam() == GameType::tes3) {
|
||||
EXPECT_EQ(0, assetCount);
|
||||
} else if (GetParam() == GameType::fo4) {
|
||||
} else if (GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr ||
|
||||
GetParam() == GameType::starfield) {
|
||||
EXPECT_EQ(2, assetCount);
|
||||
} else {
|
||||
EXPECT_EQ(1, assetCount);
|
||||
@@ -675,10 +758,13 @@ TEST_P(PluginTest,
|
||||
|
||||
TEST_P(
|
||||
PluginTest,
|
||||
hasPluginFileExtensionShouldBeTrueIfFileEndsInDotEslOnlyForFallout4AndSkyrimSE) {
|
||||
hasPluginFileExtensionShouldBeTrueIfFileEndsInDotEslOnlyForFallout4AndLater) {
|
||||
bool result = hasPluginFileExtension("file.esl", GetParam());
|
||||
|
||||
EXPECT_EQ(GetParam() == GameType::fo4 || GetParam() == GameType::tes5se,
|
||||
EXPECT_EQ(GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr ||
|
||||
GetParam() == GameType::tes5se ||
|
||||
GetParam() == GameType::tes5vr ||
|
||||
GetParam() == GameType::starfield,
|
||||
result);
|
||||
}
|
||||
|
||||
|
||||
@@ -60,8 +60,12 @@ public:
|
||||
|
||||
bool IsLightPlugin() const override { return false; }
|
||||
|
||||
bool IsOverridePlugin() const override { return false; }
|
||||
|
||||
bool IsValidAsLightPlugin() const override { return false; }
|
||||
|
||||
bool IsValidAsOverridePlugin() const override { return false; }
|
||||
|
||||
bool IsEmpty() const override { return false; }
|
||||
|
||||
bool LoadsArchive() const override { return false; }
|
||||
|
||||
@@ -108,8 +108,7 @@ protected:
|
||||
copyPlugin(sourcePluginsPath, blankPluginDependentEsp);
|
||||
copyPlugin(sourcePluginsPath, blankDifferentPluginDependentEsp);
|
||||
|
||||
if (GetParam() == GameType::tes5se || GetParam() == GameType::tes5vr ||
|
||||
GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr) {
|
||||
if (supportsLightPlugins(GetParam())) {
|
||||
copyPlugin(sourcePluginsPath, blankEsl);
|
||||
}
|
||||
|
||||
@@ -201,7 +200,7 @@ protected:
|
||||
actual.push_back(line);
|
||||
}
|
||||
} else {
|
||||
actual = readFileLines(localPath / pluginsTxtName(GetParam()));
|
||||
actual = readFileLines(localPath / "Plugins.txt");
|
||||
for (auto& line : actual) {
|
||||
if (line[0] == '*')
|
||||
line = line.substr(1);
|
||||
@@ -226,7 +225,7 @@ protected:
|
||||
{blankDifferentPluginDependentEsp, false},
|
||||
});
|
||||
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) {
|
||||
if (supportsLightPlugins(GetParam())) {
|
||||
loadOrder.insert(loadOrder.begin() + 5, std::make_pair(blankEsl, false));
|
||||
}
|
||||
|
||||
@@ -239,7 +238,7 @@ protected:
|
||||
return absolute("./Morrowind/Data Files");
|
||||
else if (GetParam() == GameType::tes4)
|
||||
return absolute("./Oblivion/Data");
|
||||
else if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se)
|
||||
else if (supportsLightPlugins(GetParam()))
|
||||
return absolute("./SkyrimSE/Data");
|
||||
else
|
||||
return absolute("./Skyrim/Data");
|
||||
@@ -281,14 +280,19 @@ private:
|
||||
return "Morrowind.esm";
|
||||
else if (GetParam() == GameType::tes4)
|
||||
return "Oblivion.esm";
|
||||
else if (GetParam() == GameType::tes5 || GetParam() == GameType::tes5se)
|
||||
else if (GetParam() == GameType::tes5 || GetParam() == GameType::tes5se ||
|
||||
GetParam() == GameType::tes5vr)
|
||||
return "Skyrim.esm";
|
||||
else if (GetParam() == GameType::fo3)
|
||||
return "Fallout3.esm";
|
||||
else if (GetParam() == GameType::fonv)
|
||||
return "FalloutNV.esm";
|
||||
else
|
||||
else if (GetParam() == GameType::fo4 || GetParam() == GameType::fo4vr)
|
||||
return "Fallout4.esm";
|
||||
else if (GetParam() == GameType::starfield)
|
||||
return "Starfield.esm";
|
||||
else
|
||||
throw std::logic_error("Unrecognised game type");
|
||||
}
|
||||
|
||||
std::string getPluginsFolder() const {
|
||||
@@ -320,9 +324,9 @@ private:
|
||||
}
|
||||
}
|
||||
} else {
|
||||
std::ofstream out(localPath / pluginsTxtName(GetParam()));
|
||||
std::ofstream out(localPath / "Plugins.txt");
|
||||
for (const auto& plugin : loadOrder) {
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) {
|
||||
if (supportsLightPlugins(GetParam())) {
|
||||
if (plugin.second)
|
||||
out << '*';
|
||||
} else if (!plugin.second)
|
||||
@@ -358,8 +362,10 @@ private:
|
||||
gameType == GameType::fo3 || gameType == GameType::fonv;
|
||||
}
|
||||
|
||||
static std::string pluginsTxtName(GameType gameType) {
|
||||
return gameType == GameType::tes4 ? "Plugins.txt" : "plugins.txt";
|
||||
static bool supportsLightPlugins(GameType gameType) {
|
||||
return gameType == GameType::tes5se || gameType == GameType::tes5vr ||
|
||||
gameType == GameType::fo4 || gameType == GameType::fo4vr ||
|
||||
gameType == GameType::starfield;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user