mirror of
https://github.com/lhns/steam-frame-nix.git
synced 2026-10-06 01:00:13 +02:00
docs/pet.md (usage, configuration, how it works), docs/pet-models.md (the model spec), the README's feature line, options, changes outside Nix and Credits (Toon Cat FREE and Tuxedo Cat, CC-BY 4.0; Quaternius Shiba Inu and Fox, CC0), the cleanup artifact, the layout and runtime names, and the template.
227 lines
9.5 KiB
Markdown
227 lines
9.5 KiB
Markdown
# UI patches
|
|
|
|
`uiPatches.patches`, `uiPatches.lib` (modules `steam-ui-patches`,
|
|
`steamvr-debugger`). For patch authors, and for fixing patches after a Steam
|
|
update. Options: [README, Options](../README.md#options). Which file runs
|
|
where: [Development](development.md).
|
|
|
|
## Problem
|
|
|
|
Steam's UI and SteamVR's dashboard can't be extended: their files belong to
|
|
Steam, and Steam overwrites them on every update.
|
|
|
|
## What you get
|
|
|
|
Steam's UI and SteamVR's dashboard (`vrwebhelper`) are CEF web pages with a
|
|
local DevTools port: `127.0.0.1:8080` for Steam (SteamOS passes
|
|
`-cef-enable-debugging`), `127.0.0.1:8087` for SteamVR once
|
|
[its debugger](steamvr-debugger.md) is on. The `steam-ui-patches` user
|
|
service (`injector.mjs`) patches the running pages through them; Steam's
|
|
files are never modified. The [launcher menu](launcher-menu.md),
|
|
[dashboard](#steamvr-dashboard-patches) and [VR keyboard](keyboard.md)
|
|
features are such patches (the extra keys have their own injector,
|
|
`steam-keyboard-patch`), and you can add your own.
|
|
|
|
Turning a feature off (or stopping the `steam-ui-patches` /
|
|
`steam-keyboard-patch` user services) restores the stock UI without a Steam
|
|
restart. Log: `journalctl --user -u steam-ui-patches -u steam-keyboard-patch`.
|
|
|
|
## Caveats and security
|
|
|
|
- Patches depend on Steam UI internals and can break with an update; find
|
|
modules by signature, not id ([below](#finders-and-signatures)).
|
|
- Don't expose the DevTools ports ([DevTools on the LAN](#devtools-on-the-lan)).
|
|
|
|
### DevTools on the LAN
|
|
|
|
Steam's Developer Mode enables `steam-web-debug-portforward`
|
|
(`0.0.0.0:8081` → `8080`) and `steamvr-web-debug-portforward`
|
|
(`0.0.0.0:8088` → `8087`), and firewalld allows ports 1024-65535, so anyone
|
|
on the network could run code in Steam's UI. No patch needs Developer Mode,
|
|
so keep it off. If it was on while those units were masked, they may stay
|
|
enabled: check with
|
|
`systemctl is-enabled steam-web-debug-portforward steamvr-web-debug-portforward`
|
|
and `sudo systemctl disable` them.
|
|
|
|
## SteamVR dashboard patches
|
|
|
|
[Dashboard windows](dashboard-windows.md),
|
|
[Steam close button](steam-close-button.md),
|
|
[window curvature](window-curvature.md),
|
|
[window control bar](window-control-bar.md) and the [VR pet](pet.md) patch
|
|
SteamVR's dashboard while it runs. What they have in common:
|
|
|
|
- Each turns on the [SteamVR debugger](steamvr-debugger.md) (**the first
|
|
time, restart SteamVR once**).
|
|
- They depend on SteamVR UI internals: each is found by signature (its
|
|
entry in `modules/steam-ui-patches/lib/signatures.json` has the patch's
|
|
name; the VR pet's are inline in `modules/pet/systemui.js`); after an update that changes them the dashboard stays stock (see
|
|
[after a Steam update](#after-a-steam-update)). Tested with SteamVR build
|
|
11008059.
|
|
- All are laser-only: gamepad navigation sees the stock dashboard.
|
|
- All are `mkPatch` patches of `vrwebhelper` (page title `systemui`,
|
|
DevTools `127.0.0.1:8087`, webpack chunk `webpackChunkvrwebui`),
|
|
registered only when enabled. Sources: `modules/<name>/patch.js`, whose
|
|
header comments go into more detail.
|
|
|
|
## Defining a patch
|
|
|
|
A `steamFrame.uiPatches.patches` entry (fields:
|
|
[README, Options](../README.md#options)):
|
|
|
|
```nix
|
|
steamFrame.uiPatches.patches = [ {
|
|
name = "my-patch";
|
|
target.title = "SharedJSContext"; # Steam's main JS context
|
|
patch = ./my-patch/patch.js;
|
|
unpatch = ./my-patch/unpatch.js;
|
|
} ];
|
|
```
|
|
|
|
Patches are evaluated on attach, after new JS contexts (reloads) and every
|
|
15 s, so they must be idempotent: return e.g. `"patched"` once, then
|
|
`"unchanged"` (not logged); other results are logged when they change
|
|
(`journalctl --user -u steam-ui-patches`). On stop, `unpatch` restores the
|
|
stock UI. The service exists only while the list is non-empty and is
|
|
restarted on every switch.
|
|
|
|
## Persistent state
|
|
|
|
With `state = true`: a patch's choices made in the UI can't be kept in the
|
|
page's own storage, since SteamOS's `steamvr.service` runs
|
|
`rm -rf ~/.cache/SteamVR` (vrwebhelper's browser profile, incl.
|
|
localStorage) on every SteamVR start. So the service keeps one JSON value
|
|
per such patch in `~/.local/state/steam-frame-nix/ui-patches/<name>.json`
|
|
(`$XDG_STATE_HOME`):
|
|
|
|
- before each evaluation it defines `window.__sfuiStore.get(name)` /
|
|
`.set(name, value)` in the page and fills `get` from the file only while
|
|
the page has no value yet (a fresh page after a SteamVR restart, reboot or
|
|
reload);
|
|
- `set` goes through the DevTools binding `window.__sfuiStoreSave`; the
|
|
service writes the file atomically, only on change, only for that page's
|
|
`state` patches, at most 64 KiB.
|
|
|
|
Used by the [window control bar](window-control-bar.md), the
|
|
[Steam close button](steam-close-button.md) and the [VR pet](pet.md). The file is user data, not
|
|
generated by Nix: it is kept when the patch is disabled or removed and
|
|
removed only by `steam-frame-nix-cleanup --all` or `install.sh uninstall`;
|
|
see [changes outside Nix](../README.md#changes-outside-nix-exceptions).
|
|
|
|
## Finders and signatures
|
|
|
|
Webpack module ids and export names change with every Steam UI build, so
|
|
patches never use them. `modules/steam-ui-patches/lib/finders.js` (like
|
|
Decky Loader's `findModule`/`findInReactTree` or Vencord's `find`) locates
|
|
by *signature*:
|
|
|
|
- a **module** by strings/regexes in its factory source;
|
|
- an **export** by shape: type, function source, arity, data properties,
|
|
prototype methods or getters;
|
|
- **React** fibers by props (`findFiberUp`, `findFiberDown`,
|
|
`findInReactTree`).
|
|
|
|
Every signature must match exactly once, otherwise the patch changes nothing
|
|
and reports it: `resolvePatch` returns e.g. `signature not found, Steam left
|
|
unpatched: layouts.currentLayout (module 40222): ambiguous export,
|
|
candidates r_, xy` as the patch's result. Results are cached per page. The
|
|
library also has `ensureStyle(doc, id, css)`, `logger(buffer)` (a capped
|
|
debug log) and `keyboardPopup()` (Steam's VR keyboard popup or `null`).
|
|
|
|
Signatures live in `modules/steam-ui-patches/lib/signatures.json`, shared
|
|
by patches and the offline checker. An entry can also list `expects` (strings the patch relies
|
|
on, checked offline only) or be `checkOnly` (anchors not used through the
|
|
finder, checked offline only; with `stylesheet` instead of `module` it is
|
|
matched against the bundle's CSS).
|
|
|
|
### mkPatch
|
|
|
|
`steamFrame.uiPatches.lib.mkPatch` wraps a patch file — a function
|
|
expression `(find, sigs, opts, hooks) => …` returning a status string — with
|
|
the finder library, its signatures, options and the shared hooks (details in
|
|
`modules/steam-ui-patches/lib/default.nix`):
|
|
|
|
```nix
|
|
steamFrame.uiPatches.patches = [ {
|
|
name = "my-patch";
|
|
target.title = "SharedJSContext";
|
|
patch = config.steamFrame.uiPatches.lib.mkPatch {
|
|
name = "my-patch";
|
|
src = ./my-patch/patch.js; # ((find, sigs, opts, hooks) => { … })
|
|
signatures.thing = {
|
|
module.includes = [ "SomeUniqueString" ];
|
|
exports.Thing = { type = "class"; protoMethods = [ "DoIt" ]; };
|
|
};
|
|
opts.factor = 2;
|
|
};
|
|
unpatch = ./my-patch/unpatch.js;
|
|
} ];
|
|
```
|
|
|
|
```js
|
|
((find, sigs, opts, hooks) => {
|
|
const mods = find.resolvePatch('webpackChunksteamui', sigs); // SteamVR dashboard: 'webpackChunkvrwebui'
|
|
if (typeof mods === 'string') return mods; // signature not found
|
|
const Thing = mods.thing.exports.Thing;
|
|
…
|
|
})
|
|
```
|
|
|
|
### Shared method hooks
|
|
|
|
`modules/steam-ui-patches/lib/hooks.js`, argument `hooks`, also
|
|
`window.__sfuiHooks`: patches intercepting the same method (e.g. the
|
|
dashboard mailbox's `SendMessage`, used by [dashboard windows](dashboard-windows.md#how-it-works) and
|
|
[window curvature](window-curvature.md#how-it-works)) register named hooks;
|
|
one wrapper per method runs them in registration order, so patches can be
|
|
injected, upgraded and reverted in any order.
|
|
|
|
```js
|
|
hooks.before(Mailbox.prototype, 'SendMessage', 'my-patch', (args) => {
|
|
if (args[1]?.type === 'update_scene_graph') rewrite(args[1].scene_graph);
|
|
});
|
|
hooks.remove(Mailbox.prototype, 'SendMessage', 'my-patch'); // in unpatch
|
|
```
|
|
|
|
Patches reacting to presses on the curvature controls follow the
|
|
[window curvature contract](window-curvature.md#contract-for-other-patches)
|
|
(`sfui-curv-*`).
|
|
|
|
## After a Steam update
|
|
|
|
If a Steam or SteamVR update changes Steam's code so a signature no longer
|
|
matches exactly once, that patch changes nothing: the feature stays stock
|
|
and the journal (see [above](#what-you-get)) says why, e.g.
|
|
`signature not found, Steam left unpatched: …`. Update steam-frame-nix
|
|
(`nix flake update steam-frame-nix`, then switch) once it supports the new
|
|
build.
|
|
|
|
Check the signatures offline (Steam need not run) from a checkout:
|
|
|
|
```sh
|
|
nix shell nixpkgs#nodejs -c node scripts/check-signatures.mjs
|
|
```
|
|
|
|
It loads Steam's UI bundle (`~/.local/share/Steam/steamui`) and SteamVR's
|
|
dashboard (`/opt/steamvr/resources/webinterface/dashboard/systemui.html`),
|
|
evaluates every signature with the patches' finder code (exports in an inert
|
|
sandbox) and prints per patch `found` (module id, export name), `ambiguous`
|
|
or `missing`, plus warnings for missing `expects`; exit status 1 if anything
|
|
is missing or ambiguous:
|
|
|
|
```
|
|
bundle steamui: 2827 modules in /home/deck/.local/share/Steam/steamui (build 11041156)
|
|
|
|
steam-keyboard-patch (steamui)
|
|
layouts found module 40222 (chunk~2dcc5aaf7.js)
|
|
.currentLayout found export r_
|
|
…
|
|
OK: all signatures match exactly once
|
|
```
|
|
|
|
To fix a signature, inspect the new module sources
|
|
(`scripts/webpack-modules.mjs`), adjust `signatures.json` and bump the
|
|
patch's `VERSION` if its code changes. Flags: `--signatures FILE` (your own,
|
|
bundles `steamui` / `vrwebui-systemui`), `--dir steamui=DIR`,
|
|
`--patch NAME`, `--strict` (fail on warnings), `--json`.
|