aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
...
* nxmaccessmanager: fix optional<NexusOAuthTokens> deref in legacy-key pathSulfurNitride2026-05-021-5/+10
| | | | | | | | | | | | | m_Tokens on NXMAccessManager is std::optional<NexusOAuthTokens> (the NexusOAuthTokens member belongs to ValidationAttempt). The new legacy API key migration code accessed .accessToken/.apiKey directly on the optional, breaking the build. Use the optional's check + arrow access throughout, and re-check it inside the network reply continuation since the user could clear credentials before the request resolves. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* nxmaccessmanager: derive legacy API key from OAuth sessionSulfurNitride2026-05-022-0/+71
| | | | | | | | | | | | | | | | | | | | Some plugins (Plugin Browser for MO2 and a number of older Python plugins) read only the legacy `apiKey` field. The OAuth login flow populates `accessToken` but never the legacy key, so those plugins fail with "User API Key is missing" even after a successful sign-in. Nexus' v1 `/users/validate.json` endpoint accepts the OAuth bearer token and returns the user's personal API key in the `key` field. We hit it once after `notifyTokens()` (fresh login) and once via the constructor queued call (already-logged-in users on first launch with this build), then persist the result through `GlobalSettings::setNexusApiKey()`. The fetch is a no-op when an apiKey is already present (manual entry or prior session) so it never overwrites a user-set value, and silently fails on non-200 / missing field so we don't break the OAuth-only path when Nexus eventually deprecates personal API keys. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* sanitychecks: probe libfuse3 via dlopen, accept .so.4SulfurNitride2026-05-021-13/+36
| | | | | | | | | | | | | Fedora 44 and Arch ship libfuse3.so.4; the static path list only checked .so.3 / unversioned .so, so the sanity check warned "libfuse3 not found" on systems where FUSE actually works. Use dlopen() with RTLD_NOLOAD then RTLD_LAZY against libfuse3.so.{4,3,} so the dynamic loader resolves through /etc/ld.so.cache and picks up any SOVERSION the distro ships. Fallback path list extended with .so.4 for sandboxes that block dlopen. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* meta.ini: revert to upstream CamelCase, canonicalize on readSulfurNitride2026-05-016-134/+173
| | | | | | | | | | | | | | | | | | | | Restore upstream MO2's exact key cases (gameName, installationFile, nexusDescription, etc.) in readMeta/saveMeta/doInstall/createMod — matches mod-shipped meta.ini cases, so on Linux Qt6's case-sensitive QSettings IniFormat, setValue updates in place without producing a duplicate key. normalizeMetaIniCase() repurposed: instead of folding everything to lowercase (which mismatched the upstream-style setValue calls and caused the very dupes it was trying to fix), it now canonicalizes known per-mod meta.ini keys to their upstream CamelCase, deduping case-insensitive collisions per section. Cleans up dirty files written during the 5d1fb29 -> 1f19892 window so they collapse to a single canonical key per setting on first read. Tests updated to assert canonical-CamelCase output. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* meta.ini: lowercase code keys to match normalize, kill dup writesSulfurNitride2026-05-013-34/+34
| | | | | | | | | | | | | | | | | | | normalizeMetaIniCase folds keys to lowercase, but readMeta/saveMeta and the install paths still used CamelCase ("installationFile", "gameName", "nexusDescription", etc.). Qt6 IniFormat is case-sensitive on Linux, so setValue under CamelCase wrote a NEW key alongside the lowercase entry left by normalize, and value() reads with CamelCase missed the lowercase key and fell back to defaults — saveMeta then persisted those defaults under CamelCase. Net effect: every save grew meta.ini by ~12 duplicate lines (e.g. installationFile= + installationfile=<real value>; gameName=SkyrimSE + gamename=Skyrim). Lowercased all per-mod meta.ini key names in modinforegular.cpp, installationmanager.cpp, and organizercore.cpp. Section/group names (installedFiles, Plugins, INI Tweaks) stay CamelCase because normalize preserves section headers verbatim. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* nix: add Qt6NetworkAuth to mobase shellSulfurNitride2026-05-011-1/+2
| | | | | | | PR #2374 (Nexus OAuth) added Qt6NetworkAuth dependency, breaking the NixOS mobase build. Add qtnetworkauth to buildInputs and CMAKE_PREFIX_PATH. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* shell::Rename: report both rename + copy errors on fallbackSulfurNitride2026-05-011-4/+16
| | | | | | | | | | | | | | | | | | Cherry-picked the two surviving improvements from stale branch claude/fix-reported-issues-Iyt4s (issue #54). Rest of the branch's content (Starfield data dir, My Games spam fix) was already absorbed in main from a separate pass. - Rename copy fallback: previously dropped the original rename errno and returned generic ERROR_ACCESS_DENIED with no context. Now bubbles "Rename failed: %1. Copy fallback failed: %2" with both QFile::errorString() values so the log line actually identifies what went wrong. - formatSystemMessage: added an explicit comment block on why the codebase's Windows error macros (5=ACCESS_DENIED) must not be passed through strerror (5=EIO) on Linux. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Expose root storage paths to SLRSulfurNitride2026-05-011-1/+245
|
* Remove AppImage build target and runtime referencesSulfurNitride2026-05-0115-383/+59
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | We ship via portable tarball directory + .bin self-extracting installer only; the AppImage path was unmaintained and pulling in linuxdeploy tooling for a format we no longer publish. Build: - build.sh: drop appimage/all-includes-appimage modes (now: tarball, installer, all=both, shell). Output listing tracks the directory. - docker/Dockerfile: drop BUILD_APPIMAGE arg and the linuxdeploy download/extract block. - docker/build-inner.sh: drop build_appimage() (~120 lines), AppImage arm of the BUILD_MODE switch, *.AppImage summary listing. - docker/AppRun.sh: deleted. - .gitignore: drop *.AppImage / squashfs-root / *.flatpak entries. Runtime: no longer key off APPIMAGE/APPDIR env vars or AppRun-set state. The fluorine-manager launcher script already exports FLUORINE_ORIG_*, MO2_BASE_DIR, MO2_PLUGINS_DIR, MO2_LIBS_DIR, MO2_PYTHON_DIR for the same purpose, so simplify: - envshortcut: appImageOrBinary -> launcherOrBinary; bundledFluorineIcon no longer probes APPDIR. - nxmhandler: drop APPIMAGE-based wrapper path; use applicationFilePath. - protonlauncher: rename cleanAppImageEnv -> cleanFluorineEnv, drop APPIMAGE/APPDIR/OWD/ARGV0/APPIMAGE_ORIGINAL_EXEC/DESKTOPINTEGRATION removals and .mount_Fluori pattern strip. - prefixsetuprunner: same env-cleaning trim. - utility, library.h, proxypython, mainwindow, moapplication, appconfig: comment cleanup pointing at the launcher instead of AppRun. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix meta.ini duplicate keys and SKSE Log Redirector mapped foldersSulfurNitride2026-05-0110-7/+793
| | | | | | | | | | | | | | | | | | | | | | | | | | | | meta.ini: Qt6 QSettings IniFormat is case-sensitive on Linux. Pre-existing CamelCase keys + lowercase setValue() left both casings in the file, so mods grew duplicate entries on every install. Added MetaIniUtils:: normalizeMetaIniCase() to fold keys to lowercase and dedupe per-section (keeping non-empty values) before any QSettings open in readMeta, saveMeta, doInstall, and createMod. SKSE Log Redirector: deployExternalMappings created per-file symlinks under the redirected destination, so any new log file the game wrote post-deploy ended up as a real file outside the mapping. For isDirectory && createTarget mappings, now publish a single directory symlink dst -> src when the destination is missing, an existing symlink, or empty. Falls back to per-file symlinks when dst already has real content (preserves user data). Cleanup removes the symlink, leaving no files behind. Tests under src/tests/ (gtest, opt-in via BUILD_TESTING): - test_metainiutils: 6 tests covering no-op cases, case-only dedup, multi-line continuations, per-section scoping, and end-to-end verification that QSettings duplication is fixed. - test_external_dir_mapping: 6 tests covering directory symlink deploy, flow-through writes, idempotency, empty-dir replacement, real-file fallback, and cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Port upstream PR #2368: preview parent widget fixesSulfurNitride2026-05-013-3/+4
| | | | | | | | | | | | | | | Cherry-picked the three preview-parent fixes from #2368: parentWidget() in ConflictsTab/FileTreeTab/ImagesTab returns the tab widget itself, not the dialog — preview windows ended up parented to the wrong widget. m_parent->parentWidget() returns the actual mod info dialog so previews position and z-order correctly. Skipped the Starfield blueprint feature from the same PR — requires coordinated changes across uibase (add IPluginGame::blueprintPrefix) and modorganizer-game_bethesda (Starfield override). See project_upstream_pending_prs.md. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Port upstream PR #2384: --name/--modname/--version/--source on download CLISulfurNitride2026-05-014-1/+50
| | | | | | | | | | | | | | Adds metadata flags to the `download` subcommand and a corresponding DownloadManager::startDownloadURLWithMeta() entry point that populates ModRepositoryFileInfo before queueing. Skipped the upstream settings.cpp hunk that re-registers the Windows nxmhandler.exe binary for both nxm:// and modl:// — our nxmhandler_linux already registers both MIME schemes in registerHandler(). Cherry-picked from upstream/dev/modl-handler against merge-base dc420a25. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Port upstream PR #2341: -i "" launches portable instanceSulfurNitride2026-05-011-5/+19
| | | | | | | | | | Distinguish between -i (no value, prints current instance and exits) and -i "" (launches the portable instance if one exists) by switching the option parser from std::string to boost::optional<std::string>. Cherry-picked from upstream 49da80c. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Port upstream PR #2379: reset CWD to app dir on startupSulfurNitride2026-05-011-0/+7
| | | | | | | | | | | | | When MO2 is launched by the nxm handler from a browser, CWD is whatever the browser inherited (often /, $HOME, or the user's Desktop). Reset it to the application directory so any code path relying on CWD behaves the same as a normal launch. Cherry-picked from upstream f80ad04. Comment expanded for the Linux context (Qt resource lookup, relative QFile paths, QtWebEngine sandbox helper) — upstream's note only covered QtWebEngineProcess on Windows. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Port upstream PR #2374: Nexus OAuth authenticationSulfurNitride2026-05-0124-1445/+2086
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Replaces legacy API-key flow with OAuth 2.0 PKCE (public client, client_id "modorganizer2", overridable via MO2_NEXUS_CLIENT_ID env). Backwards-compatible: stored API key is used as fallback if no OAuth token is present. Fixes the symptom users hit after Nexus deprecated personal API keys for download endpoints — Settings showed "Connected." (key still validates) but downloads/CDN list returned empty. Cherry-picked from upstream/dev/oauth-graphql against merge-base 925bade3, with these adjustments for our Linux-only fork: - Dockerfile: aqtinstall +qtnetworkauth module - CMakeLists.txt: find_package + link Qt6::NetworkAuth - Dropped Windows-only hunks: dlls.manifest.qt6{,debug}, pch.h QWebSocket include - Skipped src/CMakeLists.txt and .gitignore upstream hunks (their layout differs from ours) - Skipped organizer_en.ts (regenerable via lupdate) - Skipped tutorials/tutorial_firststeps_settings.js (defer to the tutorials-disable PR port) - Kept our defensive default member initialisers in nxmaccessmanager.h (m_Reply{nullptr}, m_Result{None}, etc.) and `override` on createRequest() - Resolved trivial conflicts in modlistviewactions.cpp by taking upstream's NexusOAuthTokens API while retaining our [=, this] capture style - createinstancedialog.h, instancemanager.cpp: only meaningful PR delta is a comment text change / unrelated drift; ours retained OAuth callback uses local loopback http://127.0.0.1:28635/callback served by QOAuthHttpServerReplyHandler (no firewall punch needed). Token storage piggybacks the existing Linux QSettings INI credential backend (~/.config/ModOrganizer/credentials.ini). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Clean up empty dirs left by VFS, suppress noisy Qt debugSulfurNitride2026-04-304-20/+124
| | | | | | | | | | | | | | | Fixes #64: VFS now tracks the directories it creates outside the data dir (external mappings + RootBuilder game-root deploys) and removes them on unmount, but only if still empty. Pre-existing user dirs and the game root itself are never touched. RootBuilder manifest gains a "dirs" field so the cleanup survives a crashed session. Also drops a Path-repr qDebug in bg3_file_mapper that crashed Qt's logger on surrogate-escaped paths, and defaults QT_LOGGING_RULES to default.debug=false in the launcher and AppRun (still overridable) so the same class of plugin bug stays muted in shipped builds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Clean up remaining Linux packaging namesSulfurNitride2026-04-298-16/+16
|
* Clean up clang-tidy diagnosticsSulfurNitride2026-04-2929-121/+121
|
* Finalize Linux build cleanupSulfurNitride2026-04-2997-53204/+53058
|
* clang-tidy: misc-const-correctness + performance-for-range-copy passSulfurNitride2026-04-2982-917/+917
| | | | | | | | | | | | | | Final auto-fix sweep that adds const to local variables and switches range-for-by-value to range-for-by-const-ref where the element type is non-trivially-copyable. The auto-fixer also emitted ~15 invalid \`for (const T& const x : ...)\` range loops where \`const-correctness\` and \`for-range-copy\` both fired on the same line. Hand-fixed via sed: \`& const \` -> \`& \`. Build verified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* clang-tidy: third auto-fix passSulfurNitride2026-04-28139-518/+520
| | | | | | | | | | | | | | | | | | | | | | | | | | Sequentially applied a tighter set of safer checks: - modernize-return-braced-init-list - modernize-use-equals-default - modernize-use-noexcept - modernize-use-using - modernize-raw-string-literal - readability-enum-initial-value - readability-make-member-function-const - readability-convert-member-functions-to-static - readability-redundant-member-init - readability-container-contains - readability-container-size-empty One file (mainwindow.h) had to be hand-fixed: extractProgress was auto-marked static by readability-convert-member-functions-to-static, but it's bound via boost::bind(&MainWindow::extractProgress, this, ...) — making it static breaks the bind. Restored to a non-static member. Build verified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* clang-tidy: second auto-fix pass + manual narrowing-conversion fixesSulfurNitride2026-04-2871-191/+182
| | | | | | | | | | | | | | | | | | | The first auto-fix run kept going in the background after the wait loop returned (a podman-detached quirk), so it produced another batch of fixes against ~52 more files that needed verification. Two cases the auto-fixer can't fix safely on its own: - DWORD m_exitCode{-1} -> static_cast<DWORD>(-1) on the brace init, because -1 narrows to unsigned int. - {EndorsedState::X} / {TrackedState::X} initializers had to be qualified with their parent namespace (MOBase::) because the identifier wasn't in scope at the point of in-class default-init. Build verified. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Ignore .claude/ local stateSulfurNitride2026-04-282-1/+3
|
* clang-tidy: apply auto-fixes from safest checks across our authored codeSulfurNitride2026-04-28140-789/+785
| | | | | | | | | | | | | | | | | | | | | | Ran sequentially (not parallel — concurrent fixes corrupt shared headers) on src/src/ + libs/skse_log_redirector/. The check set: - modernize-use-override - modernize-use-nullptr - modernize-use-default-member-init - readability-redundant-member-init - readability-container-contains - readability-container-size-empty Net effect: ~108 files updated, 425/426 lines changed (overrides added, NULL/0 -> nullptr, member-init lists pruned where the same value is already in the in-class default). Build verified after revert/re-apply. Also disable readability-redundant-access-specifiers in .clang-tidy: the check is confused by Qt's `private slots:` specifier and strips the `private:` that follows it, breaking MOC. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add clang-tidy lint infrastructure (.clang-tidy + lint.sh)SulfurNitride2026-04-283-0/+174
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | `./lint.sh` runs clang-tidy inside the fluorine-builder Docker image so the toolchain matches what compile_commands.json was generated with. Strips -mno-direct-extern-access (gcc-only) before invoking clang and skips browserview/browserdialog since QtWebEngine is Windows-only in this fork. Scope: src/src/ + libs/skse_log_redirector/ (everything we authored). Vendored upstream libs follow their own coding standards and stay out-of-scope. Default-off the noisiest categories (fuchsia/google/llvm/altera, pro-type-*, magic-numbers, named-parameter, function-cognitive- complexity, identifier-length, etc.) so the surfaced warnings are actionable. Dockerfile: install clang-tidy alongside the build deps so the image ships everything lint.sh needs. Usage: ./build.sh tarball # build the image (one-time after this commit) # and produce compile_commands.json ./lint.sh # lint everything ./lint.sh --fix # apply auto-fixes ./lint.sh src/src/foo.cpp # lint a single file Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Strip remaining Windows-only conditionals across src/src/SulfurNitride2026-04-2859-10126/+4206
| | | | | | | | | | | | | | | | | | | | | | | | Removes every #ifdef _WIN32 / #ifndef _WIN32 / Q_OS_WIN block from the organizer source tree. The Linux build is the only target this fork produces, so the dead Windows branches were just noise. Stubs for Win32-shaped APIs that the upstream code references (FILETIME, HANDLE, DWORD, etc.) live in shared/windows_compat.h so the surrounding source keeps compiling without rewriting every signature. Also: - Reimplemented env::DirectoryWalker / env::forEachEntry / env::Module / env::Process / env::WindowsInfo as Linux-only (uname, /etc/os-release, /proc, std::filesystem) since the Win32 branches that previously held those implementations are gone. - Reduced spawn.cpp / processrunner.cpp / env.cpp to their Linux paths; dropped the helper:: namespace, Win-only waitForAllUSVFSProcesses- WithLock(), Steam Win-registry probe, and Windows mini-dump path. - Linux launcher uses BUILD_JOBS (default 4) so docker rebuilds during the audit don't pin the host. Build verified after every batch with ./build.sh tarball. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 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>