From 7ee008e150bc5bcf76082d726f719ee0fdfda982 Mon Sep 17 00:00:00 2001 From: SulfurNitride Date: Wed, 11 Feb 2026 02:37:39 -0600 Subject: Fluorine Manager: full Linux port of Mod Organizer 2 Complete native Linux port with FUSE-based virtual filesystem, Proton/umu-run integration, and Flatpak packaging. Key features: - FUSE VFS replacing Windows USVFS (in-process + standalone helper for Flatpak) - Proton/GE-Proton/umu-run launcher with env var forwarding - Flatpak support (sandbox-aware VFS, NXM handler, umu-run) - Wine prefix management UI - Case-insensitive path resolution for Linux filesystems - QSettings-safe INI handling (avoids Bethesda INI corruption) - Portable instance support with auto-generated launcher scripts Co-Authored-By: Claude Opus 4.6 --- libs/basic_games/epic_utils.py | 108 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 libs/basic_games/epic_utils.py (limited to 'libs/basic_games/epic_utils.py') diff --git a/libs/basic_games/epic_utils.py b/libs/basic_games/epic_utils.py new file mode 100644 index 0000000..94ae6a9 --- /dev/null +++ b/libs/basic_games/epic_utils.py @@ -0,0 +1,108 @@ +# -*- encoding: utf-8 -*- +from __future__ import annotations + +import itertools +import json +import os +import sys +from collections.abc import Iterable +from pathlib import Path + +try: + import winreg +except ImportError: + winreg = None + +ErrorList = list[tuple[str, Exception]] + + +def find_epic_games( + errors: ErrorList | None = None, +) -> Iterable[tuple[str, Path]]: + if winreg is None: + return + + try: + with winreg.OpenKey( + winreg.HKEY_LOCAL_MACHINE, + r"Software\Wow6432Node\Epic Games\EpicGamesLauncher", + ) as key: + epic_data_path, _ = winreg.QueryValueEx(key, "AppDataPath") + except FileNotFoundError: + epic_data_path = r"%ProgramData%\Epic\EpicGamesLauncher\Data" + + manifests_path = Path(os.path.expandvars(epic_data_path)).joinpath("Manifests") + if manifests_path.exists(): + for manifest_file_path in manifests_path.glob("*.item"): + try: + with open(manifest_file_path, encoding="utf-8") as manifest_file: + manifest_file_data = json.load(manifest_file) + yield ( + manifest_file_data["AppName"], + Path(manifest_file_data["InstallLocation"]), + ) + except (json.JSONDecodeError, KeyError) as e: + error_message = ( + f'Unable to parse Epic Games manifest file: "{manifest_file_path}"\n' + " Try to run the launcher recreate it." + ) + print( + error_message, + e, + file=sys.stderr, + ) + if errors is not None: + errors.append((error_message, e)) + + +def find_legendary_games( + config_path: str | None = None, errors: ErrorList | None = None +) -> Iterable[tuple[str, Path]]: + # Based on legendary source: + # https://github.com/derrod/legendary/blob/master/legendary/lfs/lgndry.py + if config_path := config_path or os.environ.get("XDG_CONFIG_HOME"): + legendary_config_path = Path(config_path, "legendary") + else: + legendary_config_path = Path("~/.config/legendary").expanduser() + + installed_path = legendary_config_path / "installed.json" + if installed_path.exists(): + try: + with open(installed_path, encoding="utf-8") as installed_file: + installed_games = json.load(installed_file) + for game in installed_games.values(): + yield game["app_name"], Path(game["install_path"]) + except (json.JSONDecodeError, AttributeError, KeyError) as e: + error_message = ( + f'Unable to parse installed games from Legendary/Heroic launcher: "{installed_path}"\n' + " Try to run the launcher to recrated the file." + ) + print( + error_message, + e, + file=sys.stderr, + ) + if errors is not None: + errors.append((error_message, e)) + + +def find_heroic_games(errors: ErrorList | None = None): + return find_legendary_games( + os.path.expandvars(r"%AppData%\heroic\legendaryConfig"), errors + ) + + +def find_games(errors: ErrorList | None = None) -> dict[str, Path]: + return dict( + itertools.chain( + find_epic_games(errors=errors), + find_legendary_games(errors=errors), + find_heroic_games(errors=errors), + ) + ) + + +if __name__ == "__main__": + games = find_games() + for k, v in games.items(): + print("Found game with id {} at {}.".format(k, v)) -- cgit v1.3.1