commit 3467cccc24c4b92057bb0a53098a17fba21f4b3e Author: mjallen18 Date: Thu Feb 20 10:12:37 2025 -0600 init diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..e0e9a0c --- /dev/null +++ b/flake.lock @@ -0,0 +1,48 @@ +{ + "nodes": { + "home-manager": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1740060750, + "narHash": "sha256-FOC9OzJ5Ckh6VjzGSRh4F3UCUOdM8NrzQT19PQcQJ44=", + "owner": "nix-community", + "repo": "home-manager", + "rev": "0c0b0ac8af6ca76b1fcb514483a9bd73c18f1e8c", + "type": "github" + }, + "original": { + "owner": "nix-community", + "repo": "home-manager", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1739866667, + "narHash": "sha256-EO1ygNKZlsAC9avfcwHkKGMsmipUk1Uc0TbrEZpkn64=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "73cf49b8ad837ade2de76f87eb53fc85ed5d4680", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "home-manager": "home-manager", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..fc59cc6 --- /dev/null +++ b/flake.nix @@ -0,0 +1,21 @@ +{ + description = "Steam ROM Manager Home Manager Module"; + + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + home-manager = { + url = "github:nix-community/home-manager"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, home-manager, ... }: + let + supportedSystems = [ "x86_64-linux" "aarch64-linux" ]; + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; + in + { + homeManagerModules.default = import ./modules; + homeManagerModules.steam-rom-manager = import ./modules/steam-rom-manager; + }; +} \ No newline at end of file diff --git a/modules/default.nix b/modules/default.nix new file mode 100644 index 0000000..81f32ff --- /dev/null +++ b/modules/default.nix @@ -0,0 +1,7 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./steam-rom-manager + ]; +} \ No newline at end of file diff --git a/modules/options.nix b/modules/options.nix new file mode 100644 index 0000000..73992d7 --- /dev/null +++ b/modules/options.nix @@ -0,0 +1,276 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.steam-rom-manager; +in { + options.programs.steam-rom-manager = { + enable = mkEnableOption "Steam ROM Manager"; + + package = mkOption { + type = types.package; + default = steam-rom-manager; + description = "Steam ROM Manager package"; + }; + + fuzzyMatcher = { + timestamps = { + check = mkOption { + type = types.int; + default = 0; + description = "Timestamp for fuzzy matcher check"; + }; + download = mkOption { + type = types.int; + default = 0; + description = "Timestamp for fuzzy matcher download"; + }; + }; + verbose = mkOption { + type = types.bool; + default = false; + description = "Enable verbose logging for fuzzy matcher"; + }; + filterProviders = mkOption { + type = types.bool; + default = true; + description = "Filter image providers"; + }; + }; + + environmentVariables = { + steamDirectory = mkOption { + type = types.str; + default = "${config.home.homeDirectory}/.local/share/Steam"; + description = "Steam installation directory"; + }; + + romsDirectory = mkOption { + type = types.str; + default = "${config.home.homeDirectory}/Emulation/roms"; + description = "Base directory for ROM files"; + }; + + retroarchPath = mkOption { + type = types.str; + default = ""; + description = "Path to RetroArch executable"; + }; + + raCoresDirectory = mkOption { + type = types.str; + default = ""; + description = "RetroArch cores directory"; + }; + + localImagesDirectory = mkOption { + type = types.str; + default = ""; + description = "Directory for local images"; + }; + }; + + previewSettings = { + retrieveCurrentSteamImages = mkOption { + type = types.bool; + default = true; + description = "Retrieve current Steam images"; + }; + + disableCategories = mkOption { + type = types.bool; + default = false; + description = "Disable Steam categories"; + }; + + deleteDisabledShortcuts = mkOption { + type = types.bool; + default = false; + description = "Delete disabled shortcuts"; + }; + + imageZoomPercentage = mkOption { + type = types.int; + default = 30; + description = "Image zoom percentage in preview"; + }; + + preload = mkOption { + type = types.bool; + default = false; + description = "Preload images"; + }; + + hideUserAccount = mkOption { + type = types.bool; + default = false; + description = "Hide user account in preview"; + }; + }; + + enabledProviders = mkOption { + type = types.listOf types.str; + default = [ "sgdb" "steamCDN" ]; + description = "Enabled image providers"; + }; + + imageProviderSettings = { + sgdb = { + nsfw = mkOption { + type = types.bool; + default = false; + description = "Allow NSFW content from SteamGridDB"; + }; + + humor = mkOption { + type = types.bool; + default = false; + description = "Allow humor content from SteamGridDB"; + }; + + styles = mkOption { + type = types.listOf types.str; + default = []; + description = "Preferred art styles for SteamGridDB"; + }; + + stylesHero = mkOption { + type = types.listOf types.str; + default = []; + description = "Preferred hero art styles for SteamGridDB"; + }; + + stylesLogo = mkOption { + type = types.listOf types.str; + default = []; + description = "Preferred logo styles for SteamGridDB"; + }; + + stylesIcon = mkOption { + type = types.listOf types.str; + default = []; + description = "Preferred icon styles for SteamGridDB"; + }; + + imageMotionTypes = mkOption { + type = types.listOf types.str; + default = [ "static" ]; + description = "Allowed image motion types"; + }; + + sizes = mkOption { + type = types.listOf types.str; + default = []; + description = "Preferred image sizes"; + }; + + sizesHero = mkOption { + type = types.listOf types.str; + default = []; + description = "Preferred hero image sizes"; + }; + + sizesIcon = mkOption { + type = types.listOf types.str; + default = []; + description = "Preferred icon sizes"; + }; + }; + }; + + batchDownloadSize = mkOption { + type = types.int; + default = 50; + description = "Number of images to download in a batch"; + }; + + dnsServers = mkOption { + type = types.listOf types.str; + default = []; + description = "Custom DNS servers for image downloads"; + }; + + language = mkOption { + type = types.str; + default = "en-US"; + description = "Application language"; + }; + + theme = mkOption { + type = types.str; + default = "Deck"; + description = "Application theme"; + }; + + emudeckInstall = mkOption { + type = types.bool; + default = false; + description = "Is this an EmuDeck installation"; + }; + + autoUpdate = mkOption { + type = types.bool; + default = false; + description = "Enable automatic updates"; + }; + + offlineMode = mkOption { + type = types.bool; + default = false; + description = "Run in offline mode"; + }; + + navigationWidth = mkOption { + type = types.int; + default = 0; + description = "Navigation panel width"; + }; + + clearLogOnTest = mkOption { + type = types.bool; + default = true; + description = "Clear log when testing configuration"; + }; + + steamUsername = mkOption { + type = types.str; + description = "Steam username for configuration"; + }; + + emulators = mkOption { + type = types.attrsOf (types.submodule { + options = { + enable = mkEnableOption "emulator configuration"; + package = mkOption { + type = types.package; + default = null; + description = "Emulator package (defaults to common configuration if not set)"; + }; + binaryName = mkOption { + type = types.str; + default = ""; + description = "Name of the emulator binary (defaults to automatically detected from package)"; + }; + romFolder = mkOption { + type = types.str; + default = ""; + description = "Name of the ROM folder (defaults to common configuration)"; + }; + fileTypes = mkOption { + type = types.listOf types.str; + default = []; + description = "List of ROM file types (defaults to common configuration)"; + }; + extraArgs = mkOption { + type = types.str; + default = "--fullscreen \"\${filePath}\""; + description = "Additional emulator arguments"; + }; + }; + }); + default = {}; + description = "Emulator configurations"; + }; + }; +} \ No newline at end of file diff --git a/modules/steam-rom-manager/config.nix b/modules/steam-rom-manager/config.nix new file mode 100644 index 0000000..3f9a037 --- /dev/null +++ b/modules/steam-rom-manager/config.nix @@ -0,0 +1,344 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.programs.steam-rom-manager; + + # Function to find the main binary in a package + findMainBinary = pkg: name: + let + pkgName = pkg.pname or (builtins.parseDrvName pkg.name).name; + binPath = "${pkg}/bin/${pkgName}"; + + commonVariants = [ + pkgName + "${pkgName}-qt" + "${pkgName}-gtk" + "${pkgName}-emu" + ]; + + existingVariant = findFirst + (variant: builtins.pathExists "${pkg}/bin/${variant}") + null + commonVariants; + in + if name != "" + then name + else if existingVariant != null + then existingVariant + else pkgName; + + # Common emulator configurations with default packages + commonEmulatorConfigs = { + ryujinx = { + romFolder = "switch"; + fileTypes = [ ".nca" ".NCA" ".nro" ".NRO" ".nso" ".NSO" ".nsp" ".NSP" ".xci" ".XCI" ]; + package = pkgs.ryujinx; + }; + yuzu = { + romFolder = "switch"; + fileTypes = [ ".nsp" ".NSP" ".xci" ".XCI" ]; + package = pkgs.yuzu; + }; + pcsx2 = { + romFolder = "ps2"; + fileTypes = [ ".iso" ".ISO" ".bin" ".BIN" ".chd" ".CHD" ]; + package = pkgs.pcsx2; + }; + rpcs3 = { + romFolder = "ps3"; + fileTypes = [ ".iso" ".ISO" ".bin" ".BIN" ".pkg" ".PKG" ]; + package = pkgs.rpcs3; + }; + dolphin-emu = { + romFolder = "gc"; + fileTypes = [ ".iso" ".ISO" ".gcm" ".GCM" ".ciso" ".CISO" ]; + package = pkgs.dolphinEmu; + }; + duckstation = { + romFolder = "psx"; + fileTypes = [ ".iso" ".ISO" ".bin" ".BIN" ".chd" ".CHD" ".pbp" ".PBP" ]; + package = pkgs.duckstation; + }; + melonDS = { + romFolder = "nds"; + fileTypes = [ ".nds" ".NDS" ]; + package = pkgs.melonDS; + }; + cemu = { + romFolder = "wiiu"; + fileTypes = [ ".wud" ".WUD" ".wux" ".WUX" ".rpx" ".RPX" ]; + package = pkgs.cemu; + }; + ppsspp = { + romFolder = "psp"; + fileTypes = [ ".iso" ".ISO" ".cso" ".CSO" ".pbp" ".PBP" ]; + package = pkgs.ppsspp; + }; + mame = { + romFolder = "arcade"; + fileTypes = [ ".zip" ".ZIP" ".7z" ".7Z" ]; + package = pkgs.mame; + }; + dosbox = { + romFolder = "dos"; + fileTypes = [ ".exe" ".EXE" ".bat" ".BAT" ".com" ".COM" ]; + package = pkgs.dosbox; + }; + snes9x = { + romFolder = "snes"; + fileTypes = [ ".smc" ".SMC" ".sfc" ".SFC" ".fig" ".FIG" ]; + package = pkgs.snes9x-gtk; + }; + mgba = { + romFolder = "gba"; + fileTypes = [ ".gba" ".GBA" ]; + package = pkgs.mgba; + }; + mupen64plus = { + romFolder = "n64"; + fileTypes = [ ".n64" ".N64" ".v64" ".V64" ".z64" ".Z64" ]; + package = pkgs.mupen64plus; + }; + retroarch = { + romFolder = "retroarch"; + fileTypes = [ ".zip" ".ZIP" ".7z" ".7Z" ".iso" ".ISO" ".bin" ".BIN" ".chd" ".CHD" ]; + package = pkgs.retroarch; + }; + flycast = { + romFolder = "dreamcast"; + fileTypes = [ ".gdi" ".GDI" ".cdi" ".CDI" ".chd" ".CHD" ]; + package = pkgs.flycast; + }; + citra = { + romFolder = "3ds"; + fileTypes = [ ".3ds" ".3DS" ".cia" ".CIA" ".cxi" ".CXI" ]; + package = pkgs.citra-nightly; + }; + }; + + # Create parser configuration + mkParserConfig = name: emu: + let + # Use the provided package or fall back to the default + package = if emu ? package then emu.package else commonEmulatorConfigs.${name}.package; + # Get the binary name dynamically + binaryName = findMainBinary package emu.binaryName; + + orderedConfig = [ + # Basic parser configuration + { name = "parserType"; value = "Glob"; } + { name = "configTitle"; value = name; } + { name = "steamDirectory"; value = "\${steamdirglobal}"; } + { name = "romDirectory"; value = "${cfg.environmentVariables.romsDirectory}/${if emu.romFolder != "" then emu.romFolder else commonEmulatorConfigs.${name}.romFolder}"; } + { name = "steamCategories"; value = [""]; } + + # Executable configuration + { name = "executableArgs"; value = emu.extraArgs; } + { name = "executableModifier"; value = "\"\${exePath}\""; } + { name = "startInDirectory"; value = "${cfg.environmentVariables.romsDirectory}/${if emu.romFolder != "" then emu.romFolder else commonEmulatorConfigs.${name}.romFolder}"; } + { name = "titleModifier"; value = "\${fuzzyTitle}"; } + + # Controller settings + { name = "fetchControllerTemplatesButton"; value = null; } + { name = "removeControllersButton"; value = null; } + { name = "steamInputEnabled"; value = "1"; } + + # Image provider configuration + { name = "imageProviders"; value = cfg.enabledProviders; } + { name = "onlineImageQueries"; value = [ "\${fuzzyTitle}" ]; } + { name = "imagePool"; value = "\${fuzzyTitle}"; } + + # DRM and user account settings + { name = "drmProtect"; value = false; } + { name = "userAccounts"; value = { + specifiedAccounts = [ "Global" ]; + }; } + + # Parser-specific settings + { name = "parserInputs"; value = { + glob = "\${title}@(${concatStringsSep "|" (if emu.fileTypes != [] then emu.fileTypes else commonEmulatorConfigs.${name}.fileTypes)})"; + }; } + + # Executable details + { name = "executable"; value = { + path = "${package}/bin/${binaryName}"; + shortcutPassthrough = false; + appendArgsToExecutable = true; + }; } + + # Title and fuzzy matching configuration + { name = "titleFromVariable"; value = { + limitToGroups = []; + caseInsensitiveVariables = false; + skipFileIfVariableWasNotFound = false; + }; } + + { name = "fuzzyMatch"; value = { + replaceDiacritics = true; + removeCharacters = true; + removeBrackets = true; + }; } + + # Controller configuration + { 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; + }; } + + # Image provider API configuration + { name = "imageProviderAPIs"; value = { + sgdb = cfg.imageProviderSettings.sgdb; + }; } + + # Default and local image settings + { name = "defaultImage"; value = { + tall = ""; + long = ""; + hero = ""; + logo = ""; + icon = ""; + }; } + + { name = "localImages"; value = { + tall = ""; + long = ""; + hero = ""; + logo = ""; + icon = ""; + }; } + + # Parser identification + { name = "parserId"; value = name; } + { name = "version"; value = 25; } + ]; + + # Function to convert our ordered list into properly formatted JSON + makeOrderedJSON = pairs: + let + joined = builtins.concatStringsSep "," + (map (pair: "\"${pair.name}\":${builtins.toJSON pair.value}") pairs); + in + "{${joined}}"; + in + makeOrderedJSON orderedConfig; + + # Fetch the SVG icon file + steam-rom-manager-icon = pkgs.fetchurl { + name = "steam-rom-manager.svg"; + url = "https://raw.githubusercontent.com/SteamGridDB/steam-rom-manager/master/src/assets/icons/steam-rom-manager.svg"; + hash = "sha256-DKzNIs5UhIWAVRTfinvCb8WqeDniPWw9Z08/p/Zpa9E="; + }; + + # Create Steam ROM Manager package + steam-rom-manager = pkgs.stdenv.mkDerivation { + name = "steam-rom-manager-2.5.29"; + src = pkgs.fetchurl { + name = "steam-rom-manager-2.5.29.AppImage"; + url = "https://github.com/SteamGridDB/steam-rom-manager/releases/download/v2.5.29/Steam-ROM-Manager-2.5.29.AppImage"; + hash = "sha256-6ZJ+MGIgr2osuQuqD6N9NnPiJFNq/HW6ivG8tyXUhvs="; + }; + + nativeBuildInputs = [ pkgs.makeWrapper ]; + + dontUnpack = true; + dontBuild = true; + dontPatch = true; + dontConfigure = true; + + installPhase = '' + mkdir -p $out/{bin,share/icons/hicolor/scalable/apps,share/applications} + cp $src $out/bin/steam-rom-manager.AppImage + chmod +x $out/bin/steam-rom-manager.AppImage + cp ${steam-rom-manager-icon} $out/share/icons/hicolor/scalable/apps/steam-rom-manager.svg + ''; + }; + +in { + imports = [ + ../options.nix + ]; + + config = mkIf cfg.enable { + home.packages = [ pkgs.appimage-run ] + ++ mapAttrsToList (_: v: v.package) (filterAttrs (_: v: v.enable) cfg.emulators); + + 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 = "${pkgs.appimage-run}/bin/appimage-run ${steam-rom-manager}/bin/steam-rom-manager.AppImage"; + 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 = { + "steam-rom-manager/userData/userSettings.json".text = builtins.toJSON { + fuzzyMatcher = { + timestamps = { + check = cfg.fuzzyMatcher.timestamps.check; + download = cfg.fuzzyMatcher.timestamps.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 = mapAttrsToList (name: emu: + mkParserConfig name (emu // { + romFolder = if emu.romFolder != "" then emu.romFolder else commonEmulatorConfigs.${name}.romFolder; + binaryName = if emu.binaryName != "" then emu.binaryName else commonEmulatorConfigs.${name}.binaryName; + }) + ) (filterAttrs (_: v: v.enable) cfg.emulators); + + configsJson = "[${concatStringsSep "," configs}]"; + in + configsJson; + }; + }; +} \ No newline at end of file diff --git a/modules/steam-rom-manager/default.nix b/modules/steam-rom-manager/default.nix new file mode 100644 index 0000000..eea5ae9 --- /dev/null +++ b/modules/steam-rom-manager/default.nix @@ -0,0 +1,7 @@ +{ config, lib, pkgs, ... }: + +{ + imports = [ + ./config.nix + ]; +} \ No newline at end of file