aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
* Strip Windows-only blocks from env*.cpp/.hSulfurNitride2026-04-286-1785/+295
| | | | | | | | | | | | | | | | | | These files were ported from upstream MO2 with the Windows code wrapped in #ifdef _WIN32 and Linux equivalents in the #else branch. The Windows branches never compile on this fork — drop them so the source matches what's actually built. - envwindows.cpp/.h: kept Linux uname()/os-release implementation; the WindowsInfo::Version/Release fields drop their DWORD/uint32_t fork. - envshell.cpp: deleted entirely (Linux body was empty — header declares no-op stubs inline). - envshell.h: drop Win32 ShellMenu declarations, keep Linux stubs. - envmetrics.cpp: kept QScreen-based Linux Display/Metrics implementation. - envshortcut.cpp: kept Linux .desktop shortcut writer + PE icon extractor, drop Win32 IShellLink path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Native port of SKSELogRedirector plugin family (sysdmp), default offSulfurNitride2026-04-2817-0/+322
| | | | | | | | | | | | | | | | | | | | Three FileMapper variants — Steam SSE, GOG, VR — that redirect <docs>/My Games/<X> onto a sibling folder so SKSE writes logs to the right place. Only one applies per game; all default off so the user opts in to the matching variant. Layout mirrors game_bethesda: shared static lib in src/common/ with the QObject base (init/author/version/settings/enabledByDefault/mappings), three plugin .so subdirs each adding Q_PLUGIN_METADATA + their own name/description and destFolderName override. Q_PLUGIN_METADATA can only appear once per .so, hence the split. Paths preserved verbatim from the upstream .py — including the SSE variant's "Skyrim.INI" sibling folder, which is intentional: a Skyrim bug sometimes creates a directory of that name and the redirect makes SKSE find logs in SSE's docs folder. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ProtonLauncher: empty UMU_ID to suppress GE-Proton X:=$HOME mountSulfurNitride2026-04-281-1/+9
| | | | | | | | | | | | | | | | | | | | | | | | Setting UMU_ID="fluorine" triggered protonfixes' setup_mount_drives (GE-Proton/utilities.py), which mounts X:=$HOME, U:=/media, V:=/run/media, W:=/mnt every launch — undoing pruneExtraDrives. Wine's find_drive_nt_root walks the path innermost-out and picks the first matching drive, so X:=$HOME wins over Z:=/ for any path under home, producing X:\games\... launches. Empty UMU_ID: - protonfixes utilities.py:38 `os.environ.get('UMU_ID','')` -> '' falsy -> setup_mount_drives early-exits, no X/U/V/W created - proton:1590 same falsy check -> 'gamedrive' not added to compat_config -> setup_game_dir_drive's setup_dir_drive takes the elif branch and actively removes any stale s: symlink - proton:2340 `"UMU_ID" in os.environ` is still True for "" -> still skips the c:\Program Files (x86)\Steam\steam.exe bridge for non-Steam executables (the original reason we set UMU_ID) Verified against installed Proton-GE proton script + utilities.py and GloriousEggroll's confirmation in umu-launcher#634. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Launcher: overlay update instead of rm -rf bin/SulfurNitride2026-04-271-16/+42
| | | | | | | | | | | | | | | The version-sync step wiped ~/.local/share/fluorine/bin/ wholesale before staging the new bundle in, which destroyed any user content sitting alongside ours: portable instances (ModOrganizer.ini lives in bin/ via MO2_BASE_DIR), custom plugins under bin/plugins/, etc. Replace the wipe with a manifest-driven overlay: orphan top-level entries we no longer ship get removed, then tar streams the manifested entries from the extracted bundle on top of the live dir. Existing directories are kept; user-added files inside our directories survive because tar -x only writes paths it sees in the input stream. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Prefix init: keep host DISPLAY so xrandr works in protonfixesSulfurNitride2026-04-271-2/+4
| | | | | | | | | Blanking DISPLAY/WAYLAND_DISPLAY made xrandr exit 1 inside the container, which Proton-GE 10-34's protonfixes treats as fatal during wineboot -u. wineboot -u is unattended and won't pop UI, so leaking the host display vars is safe. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Per-instance Steam overlay toggleSulfurNitride2026-04-265-4/+107
| | | | | | | | | | | | | | | | | | | | | | | | | New "Enable Steam Overlay" checkbox in Instance Manager (off by default, key fluorine/steam_overlay). When enabled and the executable has a Steam App ID, ProtonLauncher now: - LD_PRELOADs gameoverlayrenderer.so (32+64 bit) from the running Steam install for legacy GL/X11 hooks, - sets SteamOverlayGameId so Steam's IPC handshake matches the app, - exports ENABLE_VK_LAYER_VALVE_steam_overlay_1=1 and clears the DISABLE_... counterpart so the Steam Vulkan implicit layer loads even under pressure-vessel (this is what makes overlay work for DXVK-rendered Bethesda games — most "manual" overlay setups skip this and only get the GL hook), - ensures Steam is running, and - exposes <steam>/ubuntu12_{32,64} as SLR --filesystem= mounts so the .so paths resolve inside the container. No reaper wrapper, so Steam's "in-game" indicator may stay blank, but the overlay itself attaches because the .so/Vulkan-layer hook the process directly. Requires the user to actually own the game on the running Steam account. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Spawn: also strip non-{C:,Z:} drives from system.regSulfurNitride2026-04-261-9/+97
| | | | | | | | | | Pruning only the dosdevices symlinks let Wine recreate them on the next prefix start from [Software\\Wine\\Drives] in system.reg, so external mappings like X:=/home/user/Games kept winning over Z: when Wine canonicalised launch paths (game spawned with X:\Games\... cwd instead of Z:\home\user\Games\...). Strip those registry lines too. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix: fall back to "C" locale when system lacks user's LANGSulfurNitride2026-04-261-1/+13
| | | | | | | | | std::locale("") at static-init throws std::runtime_error on systems where the requested locale (e.g. en_US.UTF-8) isn't generated, killing Fluorine before main() runs. Wrap in try/catch and fall back to the classic locale. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Spawn: prune non-{C:,Z:} drive letters from prefix at launchSulfurNitride2026-04-261-0/+42
| | | | | | | | | | | | | Wine canonicalises paths against the dosdevices map and prefers the most specific (longest-prefix) drive letter. An external symlink such as dosdevices/x: -> /home/<user> turns every Z:\home\<user>\... binary path into X:\..., which confuses tools and obscures launch logs. PrefixSetupRunner::stepDriveCleanup already enforces the {C:, Z:} policy but only runs as part of the explicit prefix-setup pipeline; drives added later (Faugus, manual edits, modlist installers) survive forever. Mirror that cleanup at launch in spawn.cpp's pruneExtraDrives() so every Proton launch sees a known-good dosdevices layout.
* Detect native-Linux Stardew Valley, default executables to no-ProtonSulfurNitride2026-04-255-3/+58
| | | | | | | | | | | | | | | | | Add IPluginGame::isNativeLinux() (virtual, default false) so a plugin can advertise that the discovered installation is a native Linux build. Bound through the Python plugin trampoline so basic_games subclasses can override it. Stardew Valley plugin: when the install dir contains the StardewValley launcher script and no Stardew Valley.exe, report isNativeLinux=true and return Linux-side binary names (StardewValley, StardewModdingAPI). Also fix GameDataPath to "Mods" — Linux ships the SMAPI mods dir with a capital M. ExecutablesList::getPluginExecutables: skip the UseProton flag when the plugin reports native Linux, so the spawn path falls into launchDirect() and the game runs without a Wine prefix.
* VFS: harden crash path so FUSE bad_alloc no longer deadlocks systemSulfurNitride2026-04-253-34/+251
| | | | | | | | | | | | | | | | Wrap every FUSE callback in a noexcept thunk that catches std::bad_alloc (→ ENOMEM) and other exceptions (→ EIO) so they never unwind into libfuse3 (C, no unwind support). Stops std::terminate from orphaning the mount. Crash handler: replace raise(sig) with _exit(128+sig) — raise can hang when the signal is masked on libfuse worker threads. Try direct umount2(MNT_DETACH) before fork+exec fusermount3 -uz, and double-fork the fallback so we don't waitpid on a stuck child. Switch signal() to sigaction(SA_RESETHAND), add SIGBUS, install std::set_terminate. Cap readdir/readdirplus kernel-supplied size at 1 MiB to defend against oversized vector(size) allocations.
* Fix: leftover tar-> reference after rename to extractor->SulfurNitride2026-04-231-1/+1
| | | | | | | | Last commit renamed the QProcess variable but missed the final extractor->start(), breaking the build. No logic change beyond completing the rename. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Updater: accept .zip release assets tooSulfurNitride2026-04-232-25/+63
| | | | | | | | | GitHub Releases serves whatever's uploaded, but some CI flows produce .zip artifacts (or users re-upload as zip). Teach the release-info parser to pick up .zip when no .tar.gz is attached, and teach the install flow to call `unzip` vs `tar` based on the URL extension. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Updates tab: Install & restart buttonSulfurNitride2026-04-232-24/+246
| | | | | | | | | | | | | | | Adds an "Install update & restart" button that: 1. Downloads the release asset (with progress bar). 2. Extracts tarball to ~/.local/share/fluorine/update-staging/extract/. 3. Writes a small install.sh helper that waits for the current Fluorine PID to exit, then execs the new launcher. 4. Launches the helper detached and quits the current process. The new launcher runs its existing sync-into-bin/ logic and starts from there. Button stays disabled until a check has reported an update with a valid .tar.gz asset attached. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Stop Category Migration dialog firing on every Fluorine launchSulfurNitride2026-04-231-1/+9
| | | | | | | | | Upstream MO2 gates the one-time MO2 2.4 -> 2.5 category-migration dialog on `lastVersion < 2.5`. Fluorine's 0.x.y schema change means lastVersion is 0.x.y, which is always < 2.5, so the dialog nagged on every startup. Skip it when lastVersion has major=0 (Fluorine). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix Updates tab compile + CI failure propagationSulfurNitride2026-04-233-1/+5
| | | | | | | | | | | | | | | - settingsdialogupdates.cpp missed an include of settings.h (it was only getting the forward-declared version from settingsdialog.h), which broke every settings().setX() call. Now included directly. - UpdatesSettingsTab is not a QObject so it has no tr() member; add Q_DECLARE_TR_FUNCTIONS to get a static tr() via QCoreApplication. - CI: the docker run chained commands with `;` so a ninja failure got masked by the trailing `ccache -s`. Capture the build-inner.sh exit status and `exit $status` at the end so real build failures fail the step instead of bleeding into the tar packaging step and producing a confusing "Cannot stat: No such file" error. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Dedicated Updates tab, controller + ntsync fixes, titlebar polishSulfurNitride2026-04-237-33/+283
| | | | | | | | | | | | | | | | | | | | | | | | | - New Settings > Updates tab (src/src/settingsdialogupdates.{h,cpp}): current build info (version, channel, commit, timestamp), channel picker, startup-check toggle, and a "Check for updates now" button with live status. Legacy widgets on the General tab are now hidden. - Downgrade warning: suppress the "your version is lower than before" log when lastVersion is 2.x.y (upstream MO2 / pre-Fluorine schema) and currentVersion is 0.x.y — that's a schema switch, not a downgrade. - Titlebar: beta builds show "beta @ <short-sha>" instead of a static semver so each rolling build is identifiable at a glance. Stable still shows semver. Renamed to "Fluorine Manager" in the title. - Controllers: remove the four xinput* "native" overrides from the prefix registry seed. Games that ship their own xinput1_3.dll (Fallout 4, Skyrim, etc.) make Wine's builtin (SDL-backed) lose to a Windows stub that can't talk to /dev/input, so controllers went silent. Leaving xinput unset lets Wine builtin win. - ntsync fallback: detect a missing /dev/ntsync at launch and set PROTON_NO_NTSYNC=1 / WINENTSYNC=0 / WINE_DISABLE_FAST_SYNC=1 so pre-6.14 kernels stop dying with "Cannot open synchronization device: No such file or directory". Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: chown build dir before packaging tarballSulfurNitride2026-04-231-0/+5
| | | | | | | | The docker build runs as root inside the container and the bind-mounted build/ tree ends up root-owned on the runner. The runner-user tar step then can't create the archive. Fix up ownership before packaging. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Drop stable release step from CISulfurNitride2026-04-231-33/+6
| | | | | | | | | Stable releases are cut manually against a tag (see CHANGELOG.md). CI only publishes the rolling `beta` release now, so the always-skipped Publish stable release step and the `v*` tag trigger were just noise in the job graph. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix VFS rmdir, prefix/launch issues, add version + beta channelSulfurNitride2026-04-2342-115/+1378
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - VFS: add mo2_rmdir + OverwriteManager::removeDirectory so Wrye Bash shutil.rmtree stops failing with WinError 267 (issue #47). - FileRenamer: case-insensitive fallback via new resolvePathCaseInsensitive walks each path component; bulk hide in Conflict tab no longer dies with a misleading "Input/output error" when DirectoryEntry normalised parent dir case (issue #54). formatSystemMessage stops aliasing Windows error code 5 to errno 5 (EIO). - Versioning: FLUORINE_VERSION_* + fluorine_build_info.h generated from CMake; Linux createVersionInfo returns Fluorine's version. About dialog shows Fluorine version + MO2 engine + commit. Beta builds stamp "<semver>B<yyyymmddhhmm>" (issue #51). - CI: ci.yml determines channel from ref (v* tags = stable, main = beta), publishes a rolling `beta` GitHub release whose body carries a machine-parseable fluorine-meta block (timestamp, commit) for the in-app updater. - Updater: new FluorineUpdater polls GitHub API on startup, compares against embedded metadata, notifies on new release without auto-install. Channel toggle piggybacks on the existing "beta versions" checkbox. - Bethesda plugins: determineMyGamesPath no longer creates Documents/My Games/<GameName> for every possible title at plugin load; only games with a detected install path get their dirs created (issue #55). - Starfield: dataDirectory() on Linux now points at <game>/Data instead of My Games/Starfield/Data so the single FUSE mount lands where SFSE loads plugins; My Games Data becomes a secondary symlinked mapping (issue #56). - Prefix resolution: prefer Fluorine config, then explicit fluorine/prefix_path, then legacy Settings/* keys (with a warning) so auto-detected Heroic/Bottles prefixes can't silently override a user- configured Fluorine prefix (issue #52). compatDataPathFromPrefix no longer hands Proton the wrong parent dir for plain wine prefixes. - xrandr: ensureXrandrInstalled back-fills the helper for existing SLR installs, prefix init runs it before wineboot, SLR wrap exposes xrandr-bin via --filesystem= and prepends PATH inside the container (issue #49). - Process tracking: drop the wineserver-as-last-resort fallback that made MO2 hang on Proton's session manager after game exit. Rescan the prefix for matching game executables instead (covers reparented launcher children like f4se_loader → Fallout4.exe). Unlock button now kills wineserver for the prefix (SIGTERM then SIGKILL on timeout). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Bind-mount profile saves, fix multi-download deleteSulfurNitride2026-04-208-26/+178
| | | | | | | | | | | | | | | | | | | | | | Replace the per-launch __MO_Saves symlink with a kernel bind mount inside an unprivileged user+mount namespace. Writes from the game land directly in the profile's saves dir via the mount, which Wine can't replace with a real directory the way it could with a symlink. The namespace (and therefore the bind) tears down automatically when the game process tree exits, so afterRun no longer needs to sync saves or undeploy anything. Fall back to the symlink path on kernels where unprivileged_userns_clone is disabled (Debian buster, linux-hardened); detection is via /proc/sys/kernel/unprivileged_userns_clone. DownloadListView::issueDeleteSelected captured indices up-front and looped emit removeDownload(row). DownloadManager::removeDownload calls refreshList() at the end, which clears finished entries from m_ActiveDownloads and rebuilds from disk enumeration — stale indices after the first delete pointed at the wrong file or triggered the invalid-index error. Capture filenames instead and re-resolve the current row each iteration. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Force C.UTF-8 locale for Proton when user env isn't UTF-8SulfurNitride2026-04-181-0/+29
| | | | | | | | | | | | | | Wine picks its Unix codepage from LC_ALL/LC_CTYPE/LANG via nl_langinfo(CODESET); a C/POSIX locale falls back to CP1252, so non-ASCII Linux filenames (CJK, Cyrillic) fail UTF-8→UTF-16 conversion and MSVC std::filesystem throws "Invalid name" when Windows tools like PGPatcher iterate mod dirs through the Z:\ drive. Steam's pressure-vessel can strip the user locale, triggering this. Detect UTF-8 in LC_ALL/LC_CTYPE/LANG and only inject C.UTF-8 when none is UTF-8 already — preserves de_DE.UTF-8, ja_JP.UTF-8, etc. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Gate panel-plugin swap on enabled, pre-sync plugins.txt from prefixSulfurNitride2026-04-185-0/+84
| | | | | | | | | | | | | Bethesda Plugin Manager disabled in settings still swapped the espTab at UI init — the callback ran unconditionally. Gate it on isPluginEnabled and warn + flag restart when toggling panel plugins, since the swap is one-shot. Pre-deploy sync-back: if prefix Plugins.txt mtime is newer than profile (external LOOT run), syncPluginsBack first so the deploy doesn't clobber the external edits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Symlink saves, rank prefixes by Steam app type, ntsync safeguardsSulfurNitride2026-04-1755-57/+6435
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - Replace copy-in/copy-out save deployment with a direct symlink from the prefix's __MO_Saves to the profile's saves/. Writes land in the profile immediately; afterRun reverts the symlink + prefix INI so a vanilla launch outside MO2 uses the default Saves dir. - Parse Steam's binary appinfo.vdf (v41) via a new Rust FFI crate backed by steam-vdf-parser. Rank detected games by common.type so real Games beat Tools/Editors when multiple prefixes share a My Games folder name (fixes Creation Kit shadowing Skyrim SE). Top-ranked candidate overwrites stale symlinks from earlier runs. - Switch bundled Steam Linux Runtime downloader from sniper (steamrt3) to steamrt4 so Proton 11+ works out of the box. steamrt4 omits xrandr so we inject it from the Debian x11-xserver-utils .deb into a dedicated dir and prepend it to PATH via `env PATH=...` inside the pressure-vessel container (PATH env doesn't propagate). - Auto-kill stale wineboot/wineserver/pv-adverb processes still bound to the prefix before starting a new wineboot -u; match by /proc/<pid>/environ WINEPREFIX / STEAM_COMPAT_DATA_PATH since Wine cmdlines are Windows-style. Same sweep runs in destroyPrefix so a delete actually frees file handles. - Blacklist Proton 11 (Steam-bundled) — its current Wine tree deadlocks during wineboot -u on modern kernels via ntsync / futex_wait_multiple races. Use "waitforexitandrun" (not "run") for prefix init so use_sessions=1 Protons don't fork into a persistent session manager. - Drop WINEDEBUG=-all during prefix init so wineboot progress is visible. Suppress focus-stealing by setting WA_ShowWithoutActivating on the PrefixSetupDialog and SLR download progress dialogs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix Proton 11 prefix init hang, switch SLR to steamrt4SulfurNitride2026-04-165-15/+25
| | | | | | | | | | | | Proton 11's toolmanifest sets use_sessions=1, so the "run" verb forks into a persistent session manager that never exits — prefix init hung forever. Use "waitforexitandrun" instead. Also drop WINEDEBUG=-all so init output is visible, and migrate the bundled Steam Linux Runtime downloader from steamrt3/sniper to steamrt4 (Proton 11's required runtime). SLR lookup paths fall back to sniper for users with an existing install. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Stop writing loadorder.txt to prefix, propagate save deletionsSulfurNitride2026-04-163-118/+198
| | | | | | | | | | | | | | | | | deployPlugins now writes only the plugin-list file the game actually reads (Plugins.txt for PluginsTxt games, lowercase plugins.txt for FileTime games). loadorder.txt is MO2-internal and never belonged in AppData/Local/<Game>/; previously writing a stale copy there defeated LOOT's edits because syncPluginsBack pulled the stale loadorder.txt back into the profile, and getLoadOrder() preferred it over the freshly-synced plugins.txt. syncSavesBack now mirrors deletions: files present in the profile but absent from the prefix are removed before the copy pass. Without this, saves the user deleted in-game stayed in the profile forever because copyTreeContents only copies files that exist in the source. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Abort setup on prefix-init failure, flag broken Proton installsSulfurNitride2026-04-152-9/+51
| | | | | | | | | | | | | | | | | | | When stepProtonInit failed, the runner kept marching through every downstream step against a half-initialized prefix, producing a cascade of misleading "wine client error: version mismatch" failures that masked the real problem. Seen in the wild with a GE-Proton10-34 install that was missing concrt140.dll from its bundled default_pfx template: setup_prefix crashes with FileNotFoundError, then seven unrelated steps are reported as broken. Break out of the step loop as soon as proton_init fails, and inspect the captured Proton output for the "FileNotFoundError" + "default_pfx" hint so the error message points at the broken Proton distribution instead of a generic "exit code 1". runProcess gains an optional QByteArray* out param so the caller can pattern-match on the merged output without changing any existing call sites. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Refresh Data tab automatically when mods toggleSulfurNitride2026-04-151-0/+7
| | | | | | | | | | | | | | OrganizerCore::modStatusChanged updates the DirectoryStructure in place (enabling/disabling origins) but never fires directoryStructureReady, which is the only signal MainWindow used to drive DataTab::updateTree. Result: after enabling or disabling a mod the Data tab kept showing the old virtual tree until the user hit Refresh manually. Connect ModList::modStatesChanged directly to DataTab::updateTree so the file tree re-queries the structure as soon as the toggle lands. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Mirror synced plugins across case variants, fix OMOD loggingSulfurNitride2026-04-152-42/+59
| | | | | | | | | | | | | | | syncPluginsBack previously updated the profile from the newest case variant but left the stale sibling (e.g. lowercase plugins.txt after LOOT edited Plugins.txt) untouched in the prefix, so anything reading the prefix before the next deployPlugins saw divergent content. Mirror the newest variant into every sibling after the profile sync. installer_omod.py called mobase.log / mobase.LogLevel which the Python bindings do not expose, crashing .omod installs with AttributeError. Route messages through stderr so the Python runner forwards them to MOBase::log::error. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Sync plugins.txt/loadorder.txt back from prefix after tool runSulfurNitride2026-04-133-8/+97
| | | | | | | | | | | | | | | | | LOOT (and any other tool that edits the plugin list) writes to the deployed copies in the Wine prefix AppData. afterRun was syncing saves and INIs back but not plugins, so LOOT's reordering was silently reverted by savePluginList() writing the old in-memory order on top of the untouched profile file. Add WinePrefix::syncPluginsBack that picks the newest case variant of plugins.txt and loadorder.txt from the prefix and copies them back to the profile. Run it in afterRun alongside the existing save/INI sync. For FileTime-based games (Skyrim LE, FO3, FNV) the loadorder.txt removal now happens after the sync, so the file-time fallback still works. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove Fix Game Registries button, fix BethINI New Vegas detectionSulfurNitride2026-04-134-280/+15
| | | | | | | | | | | The auto-prompt in organizercore already offers to fix the registry on launch, so the manual button is redundant. BethINI Pie plugin compared against managedGame()->gameName(), but GameFalloutNV returns "New Vegas" while BethINI's app folder is "Fallout New Vegas" — add an alias map so NV is detected correctly. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* CI: fix ccache misses, surface hit rateSulfurNitride2026-04-131-1/+5
| | | | | | | | | | Sets CCACHE_BASEDIR=/src and CCACHE_COMPILERCHECK=content so hashes are stable across runs (absolute-path and compiler-mtime drift were causing full rebuilds). Adds sloppiness flags, 5G max size, and prints `ccache -s` before/after the build so cache effectiveness is visible in the job log. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* VFS: log process CPU + RSS alongside op statsSulfurNitride2026-04-132-0/+47
| | | | | | | | Adds per-tick rusage snapshot (user/sys seconds, delta since last tick, wall delta, busy%, max RSS) to the [VFS] stats lines so we can tell CPU-bound from IO-bound slowness without external tools. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* VFS perf fixes, icon/stylesheet compat, download filename fixSulfurNitride2026-04-12121-64/+17340
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | VFS: - Open backing fd at mo2_open time for read-only handles so mo2_read can splice without re-opening the file on every read call. - Bump RLIMIT_NOFILE at mount time to fit the resulting fd pressure when the game keeps hundreds of BSAs open. - Dedicated node_cache_mutex so resolveByInode / mo2_lookup stop racing unordered_map writes while multiple tree_mutex readers are active. - max_read=1MB + matching conn->max_read and raised max_readahead/max_write so the kernel merges Wine's small sequential reads into bigger FUSE requests. - Per-op wall-clock counters in mo2_init() logs so we can distinguish VFS latency from game-side work. Proton launch: - Write dxvk.conf to the prefix and set DXVK_CONFIG_FILE to force dxvk.enableGraphicsPipelineLibrary=False, avoiding long GPL compile stalls on first run. UI / plugin compat: - Clamp IconDelegate's per-icon width to [8, 16] so narrow content columns don't trigger QIcon::pixmap(0,0) returning null and logging "failed to load icon" every repaint. - Pre-create lowercase symlinks in the stylesheet directory so QSS files authored on Windows resolve url(foo.svg) when on-disk files are Foo.svg. - Flip content icons empty-chessboard.png and facegen.png to 8-bit RGBA so Qt's built-in PNG reader loads them uniformly. - Add mobase.Version.canonicalString shim for legacy Python plugins that still call the old VersionInfo method on appVersion()'s return. - BSPluginList: compare normalized plugin name lists instead of byte hashes so the post-run case-fix refresh stops spuriously firing the "load order changed" dialog. - BSPluginList disabled by default. Download manager: - Parse CDN URL with QUrl + QUrlQuery and read the filename from the response-content-disposition query param (RFC 6266 quoted / unquoted / ext-value forms), not QFileInfo on the raw URL. - When the API or URL gives us a CDN object key (no archive extension), prefer the actual Content-Disposition header from the live response in metaDataChanged and downloadFinished. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add BSA in-memory extraction and nested preview supportSulfurNitride2026-04-1213-122/+619
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | bsatk: - Archive::extractToMemory() extracts a file directly into a byte buffer instead of a filesystem path, backed by a std::ostringstream sink. - Archive::findFile() locates a file by relative path, case-insensitive and separator-agnostic, walking the folder tree recursively. - extractDirect/extractCompressed now take std::ostream& so both the ofstream and ostringstream paths share the same implementation. libbsarch: shim rewrite on Linux to map the Windows API surface onto bsatk::Archive, enabling preview_bsa to open vanilla BSAs via the same interface as other plugins. preview_bsa: double-click on a file in the BSA tree now extracts the bytes via extractToMemory() and hands them to IOrganizer::previewFileData for nested preview rendering (e.g. NIF inside a BSA). uibase + organizerproxy + organizercore: new IOrganizer::previewFileData virtual that routes in-memory file bytes through the first registered IPluginPreview matching the extension. Uses heap + WA_DeleteOnClose + open() to avoid nested exec() softlock from mod info filetree -> preview_bsa tree -> nif preview. docker/build-inner.sh: stage preview_nif shaders into plugin_data/shaders so ShaderManager can load them via IOrganizer::getPluginDataPath(). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add NIF previewer plugin with Linux-specific fixesSulfurNitride2026-04-1235-10/+4260
| | | | | | | | | | | | | | | | | | | | | | | | | Vendored from github.com/Parapets/mo2-preview_nif and adapted for Linux: - BSA texture resolution via MO2 virtual tree with case-insensitive fallback walk (Linux FS is case-sensitive; NIFs reference textures with arbitrary case against files on disk) - Process-wide cached BSA candidate list, keyed by profile path, priority-sorted, filtering disabled mods - Path backslash normalization before loose-file lookup - Polygon offset on SLSF1_Decal-flagged shapes to break z-ties with coincident base meshes (road decals, moss overlays) - Opaque framebuffer via glColorMask alpha lock so alpha-blended shapes can't bleed the dialog background through the preview area - QMouseEvent::globalPosition().toPoint() for Qt6 compatibility Preview dialog behavior (organizercore.cpp): - Switch previewFile and previewFileWithAlternatives from stack-allocated exec() to heap + ApplicationModal + show() + WA_DeleteOnClose. - Parent passed as nullptr so preview lifetime is decoupled from the stack-allocated ModInfoDialog that spawns it. Nested exec() inside the enclosing dialog's exec() was softlocking on close. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Rename launcher manifest to drop leading dotSulfurNitride2026-04-121-3/+3
| | | | | | | | | | | | actions/upload-artifact@v4 defaults include-hidden-files to false, so .fluorine-manifest was getting stripped from the CI release artifact and users saw the launcher bail with "can't find its bundle files" despite the build log reporting "Wrote manifest: 10 entries". Rename to fluorine-manifest.txt. Sidesteps the upload-artifact default and also survives any user-side extractor that hides dotfiles. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix launcher sync copying unrelated files from extraction dirSulfurNitride2026-04-121-1/+27
| | | | | | | | | | | | | | | | Previous sync ran `tar -cf - .` inside the launcher's HERE dir, which slurped anything sitting next to the fluorine-manager script. If a user extracted the release archive into ~/Downloads (alongside a 38 GB mod stash), the launcher happily copied the whole lot into ~/.local/share/fluorine/bin/ on first run. Write a `.fluorine-manifest` at build time listing the top-level entries that belong to Fluorine, then have the launcher copy only those entries. Also bail with a clear error if the manifest or ModOrganizer-core is missing — catches "launcher dropped into some random folder" before any damage is done. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix launcher sync triggering on every fresh tarball extractionSulfurNitride2026-04-121-5/+12
| | | | | | | | | | | | | | | | Version fingerprint was stat -c '%i:%Y' which includes the inode number. Re-extracting the same tarball produces new inodes, so the marker never matched and the 237MB copy to ~/.local/share/fluorine/bin/ ran on every launch — painful cross-drive (tarball on one disk, home on another). Switch to '%s:%Y' (size + mtime). Survives re-extraction, still detects real updates. Also stage into bin.new/ and rename instead of rm -rf'ing bin/ before the tar pipe. If the user Ctrl+C's mid-sync (or the copy fails), the existing install stays intact instead of being left empty. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove LOOT integration entirelySulfurNitride2026-04-1248-5380/+89
| | | | | | | | | | | | | | | | | | | | | | | | The sort button was already hidden on Linux (setVisible(false)) and the LOOT feedback path (LootDialog -> addLootReport) has always been Windows-only, meaning the dirty/incompatibilities/missingMasters/messages tooltip bullets and the dirty-plugin icon could never fire on Linux. MO2 shipped lootcli + libloot for a load-order sort mechanism that was unreachable from the UI. Users who want LOOT can download it from https://github.com/loot/loot and run it against their instance directly. - Drop libs/lootcli/, libloot Dockerfile build step, and lootcli staging/patchelf in build-inner.sh - Drop src/src/loot.{cpp,h}, src/src/lootdialog.{cpp,h,ui} - Extract MarkdownDocument / MarkdownPage (used by UpdateDialog for its changelog view) into src/src/markdowndocument.{h,cpp} - Strip addLootReport, makeLootTooltip, Loot::Plugin field, LOOT icon checks, and LOOT tooltip/isProblematic/hasInfo paths from pluginlist - Delete sortButton widget, updateSortButton(), onSortButtonClicked(), and all m_didUpdateMasterList wiring - Delete lootLogLevel setting, combo box, and "Integrated LOOT" group from the diagnostics settings tab
* Remove vendored usvfs and Windows-only usvfs code pathsSulfurNitride2026-04-12284-24957/+5
| | | | | | | | | | | | | | | | The Linux port uses its own in-process FUSE VFS (FuseConnector), so usvfs was dead weight: libs/usvfs/ was never built, usvfsconnector.cpp was explicitly REMOVE_ITEM'd from the source list, and every call site was already guarded by #ifdef _WIN32 with a working Linux branch (the Windows build is also broken on this branch, since FUSE3 is a hard find_package dependency). - Drop libs/usvfs/ and src/src/usvfsconnector.{cpp,h} - Collapse #ifdef _WIN32 / usvfs include branches in organizercore, mainwindow, settings, util, spawn - Remove getUsvfsVersionString() and the "usvfs: ..." log field - Remove vfs32/64DLLName APPPARAMs (nothing reads them) - Relabel About dialog "usvfs:" row to "VFS: FUSE 3"
* Remove dead plugin scaffolds and duplicate lib dirsSulfurNitride2026-04-1293-12956/+0
| | | | | | | | | | | | | | | | | | | The *_native C++ ports were replaced by bundled Python + .py plugins in de4db79, but the source dirs were left behind without any CMake wiring — causing confusion when editing them appears to do nothing. Same story for libs/dds_header (stale duplicate of libs/preview_dds, unrelated to the actively-used libs/dds-header) and libs/game_gamebryo (superseded by libs/game_bethesda/src/gamebryo). Removes: - libs/basic_games_native - libs/form43_checker_native - libs/installer_omod_native - libs/preview_dds_native - libs/rootbuilder_native - libs/script_extender_checker_native - libs/dds_header - libs/game_gamebryo
* Add Stalker 2: Heart of Chornobyl pluginSulfurNitride2026-04-121-0/+52
| | | | | | Vendored BasicGame plugin from upstream modorganizer-basic_games. Auto-moves loose *.pak/*.utoc/*.ucas files under Content/Paks/~mods/ on install.
* Fix registry path mismatch dialog writing wrong casing and missing trailing ↵SulfurNitride2026-04-111-12/+15
| | | | | | | | | | | backslash The checkGameRegistryKey map still had "Installed Path" (capitalized) and the winePath was missing the trailing backslash. When the user clicked "Yes" on the mismatch dialog it would overwrite the correct lowercase key with the wrong casing and strip the trailing separator. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix game launchers failing to find INI files due to registry key casingSulfurNitride2026-04-113-11/+23
| | | | | | | | | | Wine registry value names are case-sensitive. Game launchers (FNV, Oblivion, etc.) look up "installed path" (lowercase) but Fluorine was writing "Installed Path" (capitalized), causing "Unable to find an INI file" errors. Also adds trailing backslash to match Steam's path format and fixes the registry mismatch dialog to ignore trailing separator differences. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix VFS rename failing for backing and mod filesSulfurNitride2026-04-111-8/+31
| | | | | | | | | | | | | | | xEdit (and similar tools) rename game files to create backups before saving (e.g. DLCHorseArmor.esp -> TES4Edit Backups/...backup...). OverwriteManager::rename() only searches staging and overwrite directories, so renames of backing (game) or mod files failed with EACCES since those files live in the real game/mod directories. Now when OverwriteManager::rename() can't find the source file, the VFS copies it to staging at the destination path via COW instead of failing. The file disappears from its old path and appears at the new path in the virtual view, without modifying the real game/mod dirs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix SLR download silently failing due to response data being discardedSulfurNitride2026-04-101-4/+4
| | | | | | | | | The readyRead handler consumed data via readAll() but discarded it when no destination file was set. The final readAll() then returned empty since the buffer was already drained, causing all in-memory HTTP responses (like BUILD_ID.txt) to appear empty. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Sync upstream MO2 2.5.3 Beta 3 fixesSulfurNitride2026-04-105-6/+12
| | | | | | | | | - FO4VR: Add Daytripper4.dll to ESL support detection - Remove empty categories tooltip when mod has no categories - Fix QDialogButtonBox/QMessageBox enum mismatch in profile dialogs - Use refreshESPList(false) after LOOT sort (no full re-read needed) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix VFS rename rejecting RENAME_NOREPLACE flag from WineSulfurNitride2026-04-101-1/+20
| | | | | | | | | | | | | | Wine uses renameat2(RENAME_NOREPLACE) when translating MoveFileW() calls where ReplaceIfExists is FALSE (e.g. xEdit saving plugins). The VFS was rejecting all non-zero flags with EINVAL, which a real filesystem like ext4 handles correctly. This caused "Could not rename" errors in xEdit and similar tools. Now properly handles RENAME_NOREPLACE: checks if the destination exists in the VFS tree and returns EEXIST (matching ext4 behavior), otherwise proceeds with the rename. Adds diagnostic logging on EACCES failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add BethINI Pie tool pluginSulfurNitride2026-04-107-0/+544
| | | | | | | | | | | | | | | | Native IPluginTool that integrates BethINI Pie (performance INI editor) into the Tools menu. Auto-downloads the latest Linux build from GitHub releases, checks for updates via commit SHA, and pre-configures it for the current game and profile INI directory. Supports: Skyrim SE, Fallout 4, Fallout New Vegas, Starfield. Includes post-extraction fixes for PyInstaller bundle quirks: - Patches tm.tcl hardcoded Tcl module path - Symlinks apps/icons/fonts from _internal/ to executable directory - Sets TCL_LIBRARY/TK_LIBRARY env vars on launch Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>