feat: replace katwine with Mewtris; they're gonna think i'm on crack for

this
This commit is contained in:
Kat Inskip 2025-10-11 21:53:57 -07:00
parent b6f92728fb
commit 3700d3ac48
Signed by: kat
GPG key ID: 465E64DECEA8CF0F
2 changed files with 446 additions and 44 deletions

214
modules/nixos/mewtris.nix Normal file
View file

@ -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;
};
}

View file

@ -1,44 +1,232 @@
{
config,
pkgs,
inputs,
lib,
...
}: let
inherit (lib.meta) getExe;
inherit (lib.attrsets) mapAttrs;
environment = {
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;
};
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";
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
#
hanahira = "Hanahira";
kanon = "Kanon";
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;
};
};
};
};
in {
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/