aboutsummaryrefslogtreecommitdiff
path: root/libs
Commit message (Collapse)AuthorAgeFilesLines
* Fix VFS rmdir, prefix/launch issues, add version + beta channelSulfurNitride2026-04-2315-29/+188
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - 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>
* Gate panel-plugin swap on enabled, pre-sync plugins.txt from prefixSulfurNitride2026-04-181-0/+4
| | | | | | | | | | | | | 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-1736-0/+5912
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | - 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>
* Remove Fix Game Registries button, fix BethINI New Vegas detectionSulfurNitride2026-04-131-5/+14
| | | | | | | | | | | 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>
* VFS perf fixes, icon/stylesheet compat, download filename fixSulfurNitride2026-04-12109-2/+17029
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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-129-122/+598
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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-1233-0/+4198
| | | | | | | | | | | | | | | | | | | | | | | | | 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>
* Remove LOOT integration entirelySulfurNitride2026-04-1223-2548/+0
| | | | | | | | | | | | | | | | | | | | | | | | 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-12270-24441/+0
| | | | | | | | | | | | | | | | 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.
* Sync upstream MO2 2.5.3 Beta 3 fixesSulfurNitride2026-04-101-1/+1
| | | | | | | | | - 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>
* Add BethINI Pie tool pluginSulfurNitride2026-04-106-0/+543
| | | | | | | | | | | | | | | | 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>
* Fix backup restore failing when destination file already existsSulfurNitride2026-04-101-0/+2
| | | | | | | | QFile::copy() does not overwrite existing files, unlike Windows SHFileOperation. Remove destination before copying to match the expected overwrite behavior. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Remove NaK/Rust dependency, port remaining functionality to native C++SulfurNitride2026-04-0837-8129/+48
| | | | | | | | | | | | | | | Replace NaK Rust crate and nak_ffi with native C++ implementations: - Game detection (Steam, Heroic, Bottles), VDF parser, icon extraction - Prefix symlinks, SLR manager, Steam path detection - Known games database Add FUSE VFS optimizations: zero-copy reads via fuse_reply_data, default_permissions mount option, FUSE_CAP_ASYNC_DIO, FUSE_CAP_EXPIRE_ONLY, lookup cache parent index, cached mode bits, increased I/O buffer sizes. Update build system, prefix setup, and various fixes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Replace winetricks dependency setup with native implementationsSulfurNitride2026-04-052-2/+22
| | | | | | | | | | | | | | | | | | | | Direct DLL extraction and installer execution instead of shelling out to winetricks for all prefix dependencies. Fixes d3dcompiler_47 being sourced from wrong SDK cabs (broke CommunityShaders), and eliminates the fragile cabextract-in-pressure-vessel PATH issue. - d3dcompiler_47: direct download from Mozilla fxc2 repo - d3dcompiler_43, d3dx9, d3dx11_43: cabextract from DirectX June 2010 redist with correct system32/syswow64 mapping - xact/xact_x64: cab extraction + batched regsvr32 registration - vcrun2022: direct installer with msvcp140.dll workaround (Wine #57518) - dotnet6/7/8, dotnetdesktop6: direct x86+x64 installer pairs - Game detection: batched into single .reg file import - All DLL overrides added to WINE_SETTINGS_REG - SHA256 verification for downloaded files - Winetricks kept only for win11 mode step Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add modl:// protocol support for mod.pub downloadsSulfurNitride2026-04-031-1/+1
| | | | | | | | | | | | | | Handle modl:// links alongside nxm:// for downloading mods from mod.pub. mod.pub uses a different URL format than Nexus — the download URL is embedded as a query parameter: modl://game/?url=<encoded-url> - Register x-scheme-handler/modl in desktop files and MIME lists - Accept both nxm and modl schemes in URL parsing (NxmLink, NXMUrl) - Parse modl:// direct download links and route to startDownloadURLs - Make key/expires optional in NxmLink::parse (not used by mod.pub) - Add debug logging for received URLs to aid troubleshooting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Replace Rust prefix setup with C++ step-by-step dialogSulfurNitride2026-04-031-0/+2
| | | | | | | | | | | | | | | | | | | | Move Wine prefix creation from NaK Rust FFI to native C++ with a new modal dialog showing per-step progress, retry support, and proper failure cleanup. - PrefixSetupRunner: runs each step (proton init, drive cleanup, 11 individual winetricks verbs, .NET installers, game detection, wine registry, win11 mode, post-setup) as discrete retryable operations - PrefixSetupDialog: step list with status icons, filtered log output, cancel/retry-failed/delete-prefix buttons - Downloads use QNetworkAccessManager (no host curl dependency) - Zip extraction uses downloaded 7zz binary (no host unzip dependency) - Winetricks runs on host (not in pressure-vessel) so cabextract is always findable via PATH - Wine/winetricks debug spam filtered from log output - Skip Python basic_games proxy since native equivalent exists Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix cabextract not found in pressure-vessel and TTW game detectionSulfurNitride2026-04-033-16/+39
| | | | | | | | | | | | | | | | cabextract: The system binary under /usr is invisible inside the pressure-vessel container where winetricks runs. Always download a local copy to ~/.local/share/fluorine/bin/ instead of short-circuiting when the host has it installed. TTW: The game plugin was calling the base class identifyGamePath() which tried matching "TTW" against NaK's detected game names — never matching "Fallout New Vegas". Now explicitly searches for FNV in the NaK results, mirroring how the Windows path uses the FalloutNV registry key. Also adds HAS_NAK_FFI and nak_ffi linkage to the TTW CMake target since it was previously only defined for the game_gamebryo target. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix SLR download failures and CMake rebuild detectionSulfurNitride2026-04-014-269/+283
| | | | | | | | | | | | | | | Replace SHA256SUMS verification with Content-Length check for SLR downloads. Valve's CDN frequently serves stale SHA256SUMS that don't match the current archive, causing persistent checksum mismatches. Fix CMake DEPENDS for nak_ffi to glob all .rs sources instead of hardcoding a few files — previously changes to slr.rs (and most other Rust files) were silently ignored by ninja. Also wrap prefix init and runtime commands through SLR pressure-vessel container, and expose Proton install directories to the container. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Block rootbuilder directory plugin in Python proxySulfurNitride2026-03-281-1/+2
| | | | | | | | The hard-blacklist in plugincontainer.cpp only catches rootbuilder at the top-level scan, but the Python proxy independently scans the plugins directory for packages (dirs with __init__.py) and was still loading it. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Add debug logging to game symlink creationSulfurNitride2026-03-281-0/+18
| | | | | | | | | | Log each game's prefix path, detected username, and whether the Documents/My Games directory exists. Also log when scan_and_link_all skips a game due to missing directories. This will help diagnose why some games are not getting their Documents folders symlinked during prefix setup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Search all Steam library folders for game compatdataSulfurNitride2026-03-281-18/+20
| | | | | | | | | | Steam can place a game's compatdata in a different library folder than the game itself (e.g. game on SD card, compatdata on internal storage). Previously we only looked in the same steamapps directory as the appmanifest, causing games to have no prefix_path and their Documents/My Games folders to not get symlinked during prefix setup. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Multi-dir plugin loading, VFS Root Builder, Wine registry check, column ↵SulfurNitride2026-03-221-1/+7
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | visibility fix Plugin loading: - Remove symlink-based ensureBundledPluginsLinked(), replace with multi-dir search (bundled dir takes priority, instance dir adds extras only) - Auto-clean stale symlinks from instance plugins/ dirs on startup - Add mergedProxyList() for proxy plugin discovery across both dirs VFS Root Builder (replaces Python rootbuilder.py): - Native C++ Root Builder integrated into FuseConnector - Deploy mod Root/ files to game dir before FUSE mount, clear on unmount - Per-instance toggle in Instance Manager (default: on) - Root Builder path normalization in processrunner.cpp: rewrite binary and start-in paths from mods/.../Root/ to Game Root for correct Wine path resolution (fixes SKSE + EngineFixes versionlib lookup) - Expose game dir and prefix to SLR pressure-vessel via --filesystem= Wine registry check: - Read/write Wine prefix system.reg (HKLM) with timestamp-aware parsing - Auto-check game install path before launch, prompt to fix if mismatched - Updates both Software\ and Wow6432Node\ registry keys Column visibility fix: - Guard updateGroupByProxy() during QHeaderView::restoreState() to prevent sort indicator restoration from triggering setSourceModel() which resets all columns to visible Python proxy: - Skip FixGameRegKey.py, crashlogtools, rootbuilder.py (Windows-only or replaced by native implementations) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* Fix Steam game mode: strip LD_PRELOAD, robust looksValid(), restore env for ↵SulfurNitride2026-03-172-2/+50
| | | | | | | | | | | | | | | | | | games Steam injects 32-bit gameoverlayrenderer.so via LD_PRELOAD which causes "wrong ELF class" errors for 64-bit Qt6 apps. Save and clear LD_PRELOAD in both launcher and AppRun scripts, restore it in ProtonLauncher for game processes. Also hardens looksValid() with QFileInfo::exists() for absolute paths (QDir::exists() has edge cases with relative subdirectory paths), adds case-insensitive directory walking fallback, and adds debug logging to both looksValid() and detectGame(). Adds ~/.steam/debian-installation to Python steam_utils.py candidates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix Steam game mode: clear LD_LIBRARY_PATH, deploy native basic_games, add ↵SulfurNitride2026-03-173-1/+13
| | | | | | | | | | | | | | | | | | | | | env var detection Steam game mode injects its scout/soldier runtime into LD_LIBRARY_PATH, which breaks Python extension modules and Qt internals that resolve dependencies via LD_LIBRARY_PATH rather than RPATH. This caused all Python-based basic_games plugins (Witcher 3, Oblivion Remastered, BG3, etc.) to fail to load when launched through Steam. Changes: - Launcher script: set LD_LIBRARY_PATH to only bundled lib/ dir instead of leaving Steam's runtime in the path - AppRun: same fix — replace rather than append to LD_LIBRARY_PATH - Deploy libbasic_games_native.so in tarball (was built but never copied) so basic_games entries work without Python dependency - Add STEAM_COMPAT_CLIENT_INSTALL_PATH env var check to Python steam_utils.py (mirrors C++ fix from e41a79a) for game mode detection Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix Steam game detection in game mode + add result cacheSulfurNitride2026-03-161-4/+26
| | | | | | | | | | | | | | | | | - Prepend STEAM_COMPAT_CLIENT_INSTALL_PATH as first Steam path candidate. Steam always sets this env var when launching any app (including in gamescope/game mode), so it reliably points to the correct Steam root even when $HOME resolves differently inside the gamescope session. - Canonicalize VDF-sourced library paths before dedup in getAllLibraryFolders() so /home/ ↔ /var/home/ symlinks (Bazzite/Fedora immutable distros) don't produce duplicate scan entries. - Cache findSteamGames() results in a static hash. Previously, findSteamGamePath() rebuilt the full library scan 45+ times per launch (once per registered game plugin). Now the scan runs at most once. Fixes game detection on Bazzite in Steam Game Mode. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix looksValid() for games with nested data directories (e.g. Oblivion ↵SulfurNitride2026-03-161-1/+20
| | | | | | | | | | | | | | | Remastered) Some UE5 games have their binary nested under a subdirectory of the Steam installdir even though the appmanifest points one level up. looksValid() was checking only for the binary at the game root, causing "does not seem to contain a game Mod Organizer can manage" when manually browsing. Add a fallback: if the binary isn't found at root, resolve the game's %GAME_PATH%-based dataDirectory and check if that path exists. The data path is unique enough to positively identify the game directory. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Add Steam Linux Runtime (SLR) support + fix Use Proton defaultSulfurNitride2026-03-154-2/+360
| | | | | | | | | | | | | | | | | | | | | | Steam Linux Runtime: - New Rust module (libs/nak/src/slr.rs): download SteamLinuxRuntime_sniper from repo.steampowered.com, SHA256 verification, BUILD_ID update checks - nak_ffi exports: nak_slr_is_installed, nak_slr_get_run_script, nak_download_slr - ProtonLauncher: wraps Proton launch inside SLR pressure-vessel container when enabled (run -- proton waitforexitandrun game.exe) - Instance Manager: SLR checkbox (default on) beside Steam DRM checkbox - Main window: pre-check downloads SLR before launch if needed - Proton settings tab: "Download Steam Linux Runtime" button - spawn.cpp: reads fluorine/use_slr per-instance INI key - Logs "Final command:" showing full assembled launch command Bug fixes: - Fix Use Proton off by default for plugin executables (executableslist.cpp) - Fix Use Proton off by default for file tree right-click (filetree.cpp) - Fix Fedora 43 Qt platform plugin crash: explicit QT_QPA_PLATFORM_PLUGIN_PATH - Add Qt6::Concurrent to CMakeLists for QtConcurrent background downloads Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix DDS and winreg missing in Python pluginsSulfurNitride2026-03-131-1/+9
| | | | | | | | | | | - Stage DDS/ package from source directly to plugins/data/DDS/ in build-inner.sh (cmake MO2_STAGE_PYTHON_PLUGIN_PAYLOAD is OFF by default so the cmake-sourced path was silently skipped) - Stage winreg.py from libs/ directly as well (same root cause) - Guard `import winreg` in gog_utils.py with try/except ImportError and early-exit to Heroic GOG path if the shim is unavailable Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Bundle PBS Python 3.12 + PyQt6, restore Python plugins as .py filesSulfurNitride2026-03-131-67/+9
| | | | | | | | | | | | | | | | | | | | | | | | | | | | - Replace portable Python approach with python-build-standalone 3.12.13 (20260310 release) bundled directly in the distribution. Python is now always available without user setup — removes the Python settings tab. - PyQt6 staged to plugins/libs/PyQt6/ with bundled Qt stripped out; patchelf'd to use our existing bundled Qt in lib/ instead of duplicating it. - Restored native-converted plugins back to .py files: Form43Checker, ScriptExtenderPluginChecker, DDSPreview, basic_games, rootbuilder, installer_omod. Removed all *_native CMake targets. - pythonrunner.cpp: removed venv/MO2_PYTHON_DIR lookup; always uses bundled Python at <exe_dir>/python, system Python as last-resort fallback. - plugincontainer.cpp: removed fluorine/python_enabled gate (Python always loads), demoted proxy loading log messages from warn to debug, silenced [plugin-diag] stderr spam. - Dockerfile: switched from uv venv to PBS install_only tarball; PBS Python used for both build-time pybind11 compilation and runtime distribution. - build-inner.sh: aggressive Python staging (strip test/tkinter/ensurepip/ distutils/lib2to3/idlelib; wipe build-time site-packages, restore psutil+vdf). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Replace wrestool with native Rust PE icon extraction (pelite)SulfurNitride2026-03-1211-69/+430
| | | | | | | | | | | | | | | | | - Add icons.rs to nak crate: parses PE resource sections, extracts RT_GROUP_ICON/RT_ICON and builds ICO files in memory - Expose nak_extract_exe_icon() FFI for C++ consumption - Update iconForExecutable() to use Rust FFI instead of shelling out to wrestool; reads all ICO entries for correct color depth - Fix BasicGamePlugin::gameIcon() to call iconForExecutable() (was returning empty QIcon, causing placeholder icons for BG3/Cyberpunk) - Move app icon setup: setDesktopFileName + setWindowIcon to MOApplication constructor (fixes Wayland taskbar/decoration icon) - Move data/com.fluorine.* to data/icons/, install to XDG paths at launch for Wayland compositor icon resolution - Remove icoutils dependency from Docker image and build output Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix build: add missing Qt6 find_package for OMOD installer, fix default argSulfurNitride2026-03-121-0/+3
| | | | | | | - Add find_package(Qt6) and qt_standard_project_setup() to installer_omod_native CMakeLists - Remove duplicate default argument from fillStatForFile definition in mo2filesystem.cpp Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add native Root Builder and OMOD Installer pluginsSulfurNitride2026-03-128-0/+1363
| | | | | | | | | | | | | | | - libs/rootbuilder_native/: Native C++ port of rootbuilder.py (IPluginTool) - Root/ dir scanning, copy (reflink/CoW) and symlink modes - JSON manifest tracking, backup/restore, auto-deploy hooks - Legacy migration and third-party conflict detection - libs/installer_omod_native/: Native C++ port of installer_omod.py - Handles .omod archives (Oblivion Mod Manager format) - Binary config parsing, .NET string format, CRC file parsing - zlib raw deflate and LZMA decompression - Repackages to zip for MO2's installation manager - Added both to build-inner.sh plugin packaging Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Replace Python plugins with native C++ .so, remove bundled Python (~95MB)SulfurNitride2026-03-1229-68/+4107
| | | | | | | | | | | | | | - Add native plugins: form43_checker, script_extender_checker, preview_dds, basic_games (75 game defs via IPluginProxy pattern with Steam/GOG detection) - Remove portable Python from Dockerfile and build-inner.sh - Add Python settings tab with venv support (system python3 + PyQt6) - Skip loading plugin_python.so when Python disabled (default: off) - Update pythonrunner.cpp to use venv at ~/.local/share/fluorine/python-venv/ - Preserve real file permissions in FUSE VFS (fixes native executable +x bits) - Add FUSE_SET_ATTR_MODE handler so chmod works through VFS - Fix desktop shortcut Exec= to use fluorine-manager launcher, not bare binary Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix build: use QString::fromLocal8Bit instead of QStringEncoder::encodeSulfurNitride2026-03-111-2/+1
| | | | | | | | QStringEncoder::encode() returns DecodedData which can't be directly assigned to QString in a declaration context. Use fromLocal8Bit() which is simpler and equivalent for system-encoded byte arrays. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix LOOT disabling plugins: handle Bethesda asterisk format in plugins.txtSulfurNitride2026-03-111-6/+30
| | | | | | | | | | | | | LOOT writes plugins.txt in Bethesda format where *Plugin.esp = enabled and Plugin.esp (no prefix) = disabled. MO2's reader didn't strip the asterisk, so it tried to find plugins literally named "*Plugin.esp", failed to match, and marked them all as inactive. Two-pass approach: detect whether file uses asterisks. If yes (LOOT/Bethesda format), only *-prefixed plugins are active. If no (MO2's own format), all listed plugins are active. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* FUSE VFS performance, tracked writes fix, remove steam-run, add NixOS flakeSulfurNitride2026-03-111-27/+0
| | | | | | | | | | | | | - FUSE: disable AUTO_INVAL_DATA, enable readdirplus, 24h TTLs, clone_fd, FUSE_USE_VERSION=312 — cuts game load time from 2:40 to 1:57 - Fix tracked writes going to wrong (lower-priority) mod in both detectManualMoves and syncOverwrite - Remove steam-run entirely (UI, C++, Rust layers) - Build system: default to tarball, add installer/appimage as opt-in modes - Add flake.nix for NixOS — builds from source with Rust crates as separate pure derivations, FHS wrapper, NixOS module with FUSE passthrough Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove FUSE passthrough (CAP_SYS_ADMIN incompatible with AppImage), remove ↵SulfurNitride2026-03-084-16/+16
| | | | | | | | | | | bsplugins, fix bsatk types - Remove FUSE passthrough code: requesting FUSE_CAP_PASSTHROUGH without CAP_SYS_ADMIN caused session cleanup crash on shutdown - Remove bsplugins library (unneeded port) - Fix bsatk: use uint32_t instead of unsigned long for BSAULong (LP64 compat) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Replace lootcli with standalone LOOT GUI AppImage on LinuxSulfurNitride2026-03-072-6/+4
| | | | | | | | | | | | | | | - Download LOOT AppImage from GitHub releases on first sort - Pre-seed LOOT settings.toml with game type, path, and masterlist URL - Mount FUSE VFS before launching LOOT, unmount after exit - Copy plugins.txt/loadorder.txt to LOOT local path and back - Fix VFS file timestamps (separate error_code for mtime) - Add directory timestamps to FUSE (was showing 1969) - Stamp plugin files with load-order-based incrementing timestamps - Remove WebEngine dependency on Linux (QTextBrowser fallback) - Enable LOOT sorting for FalloutNV and FalloutTTW - Add unmountVFS() to OrganizerCore Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix log copy truncation, enable ccache for build cacheSulfurNitride2026-02-251-5/+9
| | | | | | | | | | | | | | | Fix "Copy all" log truncation: the code unconditionally stripped 2 chars (\r\n) from formatted log messages, but on Linux spdlog only appends \n. Every log message lost its last real character. Now strips trailing \r and \n dynamically. Enable ccache: it was installed in the Docker image but never activated. build-inner.sh now auto-detects ccache and sets CMAKE_*_COMPILER_LAUNCHER. build.sh mounts a persistent ccache volume (~/.cache/fluorine-ccache) into the container. CI workflow uses actions/cache to persist ccache across runs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Remove umu-run, keep game as child process for Steam trackingSulfurNitride2026-02-237-81/+148
| | | | | | | | | | | | | | | - Remove umu-run entirely (crashed due to pressure-vessel/FUSE incompatibility) — use raw `proton run` instead - Replace startDetached() with QProcess::start() so the game stays in Fluorine's process tree. This lets Steam track the game lifetime and makes the "close game" button work when Fluorine is added as a non-Steam game. - Add writeIniValueDirect/readIniValueDirect for safe Bethesda INI handling without QSettings corruption - Fix prefix_setup.rs to skip umu-run bootstrapping - Clean up settings UI, build scripts, and docs for umu-run removal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix AppImage plugin loading, save timestamps, and INI corruptionSulfurNitride2026-02-206-62/+256
| | | | | | | | | | | | | | | | | | - Add AppConfig::pluginsPath()/dllsPath() that respect MO2_PLUGINS_DIR and MO2_DLLS_DIR env vars, fixing plugin discovery when plugins live inside read-only AppImage squashfs (#22, game plugin not found errors) - Preserve file modification times in save deploy/sync so games display correct save timestamps instead of all showing the same time (#17) - Replace QSettings INI reader in saves tab with direct line-by-line parser to avoid backslash-as-line-continuation corruption of sLocalSavePath=__MO_Saves\ (#22, saves tab showing wrong directory) - Add diagnostic logging to plugin symlink creation, save backup/restore operations to help diagnose silent failures through prefix symlinks - Fix Python SONAME mismatch in AppImage build (patch portable Python SONAME to match librunner.so DT_NEEDED, preventing dual libpython load) - Bundle PyQt6 Qt dependencies, improve RPATH for lib/ and plugins/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Add Python init diagnostics, AppImage build system, misc Linux fixesSulfurNitride2026-02-1916-122/+164
| | | | | | | | | | | | | | | | | | | | - python: switch Py_InitializeEx → Py_InitializeFromConfig for explicit error reporting; add dladdr + fprintf diagnostics to trace which DSO Python symbols resolve to and whether Py_IsInitialized succeeds - python: log RTLD_NOLOAD vs fresh-load outcome when promoting libpython to RTLD_GLOBAL - build: add Docker-based AppImage build script (build.sh), update Dockerfile and build-inner.sh for AppImage-only workflow - build: remove Flatpak support entirely; move AppImage metadata (desktop/metainfo/icon) from flatpak/ to data/ - Add Monster Hunter Wilds basic_games plugin - .gitignore: exclude squashfs-root, build-source, flatpak-repo, __pycache__, fomod-plus-settings.ini - Various Linux port fixes across wineprefix, processrunner, proton launcher, FUSE connector, instance manager, download manager, sanitychecks, and NaK Rust paths/runtime_wrap Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Fix fomod-plus image loading and settings file location on LinuxSulfurNitride2026-02-182-5/+10
| | | | | | | | | | Normalize Windows backslashes in fomod image paths so images like "fomod\MCM.png" resolve correctly on Linux. Move fomod-plus-settings.ini from QDir::currentPath() (home directory) to QStandardPaths::AppConfigLocation (Flatpak sandbox config dir). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Read Fluorine prefix path from config.json instead of QSettingsSulfurNitride2026-02-181-5/+20
| | | | | | | | Replace QSettings().value("fluorine/prefix_path") with a helper that reads fluorine/config.json directly, matching how the rest of the app resolves the prefix path. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix save deploy/sync for non-Bethesda games using Saved Games directorySulfurNitride2026-02-181-1/+1
| | | | | | | | | | | | | | | | | Games like Cyberpunk 2077 store saves in %USERPROFILE%/Saved Games/ instead of Documents/My Games/.../Saves. The save pipeline previously hardcoded Documents/My Games as the base path, so profile-specific saves never reached the correct location. Replace relative save path resolution with absolute path resolution that extracts the user-relative portion from LocalSavegames::mappings() or savesDirectory() and reconstructs it under the Fluorine prefix. Update restoreStaleBackups to scan the entire prefix for backup dirs. Remove "Saved Games" from NaK symlink skip list. Fixes #17 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix Wine prefix deployment, INI handling, and data directory pathsSulfurNitride2026-02-1740-152/+323
| | | | | | | | | | | | | | | | | | | | - Fix data directory path to use ~/.var/app/com.fluorine.manager consistently (was ~/.local/share/fluorine in committed code) - Fix VFS helper path in fuseconnector.cpp to use fluorineDataDir() - Fix localAppFolder() to resolve Wine prefix AppData/Local on Linux instead of returning XDG ~/.local/share - Add localAppName() virtual method for correct AppData folder mapping (Enderal→"enderal", Nehrim→"Oblivion") - Fix mergeTweak() to use direct INI parser instead of QSettings which corrupts backslashes and URL-encodes spaces in keys - Make resolveWineDataDirName() more robust with existence checks and fallback chain (documentsDirectory → gameShortName → gameName) - Add comprehensive debug logging throughout Wine prefix deployment - Fix INI case handling: copy instead of symlink for case-mismatched INIs, ensure both proper-case and lowercase aliases exist - Remove native build script (Flatpak only) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Replace Python build deps with uv across all build pathsSulfurNitride2026-02-151-3/+0
| | | | | | | | | | | | | | | | | Use uv as the single Python package manager for Docker, source, and Flatpak builds. Python 3.13 and pybind11==2.13.6 are now consistent across all three paths. - Docker: install uv + build venv instead of python3-dev/pip/pybind11-dev - CMake: bootstrap pyvenv with uv (REQUIRED/FATAL_ERROR) when no explicit Python provided; pin pybind11==2.13.6, add sip - Flatpak: remove pybind11 cmake module, use uv pip install --target for build deps, uv for portable Python packages - build-inner.sh: use BUILD_PY throughout, uv pip install for portable runtime packages, simplify embed check to direct Python invocation - VFS helper: fall back to shared libfuse3 when static .a unavailable Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Fix RPATH, absolute instance paths, Proton symlink matching, umu-run ↵SulfurNitride2026-02-152-20/+25
| | | | | | | | | | | | | | | | logging, pybind11 warning - Set $ORIGIN-relative RPATH on binaries so they find libs without LD_LIBRARY_PATH (fixes #12) - Check QDir::isAbsolutePath before concatenating instance paths to prevent doubling of custom portable paths - Canonicalize paths when matching Proton installations to handle symlinks in /usr/share/steam/compatibilitytools.d/ (fixes #8) - Add diagnostic logging for umu-run resolution showing preference, bundled/system paths, and final selection (helps diagnose #8) - Warn at CMake time if pybind11 >= 2.14 is detected (documents #9) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>