aboutsummaryrefslogtreecommitdiff
path: root/libs/installer_fomod_plus/installer/lib/ConditionTester.cpp
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-14 02:45:12 -0600
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-02-14 02:45:25 -0600
commit817e8f5cd26739c69d930d21cd9dc4c0b6e4984e (patch)
tree52aed11d6751bb7d20b06acf197d56fac113203d /libs/installer_fomod_plus/installer/lib/ConditionTester.cpp
parent51a9f8f197727f00896e5de44569b098923527dd (diff)
Add FUSE external mapping support, BG3/Oblivion Remastered fixes, fomod-plus and NaK integration
FUSE VFS now deploys non-data-dir mod mappings (Paks, OBSE, UE4SS, etc.) via real symlinks and injects file-level data-dir mappings (plugins.txt, loadorder.txt) into the VFS tree. Fixes game launches for Oblivion Remastered (Root Builder path resolution, script extender support) and BG3 (Wine prefix documents directory, file mapper symlinks on Linux). Vendors mo2-fomod-plus plugin and NaK crate for FOMOD installer and game finder/runtime support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'libs/installer_fomod_plus/installer/lib/ConditionTester.cpp')
-rw-r--r--libs/installer_fomod_plus/installer/lib/ConditionTester.cpp176
1 files changed, 176 insertions, 0 deletions
diff --git a/libs/installer_fomod_plus/installer/lib/ConditionTester.cpp b/libs/installer_fomod_plus/installer/lib/ConditionTester.cpp
new file mode 100644
index 0000000..c35bf47
--- /dev/null
+++ b/libs/installer_fomod_plus/installer/lib/ConditionTester.cpp
@@ -0,0 +1,176 @@
+#include "ConditionTester.h"
+
+#include "ui/FomodViewModel.h"
+
+#include <iplugingame.h>
+#include <ipluginlist.h>
+
+std::string setToString(const std::set<int> &set)
+{
+ std::string str;
+ for (const auto& i : set) {
+ str += std::to_string(i) + ", ";
+ }
+ return str;
+}
+
+bool ConditionTester::isStepVisible(const std::shared_ptr<FlagMap>& flags,
+ const CompositeDependency& compositeDependency,
+ const int stepIndex,
+ const std::vector<std::shared_ptr<StepViewModel>>& steps) const
+{
+
+ // first things first: is it visible?
+ if (!testCompositeDependency(flags, compositeDependency)) {
+ return false;
+ }
+
+ const auto flagDependencies = compositeDependency.flagDependencies;
+ if (flagDependencies.empty()) {
+ return true;
+ }
+
+ std::set<int> stepsThatSetThisFlag;
+
+ for (const auto& flagDependency : flagDependencies) {
+ // for this flag, find the plugins that set it
+ for (int i = stepIndex - 1; i >= 0; --i) {
+ for (const auto& group : steps[i]->getGroups()) {
+ for (const auto& plugin : group->getPlugins()) {
+ if (std::ranges::any_of(plugin->getPlugin()->conditionFlags.flags,
+ [&flagDependency](const ConditionFlag& flag) {
+ return flag.name == flagDependency.flag && flag.value == flagDependency.value;
+ })) {
+ stepsThatSetThisFlag.insert(i);
+ }
+ }
+ }
+ }
+ }
+ const auto anyVisible = std::ranges::any_of(stepsThatSetThisFlag, [this, &steps, &flags](const int index) {
+ return isStepVisible(flags, steps[index]->getVisibilityConditions(), index, steps);
+ });
+ if (!anyVisible) {
+ log.logMessage(DEBUG, "Step " + steps[stepIndex]->getName() + " has no dependent steps that are visible.");
+ log.logMessage(DEBUG, "Steps that set this flag: " + setToString(stepsThatSetThisFlag));
+ }
+ return anyVisible;
+
+}
+
+bool ConditionTester::testCompositeDependency(const std::shared_ptr<FlagMap>& flags,
+ const CompositeDependency& compositeDependency) const
+{
+ const auto fileDependencies = compositeDependency.fileDependencies;
+ const auto flagDependencies = compositeDependency.flagDependencies;
+ const auto gameDependencies = compositeDependency.gameDependencies;
+ const auto nestedDependencies = compositeDependency.nestedDependencies;
+ const auto globalOperatorType = compositeDependency.operatorType;
+
+ // For the globalOperatorType
+ // Evaluate all conditions and store the results in a vector<bool>, then return based on operator.
+ // These aren't expensive to calculate so rather than do some fancy logic to short-circuit, just calculate all of 'em.
+ std::vector<bool> results;
+ for (const auto& fileDependency : fileDependencies) {
+ results.emplace_back(testFileDependency(fileDependency));
+ }
+ for (const auto& flagDependency : flagDependencies) {
+ results.emplace_back(testFlagDependency(flags, flagDependency));
+ }
+ for (const auto& gameDependency : gameDependencies) {
+ results.emplace_back(testGameDependency(gameDependency));
+ }
+ for (const auto& nestedDependency : nestedDependencies) {
+ results.emplace_back(testCompositeDependency(flags, nestedDependency));
+ }
+
+ if (globalOperatorType == OperatorTypeEnum::AND) {
+ return std::ranges::all_of(results, [](const bool result) { return result; });
+ }
+ return std::ranges::any_of(results, [](const bool result) { return result; });
+}
+
+
+bool ConditionTester::testFlagDependency(const std::shared_ptr<FlagMap>& flags, const FlagDependency& flagDependency)
+{
+ // Every instance of this flag being set in the map.
+ const auto flagList = flags->getFlagsByKey(flagDependency.flag);
+
+ // Find the first instance of this flag being set (in the order specified by getFlagsByKey)
+ if (flagList.empty()) {
+ // If the dependency value is an empty string, it means this flag should be unset.
+ // So if we don't have any value for this flag, the result is true.
+ return flagDependency.value.empty();
+ }
+
+ return flagList.front().second == flagDependency.value;
+}
+
+bool ConditionTester::testFileDependency(const FileDependency& fileDependency) const
+{
+ const std::string& pluginName = fileDependency.file;
+ const auto pluginState = getFileDependencyStateForPlugin(pluginName);
+ return pluginState == fileDependency.state;
+}
+
+bool ConditionTester::testGameDependency(const GameDependency& gameDependency) const
+{
+ const auto gameVersion = mOrganizer->managedGame()->gameVersion().toStdString();
+ log.logMessage(DEBUG, "Comparing condition version " + gameDependency.version + " against " + gameVersion);
+ if ( gameDependency.version <= gameVersion) {
+ log.logMessage(DEBUG, "Version matches!");
+ }
+ return gameDependency.version <= gameVersion;
+}
+
+FileDependencyTypeEnum ConditionTester::getFileDependencyStateForPlugin(const std::string& pluginName) const
+{
+ if (const auto it = pluginStateCache.find(pluginName); it != pluginStateCache.end()) {
+ return it->second;
+ }
+
+ const QFlags<MOBase::IPluginList::PluginState> pluginState = mOrganizer->pluginList()->state(
+ QString::fromStdString(pluginName));
+
+ FileDependencyTypeEnum state;
+
+ if (pluginState == MOBase::IPluginList::STATE_MISSING) {
+ state = FileDependencyTypeEnum::Missing;
+ } else if (pluginState == MOBase::IPluginList::STATE_INACTIVE) {
+ state = FileDependencyTypeEnum::Inactive;
+ } else if (pluginState == MOBase::IPluginList::STATE_ACTIVE) {
+ state = FileDependencyTypeEnum::Active;
+ } else {
+ state = FileDependencyTypeEnum::UNKNOWN_STATE;
+ }
+
+ pluginStateCache[pluginName] = state;
+ return state;
+}
+
+PluginTypeEnum ConditionTester::getPluginTypeDescriptorState(const std::shared_ptr<Plugin>& plugin,
+ const std::shared_ptr<FlagMap>& flags) const
+{
+ // NOTE: A plugin's ConditionFlags aren't the same thing as a step visibility one.
+ // A plugin's ConditionFlags are toggled based on the selection state of the plugin
+ // We only evaluate the typeDescriptor here.
+
+ // We will return the 'winning' type or the default. If multiple conditions are met,
+ // ...well, I'm not sure.
+ // ReSharper disable once CppTooWideScopeInitStatement
+ const auto& dependencyType = plugin->typeDescriptor.dependencyType;
+ for (const auto& pattern : dependencyType.patterns.patterns) {
+ if (testCompositeDependency(flags, pattern.dependencies)) {
+ return pattern.type;
+ }
+ }
+
+ // Sometimes authors do this.
+ if (plugin->typeDescriptor.type != PluginTypeEnum::Optional) {
+ return plugin->typeDescriptor.type;
+ }
+ if (plugin->typeDescriptor.dependencyType.defaultType.has_value()) {
+ return plugin->typeDescriptor.dependencyType.defaultType.value();
+ }
+ return PluginTypeEnum::Optional;
+} \ No newline at end of file