This commit is contained in:
mjallen18
2026-03-15 19:40:42 -05:00
parent 2fbb4d1f1b
commit 3c54fd5412
4 changed files with 793 additions and 152 deletions

View File

@@ -56,8 +56,19 @@
programs.steam-rom-manager = { programs.steam-rom-manager = {
enable = true; enable = true;
steamUsername = "testuser"; steamUsername = "testuser";
# retroarch is cross-platform; use it as the check target emulators = {
emulators.retroarch.enable = true; # 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;
};
};
}; };
} }
]; ];

View File

@@ -12,9 +12,28 @@ let
cfg = config.programs.steam-rom-manager; cfg = config.programs.steam-rom-manager;
# Single source of truth for built-in emulator metadata (package, romFolder, # 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; 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. # Build an ordered JSON object for one parser entry.
# SRM requires keys in a specific order; builtins.toJSON does not guarantee # SRM requires keys in a specific order; builtins.toJSON does not guarantee
@@ -27,24 +46,50 @@ let
name: emu: name: emu:
let let
known = knownEmulators.${name} or null; known = knownEmulators.${name} or null;
parserType = emu.parserType;
platform = isPlatformParser parserType;
romFolder = romFolder =
if emu.romFolder != "" then if emu.romFolder != "" then
emu.romFolder emu.romFolder
else if known != null then else if known != null && known ? romFolder then
known.romFolder known.romFolder
else else
lib.warn "steam-rom-manager: unknown emulator '${name}', romFolder not set" ""; lib.warn "steam-rom-manager: unknown emulator '${name}', romFolder not set" "";
fileTypes = fileTypes =
if emu.fileTypes != [ ] then if emu.fileTypes != [ ] then
emu.fileTypes emu.fileTypes
else if known != null then else if known != null && known ? fileTypes then
known.fileTypes known.fileTypes
else 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. # 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 = [ orderedConfig = [
{ {
@@ -61,7 +106,7 @@ let
} }
{ {
name = "romDirectory"; name = "romDirectory";
value = "${cfg.environmentVariables.romsDirectory}/${romFolder}"; value = if platform then "" else "${cfg.environmentVariables.romsDirectory}/${romFolder}";
} }
{ {
name = "steamCategories"; name = "steamCategories";
@@ -69,7 +114,7 @@ let
} }
{ {
name = "executableArgs"; name = "executableArgs";
value = emu.extraArgs; value = if platform then "" else emu.extraArgs;
} }
{ {
name = "executableModifier"; name = "executableModifier";
@@ -77,15 +122,15 @@ let
} }
{ {
name = "startInDirectory"; name = "startInDirectory";
value = "${cfg.environmentVariables.romsDirectory}/${romFolder}"; value = if platform then "" else "${cfg.environmentVariables.romsDirectory}/${romFolder}";
} }
{ {
name = "titleModifier"; name = "titleModifier";
value = emu.titleModifier; value = emu.titleModifier;
} }
# fetchControllerTemplatesButton / removeControllersButton are UI-only # fetchControllerTemplatesButton / removeControllersButton are UI-only
# action triggers in SRM; they have no meaningful config value and must # action triggers in SRM; they carry no config value and must be null
# be present as null for schema compatibility. # for schema compatibility.
{ {
name = "fetchControllerTemplatesButton"; name = "fetchControllerTemplatesButton";
value = null; value = null;
@@ -122,9 +167,7 @@ let
} }
{ {
name = "parserInputs"; name = "parserInputs";
value = { value = resolvedParserInputs;
glob = "\${title}@(${lib.concatStringsSep "|" fileTypes})";
};
} }
{ {
name = "executable"; name = "executable";
@@ -192,6 +235,10 @@ let
icon = ""; icon = "";
}; };
} }
{
name = "disabled";
value = emu.disabled;
}
{ {
name = "parserId"; name = "parserId";
value = name; 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 in
{ {
imports = [ ./options.nix ]; imports = [ ./options.nix ];
@@ -249,7 +300,7 @@ in
pkgs.appimage-run pkgs.appimage-run
steam-rom-manager-appimage 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; xdg.dataFile."icons/hicolor/scalable/apps/steam-rom-manager.svg".source = steam-rom-manager-icon;

View File

@@ -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 # Both default.nix (romFolder/fileTypes/executableArgs fallbacks) and options.nix
# package defaults) import this file directly, preventing the two files from # (package defaults) import this file so the two stay in sync.
# drifting out of sync.
# #
# Each entry may contain: # Each entry must have:
# package — nixpkgs derivation (required) # romFolder — sub-dir under romsDirectory (empty string for platform/artwork parsers)
# romFolder — sub-directory name under romsDirectory (required) # fileTypes — list of extensions for glob pattern (empty for non-Glob parsers)
# fileTypes — list of file extensions matched by the glob parser (required) #
# parserType — SRM parser type string; omit to use the default "Glob" # 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: { pkgs: {
# ---------------------------------------------------------------------------
# Nintendo Switch
# ---------------------------------------------------------------------------
ryujinx = { 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; package = pkgs.ryubing;
romFolder = "switch"; romFolder = "switch";
fileTypes = [ fileTypes = [
@@ -28,88 +35,173 @@ pkgs: {
".xci" ".xci"
".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 = { yuzu = {
package = pkgs.eden; package = pkgs.eden;
romFolder = "switch"; romFolder = "switch";
fileTypes = [ fileTypes = [
".kip"
".KIP"
".nca"
".NCA"
".nro"
".NRO"
".nso"
".NSO"
".nsp" ".nsp"
".NSP" ".NSP"
".xci" ".xci"
".XCI" ".XCI"
]; ];
executableArgs = "-f -g \"\${filePath}\"";
steamCategory = "Switch";
}; };
pcsx2 = { # ---------------------------------------------------------------------------
package = pkgs.pcsx2; # Nintendo NES
romFolder = "ps2"; # ---------------------------------------------------------------------------
mesen = {
package = pkgs.mesen;
romFolder = "nes";
fileTypes = [ fileTypes = [
".iso" ".7z"
".ISO" ".7Z"
".bin" ".fds"
".BIN" ".FDS"
".chd" ".nes"
".CHD" ".NES"
".unif"
".UNIF"
".unf"
".UNF"
".zip"
".ZIP"
]; ];
executableArgs = "\"\${filePath}\"";
steamCategory = "NES";
}; };
rpcs3 = { # ---------------------------------------------------------------------------
package = pkgs.rpcs3; # Nintendo SNES
romFolder = "ps3"; # ---------------------------------------------------------------------------
snes9x = {
package = pkgs.snes9x-gtk;
romFolder = "snes";
fileTypes = [ fileTypes = [
".iso" ".7z"
".ISO" ".7Z"
".bin" ".bs"
".BIN" ".BS"
".pkg" ".sfc"
".PKG" ".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 = { dolphin-emu = {
# Use pkgs.dolphin-emu (the user-facing wrapper), not pkgs.dolphinEmu
package = pkgs.dolphin-emu; package = pkgs.dolphin-emu;
romFolder = "gc"; romFolder = "gc";
fileTypes = [ fileTypes = [
".iso"
".ISO"
".gcm"
".GCM"
".ciso" ".ciso"
".CISO" ".CISO"
".rvz" ".dol"
".RVZ" ".DOL"
".wbfs" ".elf"
".WBFS" ".ELF"
]; ".gcm"
}; ".GCM"
".gcz"
duckstation = { ".GCZ"
package = pkgs.duckstation;
romFolder = "psx";
fileTypes = [
".iso" ".iso"
".ISO" ".ISO"
".bin" ".rvz"
".BIN" ".RVZ"
".chd" ".wad"
".CHD" ".WAD"
".pbp" ".wbfs"
".PBP" ".WBFS"
]; ".wia"
}; ".WIA"
melonDS = {
package = pkgs.melonDS;
romFolder = "nds";
fileTypes = [
".nds"
".NDS"
]; ];
executableArgs = "-b -e \"\${filePath}\"";
steamCategory = "GameCube";
}; };
# ---------------------------------------------------------------------------
# Nintendo Wii U
# ---------------------------------------------------------------------------
cemu = { cemu = {
package = pkgs.cemu; package = pkgs.cemu;
romFolder = "wiiu"; romFolder = "wiiu";
@@ -121,32 +213,283 @@ pkgs: {
".rpx" ".rpx"
".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 = { ppsspp = {
package = pkgs.ppsspp; package = pkgs.ppsspp;
romFolder = "psp"; romFolder = "psp";
fileTypes = [ fileTypes = [
".iso" ".elf"
".ISO" ".ELF"
".cso" ".cso"
".CSO" ".CSO"
".iso"
".ISO"
".pbp" ".pbp"
".PBP" ".PBP"
".prx"
".PRX"
]; ];
executableArgs = "\"\${filePath}\"";
steamCategory = "PSP";
}; };
mame = { # ---------------------------------------------------------------------------
package = pkgs.mame; # Sega Genesis / Mega Drive
romFolder = "arcade"; # ---------------------------------------------------------------------------
blastem = {
package = pkgs.blastem;
romFolder = "genesis";
fileTypes = [ fileTypes = [
".zip"
".ZIP"
".7z" ".7z"
".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 = { dosbox = {
package = pkgs.dosbox; package = pkgs.dosbox;
romFolder = "dos"; romFolder = "dos";
@@ -157,93 +500,256 @@ pkgs: {
".BAT" ".BAT"
".com" ".com"
".COM" ".COM"
".conf"
".CONF"
]; ];
executableArgs = "\"\${filePath}\"";
steamCategory = "DOS";
}; };
snes9x = { dosbox-staging = {
package = pkgs.snes9x-gtk; package = pkgs.dosbox-staging;
romFolder = "snes"; romFolder = "dos";
fileTypes = [ fileTypes = [
".smc" ".exe"
".SMC" ".EXE"
".sfc" ".bat"
".SFC" ".BAT"
".fig" ".com"
".FIG" ".COM"
".conf"
".CONF"
]; ];
executableArgs = "\"\${filePath}\"";
steamCategory = "DOS";
}; };
mgba = { dosbox-x = {
package = pkgs.mgba; package = pkgs.dosbox-x;
romFolder = "gba"; romFolder = "dos";
fileTypes = [ fileTypes = [
".gba" ".exe"
".GBA" ".EXE"
".bat"
".BAT"
".com"
".COM"
".conf"
".CONF"
]; ];
executableArgs = "\"\${filePath}\"";
steamCategory = "DOS";
}; };
mupen64plus = { # ---------------------------------------------------------------------------
package = pkgs.mupen64plus; # Arcade (MAME)
romFolder = "n64"; # ---------------------------------------------------------------------------
mame = {
package = pkgs.mame;
romFolder = "arcade";
fileTypes = [ 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"
".N64" ".N64"
".v64" ".v64"
".V64" ".V64"
".z64" ".z64"
".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; # ScummVM
romFolder = "retroarch"; # ---------------------------------------------------------------------------
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 = [ fileTypes = [
".zip"
".ZIP"
".7z" ".7z"
".7Z" ".7Z"
".iso" ".a26"
".ISO" ".A26"
".bin" ".bin"
".BIN" ".BIN"
".chd" ".zip"
".CHD" ".ZIP"
]; ];
executableArgs = "\"\${filePath}\"";
steamCategory = "Atari 2600";
}; };
flycast = { # ---------------------------------------------------------------------------
package = pkgs.flycast; # Commodore Amiga
romFolder = "dreamcast"; # ---------------------------------------------------------------------------
fs-uae = {
package = pkgs.fs-uae;
romFolder = "amiga";
fileTypes = [ fileTypes = [
".gdi" ".7z"
".GDI" ".7Z"
".cdi" ".adf"
".CDI" ".ADF"
".chd" ".adz"
".CHD" ".ADZ"
".dms"
".DMS"
".lha"
".LHA"
".zip"
".ZIP"
]; ];
executableArgs = "\"\${filePath}\"";
steamCategory = "Amiga";
}; };
# citra-nightly was removed from nixpkgs; azahar is the maintained successor # ---------------------------------------------------------------------------
citra = { # Platform parsers (no ROM file scanning; no package required at runtime)
package = pkgs.azahar; # parserType must match exactly what SRM expects.
romFolder = "3ds"; # ---------------------------------------------------------------------------
fileTypes = [
".3ds"
".3DS"
".cia"
".CIA"
".cxi"
".CXI"
];
};
"Non-SRM Shortcuts" = { "Non-SRM Shortcuts" = {
package = pkgs.steam;
parserType = "Non-SRM Shortcuts"; parserType = "Non-SRM Shortcuts";
romFolder = ""; romFolder = "";
fileTypes = [ ]; 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 = "";
};
}; };
} }

View File

@@ -16,6 +16,25 @@ let
"sgdb" "sgdb"
"steamCDN" "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 in
{ {
options.programs.steam-rom-manager = { options.programs.steam-rom-manager = {
@@ -251,29 +270,49 @@ in
example = "john"; example = "john";
description = '' description = ''
Steam account username used to build the SRM environment-variable Steam account username used to build the SRM environment-variable
reference <literal>''${<username>}</literal> in userSettings.json. reference ''${<username>} in userSettings.json.
This must match the account name shown in Steam (not the display name). This must match the account name shown in Steam (not the display name).
''; '';
}; };
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
# Emulator parser configurations # Emulator / parser configurations
# ------------------------------------------------------------------------- # -------------------------------------------------------------------------
emulators = lib.mkOption { emulators = lib.mkOption {
type = lib.types.attrsOf ( type = lib.types.attrsOf (
lib.types.submodule ( lib.types.submodule (
{ name, ... }: { name, config, ... }:
{ {
options = { 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 { package = lib.mkOption {
type = lib.types.package; type = lib.types.nullOr lib.types.package;
# Resolve against knownEmulators first; fall back to pkgs.<name> so # Resolve against knownEmulators, then try pkgs.<name>, then null
# users can supply any arbitrary emulator key without a code change. # for pure platform parsers that have no associated binary.
default = if knownEmulators ? ${name} then knownEmulators.${name}.package else pkgs.${name}; default =
description = "Package providing the emulator binary."; 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 { parserType = lib.mkOption {
@@ -283,7 +322,12 @@ in
knownEmulators.${name}.parserType knownEmulators.${name}.parserType
else else
"Glob"; "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 { configTitle = lib.mkOption {
@@ -296,21 +340,30 @@ in
type = lib.types.str; type = lib.types.str;
default = ""; default = "";
description = '' description = ''
Sub-folder under <option>environmentVariables.romsDirectory</option> Sub-folder under environmentVariables.romsDirectory containing
that contains ROMs for this emulator. Defaults to the built-in ROMs for this emulator. Defaults to the built-in value for known
value for known emulators; must be set explicitly for custom ones. emulators; must be set explicitly for custom entries.
Leave empty for platform/artwork parsers.
''; '';
}; };
steamCategories = lib.mkOption { steamCategories = lib.mkOption {
type = lib.types.listOf lib.types.str; 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."; description = "Steam categories / collection tags to apply to shortcuts.";
}; };
extraArgs = lib.mkOption { extraArgs = lib.mkOption {
type = lib.types.str; 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."; description = "Command-line arguments passed to the emulator.";
}; };
@@ -360,8 +413,9 @@ in
type = lib.types.listOf lib.types.str; type = lib.types.listOf lib.types.str;
default = [ ]; default = [ ];
description = '' description = ''
File extensions matched by the glob parser (e.g. <literal>".iso"</literal>). 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. Defaults to the built-in list for known emulators.
Leave empty for platform/artwork parsers.
''; '';
}; };
@@ -374,14 +428,33 @@ in
appendArgsToExecutable = lib.mkOption { appendArgsToExecutable = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = true; default = true;
description = "Append <option>extraArgs</option> 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 = { }; default = { };
description = "Attribute set of emulator parser configurations keyed by emulator name."; description = "Attribute set of emulator/parser configurations keyed by name.";
}; };
}; };
} }