From 3c54fd54121c15eca1aa25db28d8d146b256804f Mon Sep 17 00:00:00 2001 From: mjallen18 Date: Sun, 15 Mar 2026 19:40:42 -0500 Subject: [PATCH] vibes bb --- flake.nix | 15 +- modules/steam-rom-manager/default.nix | 81 ++- modules/steam-rom-manager/emulators.nix | 738 ++++++++++++++++++++---- modules/steam-rom-manager/options.nix | 111 +++- 4 files changed, 793 insertions(+), 152 deletions(-) diff --git a/flake.nix b/flake.nix index de63179..9a759ee 100644 --- a/flake.nix +++ b/flake.nix @@ -56,8 +56,19 @@ programs.steam-rom-manager = { enable = true; steamUsername = "testuser"; - # retroarch is cross-platform; use it as the check target - emulators.retroarch.enable = true; + emulators = { + # ROM parsers + retroarch.enable = true; # cross-platform + mgba.enable = true; # cross-platform + # Platform parsers (no package) + epic.enable = true; + "itch.io".enable = true; + # Disabled flag + flycast = { + enable = true; + disabled = true; + }; + }; }; } ]; diff --git a/modules/steam-rom-manager/default.nix b/modules/steam-rom-manager/default.nix index 5616c51..c4c25f6 100644 --- a/modules/steam-rom-manager/default.nix +++ b/modules/steam-rom-manager/default.nix @@ -12,9 +12,28 @@ let cfg = config.programs.steam-rom-manager; # Single source of truth for built-in emulator metadata (package, romFolder, - # fileTypes). options.nix imports the same file so both files stay in sync. + # fileTypes, executableArgs, steamCategory, parserInputs). + # options.nix imports the same file so both files stay in sync. knownEmulators = import ./emulators.nix pkgs; + # Parser types that do not scan ROM files and have no executable path. + platformParserTypes = [ + "Epic" + "Legendary" + "GOG Galaxy" + "Amazon Games" + "UPlay" + "itch.io" + "UWP" + "EA Desktop" + "Battle.net" + "Non-SRM Shortcuts" + "Steam" + "Manual" + ]; + + isPlatformParser = type: builtins.elem type platformParserTypes; + # --------------------------------------------------------------------------- # Build an ordered JSON object for one parser entry. # SRM requires keys in a specific order; builtins.toJSON does not guarantee @@ -27,24 +46,50 @@ let name: emu: let known = knownEmulators.${name} or null; + parserType = emu.parserType; + platform = isPlatformParser parserType; + romFolder = if emu.romFolder != "" then emu.romFolder - else if known != null then + else if known != null && known ? romFolder then known.romFolder else lib.warn "steam-rom-manager: unknown emulator '${name}', romFolder not set" ""; + fileTypes = if emu.fileTypes != [ ] then emu.fileTypes - else if known != null then + else if known != null && known ? fileTypes then known.fileTypes else - lib.warn "steam-rom-manager: unknown emulator '${name}', fileTypes not set" [ ]; - parserType = emu.parserType; + [ ]; # lib.getExe resolves the primary binary without IFD path probing. - exePath = lib.getExe emu.package; + # For platform parsers there is no executable; use empty string. + exePath = + if platform then + "" + else if emu.package != null then + lib.getExe emu.package + else + ""; + + # Build parserInputs: for Glob/Glob-regex parsers always include the + # generated glob key; merge any extra user-supplied inputs on top. + globKey = + if parserType == "Glob" then + "glob" + else if parserType == "Glob-regex" then + "glob-regex" + else + null; + globPattern = + if globKey != null && fileTypes != [ ] then + { ${globKey} = "\${title}@(${lib.concatStringsSep "|" fileTypes})"; } + else + { }; + resolvedParserInputs = globPattern // emu.parserInputs; orderedConfig = [ { @@ -61,7 +106,7 @@ let } { name = "romDirectory"; - value = "${cfg.environmentVariables.romsDirectory}/${romFolder}"; + value = if platform then "" else "${cfg.environmentVariables.romsDirectory}/${romFolder}"; } { name = "steamCategories"; @@ -69,7 +114,7 @@ let } { name = "executableArgs"; - value = emu.extraArgs; + value = if platform then "" else emu.extraArgs; } { name = "executableModifier"; @@ -77,15 +122,15 @@ let } { name = "startInDirectory"; - value = "${cfg.environmentVariables.romsDirectory}/${romFolder}"; + value = if platform then "" else "${cfg.environmentVariables.romsDirectory}/${romFolder}"; } { name = "titleModifier"; value = emu.titleModifier; } # fetchControllerTemplatesButton / removeControllersButton are UI-only - # action triggers in SRM; they have no meaningful config value and must - # be present as null for schema compatibility. + # action triggers in SRM; they carry no config value and must be null + # for schema compatibility. { name = "fetchControllerTemplatesButton"; value = null; @@ -122,9 +167,7 @@ let } { name = "parserInputs"; - value = { - glob = "\${title}@(${lib.concatStringsSep "|" fileTypes})"; - }; + value = resolvedParserInputs; } { name = "executable"; @@ -192,6 +235,10 @@ let icon = ""; }; } + { + name = "disabled"; + value = emu.disabled; + } { name = "parserId"; value = name; @@ -239,6 +286,10 @@ let } "$@" ''; + # Collect only enabled emulators that have a non-null package (i.e. not + # pure platform parsers like Epic/GOG that need no extra Nix package). + enabledEmulatorsWithPackage = lib.filterAttrs (_: v: v.enable && v.package != null) cfg.emulators; + in { imports = [ ./options.nix ]; @@ -249,7 +300,7 @@ in pkgs.appimage-run steam-rom-manager-appimage ] - ++ lib.mapAttrsToList (_: v: v.package) (lib.filterAttrs (_: v: v.enable) cfg.emulators); + ++ lib.mapAttrsToList (_: v: v.package) enabledEmulatorsWithPackage; xdg.dataFile."icons/hicolor/scalable/apps/steam-rom-manager.svg".source = steam-rom-manager-icon; diff --git a/modules/steam-rom-manager/emulators.nix b/modules/steam-rom-manager/emulators.nix index 5b6f394..0598431 100644 --- a/modules/steam-rom-manager/emulators.nix +++ b/modules/steam-rom-manager/emulators.nix @@ -1,19 +1,26 @@ -# emulators.nix — single source of truth for built-in emulator metadata. +# emulators.nix — single source of truth for all built-in emulator/parser metadata. # -# Both default.nix (for romFolder/fileTypes fallbacks) and options.nix (for -# package defaults) import this file directly, preventing the two files from -# drifting out of sync. +# Both default.nix (romFolder/fileTypes/executableArgs fallbacks) and options.nix +# (package defaults) import this file so the two stay in sync. # -# Each entry may contain: -# package — nixpkgs derivation (required) -# romFolder — sub-directory name under romsDirectory (required) -# fileTypes — list of file extensions matched by the glob parser (required) -# parserType — SRM parser type string; omit to use the default "Glob" +# Each entry must have: +# romFolder — sub-dir under romsDirectory (empty string for platform/artwork parsers) +# fileTypes — list of extensions for glob pattern (empty for non-Glob parsers) +# +# Each entry may have: +# package — nixpkgs derivation; omit for platform parsers that need no package +# parserType — SRM parser type (default: "Glob") +# executableArgs — default args string passed to the emulator +# steamCategory — default Steam category tag string +# parserInputs — attrset of extra parserInputs for non-Glob parsers pkgs: { + # --------------------------------------------------------------------------- + # Nintendo Switch + # --------------------------------------------------------------------------- ryujinx = { - # ryujinx was removed from nixpkgs; ryubing is the maintained community fork + # ryujinx removed from nixpkgs; ryubing is the community-maintained fork package = pkgs.ryubing; romFolder = "switch"; fileTypes = [ @@ -28,88 +35,173 @@ pkgs: { ".xci" ".XCI" ]; + executableArgs = "--fullscreen \"\${filePath}\""; + steamCategory = "Switch"; }; - # yuzu was removed from nixpkgs after the Nintendo lawsuit; eden is the successor + # yuzu removed from nixpkgs; eden is the successor yuzu = { package = pkgs.eden; romFolder = "switch"; fileTypes = [ + ".kip" + ".KIP" + ".nca" + ".NCA" + ".nro" + ".NRO" + ".nso" + ".NSO" ".nsp" ".NSP" ".xci" ".XCI" ]; + executableArgs = "-f -g \"\${filePath}\""; + steamCategory = "Switch"; }; - pcsx2 = { - package = pkgs.pcsx2; - romFolder = "ps2"; + # --------------------------------------------------------------------------- + # Nintendo NES + # --------------------------------------------------------------------------- + mesen = { + package = pkgs.mesen; + romFolder = "nes"; fileTypes = [ - ".iso" - ".ISO" - ".bin" - ".BIN" - ".chd" - ".CHD" + ".7z" + ".7Z" + ".fds" + ".FDS" + ".nes" + ".NES" + ".unif" + ".UNIF" + ".unf" + ".UNF" + ".zip" + ".ZIP" ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "NES"; }; - rpcs3 = { - package = pkgs.rpcs3; - romFolder = "ps3"; + # --------------------------------------------------------------------------- + # Nintendo SNES + # --------------------------------------------------------------------------- + snes9x = { + package = pkgs.snes9x-gtk; + romFolder = "snes"; fileTypes = [ - ".iso" - ".ISO" - ".bin" - ".BIN" - ".pkg" - ".PKG" + ".7z" + ".7Z" + ".bs" + ".BS" + ".sfc" + ".SFC" + ".smc" + ".SMC" + ".zip" + ".ZIP" ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "SNES"; }; + # --------------------------------------------------------------------------- + # Nintendo 64 + # --------------------------------------------------------------------------- + mupen64plus = { + package = pkgs.mupen64plus; + romFolder = "n64"; + fileTypes = [ + ".7z" + ".7Z" + ".n64" + ".N64" + ".v64" + ".V64" + ".z64" + ".Z64" + ".zip" + ".ZIP" + ]; + executableArgs = "--fullscreen \"\${filePath}\""; + steamCategory = "N64"; + }; + + parallel-launcher = { + package = pkgs.parallel-launcher; + romFolder = "n64"; + fileTypes = [ + ".7z" + ".7Z" + ".n64" + ".N64" + ".v64" + ".V64" + ".z64" + ".Z64" + ".zip" + ".ZIP" + ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "N64"; + }; + + simple64 = { + package = pkgs.simple64; + romFolder = "n64"; + fileTypes = [ + ".7z" + ".7Z" + ".n64" + ".N64" + ".v64" + ".V64" + ".z64" + ".Z64" + ".zip" + ".ZIP" + ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "N64"; + }; + + # --------------------------------------------------------------------------- + # Nintendo GameCube / Wii (Dolphin) + # --------------------------------------------------------------------------- dolphin-emu = { - # Use pkgs.dolphin-emu (the user-facing wrapper), not pkgs.dolphinEmu package = pkgs.dolphin-emu; romFolder = "gc"; fileTypes = [ - ".iso" - ".ISO" - ".gcm" - ".GCM" ".ciso" ".CISO" - ".rvz" - ".RVZ" - ".wbfs" - ".WBFS" - ]; - }; - - duckstation = { - package = pkgs.duckstation; - romFolder = "psx"; - fileTypes = [ + ".dol" + ".DOL" + ".elf" + ".ELF" + ".gcm" + ".GCM" + ".gcz" + ".GCZ" ".iso" ".ISO" - ".bin" - ".BIN" - ".chd" - ".CHD" - ".pbp" - ".PBP" - ]; - }; - - melonDS = { - package = pkgs.melonDS; - romFolder = "nds"; - fileTypes = [ - ".nds" - ".NDS" + ".rvz" + ".RVZ" + ".wad" + ".WAD" + ".wbfs" + ".WBFS" + ".wia" + ".WIA" ]; + executableArgs = "-b -e \"\${filePath}\""; + steamCategory = "GameCube"; }; + # --------------------------------------------------------------------------- + # Nintendo Wii U + # --------------------------------------------------------------------------- cemu = { package = pkgs.cemu; romFolder = "wiiu"; @@ -121,32 +213,283 @@ pkgs: { ".rpx" ".RPX" ]; + executableArgs = "-f -g \"\${filePath}\""; + steamCategory = "Wii U"; }; + # --------------------------------------------------------------------------- + # Nintendo DS + # --------------------------------------------------------------------------- + melonDS = { + package = pkgs.melonDS; + romFolder = "nds"; + fileTypes = [ + ".7z" + ".7Z" + ".bin" + ".BIN" + ".nds" + ".NDS" + ".zip" + ".ZIP" + ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "DS"; + }; + + # --------------------------------------------------------------------------- + # Nintendo 3DS + # --------------------------------------------------------------------------- + # citra-nightly removed from nixpkgs; azahar is the successor + citra = { + package = pkgs.azahar; + romFolder = "3ds"; + fileTypes = [ + ".3ds" + ".3DS" + ".3dsx" + ".3DSX" + ".app" + ".APP" + ".axf" + ".AXF" + ".cci" + ".CCI" + ".cxi" + ".CXI" + ".elf" + ".ELF" + ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "3DS"; + }; + + # --------------------------------------------------------------------------- + # Nintendo Game Boy / GBC + # --------------------------------------------------------------------------- + mgba = { + package = pkgs.mgba; + romFolder = "gb"; + fileTypes = [ + ".7z" + ".7Z" + ".gb" + ".GB" + ".gbc" + ".GBC" + ".dmg" + ".DMG" + ".zip" + ".ZIP" + ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "Game Boy"; + }; + + # --------------------------------------------------------------------------- + # Nintendo Game Boy Advance + # --------------------------------------------------------------------------- + # mgba handles GBA too; provide a dedicated entry for GBA-specific folder + mgba-gba = { + package = pkgs.mgba; + romFolder = "gba"; + fileTypes = [ + ".7z" + ".7Z" + ".gba" + ".GBA" + ".zip" + ".ZIP" + ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "Game Boy Advance"; + }; + + # --------------------------------------------------------------------------- + # Sony PlayStation 1 + # --------------------------------------------------------------------------- + duckstation = { + package = pkgs.duckstation; + romFolder = "psx"; + fileTypes = [ + ".cue" + ".CUE" + ".chd" + ".CHD" + ".ecm" + ".ECM" + ".iso" + ".ISO" + ".m3u" + ".M3U" + ".mds" + ".MDS" + ".pbp" + ".PBP" + ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "PS1"; + }; + + # --------------------------------------------------------------------------- + # Sony PlayStation 2 + # --------------------------------------------------------------------------- + pcsx2 = { + package = pkgs.pcsx2; + romFolder = "ps2"; + fileTypes = [ + ".bin" + ".BIN" + ".chd" + ".CHD" + ".cso" + ".CSO" + ".dump" + ".DUMP" + ".gz" + ".GZ" + ".img" + ".IMG" + ".iso" + ".ISO" + ".mdf" + ".MDF" + ".nrg" + ".NRG" + ]; + executableArgs = "\"\${filePath}\" --batch --fullscreen --no-gui"; + steamCategory = "PS2"; + }; + + # --------------------------------------------------------------------------- + # Sony PlayStation 3 + # --------------------------------------------------------------------------- + rpcs3 = { + package = pkgs.rpcs3; + romFolder = "ps3"; + # RPCS3 needs eboot.bin for extracted ISOs/installed PKGs. + # Users may want to adjust the glob; this covers the ISO case. + fileTypes = [ + ".iso" + ".ISO" + ".bin" + ".BIN" + ".pkg" + ".PKG" + ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "PS3"; + }; + + # --------------------------------------------------------------------------- + # Sony PlayStation Portable + # --------------------------------------------------------------------------- ppsspp = { package = pkgs.ppsspp; romFolder = "psp"; fileTypes = [ - ".iso" - ".ISO" + ".elf" + ".ELF" ".cso" ".CSO" + ".iso" + ".ISO" ".pbp" ".PBP" + ".prx" + ".PRX" ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "PSP"; }; - mame = { - package = pkgs.mame; - romFolder = "arcade"; + # --------------------------------------------------------------------------- + # Sega Genesis / Mega Drive + # --------------------------------------------------------------------------- + blastem = { + package = pkgs.blastem; + romFolder = "genesis"; fileTypes = [ - ".zip" - ".ZIP" ".7z" ".7Z" + ".bin" + ".BIN" + ".gen" + ".GEN" + ".md" + ".MD" + ".smd" + ".SMD" + ".zip" + ".ZIP" ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "Genesis/Mega Drive"; }; + # --------------------------------------------------------------------------- + # Sega Dreamcast + # --------------------------------------------------------------------------- + flycast = { + package = pkgs.flycast; + romFolder = "dreamcast"; + fileTypes = [ + ".cdi" + ".CDI" + ".cue" + ".CUE" + ".chd" + ".CHD" + ".gdi" + ".GDI" + ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "Dreamcast"; + }; + + # --------------------------------------------------------------------------- + # Sega Saturn + # --------------------------------------------------------------------------- + mednaffe = { + # mednaffe is a GUI frontend for mednafen; mednafen itself is the emulator. + # The SRM preset launches mednaffe which wraps mednafen. + package = pkgs.mednaffe; + romFolder = "saturn"; + fileTypes = [ + ".7z" + ".7Z" + ".ccd" + ".CCD" + ".chd" + ".CHD" + ".cue" + ".CUE" + ".m3u" + ".M3U" + ".toc" + ".TOC" + ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "Saturn"; + }; + + # --------------------------------------------------------------------------- + # Microsoft Xbox (original) + # --------------------------------------------------------------------------- + xemu = { + package = pkgs.xemu; + romFolder = "xbox"; + fileTypes = [ + ".iso" + ".ISO" + ]; + executableArgs = "-full-screen -dvd_path \"\${filePath}\""; + steamCategory = "Xbox"; + }; + + # --------------------------------------------------------------------------- + # Microsoft DOS + # --------------------------------------------------------------------------- dosbox = { package = pkgs.dosbox; romFolder = "dos"; @@ -157,93 +500,256 @@ pkgs: { ".BAT" ".com" ".COM" + ".conf" + ".CONF" ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "DOS"; }; - snes9x = { - package = pkgs.snes9x-gtk; - romFolder = "snes"; + dosbox-staging = { + package = pkgs.dosbox-staging; + romFolder = "dos"; fileTypes = [ - ".smc" - ".SMC" - ".sfc" - ".SFC" - ".fig" - ".FIG" + ".exe" + ".EXE" + ".bat" + ".BAT" + ".com" + ".COM" + ".conf" + ".CONF" ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "DOS"; }; - mgba = { - package = pkgs.mgba; - romFolder = "gba"; + dosbox-x = { + package = pkgs.dosbox-x; + romFolder = "dos"; fileTypes = [ - ".gba" - ".GBA" + ".exe" + ".EXE" + ".bat" + ".BAT" + ".com" + ".COM" + ".conf" + ".CONF" ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "DOS"; }; - mupen64plus = { - package = pkgs.mupen64plus; - romFolder = "n64"; + # --------------------------------------------------------------------------- + # Arcade (MAME) + # --------------------------------------------------------------------------- + mame = { + package = pkgs.mame; + romFolder = "arcade"; fileTypes = [ + ".7z" + ".7Z" + ".zip" + ".ZIP" + ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "Arcade"; + }; + + # --------------------------------------------------------------------------- + # RetroArch (multi-system) + # --------------------------------------------------------------------------- + retroarch = { + package = pkgs.retroarch; + romFolder = "retroarch"; + fileTypes = [ + ".7z" + ".7Z" + ".bin" + ".BIN" + ".chd" + ".CHD" + ".iso" + ".ISO" + ".zip" + ".ZIP" + ]; + executableArgs = "-L \"\${filePath}\""; + steamCategory = "RetroArch"; + }; + + # --------------------------------------------------------------------------- + # ares (multi-system — covers many classic systems) + # See: https://ares-emu.net + # --------------------------------------------------------------------------- + ares = { + package = pkgs.ares-emu; + romFolder = "ares"; + fileTypes = [ + ".7z" + ".7Z" + ".zip" + ".ZIP" + ".bin" + ".BIN" + ".rom" + ".ROM" ".n64" ".N64" ".v64" ".V64" ".z64" ".Z64" + ".nes" + ".NES" + ".fds" + ".FDS" + ".sfc" + ".SFC" + ".smc" + ".SMC" + ".gb" + ".GB" + ".gbc" + ".GBC" + ".gba" + ".GBA" + ".md" + ".MD" + ".gen" + ".GEN" + ".smd" + ".SMD" + ".sms" + ".SMS" + ".gg" + ".GG" + ".pce" + ".PCE" + ".ws" + ".WS" + ".wsc" + ".WSC" + ".col" + ".COL" + ".a26" + ".A26" ]; + executableArgs = "--fullscreen \"\${filePath}\""; + steamCategory = "ares"; }; - retroarch = { - package = pkgs.retroarch; - romFolder = "retroarch"; + # --------------------------------------------------------------------------- + # ScummVM + # --------------------------------------------------------------------------- + scummvm = { + package = pkgs.scummvm; + romFolder = "scummvm"; + fileTypes = [ ]; # ScummVM is typically pointed at a game directory, not files + executableArgs = "--path=\"\${filePath}\" --auto-detect -x"; + steamCategory = "ScummVM"; + }; + + # --------------------------------------------------------------------------- + # Atari 2600 + # --------------------------------------------------------------------------- + stella = { + package = pkgs.stella; + romFolder = "atari2600"; fileTypes = [ - ".zip" - ".ZIP" ".7z" ".7Z" - ".iso" - ".ISO" + ".a26" + ".A26" ".bin" ".BIN" - ".chd" - ".CHD" + ".zip" + ".ZIP" ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "Atari 2600"; }; - flycast = { - package = pkgs.flycast; - romFolder = "dreamcast"; + # --------------------------------------------------------------------------- + # Commodore Amiga + # --------------------------------------------------------------------------- + fs-uae = { + package = pkgs.fs-uae; + romFolder = "amiga"; fileTypes = [ - ".gdi" - ".GDI" - ".cdi" - ".CDI" - ".chd" - ".CHD" + ".7z" + ".7Z" + ".adf" + ".ADF" + ".adz" + ".ADZ" + ".dms" + ".DMS" + ".lha" + ".LHA" + ".zip" + ".ZIP" ]; + executableArgs = "\"\${filePath}\""; + steamCategory = "Amiga"; }; - # citra-nightly was removed from nixpkgs; azahar is the maintained successor - citra = { - package = pkgs.azahar; - romFolder = "3ds"; - fileTypes = [ - ".3ds" - ".3DS" - ".cia" - ".CIA" - ".cxi" - ".CXI" - ]; - }; + # --------------------------------------------------------------------------- + # Platform parsers (no ROM file scanning; no package required at runtime) + # parserType must match exactly what SRM expects. + # --------------------------------------------------------------------------- "Non-SRM Shortcuts" = { - package = pkgs.steam; parserType = "Non-SRM Shortcuts"; romFolder = ""; fileTypes = [ ]; + steamCategory = ""; + parserInputs = { }; + }; + + epic = { + parserType = "Epic"; + romFolder = ""; + fileTypes = [ ]; + steamCategory = "Epic"; + parserInputs = { + epicLauncherMode = true; + epicManifests = ""; + }; + }; + + legendary = { + # legendary-gl provides the `legendary` CLI tool for Epic Games + package = pkgs.legendary-gl; + parserType = "Legendary"; + romFolder = ""; + fileTypes = [ ]; + steamCategory = "Legendary"; + parserInputs = { + legendaryInstalledFile = ""; + }; + }; + + gog = { + parserType = "GOG Galaxy"; + romFolder = ""; + fileTypes = [ ]; + steamCategory = "GOG"; + parserInputs = { + galaxyExeOverride = ""; + gogLauncherMode = false; + }; + }; + + "itch.io" = { + parserType = "itch.io"; + romFolder = ""; + fileTypes = [ ]; + steamCategory = "itch.io"; + parserInputs = { + itchIoAppDataOverride = ""; + }; }; } diff --git a/modules/steam-rom-manager/options.nix b/modules/steam-rom-manager/options.nix index 293e174..044ac7b 100644 --- a/modules/steam-rom-manager/options.nix +++ b/modules/steam-rom-manager/options.nix @@ -16,6 +16,25 @@ let "sgdb" "steamCDN" ]; + + # Parser types that do not scan ROM files and therefore need no package. + platformParserTypes = [ + "Epic" + "Legendary" + "GOG Galaxy" + "Amazon Games" + "UPlay" + "itch.io" + "UWP" + "EA Desktop" + "Battle.net" + "Non-SRM Shortcuts" + "Steam" + "Manual" + ]; + + isPlatformParser = type: builtins.elem type platformParserTypes; + in { options.programs.steam-rom-manager = { @@ -251,29 +270,49 @@ in example = "john"; description = '' Steam account username used to build the SRM environment-variable - reference ''${} in userSettings.json. + reference ''${} in userSettings.json. This must match the account name shown in Steam (not the display name). ''; }; # ------------------------------------------------------------------------- - # Emulator parser configurations + # Emulator / parser configurations # ------------------------------------------------------------------------- emulators = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule ( - { name, ... }: + { name, config, ... }: { options = { - enable = lib.mkEnableOption "emulator parser for ${name}"; + enable = lib.mkEnableOption "parser for ${name}"; + + disabled = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Write the parser into userConfigurations.json but mark it as + disabled inside SRM (equivalent to unchecking it in the UI). + Useful for keeping a config around without having it run. + ''; + }; package = lib.mkOption { - type = lib.types.package; - # Resolve against knownEmulators first; fall back to pkgs. so - # users can supply any arbitrary emulator key without a code change. - default = if knownEmulators ? ${name} then knownEmulators.${name}.package else pkgs.${name}; - description = "Package providing the emulator binary."; + type = lib.types.nullOr lib.types.package; + # Resolve against knownEmulators, then try pkgs., then null + # for pure platform parsers that have no associated binary. + default = + if knownEmulators ? ${name} && knownEmulators.${name} ? package then + knownEmulators.${name}.package + else if isPlatformParser config.parserType then + null + else + pkgs.${name}; + description = '' + Package providing the emulator binary. + Set to null for platform parsers (Epic, GOG, etc.) that do not + need a separate package managed by Nix. + ''; }; parserType = lib.mkOption { @@ -283,7 +322,12 @@ in knownEmulators.${name}.parserType else "Glob"; - description = "SRM parser type (usually \"Glob\" for file-based ROMs)."; + description = '' + SRM parser type. One of: + Glob, Glob-regex, Manual, Steam, Non-SRM Shortcuts, + Epic, Legendary, GOG Galaxy, Amazon Games, UPlay, + itch.io, UWP, EA Desktop, Battle.net + ''; }; configTitle = lib.mkOption { @@ -296,21 +340,30 @@ in type = lib.types.str; default = ""; description = '' - Sub-folder under - that contains ROMs for this emulator. Defaults to the built-in - value for known emulators; must be set explicitly for custom ones. + Sub-folder under environmentVariables.romsDirectory containing + ROMs for this emulator. Defaults to the built-in value for known + emulators; must be set explicitly for custom entries. + Leave empty for platform/artwork parsers. ''; }; steamCategories = lib.mkOption { type = lib.types.listOf lib.types.str; - default = [ "" ]; + default = + if knownEmulators ? ${name} && knownEmulators.${name} ? steamCategory then + [ knownEmulators.${name}.steamCategory ] + else + [ "" ]; description = "Steam categories / collection tags to apply to shortcuts."; }; extraArgs = lib.mkOption { type = lib.types.str; - default = "--fullscreen \"\${filePath}\""; + default = + if knownEmulators ? ${name} && knownEmulators.${name} ? executableArgs then + knownEmulators.${name}.executableArgs + else + "\"\${filePath}\""; description = "Command-line arguments passed to the emulator."; }; @@ -360,8 +413,9 @@ in type = lib.types.listOf lib.types.str; default = [ ]; description = '' - File extensions matched by the glob parser (e.g. ".iso"). - Defaults to the built-in list for known emulators; must be set for custom ones. + File extensions matched by the glob parser (e.g. ".iso"). + Defaults to the built-in list for known emulators. + Leave empty for platform/artwork parsers. ''; }; @@ -374,14 +428,33 @@ in appendArgsToExecutable = lib.mkOption { type = lib.types.bool; default = true; - description = "Append to the executable path in the shortcut."; + description = "Append extraArgs to the executable path in the shortcut."; }; + + # Extra parser-specific inputs (used by platform parsers and Manual). + # For Glob parsers the glob pattern is generated automatically from + # fileTypes; you do not need to set parserInputs.glob manually. + parserInputs = lib.mkOption { + type = lib.types.attrsOf lib.types.anything; + default = + if knownEmulators ? ${name} && knownEmulators.${name} ? parserInputs then + knownEmulators.${name}.parserInputs + else + { }; + description = '' + Additional parser-specific input fields merged into parserInputs. + For platform parsers (Epic, Legendary, GOG, itch.io, etc.) this + carries launcher-specific settings. + For Glob parsers the "glob" key is generated automatically. + ''; + }; + }; } ) ); default = { }; - description = "Attribute set of emulator parser configurations keyed by emulator name."; + description = "Attribute set of emulator/parser configurations keyed by name."; }; }; }