game-mods: drop in-house launchOptions writer, hardcode FH5 ini

Replaces three handfuls of custom code with upstream / static data:

- Per-app Steam launch options now declared via different-name/steam-
  config-nix's `programs.steam.config.apps.<n>` instead of a custom
  ~70-line `apply_launch_options` Python function. The dropped writer
  was racy: it edited localconfig.vdf without checking for a running
  Steam, so any timer firing while Steam was open would lose its
  changes on the next Steam shutdown. steam-config-nix's `closeSteam`
  flag closes that race.

  Also moves the GE-Proton compat-tool pin to declarative config —
  one fewer manual click in Steam UI to remember.

- `mods.<>.launchOptions` option, the `launchOptionsData` aggregation,
  and `LAUNCH_OPTIONS_DATA` are removed from desktop-game-mods.nix.
  The module now does file-drops only; Steam config lives in its own
  `programs.steam.config` namespace, where it belongs.

  fh5-vkd3d-no-hvv (which existed only to set VKD3D_CONFIG) collapses
  into the FH5 launchOptions block in hosts/yarn/default.nix.

- `unitConfig.X-ConfigHash` on game-mods.service is replaced with
  `restartTriggers`. NixOS already emits `X-Restart-Triggers=<hash>`
  on the unit; the workaround was redundant. The Type=oneshot,
  RemainAfterExit=no semantics make `systemctl restart` re-run
  ExecStart cleanly on hash change.

- The awk pipeline that patched OptiScaler's stock OptiScaler.ini at
  build time is replaced with a hand-written hosts/yarn/optiscaler-
  fh5-rdna3.ini containing only the keys we override (5 of them).
  OptiScaler's Config::readString defaults missing keys to "auto"
  (Config.cpp:1568), so a minimal file is sufficient. Side benefits:
  one upstream-source dependency removed, a key-rename in upstream
  becomes a behavior change rather than a silent awk-no-match.

  Override values + sources:
    Fsr4Update=true              FH5 wiki, FSR4 Linux Setup
    DlssReactiveMaskBias=0.65    FH5 wiki, "Known Issues"
    FsrNonLinearColorSpace=true  FSR4 wiki, "Image Quality"
    EnableFsr2Inputs=false       FH5 wiki, "Known Issues"
    Dxgi=false                   FH5 wiki

- forza-trigger's three custom Python derivations (pydualsense,
  hidapi-usb, fdp) factored out of default.nix into a sibling
  python-packages.nix. Same logic, single-purpose file. Bumping a
  version is now a one-place hash roll.

- pkgs.dualsensectl removed from the daemon's environment.system-
  Packages. Single-shot writes from the CLI get clobbered by the BG
  sendReport thread within ~4ms anyway, so the tool is only useful
  with the daemon stopped — not worth the unconditional install.
  Bring it in ad-hoc with `nix-shell -p dualsensectl`.
This commit is contained in:
2026-05-03 00:25:08 -04:00
parent 1e8c294a80
commit e010b4e3c1
7 changed files with 271 additions and 285 deletions

View File

@@ -0,0 +1,107 @@
# Python packages forza_trigger.py imports that aren't in nixpkgs. Returns
# an attrset consumed by ./default.nix.
#
# Bumping a version: change `version` and `hash`, then `nix build` — Nix
# fails with the new sha256 in the error message, paste it back in.
{
lib,
pkgs,
python ? pkgs.python3,
}:
let
py = python.pkgs;
in
rec {
# CFFI bindings to libhidapi (flok/hidapi-cffi on PyPI). pydualsense's
# `import hidapi` resolves to this — nixpkgs' python3Packages.hidapi is the
# Cython wrapper from trezor/cython-hidapi which exposes a different
# `import hid` API and can't satisfy pydualsense.
hidapi-usb = py.buildPythonPackage rec {
pname = "hidapi-usb";
version = "0.3.2";
format = "setuptools";
# PyPI's project URL slug uses a hyphen (`hidapi-usb`) but the sdist file
# itself is PEP-625-normalized to an underscore (`hidapi_usb-…`). Stock
# fetchPypi assumes they match — they don't here, so fetch by direct URL.
src = pkgs.fetchurl {
url = "https://files.pythonhosted.org/packages/55/80/960ae94b615e26a7d1aeebe8e9fefda2f25608bf1016f9aec268b328c35e/hidapi_usb-${version}.tar.gz";
hash = "sha256-oxp+2i+qqYd1uwiS2Dh8/PzO62iYQQXpR936MnDIFk0=";
};
propagatedBuildInputs = [ py.cffi ];
# Upstream's hidapi.py walks a tuple of soname strings via ffi.dlopen()
# until one resolves. Pin the two Linux hidraw entries to absolute store
# paths so the wrapped Python in our writePython3Bin closure finds them
# without LD_LIBRARY_PATH wrapping. The libusb / iohidmanager / dylib /
# dll entries are dead code on Linux. --replace-fail makes a rename in
# upstream's tuple a loud build error rather than a silent ImportError
# at runtime.
postPatch = ''
substituteInPlace hidapi.py \
--replace-fail "'libhidapi-hidraw.so'," "'${pkgs.hidapi}/lib/libhidapi-hidraw.so'," \
--replace-fail "'libhidapi-hidraw.so.0'," "'${pkgs.hidapi}/lib/libhidapi-hidraw.so.0',"
'';
pythonImportsCheck = [ "hidapi" ];
meta = {
description = "CFFI wrapper for hidapi (used by pydualsense)";
homepage = "https://github.com/flok/hidapi-cffi";
license = lib.licenses.bsd3;
};
};
pydualsense = py.buildPythonPackage rec {
pname = "pydualsense";
version = "0.7.5";
format = "pyproject";
src = py.fetchPypi {
inherit pname version;
hash = "sha256-YgX8AJE4f8p7geKT3xlCD0Mlh1GcyHpBz4rEIqdwKgs=";
};
nativeBuildInputs = [ py.poetry-core ];
propagatedBuildInputs = [ hidapi-usb ];
pythonImportsCheck = [ "pydualsense" ];
meta = {
description = "Control your PS5 DualSense controller from Python";
homepage = "https://github.com/flok/pydualsense";
license = lib.licenses.mit;
};
};
# Single-file Forza UDP packet parser (nettrom/forza_motorsport). Pinned to
# a known-good commit; the repo is dormant (last commit 2021) but the FH4
# packet layout is frozen and FH5 reuses it byte-for-byte.
fdp = py.buildPythonPackage {
pname = "fdp";
version = "0-unstable-2021-05-28";
format = "other";
src = pkgs.fetchurl {
url = "https://raw.githubusercontent.com/nettrom/forza_motorsport/61845cb7ff4082211292a51ce3c49edbfd2d6503/fdp.py";
hash = "sha256-osFaVF9VaEzU4dp3x6KN6OF7SXsd9ZBwvilU+xTT7mM=";
};
dontUnpack = true;
installPhase = ''
runHook preInstall
install -Dm644 $src $out/${python.sitePackages}/fdp.py
runHook postInstall
'';
pythonImportsCheck = [ "fdp" ];
meta = {
description = "ForzaDataPacket Forza Motorsport / Horizon UDP packet parser";
homepage = "https://github.com/nettrom/forza_motorsport";
license = lib.licenses.mit;
};
};
}