mirror of
https://github.com/loot/libloot.git
synced 2026-07-27 14:16:01 -07:00
Enforce hardcoded plugin positions during sorting
It's very verbose to enforce this through masterlist metadata, and the data is available through libloadorder. Don't treat Update.esm for the original Skyrim as having a hardcoded position, as it doesn't even though it's implicitly active.
This commit is contained in:
+1
-1
@@ -100,7 +100,7 @@ set(LIBGIT2_LIBRARIES "${BINARY_DIR}/${CMAKE_CFG_INTDIR}/${CMAKE_STATIC_LIBRARY_
|
||||
|
||||
ExternalProject_Add(libloadorder
|
||||
PREFIX "external"
|
||||
URL "https://github.com/WrinklyNinja/libloadorder/archive/11.2.2.tar.gz"
|
||||
URL "https://github.com/WrinklyNinja/libloadorder/archive/11.3.0.tar.gz"
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_IN_SOURCE 1
|
||||
BUILD_COMMAND cargo build --release --manifest-path ffi/Cargo.toml --features ffi-headers --target ${RUST_TARGET}
|
||||
|
||||
@@ -129,6 +129,26 @@ std::vector<std::string> LoadOrderHandler::GetLoadOrder() const {
|
||||
return loadOrder;
|
||||
}
|
||||
|
||||
std::vector<std::string> LoadOrderHandler::GetImplicitlyActivePlugins() const {
|
||||
auto logger = getLogger();
|
||||
if (logger) {
|
||||
logger->debug("Getting implicitly active plugins.");
|
||||
}
|
||||
|
||||
char** pluginArr;
|
||||
size_t pluginArrSize;
|
||||
|
||||
unsigned int ret =
|
||||
lo_get_implicitly_active_plugins(gh_, &pluginArr, &pluginArrSize);
|
||||
|
||||
HandleError("get implicitly active plugins", ret);
|
||||
|
||||
std::vector<string> loadOrder(pluginArr, pluginArr + pluginArrSize);
|
||||
lo_free_string_array(pluginArr, pluginArrSize);
|
||||
|
||||
return loadOrder;
|
||||
}
|
||||
|
||||
void LoadOrderHandler::SetLoadOrder(
|
||||
const std::vector<std::string>& loadOrder) const {
|
||||
auto logger = getLogger();
|
||||
|
||||
@@ -48,6 +48,8 @@ public:
|
||||
|
||||
std::vector<std::string> GetLoadOrder() const;
|
||||
|
||||
std::vector<std::string> GetImplicitlyActivePlugins() const;
|
||||
|
||||
bool IsPluginActive(const std::string& pluginName) const;
|
||||
|
||||
void SetLoadOrder(const std::vector<std::string>& loadOrder) const;
|
||||
|
||||
@@ -132,6 +132,8 @@ std::vector<std::string> PluginSorter::Sort(Game& game) {
|
||||
}
|
||||
AddSpecificEdges();
|
||||
|
||||
AddHardcodedPluginEdges(game);
|
||||
|
||||
AddGroupEdges();
|
||||
|
||||
if (logger_) {
|
||||
@@ -328,6 +330,51 @@ void PluginSorter::AddEdge(const vertex_t& fromVertex,
|
||||
}
|
||||
}
|
||||
|
||||
void PluginSorter::AddHardcodedPluginEdges(Game& game) {
|
||||
if (logger_) {
|
||||
logger_->trace("Adding hardcoded plugin edges.");
|
||||
}
|
||||
|
||||
auto implicitlyActivePlugins =
|
||||
game.GetLoadOrderHandler()->GetImplicitlyActivePlugins();
|
||||
|
||||
std::set<std::string> processedPlugins;
|
||||
for (const auto& plugin : implicitlyActivePlugins) {
|
||||
auto lowercasedName = boost::locale::to_lower(plugin);
|
||||
processedPlugins.insert(lowercasedName);
|
||||
|
||||
if (game.Type() == GameType::tes5 && lowercasedName == "update.esm") {
|
||||
if (logger_) {
|
||||
logger_->trace(
|
||||
"Skipping adding hardcoded plugin edges for Update.esm as it does "
|
||||
"not have a hardcoded position for Skyrim.");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
vertex_t pluginVertex;
|
||||
|
||||
if (!GetVertexByName(plugin, pluginVertex)) {
|
||||
if (logger_) {
|
||||
logger_->trace(
|
||||
"Skipping adding harcoded plugin edges for \"{}\" as it is not "
|
||||
"installed.",
|
||||
plugin);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
vertex_it vit, vitend;
|
||||
for (tie(vit, vitend) = boost::vertices(graph_); vit != vitend; ++vit) {
|
||||
auto& graphPlugin = graph_[*vit];
|
||||
|
||||
if (processedPlugins.count(graphPlugin.GetLowercasedName()) == 0) {
|
||||
AddEdge(pluginVertex, *vit);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void PluginSorter::AddSpecificEdges() {
|
||||
// Add edges for all relationships that aren't overlaps.
|
||||
vertex_it vit, vitend;
|
||||
|
||||
@@ -59,6 +59,7 @@ private:
|
||||
|
||||
void AddPluginVertices(Game& game);
|
||||
void AddSpecificEdges();
|
||||
void AddHardcodedPluginEdges(Game& game);
|
||||
void AddGroupEdges();
|
||||
void AddOverlapEdges();
|
||||
void AddTieBreakEdges();
|
||||
|
||||
@@ -42,7 +42,8 @@ public:
|
||||
|
||||
std::unordered_set<std::string> GetAfterGroupPlugins() const;
|
||||
void SetAfterGroupPlugins(std::unordered_set<std::string> plugins);
|
||||
|
||||
|
||||
using PluginMetadata::GetLowercasedName;
|
||||
using PluginMetadata::GetGroup;
|
||||
using PluginMetadata::GetLoadAfterFiles;
|
||||
using PluginMetadata::GetRequirements;
|
||||
|
||||
@@ -59,6 +59,38 @@ protected:
|
||||
loadOrderHandler_.Init(GetParam(), dataPath.parent_path(), localPath));
|
||||
}
|
||||
|
||||
std::vector<std::string> getImplicitlyActivePlugins() {
|
||||
switch (GetParam()) {
|
||||
case GameType::tes5:
|
||||
return {"Skyrim.esm", "Update.esm"};
|
||||
case GameType::tes5se:
|
||||
return {"Skyrim.esm",
|
||||
"Update.esm",
|
||||
"Dawnguard.esm",
|
||||
"Hearthfires.esm",
|
||||
"Dragonborn.esm"};
|
||||
case GameType::tes5vr:
|
||||
return {"Skyrim.esm",
|
||||
"Update.esm",
|
||||
"Dawnguard.esm",
|
||||
"Hearthfires.esm",
|
||||
"Dragonborn.esm",
|
||||
"SkyrimVR.esm"};
|
||||
case GameType::fo4:
|
||||
return {"Fallout4.esm",
|
||||
"DLCRobot.esm",
|
||||
"DLCworkshop01.esm",
|
||||
"DLCCoast.esm",
|
||||
"DLCworkshop02.esm",
|
||||
"DLCworkshop03.esm",
|
||||
"DLCNukaWorld.esm"};
|
||||
case GameType::fo4vr:
|
||||
return {"Fallout4.esm", "Fallout4_VR.esm"};
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
LoadOrderHandler loadOrderHandler_;
|
||||
std::vector<std::string> loadOrderToSet_;
|
||||
};
|
||||
@@ -139,6 +171,25 @@ TEST_P(LoadOrderHandlerTest, getLoadOrderShouldReturnTheCurrentLoadOrder) {
|
||||
ASSERT_EQ(getLoadOrder(), loadOrderHandler_.GetLoadOrder());
|
||||
}
|
||||
|
||||
TEST_P(LoadOrderHandlerTest,
|
||||
getImplicitlyActivePluginsShouldThrowIfTheHandlerHasNotBeenInitialised) {
|
||||
EXPECT_THROW(loadOrderHandler_.GetImplicitlyActivePlugins(),
|
||||
std::system_error);
|
||||
}
|
||||
TEST_P(
|
||||
LoadOrderHandlerTest,
|
||||
getImplicitlyActivePluginsShouldReturnValidDataEvenIfStateHasNotBeenLoaded) {
|
||||
initialiseHandler();
|
||||
|
||||
ASSERT_EQ(getImplicitlyActivePlugins(),
|
||||
loadOrderHandler_.GetImplicitlyActivePlugins());
|
||||
|
||||
loadOrderHandler_.LoadCurrentState();
|
||||
|
||||
ASSERT_EQ(getImplicitlyActivePlugins(),
|
||||
loadOrderHandler_.GetImplicitlyActivePlugins());
|
||||
}
|
||||
|
||||
TEST_P(LoadOrderHandlerTest,
|
||||
setLoadOrderShouldThrowIfTheHandlerHasNotBeenInitialised) {
|
||||
EXPECT_THROW(loadOrderHandler_.SetLoadOrder(loadOrderToSet_),
|
||||
|
||||
@@ -38,6 +38,7 @@ protected:
|
||||
PluginSorterTest() :
|
||||
game_(GetParam(), dataPath.parent_path(), localPath),
|
||||
masterlistPath_("./userlist.yaml"),
|
||||
cccPath_(dataPath.parent_path() / getCCCFilename()),
|
||||
blankEslEsp("Blank.esl.esp") {}
|
||||
|
||||
void TearDown() {
|
||||
@@ -48,6 +49,7 @@ protected:
|
||||
}
|
||||
|
||||
boost::filesystem::remove(masterlistPath_);
|
||||
boost::filesystem::remove(cccPath_);
|
||||
}
|
||||
|
||||
void loadInstalledPlugins(Game &game_, bool headersOnly) {
|
||||
@@ -104,9 +106,32 @@ protected:
|
||||
masterlist.close();
|
||||
}
|
||||
|
||||
std::string getCCCFilename() {
|
||||
if (GetParam() == GameType::fo4) {
|
||||
return "Fallout4.ccc";
|
||||
}
|
||||
else {
|
||||
// Not every game has a .ccc file, but Skyrim SE does, so just assume that.
|
||||
return "Skyrim.ccc";
|
||||
}
|
||||
}
|
||||
|
||||
void GenerateCCCFile() {
|
||||
using std::endl;
|
||||
|
||||
if (GetParam() == GameType::fo4) {
|
||||
boost::filesystem::ofstream ccc(cccPath_);
|
||||
ccc << blankDifferentEsm << endl
|
||||
<< blankDifferentMasterDependentEsm << endl;
|
||||
|
||||
ccc.close();
|
||||
}
|
||||
}
|
||||
|
||||
Game game_;
|
||||
const std::string blankEslEsp;
|
||||
const boost::filesystem::path masterlistPath_;
|
||||
const boost::filesystem::path cccPath_;
|
||||
};
|
||||
|
||||
// Pass an empty first argument, as it's a prefix for the test instantation,
|
||||
@@ -319,7 +344,6 @@ TEST_P(
|
||||
blankEsm,
|
||||
blankMasterDependentEsm,
|
||||
blankDifferentEsm,
|
||||
masterFile,
|
||||
blankDifferentMasterDependentEsm,
|
||||
blankEsp,
|
||||
blankDifferentEsp,
|
||||
@@ -330,7 +354,10 @@ TEST_P(
|
||||
});
|
||||
|
||||
if (GetParam() == GameType::fo4 || GetParam() == GameType::tes5se) {
|
||||
expectedSortedOrder.insert(expectedSortedOrder.begin(), masterFile);
|
||||
expectedSortedOrder.insert(expectedSortedOrder.begin() + 5, blankEsl);
|
||||
} else {
|
||||
expectedSortedOrder.insert(expectedSortedOrder.begin() + 3, masterFile);
|
||||
}
|
||||
|
||||
std::vector<std::string> sorted = ps.Sort(game_);
|
||||
@@ -466,6 +493,38 @@ TEST_P(PluginSorterTest,
|
||||
EXPECT_EQ(expectedSortedOrder, sorted);
|
||||
}
|
||||
|
||||
TEST_P(PluginSorterTest,
|
||||
sortingShouldUseTheGameCCCFileToEnforceHardcodedLoadOrderPositions) {
|
||||
if (GetParam() != GameType::fo4) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Need to generate CCC file then recreate game object as the file is only
|
||||
// read during intialisation.
|
||||
GenerateCCCFile();
|
||||
Game newGame(GetParam(), dataPath.parent_path(), localPath);
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(newGame, false));
|
||||
|
||||
PluginSorter ps;
|
||||
std::vector<std::string> expectedSortedOrder({
|
||||
masterFile,
|
||||
blankDifferentEsm,
|
||||
blankDifferentMasterDependentEsm,
|
||||
blankEsm,
|
||||
blankMasterDependentEsm,
|
||||
blankEsl,
|
||||
blankEsp,
|
||||
blankDifferentEsp,
|
||||
blankMasterDependentEsp,
|
||||
blankDifferentMasterDependentEsp,
|
||||
blankPluginDependentEsp,
|
||||
blankDifferentPluginDependentEsp,
|
||||
});
|
||||
|
||||
std::vector<std::string> sorted = ps.Sort(newGame);
|
||||
EXPECT_EQ(expectedSortedOrder, sorted);
|
||||
}
|
||||
|
||||
TEST_P(PluginSorterTest, sortingShouldThrowIfACyclicInteractionIsEncountered) {
|
||||
ASSERT_NO_THROW(loadInstalledPlugins(game_, false));
|
||||
PluginMetadata plugin(blankEsm);
|
||||
|
||||
Reference in New Issue
Block a user