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:
@@ -17,7 +17,7 @@
|
||||
# controller (Settings → Controller → "PlayStation Configuration Support":
|
||||
# OFF). Bluetooth works too but the udev/hidraw path is more reliable
|
||||
# over USB.
|
||||
# - in Forza, HUD options \u2192 set Data Out: ON, Data Out IP: 127.0.0.1,
|
||||
# - in Forza, HUD options → set Data Out: ON, Data Out IP: 127.0.0.1,
|
||||
# Data Out IP Port: 5300, and (FM7 only) Data Out Packet Format: CAR DASH.
|
||||
#
|
||||
# System-interaction notes:
|
||||
@@ -26,102 +26,17 @@
|
||||
# in pydualsense's source). Forza Horizon is single-player so this is
|
||||
# usually fine. If you need to pin a specific controller, the cleanest
|
||||
# route is monkey-patching `pydualsense.__find_device`.
|
||||
# - The included `dualsensectl` will be overwritten by our BG thread within
|
||||
# ~4 ms; use `systemctl --user stop forza-trigger` first when debugging.
|
||||
# - `pkgs.dualsensectl` is intentionally NOT installed by default
|
||||
# (single-shot writes from it get overwritten by our BG thread within
|
||||
# ~4 ms). Bring it in ad-hoc with `nix-shell -p dualsensectl` and stop
|
||||
# this service first via `systemctl --user stop forza-trigger`.
|
||||
# - Hot-plug recovery happens in-process: the daemon polls pydualsense's BG
|
||||
# thread liveness and re-runs `pydualsense.init()` on disconnect. systemd's
|
||||
# `Restart=on-failure` exists only as a crash-recovery safety net.
|
||||
let
|
||||
cfg = config.services.forzaTrigger;
|
||||
python = pkgs.python3;
|
||||
|
||||
# CFFI bindings to libhidapi. Upstream is flok/hidapi-cffi published on
|
||||
# PyPI under the name `hidapi-usb`. The shipped hidapi.py picks a libhidapi
|
||||
# soname via ffi.dlopen() — we substitute absolute store paths so the
|
||||
# interpreter inside our wrapped python env can find it without
|
||||
# LD_LIBRARY_PATH gymnastics.
|
||||
hidapi-usb = python.pkgs.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 = [ python.pkgs.cffi ];
|
||||
|
||||
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 = python.pkgs.buildPythonPackage rec {
|
||||
pname = "pydualsense";
|
||||
version = "0.7.5";
|
||||
format = "pyproject";
|
||||
|
||||
src = python.pkgs.fetchPypi {
|
||||
pname = "pydualsense";
|
||||
inherit version;
|
||||
hash = "sha256-YgX8AJE4f8p7geKT3xlCD0Mlh1GcyHpBz4rEIqdwKgs=";
|
||||
};
|
||||
|
||||
nativeBuildInputs = [ python.pkgs.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. 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 = python.pkgs.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;
|
||||
};
|
||||
};
|
||||
pythonPackages = import ./python-packages.nix { inherit lib pkgs; };
|
||||
inherit (pythonPackages) pydualsense fdp;
|
||||
|
||||
forzaTrigger = pkgs.writers.writePython3Bin "forza-trigger" {
|
||||
libraries = [
|
||||
@@ -173,12 +88,7 @@ in
|
||||
KERNEL=="hidraw*", KERNELS=="*054C:0DF2*", MODE="0660", TAG+="uaccess"
|
||||
'';
|
||||
|
||||
environment.systemPackages = [
|
||||
forzaTrigger
|
||||
# CLI companion for sanity-checking the controller (battery, lightbar,
|
||||
# raw trigger modes, monitor add/remove events).
|
||||
pkgs.dualsensectl
|
||||
];
|
||||
environment.systemPackages = [ forzaTrigger ];
|
||||
|
||||
# User-level service so it inherits the seat-bound uaccess ACL on
|
||||
# /dev/hidraw* and dies cleanly when the user logs out.
|
||||
|
||||
Reference in New Issue
Block a user