aboutsummaryrefslogtreecommitdiff
Commit message (Collapse)AuthorAgeFilesLines
* vfs: per-executable opt-in toggle for PE bridgeSulfurNitride2026-05-087-17/+65
| | | | | | | | | | | | | | | | | | | | | | | 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) <noreply@anthropic.com>
* vfs: scope hook to mount, fix negcache to handle backing entriesSulfurNitride2026-05-081-17/+239
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | Three bugs in the PE-side bridge collided to crash NSPBR ~54s after launch with an AV in NPCWaterAIFix.dll. Root cause: the bridge said STATUS_OBJECT_NAME_NOT_FOUND for files that exist on disk but are not in the static index, masquerading as ground truth. 1. load_index() filtered every entry whose real_path didn't start with '/'. That dropped every is_backing=true entry — for NSPBR, the entire vanilla Skyrim base layer (Skyrim.esm, all base BSAs, Video/BGS_Logo.bik). Now accept both forms; backing entries get real_nt=NULL and exist only for query / negcache logic. 2. The "open negcache" comment claimed the index was authoritative for the FUSE mount tree. With (1), it is not. Comment rewritten; the comment about attr_cache_invalidate (which never existed) is also corrected to point at attr_cache_mark_exists. 3. Hook_NtCreateFile and Hook_NtOpenFile returned ENOENT on `!attr_cache_lookup() || !cexists` — the "no entry at all" case got the same treatment as a confirmed-negative cache hit. Plugin code that calls CreateFile without a preceding GetFileAttributes would never reach Real_NtCreateFile. Now mirrors what try_kernel32_attr_hit already did: only ENOENT on confirmed negatives; pure cache miss falls through to Real_, which serves correctly via Wine + FUSE. Also includes the previously-uncommitted mount-scope fast gate so out-of-mount paths (system DLLs, registry-backed objects, unrelated drives) bypass the hook entirely with a single prefix compare on the input string — no normalization, no cache lookup, no chance of a wrong answer. Verified by a self-contained 44-assertion C reproduction at /home/luke/builds/fluorine-vfs-tests/test_negcache_logic.c (six scenarios: backing-filter exclusion, first-open without pre-query, pre-query masking, SKSE plugin probes, post-fix Skyrim recovery, nt_path_to_key edge cases). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* profile: guard plugins.txt sync against crash-time rewritesSulfurNitride2026-05-082-1/+65
| | | | | | | | | | | | | | | | | | | | | | | | | Bethesda's engine rewrites Plugins.txt as part of its shutdown sequence. On a clean exit it preserves the active set; on a crash it can serialize a partially cleared set — every plugin name still listed, but most without their leading '*'. The previous syncPluginsBack code copied that file back to the profile faithfully, after which refreshESPList + savePluginList re-derived state and persisted the broken active list. NSPBR observed this as 1170 active plugins collapsing to 54. Two independent guards: - afterRun() now skips the post-launch sync when exitCode != 0. Non-zero is a strong "do not trust the prefix file" signal. - syncPluginsBack() compares starred-line counts in candidate vs profile. Refuses the copy when the candidate would drop the active count by more than 30% relative AND more than 10 plugins absolute. Belt and suspenders for cases where the engine catches its own crash and exits 0 anyway. Both guards apply regardless of the VFS bridge state and protect every launch on every modlist. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* multiprocess: recover from stale SysV shm + unix socket after crashSulfurNitride2026-05-081-3/+58
| | | | | | | | | | | | | | | | | | | Linux QSharedMemory uses SysV segments that survive process crashes. On the next launch, m_SharedMem.create() fails with AlreadyExists and the constructor would either treat the corpse as a live primary (blocking the user behind a ghost) or throw. The unix socket file is similarly left behind and would make QLocalServer::listen() fail with AddressInUseError if reclaim succeeded. Now: probe the primary's listener via QLocalSocket::connectToServer before believing the segment is alive. If the probe fails, detach and re-create. If create then fails (another attached client keeps refcount > 0), re-attach so the constructor returns in a usable secondary state instead of silently no-op'ing every later sendMessage. Also call QLocalServer::removeServer() before listen() to clear the orphaned socket file when we did successfully reclaim. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Add PE-side VFS bridge accelerationSulfurNitride2026-05-0832-14/+8300
|
* vfs: persistent scan cache to skip mod walk on warm bootSulfurNitride2026-05-037-7/+499
| | | | | | | | | | | | | | | | Saves the merged VfsTree to ~/.local/share/fluorine/vfs_cache/ keyed by data_dir + mods + overwrite. On a hit, mounting reads the tree from a single binary file instead of re-walking every mod, which dominates mount time on heavy modlists. Validation: modlist.txt mtime+size, data/overwrite dir mtime, mod-dir mtimes in priority order. Any drift invalidates. Session-scoped state (extra-file injection, plugin timestamp stamping) is re-applied after load and never cached. rebuild() also refreshes the cache so the next cold mount hits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix portable rename + route external links through xdg-open scrubberSulfurNitride2026-05-032-3/+35
| | | | | | | | | | | | | | | Issue #70: rename button was disabled for portable instances; the upstream restriction assumed portable = MO2-binary dir, which doesn't apply here (binary lives in ~/.local/share/fluorine/bin, instance dir is separate). Allow rename when not active, and update the portable registry so the renamed dir keeps showing up. Issue #67: QLabel::setOpenExternalLinks(true) and friends route through QDesktopServices::openUrl, which forks xdg-open with our bundled Qt env inherited — silently fails. Install a global url handler that defers to shell::Open, which already scrubs LD_LIBRARY_PATH/QT_PLUGIN_PATH. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* ci: bring back stable releases on v* tag pushSulfurNitride2026-05-021-5/+59
| | | | | | | | | | | | | | | | | | | | Local stable builds picked up host CPU flags (AVX-512) and shipped binaries that crashed on user CPUs without those instructions. CI runs on generic GitHub-hosted x86-64 runners, so all release builds need to go through it now. - Trigger workflow on push of any v* tag in addition to main. - Resolve channel from the ref: tags/v* -> stable, otherwise beta. - Tarball name follows channel: fluorine-manager-<version>.tar.gz on stable, fluorine-manager-beta.tar.gz on beta. - Beta publish step gated on channel==beta to avoid double-publishing when a tag also lives on main. - New "Publish stable release" step creates/updates a release at the tag, marks it --latest, and attaches the versioned tarball. To cut v0.2.0: `git tag v0.2.0 && git push origin v0.2.0`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* mainwindow: show semver in beta titlebar tooSulfurNitride2026-05-021-7/+9
| | | | | | | | | Beta titlebar was "Fluorine Manager beta @ <hash>" — the version line (0.1.x vs 0.2.x) was invisible. Switch to "<semver>-beta @ <hash>" so the running build's major/minor is obvious without opening the About dialog. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* steamdetection: stop blacklisting Proton 11SulfurNitride2026-05-021-10/+4
| | | | | | | | | The wineboot -u deadlock workaround (waitforexitandrun + ntsync env nudge) already lives in prefixsetuprunner, so the catch-all Proton 11 skip in findSteamProtons is no longer needed. Users on stock Proton 11 are now selectable alongside Proton 10 / Experimental / GE-Proton. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* updater: SLR auto-update + wire fluorine updater badge, bump 0.2.0SulfurNitride2026-05-028-37/+128
| | | | | | | | | | | | | | | | | | | | - OrganizerCore: add fluorineUpdater() getter + checkForSlrUpdates() fired from startup checkForUpdates(); detached thread runs downloadSlr when SLR already installed so steamrt4 BUILD_ID drift is picked up without a launch-time stall. - MainWindow: connect FluorineUpdater::updateAvailable to existing updateAvailable() slot so the statusbar badge + actionUpdate light up; on_actionUpdate_triggered routes to Settings -> Updates when a Fluorine update is pending (the MO2 self-updater path is no-op'd). - MainWindow launch path: drop !isSlrInstalled() short-circuit; fresh installs still get the progress dialog, up-to-date checks rely on the startup background pass. - SettingsDialog: selectTabByLabel() so MainWindow can open Updates tab. - Launcher: rm -rf update-staging/ once a new bundle has synced into bin/, so the in-app updater doesn't accumulate stale extracts. - CMakeLists.txt: 0.1.4 -> 0.2.0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* 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>