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.";
};
};
}