Files
nix-steam-rom-manager/modules/steam-rom-manager/default.nix
mjallen18 3c54fd5412 vibes bb
2026-03-15 19:40:42 -05:00

368 lines
11 KiB
Nix

{
config,
lib,
pkgs,
...
}:
let
version = "2.5.34";
cfg = config.programs.steam-rom-manager;
# Single source of truth for built-in emulator metadata (package, romFolder,
# 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
# order, so we construct the JSON string manually from an ordered list.
#
# Schema version 25 corresponds to SRM ≥ 2.5.x. If you upgrade SRM and the
# config format changes, bump `version` here and audit the field list.
# ---------------------------------------------------------------------------
mkParserConfig =
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 && 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 && known ? fileTypes then
known.fileTypes
else
[ ];
# lib.getExe resolves the primary binary without IFD path probing.
# 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 = [
{
name = "parserType";
value = parserType;
}
{
name = "configTitle";
value = emu.configTitle;
}
{
name = "steamDirectory";
value = "\${steamdirglobal}";
}
{
name = "romDirectory";
value = if platform then "" else "${cfg.environmentVariables.romsDirectory}/${romFolder}";
}
{
name = "steamCategories";
value = emu.steamCategories;
}
{
name = "executableArgs";
value = if platform then "" else emu.extraArgs;
}
{
name = "executableModifier";
value = emu.executableModifier;
}
{
name = "startInDirectory";
value = if platform then "" else "${cfg.environmentVariables.romsDirectory}/${romFolder}";
}
{
name = "titleModifier";
value = emu.titleModifier;
}
# fetchControllerTemplatesButton / removeControllersButton are UI-only
# action triggers in SRM; they carry no config value and must be null
# for schema compatibility.
{
name = "fetchControllerTemplatesButton";
value = null;
}
{
name = "removeControllersButton";
value = null;
}
{
name = "steamInputEnabled";
value = if emu.steamInputEnabled then "1" else "0";
}
{
name = "imageProviders";
value = cfg.enabledProviders;
}
{
name = "onlineImageQueries";
value = emu.onlineImageQueries;
}
{
name = "imagePool";
value = emu.imagePool;
}
{
name = "drmProtect";
value = emu.drmProtected;
}
{
name = "userAccounts";
value = {
specifiedAccounts = emu.userAccounts;
};
}
{
name = "parserInputs";
value = resolvedParserInputs;
}
{
name = "executable";
value = {
path = exePath;
shortcutPassthrough = emu.shortcutPassthrough;
appendArgsToExecutable = emu.appendArgsToExecutable;
};
}
{
name = "titleFromVariable";
value = {
limitToGroups = [ ];
caseInsensitiveVariables = false;
skipFileIfVariableWasNotFound = false;
};
}
{
name = "fuzzyMatch";
value = {
replaceDiacritics = true;
removeCharacters = true;
removeBrackets = true;
};
}
{
name = "controllers";
value = {
ps4 = null;
ps5 = null;
ps5_edge = null;
xbox360 = null;
xboxone = null;
xboxelite = null;
switch_joycon_left = null;
switch_joycon_right = null;
switch_pro = null;
neptune = null;
steamcontroller_gordon = null;
};
}
{
name = "imageProviderAPIs";
value = {
sgdb = cfg.imageProviderSettings.sgdb;
};
}
{
name = "defaultImage";
value = {
tall = "";
long = "";
hero = "";
logo = "";
icon = "";
};
}
{
name = "localImages";
value = {
tall = "";
long = "";
hero = "";
logo = "";
icon = "";
};
}
{
name = "disabled";
value = emu.disabled;
}
{
name = "parserId";
value = name;
}
# SRM userConfigurations schema version — bump when upgrading past a
# breaking config format change in SRM itself.
{
name = "version";
value = 25;
}
];
makeOrderedJSON =
pairs:
let
joined = builtins.concatStringsSep "," (
map (pair: "\"${pair.name}\":${builtins.toJSON pair.value}") pairs
);
in
"{${joined}}";
in
makeOrderedJSON orderedConfig;
# ---------------------------------------------------------------------------
# Icon — pinned to the commit that introduced the current SVG so the hash
# does not break when unrelated files change on master.
# Commit: 1670cbe8871a632708e0440ccef52c5c5c403ddc (2024-01-12)
# ---------------------------------------------------------------------------
steam-rom-manager-icon = pkgs.fetchurl {
name = "steam-rom-manager.svg";
url = "https://raw.githubusercontent.com/SteamGridDB/steam-rom-manager/1670cbe8871a632708e0440ccef52c5c5c403ddc/src/assets/icons/steam-rom-manager.svg";
hash = "sha256-DKzNIs5UhIWAVRTfinvCb8WqeDniPWw9Z08/p/Zpa9E=";
};
# ---------------------------------------------------------------------------
# Wrap the upstream AppImage with appimage-run.
# ---------------------------------------------------------------------------
steam-rom-manager-appimage = pkgs.writeShellScriptBin "steam-rom-manager" ''
exec ${pkgs.appimage-run}/bin/appimage-run ${
pkgs.fetchurl {
name = "steam-rom-manager-${version}.AppImage";
url = "https://github.com/SteamGridDB/steam-rom-manager/releases/download/v${version}/Steam-ROM-Manager-${version}.AppImage";
hash = "sha256-QbXwfT91BQ15/DL3IYC3qZcahlsQvkUKTwMUUpZY+U8=";
}
} "$@"
'';
# 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 ];
config = lib.mkIf cfg.enable {
home.packages = [
pkgs.appimage-run
steam-rom-manager-appimage
]
++ lib.mapAttrsToList (_: v: v.package) enabledEmulatorsWithPackage;
xdg.dataFile."icons/hicolor/scalable/apps/steam-rom-manager.svg".source = steam-rom-manager-icon;
xdg.desktopEntries.steam-rom-manager = {
name = "Steam ROM Manager";
exec = "${steam-rom-manager-appimage}/bin/steam-rom-manager";
icon = "steam-rom-manager";
categories = [
"Game"
"Utility"
];
type = "Application";
terminal = false;
comment = "Add ROMs to Steam with artwork";
settings = {
"X-KDE-StartupNotify" = "true";
"X-KDE-SubstituteUID" = "false";
"X-DBUS-StartupType" = "Unique";
};
};
xdg.configFile = {
# userSettings schema version 8 corresponds to SRM ≥ 2.4.x.
"steam-rom-manager/userData/userSettings.json".text = builtins.toJSON {
fuzzyMatcher = {
timestamps = { inherit (cfg.fuzzyMatcher.timestamps) check download; };
verbose = cfg.fuzzyMatcher.verbose;
filterProviders = cfg.fuzzyMatcher.filterProviders;
};
environmentVariables = {
steamDirectory = cfg.environmentVariables.steamDirectory;
userAccounts = "\${${cfg.steamUsername}}";
romsDirectory = cfg.environmentVariables.romsDirectory;
retroarchPath = cfg.environmentVariables.retroarchPath;
raCoresDirectory = cfg.environmentVariables.raCoresDirectory;
localImagesDirectory = cfg.environmentVariables.localImagesDirectory;
};
previewSettings = cfg.previewSettings;
enabledProviders = cfg.enabledProviders;
imageProviderAPIs = {
sgdb = cfg.imageProviderSettings.sgdb;
};
batchDownloadSize = cfg.batchDownloadSize;
dnsServers = cfg.dnsServers;
language = cfg.language;
theme = cfg.theme;
emudeckInstall = cfg.emudeckInstall;
autoUpdate = cfg.autoUpdate;
offlineMode = cfg.offlineMode;
navigationWidth = cfg.navigationWidth;
clearLogOnTest = cfg.clearLogOnTest;
version = 8;
};
"steam-rom-manager/userData/userConfigurations.json".text =
let
configs = lib.mapAttrsToList (name: emu: mkParserConfig name emu) (
lib.filterAttrs (_: v: v.enable) cfg.emulators
);
in
"[${lib.concatStringsSep "," configs}]";
};
};
}