From 268c9ad92f9700a855d479d91a2e47ff381216f3 Mon Sep 17 00:00:00 2001 From: SulfurNitride Date: Fri, 8 May 2026 17:33:52 -0500 Subject: vfs: per-executable opt-in toggle for PE bridge MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Executable::UseVfsBridge flag (off by default) and a "Use VFS bridge (experimental)" checkbox in the executables editor next to "Open in terminal" / "Use Proton". The flag plumbs through to SpawnParameters::useVfsBridge and gates the FLUORINE_VFS_INDEX/DATA_DIR/ MOUNT env vars in spawn(). Without those env vars, the staged fluorine_vfs.dll still AppInit-loads into every PE process in the prefix, but its hook_worker bails at load_index() and installs zero hooks — equivalent to the bridge being disabled. No separate gating is needed in protonlauncher.cpp because its hid.dll-proxy staging and prewarm are already conditioned on vfsBridgeIndexPresent(envVars). Existing instances load with useVfsBridge=false (key absent from ModOrganizer.ini; the loader treats absence as false). New executables created via the plugin path also default off. Users who want the perf win for a particular game tick the box; everyone else launches via the correct, slower Wine + FUSE path. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/src/editexecutablesdialog.cpp | 13 +++++++++++++ src/src/editexecutablesdialog.ui | 13 +++++++++++++ src/src/executableslist.cpp | 12 ++++++++++++ src/src/executableslist.h | 4 +++- src/src/processrunner.cpp | 5 +++-- src/src/spawn.cpp | 22 ++++++++++++++-------- src/src/spawn.h | 13 +++++++------ 7 files changed, 65 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/src/editexecutablesdialog.cpp b/src/src/editexecutablesdialog.cpp index 723b3f5..4b8a478 100644 --- a/src/src/editexecutablesdialog.cpp +++ b/src/src/editexecutablesdialog.cpp @@ -129,6 +129,9 @@ EditExecutablesDialog::EditExecutablesDialog(OrganizerCore& oc, int sel, connect(ui->useTerminal, &QCheckBox::toggled, [&] { save(); }); + connect(ui->useVfsBridge, &QCheckBox::toggled, [&] { + save(); + }); connect(ui->list->model(), &QAbstractItemModel::rowsMoved, [&] { saveOrder(); }); @@ -398,6 +401,8 @@ void EditExecutablesDialog::clearEdits() ui->useProton->setChecked(true); ui->useTerminal->setEnabled(false); ui->useTerminal->setChecked(false); + ui->useVfsBridge->setEnabled(false); + ui->useVfsBridge->setChecked(false); m_lastGoodTitle = ""; } @@ -416,6 +421,7 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->hide->setChecked(e.hide()); ui->useProton->setChecked(e.useProton()); ui->useTerminal->setChecked(e.useTerminal()); + ui->useVfsBridge->setChecked(e.useVfsBridge()); m_lastGoodTitle = e.title(); @@ -464,6 +470,7 @@ void EditExecutablesDialog::setEdits(const Executable& e) ui->hide->setEnabled(true); ui->useProton->setEnabled(true); ui->useTerminal->setEnabled(true); + ui->useVfsBridge->setEnabled(true); } void EditExecutablesDialog::save() @@ -547,6 +554,12 @@ void EditExecutablesDialog::save() e->flags(e->flags() & (~Executable::UseTerminal)); } + if (ui->useVfsBridge->isChecked()) { + e->flags(e->flags() | Executable::UseVfsBridge); + } else { + e->flags(e->flags() & (~Executable::UseVfsBridge)); + } + setDirty(true); } diff --git a/src/src/editexecutablesdialog.ui b/src/src/editexecutablesdialog.ui index 8ac01f6..f323558 100644 --- a/src/src/editexecutablesdialog.ui +++ b/src/src/editexecutablesdialog.ui @@ -487,6 +487,19 @@ Right now the only case I know of where this needs to be overwritten is for the + + + + Inject the PE-side VFS bridge (fluorine_vfs.dll) so file lookups in the game's data dir hit a static index instead of going through Wine + FUSE every time. Saves several seconds on first boot for large modlists. Off by default — some modlists crash with the bridge enabled until indexer/negcache work is finished. + + + Use VFS bridge (experimental) + + + false + + + diff --git a/src/src/executableslist.cpp b/src/src/executableslist.cpp index ca4f295..49b9e86 100644 --- a/src/src/executableslist.cpp +++ b/src/src/executableslist.cpp @@ -96,6 +96,12 @@ void ExecutablesList::load(const MOBase::IPluginGame* game, const Settings& s) if (map["useTerminal"].toBool()) flags |= Executable::UseTerminal; + // Default to UseVfsBridge=false. The PE-side bridge is opt-in + // because some modlists (notably ones with NPCWaterAIFix) crash + // with it enabled until the indexer/negcache work is finished. + if (map.contains("useVfsBridge") && map["useVfsBridge"].toBool()) + flags |= Executable::UseVfsBridge; + if (map.contains("custom")) { // the "custom" setting only exists in older versions needsUpgrade = true; @@ -136,6 +142,7 @@ void ExecutablesList::store(Settings& s) map["minimizeToSystemTray"] = item.minimizeToSystemTray(); map["useProton"] = item.useProton(); map["useTerminal"] = item.useTerminal(); + map["useVfsBridge"] = item.useVfsBridge(); v.push_back(std::move(map)); } @@ -511,6 +518,11 @@ bool Executable::useTerminal() const return m_flags.testFlag(UseTerminal); } +bool Executable::useVfsBridge() const +{ + return m_flags.testFlag(UseVfsBridge); +} + void Executable::mergeFrom(const Executable& other) { // this happens after executables are loaded from settings and plugin diff --git a/src/src/executableslist.h b/src/src/executableslist.h index 8ec8cbb..86a5476 100644 --- a/src/src/executableslist.h +++ b/src/src/executableslist.h @@ -50,7 +50,8 @@ public: Hide = 0x08, MinimizeToSystemTray = 0x16, UseProton = 0x20, - UseTerminal = 0x40 + UseTerminal = 0x40, + UseVfsBridge = 0x80 }; Q_DECLARE_FLAGS(Flags, Flag); @@ -83,6 +84,7 @@ public: bool hide() const override; bool useProton() const; bool useTerminal() const; + bool useVfsBridge() const; void mergeFrom(const Executable& other); diff --git a/src/src/processrunner.cpp b/src/src/processrunner.cpp index 4fb8a2c..ed081e2 100644 --- a/src/src/processrunner.cpp +++ b/src/src/processrunner.cpp @@ -911,8 +911,9 @@ ProcessRunner& ProcessRunner::setFromExecutable(const Executable& exe) setCustomOverwrite(customOverwrite); setForcedLibraries(forcedLibraries); - m_sp.useProton = exe.useProton(); - m_sp.useTerminal = exe.useTerminal(); + m_sp.useProton = exe.useProton(); + m_sp.useTerminal = exe.useTerminal(); + m_sp.useVfsBridge = exe.useVfsBridge(); return *this; } diff --git a/src/src/spawn.cpp b/src/src/spawn.cpp index 5ed3db1..f7a4248 100644 --- a/src/src/spawn.cpp +++ b/src/src/spawn.cpp @@ -476,14 +476,20 @@ int spawn(const SpawnParameters& sp, pid_t& processId) MOBase::log::info("Proton disabled for this executable, launching directly"); } - if (!sp.vfsBridgeIndexPath.isEmpty()) { - launcher.addEnvVar("FLUORINE_VFS_INDEX", sp.vfsBridgeIndexPath); - } - if (!sp.vfsBridgeDataDir.isEmpty()) { - launcher.addEnvVar("FLUORINE_VFS_DATA_DIR", sp.vfsBridgeDataDir); - } - if (!sp.vfsBridgeMountPoint.isEmpty()) { - launcher.addEnvVar("FLUORINE_VFS_MOUNT", sp.vfsBridgeMountPoint); + // PE-side VFS bridge is per-executable opt-in. Without these env + // vars the staged fluorine_vfs.dll loads but its hook_worker bails + // out at load_index() (FLUORINE_VFS_INDEX unset) and installs zero + // hooks — equivalent to the bridge being disabled. + if (sp.useVfsBridge) { + if (!sp.vfsBridgeIndexPath.isEmpty()) { + launcher.addEnvVar("FLUORINE_VFS_INDEX", sp.vfsBridgeIndexPath); + } + if (!sp.vfsBridgeDataDir.isEmpty()) { + launcher.addEnvVar("FLUORINE_VFS_DATA_DIR", sp.vfsBridgeDataDir); + } + if (!sp.vfsBridgeMountPoint.isEmpty()) { + launcher.addEnvVar("FLUORINE_VFS_MOUNT", sp.vfsBridgeMountPoint); + } } launcher.setUseTerminal(sp.useTerminal); diff --git a/src/src/spawn.h b/src/src/spawn.h index 77ad204..010edb9 100644 --- a/src/src/spawn.h +++ b/src/src/spawn.h @@ -53,6 +53,7 @@ struct SpawnParameters bool hooked = false; bool useProton = true; bool useTerminal = false; + bool useVfsBridge = false; int stdOut = -1; int stdErr = -1; // When both are set and unprivileged user namespaces are available, @@ -60,12 +61,12 @@ struct SpawnParameters // of `saveBindMountSource` for the duration of the game process tree. // Used to redirect `/__MO_Saves` to the profile's saves dir // without symlinks, which Wine can accidentally replace. - QString saveBindMountSource; - QString saveBindMountTarget; - QString vfsBridgeIndexPath; - QString vfsBridgeDataDir; - QString vfsBridgeMountPoint; -}; + QString saveBindMountSource; + QString saveBindMountTarget; + QString vfsBridgeIndexPath; + QString vfsBridgeDataDir; + QString vfsBridgeMountPoint; +}; bool checkSteam(QWidget* parent, const SpawnParameters& sp, const QDir& gameDirectory, const QString& steamAppID, const Settings& settings); -- cgit v1.3.1