Files
nix-config-ryan4yin/.agents/skills/nix-config-umu-game/scripts/umu-install.nu
T

185 lines
6.5 KiB
Nu

#!/usr/bin/env nu
#
# Install a Windows game launcher into its own umu-launcher Wine prefix, and
# generate launch/exec/desktop/uninstall helpers for it.
#
# umu-launcher is the Proton backend Lutris/Heroic use: it creates and runs a
# detached Wine prefix without Steam, and a GAMEID from the umu database pulls
# in that game's protonfixes (fonts, env quirks, save location, ...). This
# installer is fully generic -- it knows no game names. Every per-game detail
# lives under ~/Games/<name>/:
#
# prefix the Wine prefix (WINEPREFIX)
# prelaunch optional executable, run before every launch
# conf optional shell file overriding the generated defaults
# run generated launcher
# exec generated helper: run any exe/wine tool in the prefix
# <name>.desktop generated desktop entry
# uninstall generated: drop the desktop entry and (with confirmation) this dir
#
# ~/Games/global.conf, if present, is sourced by every run script before the
# game's own conf; both may set ENABLE_GAMEMODE, ENABLE_GAMESCOPE,
# GAMESCOPE_ARGS, ENABLE_LOG, PROTONPATH, GAMEID, LAUNCHER, ...
#
# PROTONPATH is resolved from $PROTONPATH, then $UMU_PROTONPATH (exported by
# modules/nixos/desktop/gaming.nix from pkgs.dwproton-bin), then Steam's
# per-user compatibilitytools.d.
#
# nu .agents/skills/nix-config-umu-game/scripts/umu-install.nu wuthering-waves \
# ~/Downloads/WutheringWaves_setup_3.5.0.exe \
# "drive_c/Program Files/Wuthering Waves/launcher.exe" \
# umu-3513350
#
# Extra arguments after [gameid] are passed to the installer, e.g. "/S" or
# "/quiet" for a silent install.
def games-dir [] {
$nu.home-dir | path join "Games"
}
# First candidate that actually contains a proton script.
def resolve-protonpath [] {
let explicit = ($env.PROTONPATH? | default "")
if (not ($explicit | is-empty)) and (($explicit | path join "proton") | path exists) {
return $explicit
}
let umu = ($env.UMU_PROTONPATH? | default "")
if (not ($umu | is-empty)) and (($umu | path join "proton") | path exists) {
return $umu
}
let steam = ($nu.home-dir | path join ".local/share/Steam/compatibilitytools.d/dwproton")
if (($steam | path join "proton") | path exists) {
return $steam
}
null
}
# "wuthering-waves" -> "Wuthering Waves"
def reject-symlink [path: string, label: string] {
let kind = (try { $path | path type } catch { "missing" })
if $kind == "symlink" {
print --stderr $"error: ($label) must not be a symlink: ($path)"
exit 2
}
}
def display-name [name: string] {
$name | split row "-" | each { |w| $w | str capitalize } | str join " "
}
def usage [] {
print "Usage: .agents/skills/nix-config-umu-game/scripts/umu-install.nu <name> <setup.exe> <launcher> [gameid] [setup-args...]"
print ""
print " <name> directory under ~/Games (e.g. wuthering-waves)"
print " <setup.exe> installer to run through umu (a host path)"
print " <launcher> launcher to run later, relative to the prefix"
print " [gameid] umu database id for protonfixes (default umu-default)"
print " [setup-args] extra args for setup.exe, e.g. /S or /quiet"
print ""
print "Writes ~/Games/<name>/{run,exec,<name>.desktop,uninstall}. A per-game"
print "fix goes in ~/Games/<name>/prelaunch; overrides go in ~/Games/<name>/conf"
print "or ~/Games/global.conf. PROTONPATH comes from $PROTONPATH or"
print "$UMU_PROTONPATH."
}
# Quote substituted shell values as data, including literal apostrophes.
def shell-quote [value: string] {
"'" + ($value | str replace --all "'" "'\\''") + "'"
}
def render-template [name: string, values: record] {
let template = ($env.FILE_PWD | path join "templates" $name)
open --raw $template | str replace --all --regex '@([A-Z_]+)@' { |key|
$values | get $key
}
}
def write-run [name: string, launcher: string] {
let dir = ((games-dir) | path join $name)
let values = {
WINEPREFIX: (shell-quote $env.WINEPREFIX)
PROTONPATH: (shell-quote $env.PROTONPATH)
GAMEID: (shell-quote $env.GAMEID)
LAUNCHER: (shell-quote $launcher)
}
mkdir $dir
for file in [run exec] {
let target = ($dir | path join $file)
render-template $"($file).sh.tpl" $values | save --force $target
^chmod +x $target
}
}
def write-integration [name: string] {
let dir = ((games-dir) | path join $name)
let apps = ($nu.home-dir | path join ".local/share/applications")
let entry = ($apps | path join $"($name).desktop")
let desktop = (render-template "launcher.desktop.tpl" {
DISPLAY_NAME: (display-name $name)
RUN: ($dir | path join "run")
DIR: $dir
})
mkdir $apps
$desktop | save --force ($dir | path join $"($name).desktop")
$desktop | save --force $entry
let uninstall = ($dir | path join "uninstall")
render-template "uninstall.sh.tpl" {
DESKTOP_FILE: (shell-quote $entry)
GAMES_DIR: (shell-quote (games-dir))
NAME: (shell-quote $name)
} | save --force $uninstall
^chmod +x $uninstall
}
def main [...args: string] {
if ($args | length) < 3 {
usage
exit 1
}
let name = ($args | get 0)
if not ($name =~ '^[a-z0-9]+(-[a-z0-9]+)*$') {
print --stderr "error: name must be a lowercase slug (letters, digits, hyphens)"
exit 2
}
let setup = ($args | get 1)
let launcher = ($args | get 2)
let gameid = (if ($args | length) >= 4 { $args | get 3 } else { "umu-default" })
let setupArgs = (if ($args | length) >= 5 { $args | skip 4 } else { [] })
let root = (games-dir)
reject-symlink $root "Games directory"
let dir = ($root | path join $name)
let prefix = ($dir | path join "prefix")
reject-symlink $dir "game directory"
reject-symlink $prefix "Wine prefix"
let protonpath = (resolve-protonpath)
if $protonpath == null {
print --stderr "error: no Proton found. Set PROTONPATH or UMU_PROTONPATH, or rebuild NixOS so modules/nixos/desktop/gaming.nix exports UMU_PROTONPATH."
exit 1
}
$env.PROTONPATH = $protonpath
$env.WINEPREFIX = $prefix
$env.GAMEID = $gameid
print $"installing '($name)' into ($env.WINEPREFIX)"
print $" PROTONPATH = ($env.PROTONPATH)"
print $" GAMEID = ($env.GAMEID)"
print "run the installer, then exit it (do not start the game)."
let inst = (^umu-run $setup ...$setupArgs | complete)
if $inst.exit_code != 0 {
print --stderr $"installer exited ($inst.exit_code); writing the launchers anyway"
}
let prelaunch = ($dir | path join "prelaunch")
if ($prelaunch | path exists) {
print $"running prelaunch: ($prelaunch)"
run-external $prelaunch
}
write-run $name $launcher
write-integration $name
print $"done. launch with: ($dir)/run"
}