diff --git a/modules/nixos/mewtris.nix b/modules/nixos/mewtris.nix new file mode 100644 index 00000000..161dc9d8 --- /dev/null +++ b/modules/nixos/mewtris.nix @@ -0,0 +1,214 @@ +{ + config, + pkgs, + lib, + std, + ... +}: { + options.mewtris = let + inherit (lib.types) path attrsOf submodule str nullOr enum either package bool lines listOf; + inherit (lib.options) mkEnableOption mkOption; + inherit (lib.meta) getExe'; + in { + enable = mkEnableOption "Enable mewtris"; + umuLauncher = mkOption { + description = "umu-launcher package"; + type = package; + default = pkgs.umu-launcher; + }; + gameStorage = mkOption { + description = "Where is the base directory for your game runner storage?"; + type = nullOr path; + default = null; + }; + runnerEnvironments = mkOption { + description = "Sets of environments to compose for your games"; + type = attrsOf (attrsOf str); + default = {}; + }; + runnerVariants = mkOption { + description = "Contains references to proton version bin/ folders"; + type = attrsOf (either path package); + default = {}; + }; + winTimezoneVariant = mkOption { + description = '' + Please refer to https://www.ibm.com/docs/en/idr/11.4.0?topic=zos-time-zone-codes-tz-environment-variable + For Proton, this allows you to fix weird timezone issues. + ''; + type = nullOr str; + default = null; + }; + globalPrerun = mkOption { + description = "Commands to run before every runner"; + default = '' + export WINE_CPU_TOPOLOGY=$(${getExe' pkgs.coreutils "nproc"} --all) + ''; + type = lines; + }; + games = let + nixosConfig = config; + gameSubmodule = { + name, + config, + ... + }: let + cfg = nixosConfig.mewtris; + in { + options = { + name = mkOption { + description = "Systemd service name for the game, a shorthand"; + type = str; + default = name; + }; + long_name = mkOption { + description = "Longhand name of the game"; + type = nullOr str; + }; + prefixFolder = mkOption { + description = "Where is the game's wine prefix?"; + type = either path str; + }; + gameFolder = mkOption { + description = "Where is the game's folder?"; + type = nullOr (either path str); + default = null; + }; + gameExecutable = mkOption { + description = "Where is the EXE to run?"; + type = either path str; + }; + gameArguments = mkOption { + description = "What arguments do we run the game with?"; + type = listOf str; + default = []; + }; + battleNetGame = mkOption { + description = "Is this a battle.net game? Not used for battle.net itself!"; + type = bool; + default = false; + }; + prefixArch = mkOption { + description = "Wine prefix architecture"; + type = enum [ + "win64" + "win32" + ]; + default = "win64"; + }; + runner = mkOption { + description = "What runner system to use?"; + type = enum [ + "proton" + "wine" + ]; + default = "proton"; + }; + prerun = mkOption { + description = "Commands to run before the game"; + default = ""; + type = lines; + }; + environments = mkOption { + description = "What environments to compose into the runner?"; + type = listOf str; + default = []; + }; + environment = mkOption { + description = "Any game specific environment variables?"; + type = attrsOf str; + default = {}; + }; + variant = mkOption { + description = "What variant of the runner?"; + type = nullOr str; + }; + startLine = mkOption { + description = "What do we run to start this game as a systemd service?"; + type = package; + internal = true; + }; + }; + config = let + inherit (lib.modules) mkIf mkMerge; + inherit (lib.strings) escapeShellArgs optionalString; + inherit (lib.attrsets) optionalAttrs; + in + mkMerge [ + { + environment = { + WINEPREFIX = config.prefixFolder; + WINEARCH = config.prefixArch; + }; + } + (mkIf (config.runner == "wine") { + startLine = pkgs.writeShellScript "${config.name}" '' + ${cfg.globalPrerun} + ${config.prerun} + ${optionalString (config.gameFolder != null) '' + "cd ${config.gameFolder}" + ''} + "${getExe' cfg.runnerVariants.${config.variant} "wine"}" "${config.gameExecutable}" ${escapeShellArgs config.gameArguments} + ''; + }) + (mkIf (config.runner == "proton") { + environment = + (optionalAttrs (cfg.winTimezoneVariant != null) { + TZ = cfg.winTimezoneVariant; + }) + // { + PROTONPATH = cfg.runnerVariants.${config.variant}; + }; + startLine = let + protonLauncher = getExe' cfg.umuLauncher "umu-run"; + in + pkgs.writeShellScript "${config.name}" '' + ${cfg.globalPrerun} + ${config.prerun} + ${optionalString config.battleNetGame '' + "${protonLauncher}" "${config.gameFolder}/Battle.net Launcher.exe" & + ''} + cd "${config.gameFolder}" + "${protonLauncher}" "${config.gameExecutable}" ${escapeShellArgs config.gameArguments} + ''; + }) + ]; + }; + in + mkOption { + type = attrsOf (submodule gameSubmodule); + default = {}; + }; + }; + config = let + cfg = config.mewtris; + inherit (lib.lists) singleton concatMap; + inherit (lib.strings) replaceStrings; + inherit (lib.attrsets) mapAttrs nameValuePair mapAttrs' attrNames attrValues; + inherit (lib.modules) mkIf; + inherit (std.set) merge; + in + mkIf cfg.enable { + systemd.user.services = mapAttrs' (_k: v: + nameValuePair v.name { + description = v.long_name; + serviceConfig = { + ExecStart = v.startLine; + Type = "simple"; + }; + environment = let + composedEnvironments = concatMap (e: singleton cfg.runnerEnvironments.${e}) v.environments; + combinedEnvironments = merge (composedEnvironments ++ (singleton v.environment)); + replacements = { + ${builtins.placeholder "prefix"} = v.prefixFolder; + ${builtins.placeholder "game"} = v.gameFolder; + ${builtins.placeholder "exe"} = v.gameExecutable; + }; + replacer = replaceStrings (attrNames replacements) (attrValues replacements); + replacePlaceholders = _k: replacer; + in + mapAttrs replacePlaceholders combinedEnvironments; + }) + config.mewtris.games; + }; +} diff --git a/nixos/profiles/gaming/wine.nix b/nixos/profiles/gaming/wine.nix index 8056490c..e0cd45c6 100644 --- a/nixos/profiles/gaming/wine.nix +++ b/nixos/profiles/gaming/wine.nix @@ -1,44 +1,232 @@ { + config, pkgs, inputs, lib, ... }: let - inherit (lib.meta) getExe; - inherit (lib.attrsets) mapAttrs; - environment = { - PROTON_CACHYOS = "${inputs.chaotic.packages.${pkgs.system}.proton-cachyos_x86_64_v3.out}/bin"; - PROTON_GE = "${inputs.chaotic.packages.${pkgs.system}.proton-ge-custom.out}/bin"; - }; - games = { - # - # Proton / contemporary video games - # - - gw = "Guild Wars"; - gw2 = "Guild Wars 2"; - battlenet = "Battle.net"; - sc = "Starcraft: Remastered"; - sc2 = "Starcraft 2"; - - wcr = "Warcraft: Remastered"; - wc2r = "Warcraft 2: Remastered"; - wc = "Warcraft Orcs & Humans"; - wc2 = "Warcraft 2: Battle.net Edition"; - # https://lutris.net/games/install/25450/view - # Dissection: - # * nvapi disables, - # * registry key for Win7 in version - wc3 = "Warcraft 3: Reforged"; - - # - # Visual Novels - # - - hanahira = "Hanahira"; - kanon = "Kanon"; - }; + inherit (lib.modules) mkMerge mkForce; + cfg = config.mewtris; in { + mewtris = let + gameStorage = "/home/kat/Games"; + in { + enable = true; + inherit gameStorage; + runnerVariants = { + PROTON_CACHYOS = "${inputs.chaotic.packages.${pkgs.system}.proton-cachyos_x86_64_v3.out}/bin"; + PROTON_GE = "${inputs.chaotic.packages.${pkgs.system}.proton-ge-custom.out}/bin"; + WINE_TKG = pkgs.wine-tkg; + }; + runnerEnvironments = { + common = { + PROTON_LOG = builtins.toString 1; + WINEDEBUG = "+warn"; + WINEUSERSANDBOX = builtins.toString 1; + }; + dxvk = { + DXVK_CONFIG_FILE = "${cfg.gameStorage}/dxvk/dxvk.conf"; + DXVK_USE_PIPECOMPILER = builtins.toString 1; + }; + vkbasalt = { + ENABLE_VKBASALT = builtins.toString 1; + VKBASALT_CONFIG_FILE = "${cfg.gameStorage}/vkbasalt/vkBasalt_FilmicMedium.cfg"; + VKBASALT_LOG_FILE = "${cfg.gameStorage}/vkbasalt/vkBasalt_FilmicMedium.log"; + }; + shaderCache = { + MESA_SHADER_CACHE_DIR = "${builtins.placeholder "game"}/shader-cache"; + __GL_SHADER_DISK_CACHE = builtins.toString 1; + __GL_SHADER_DISK_CACHE_PATH = builtins.placeholder "prefix"; + }; + mangohud = let + inherit (lib.strings) concatStringsSep; + in { + MANGOHUD = builtins.toString 1; + MANGOHUD_CONFIG = concatStringsSep "," [ + "no_display" + "vsync=1" + "gl_vsync=0" + "engine_version" + "ram" + "vram" + "gpu_name" + "cpu_stats" + "gpu_stats" + "frametime" + "time" + "wine" + "winesync" + "vkbasalt" + "position=bottom-right" + "font_size=36" + ]; + }; + proton = { + PROTON_USE_NTSYNC = builtins.toString 1; + }; + }; + winTimezoneVariant = "PST8PDT"; + games = let + protonCommon = { + runner = "proton"; + variant = "PROTON_GE"; + environments = [ + "common" + "proton" + "dxvk" + "mangohud" + "shaderCache" + ]; + }; + wineCommon = { + runner = "wine"; + variant = "WINE_TKG"; + environments = [ + "common" + "dxvk" + "shaderCache" + ]; + }; + battlenet = { + long_name, + launchArg, + }: (protonCommon + // rec { + inherit long_name; + battleNetGame = true; + prefixFolder = gameStorage + "/battlenet"; + gameFolder = prefixFolder + "/drive_c/Program Files (x86)/Battle.net"; + gameExecutable = gameFolder + "/Battle.net.exe"; + gameArguments = [ + "--in-process-gpu" + "--exec=\"launch ${launchArg}\"" + ]; + }); + vn = { + long_name, + vnDir, + vnExe, + vnArch ? "x86", + }: (wineCommon + // rec { + inherit long_name; + prefixFolder = gameStorage + "/VNs"; + gameFolder = prefixFolder; + gameExecutable = "./drive_c/cmd.exe"; + gameArguments = [ + "/k" + "C:/script.bat" + ]; + environment = { + VN_DIR = vnDir; + VN_EXE = vnExe; + VN_ARCH = vnArch; + }; + }); + in { + # + # Visual Novels + # + + kanon = vn { + long_name = "Kanon"; + vnDir = "C:/KEY/KANON_SE_ALL"; + vnExe = "./REALLIVE.exe"; + }; + + hanahira = vn { + long_name = "Hanahira"; + vnDir = "C:/hanahira"; + vnExe = "./HANA9.exe"; + }; + + # + # Guild Warses + # + + gw1 = mkMerge [ + protonCommon + rec { + long_name = "Guild Wars 1"; + prefixFolder = gameStorage + "/guild-wars"; + gameFolder = prefixFolder + "/drive_c/Program Files/Guild Wars"; + gameExecutable = gameFolder + "/Gw.exe"; + environments = ["vkbasalt"]; + } + ]; + gw2 = mkMerge [ + protonCommon + rec { + variant = mkForce "PROTON_CACHYOS"; + long_name = "Guild Wars 2"; + prefixFolder = gameStorage + "/guild-wars-2"; + gameFolder = prefixFolder + "/drive_c/Program Files/Guild Wars 2"; + gameExecutable = gameFolder + "/Gw2-64.exe"; + environments = ["vkbasalt"]; + environment = { + # https://github.com/Open-Wine-Components/umu-protonfixes/blob/master/gamefixes-steam/1284210.py + # You know, having read this it feels disturbingly fucking pointless now? + GAMEID = "umu-1284210"; + STORE = "none"; + }; + } + ]; + + # + # Battle.net games + # + + # The raw battlenet should not use the function OR the battlenet arg :p + battlenet = + protonCommon + // rec { + long_name = "Battle.net"; + prefixFolder = gameStorage + "/battlenet"; + gameFolder = prefixFolder + "/drive_c/Program Files (x86)/Battle.net"; + gameExecutable = gameFolder + "/Battle.net.exe"; + gameArguments = [ + "--in-process-gpu" + ]; + }; + + s1 = battlenet { + long_name = "Starcraft: Remastered"; + launchArg = "S1"; + }; + s2 = battlenet { + long_name = "Starcraft 2"; + launchArg = "S2"; + }; + w1 = battlenet { + long_name = "Warcraft 1"; + launchArg = "W1"; + }; + w2 = battlenet { + long_name = "Warcraft 2"; + launchArg = "W2"; + }; + w1r = battlenet { + long_name = "Warcraft 1: Remastered"; + launchArg = "W1R"; + }; + w2r = battlenet { + long_name = "Warcraft 2: Remastered"; + launchArg = "W2R"; + }; + w3 = + battlenet { + long_name = "Warcraft 3: Reforged"; + launchArg = "W3"; + } + // { + environment = { + STAGING_SHARED_MEMORY = builtins.toString 1; + __GL_SHADER_DISK_CACHE_SKIP_CLEANUP = builtins.toString 1; + PROTON_DISABLE_NVAPI = builtins.toString 1; + }; + }; + }; + }; hardware.graphics = { enable32Bit = true; extraPackages32 = with pkgs; [ @@ -54,16 +242,16 @@ in { mangohud vkbasalt ]; - systemd.user.services = - mapAttrs (k: v: { - description = v; - serviceConfig = { - ExecStart = "${getExe pkgs.katwine} ${k}"; - Type = "simple"; - }; - inherit environment; - }) - games; + #systemd.user.services = + # mapAttrs (k: v: { + # description = v; + # serviceConfig = { + # ExecStart = "${getExe pkgs.katwine} ${k}"; + # Type = "simple"; + # }; + # inherit environment; + # }) + # games; home-manager.users.kat.home.file = { # https://learnjapanese.moe/vn-linux/