docs: user-facing documentation and signing key
This commit is contained in:
@@ -0,0 +1,499 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# Configuration schema
|
||||
|
||||
All paths below follow the XDG Base Directory spec (config vs. data vs.
|
||||
cache) — see architecture.md (source repository)
|
||||
for the full table, including external paths Runi reads/writes (Steam's
|
||||
`compatibilitytools.d`, Steam library manifests, game install directories for
|
||||
OptiScaler). Runi itself is installed via `pipx`/`uv tool install` into
|
||||
an isolated environment, not system Python — see
|
||||
architecture.md (source repository).
|
||||
|
||||
## Per-game config
|
||||
|
||||
`~/.config/runi/games/<id>.toml`, where `<id>` is Runi's own unique
|
||||
registry key (`game.id` below) — **not** the same thing as umu's `GAMEID`,
|
||||
which is deliberately shared across unrelated games. See
|
||||
architecture.md (source repository) for why these are kept
|
||||
separate.
|
||||
|
||||
```toml
|
||||
# ~/.config/runi/games/steam-292030.toml
|
||||
format_version = 9
|
||||
|
||||
[game]
|
||||
id = "steam-292030" # our own unique registry key; always distinct
|
||||
installation_id = "b1e6c9a0-..." # UUID for this concrete installation
|
||||
display_name = "The Witcher 3"
|
||||
exe = "/home/user/Games/witcher3/witcher3.exe"
|
||||
args = []
|
||||
store = "steam" # local|steam|gog|egs|itch|amazon|ubisoft
|
||||
working_dir = "" # optional; defaults to exe's parent dir
|
||||
platform = "windows" # original/content platform
|
||||
port_target_os = "" # empty|windows|linux; non-empty means community port
|
||||
platform_label = "" # custom origin label only when platform="other"
|
||||
classification_source = "auto" # auto|manual; manual survives catalog reconciliation
|
||||
install_root = "" # stable root for relative launch/action paths
|
||||
|
||||
[launch]
|
||||
provider = "executable" # executable|xenia|rpcs3|ubisoft
|
||||
engine = "proton" # proton|host|steam (see below)
|
||||
content = "" # emulator content path when applicable
|
||||
|
||||
[store_binding]
|
||||
installation_id = "" # shared store installation; empty for normal games
|
||||
process_names = [] # executable names used for real game lifecycle detection
|
||||
|
||||
[[actions]]
|
||||
id = "settings"
|
||||
label = "Settings"
|
||||
role = "configuration"
|
||||
provider = "executable"
|
||||
engine = "inherit"
|
||||
exe = "Settings.exe"
|
||||
args = []
|
||||
working_dir = "."
|
||||
content = ""
|
||||
wrappers = false
|
||||
|
||||
[identity]
|
||||
runi_game_id = "pgid_..." # canonical game across stores/installations
|
||||
steam_appid = "292030" # dedicated Steam identity; empty when unknown
|
||||
umu_gameid = "umu-292030" # exact GAMEID passed to UMU
|
||||
steamgriddb_game_id = "" # confirmed SteamGridDB selection; not a Steam AppID
|
||||
ubisoft_id = "" # Ubisoft Connect deeplink identity
|
||||
ubisoft_space_id = "" # local Ubisoft catalogue correlation UUID
|
||||
ubisoft_install_id = "" # local Ubisoft installation/catalogue identity
|
||||
ubisoft_store_product_id = "" # Ubisoft Store master product/edition identity
|
||||
canonical_title = "The Witcher 3: Wild Hunt"
|
||||
source = "local Steam manifest" # evidence used for this resolution
|
||||
confidence = "verified" # unknown|review|high|verified|manual
|
||||
validated_at = "2026-07-07 15:00:00"
|
||||
|
||||
[runtime]
|
||||
backend = "umu" # umu|native
|
||||
proton = "GE-Proton9-27" # matches a discovered ProtonVersion name, or "auto"
|
||||
prefix = "" # optional override; else derived from game.id/backend
|
||||
cpu_affinity = "" # optional taskset CPU list, e.g. "0,1,2,3" or "0-3"
|
||||
|
||||
[env]
|
||||
# free-form passthrough, e.g. DXVK_HUD = "fps"; dedicated OptiScaler provider keys must not be duplicated here
|
||||
|
||||
[gamescope]
|
||||
enabled = false
|
||||
output_width = 1920
|
||||
output_height = 1080
|
||||
render_width = 1280 # unset/equal to output = no upscaling
|
||||
render_height = 720
|
||||
filter = "linear" # fsr|nis|linear|nearest (-F)
|
||||
scaler = "auto" # auto|integer|fit|fill|stretch (-S)
|
||||
fullscreen = true
|
||||
borderless = false
|
||||
refresh_rate = 120 # nested display refresh; 0 = Gamescope default (-r)
|
||||
fps_limit = 0 # optional cap; 0 = disabled (--framerate-limit)
|
||||
|
||||
[mangohud]
|
||||
enabled = false
|
||||
fps_limit = 0
|
||||
config_file = "" # optional path; else MangoHud's own default resolution order
|
||||
|
||||
[linuwux]
|
||||
enabled = false # wrap the launch with linuwux (LD_PRELOAD shim for
|
||||
# Reflex/DenuvOwO-style DRM loaders); PATH-only dependency,
|
||||
# never installed/managed by runi - see
|
||||
# github.com/brcly/linuwux-runtime. Only GE-Proton/
|
||||
# Proton-CachyOS are upstream-supported; `runi doctor`
|
||||
# flags any other selected build as an advisory finding.
|
||||
debug = false # LINUWUX_DEBUG=1; verbose, only for troubleshooting
|
||||
|
||||
[proton_tweaks]
|
||||
fsr_fullscreen_hack = false
|
||||
fsr_strength = 2
|
||||
|
||||
[optiscaler]
|
||||
enabled = false
|
||||
provider = "auto" # auto|manual|ge-integrated
|
||||
applied_provider = "" # resolved/provisioned provider; managed by Runi
|
||||
target_dll = "" # e.g. dxgi.dll
|
||||
version = "default" # provider default or an exact OptiScaler version
|
||||
fsr4_enabled = false # runner-managed FSR 4; integrated provider only
|
||||
fsr4_version = "default"
|
||||
|
||||
[optiscaler.integrated_config]
|
||||
# Structured Section.Option keys passed through PROTON_OPTISCALER_CONFIG.
|
||||
# Example: "Upscalers.Dx12Upscaler" = "fsr31"
|
||||
|
||||
[lossless_scaling]
|
||||
enabled = false # configure LSFG for the main Play action
|
||||
multiplier = 2 # 2|3|4 displayed-frame multiplier
|
||||
flow_scale = 1.0 # motion-estimation scale, 0.25..1.0
|
||||
performance_mode = false # faster model with a small quality tradeoff
|
||||
hdr_mode = false # compatibility mirror; managed by [hdr]
|
||||
present_mode = "default" # default|fifo|mailbox|immediate; lsfg-vk 1.x
|
||||
|
||||
[hdr]
|
||||
enabled = false # native game HDR; requires preconfigured Gamescope
|
||||
|
||||
[logging]
|
||||
output = "console" # console|file - never both at once; see below
|
||||
|
||||
[customize]
|
||||
applied_profile = "" # name of the currently-applied customize profile, if any
|
||||
|
||||
[notes]
|
||||
key = "" # relates notes across multiple registrations of the same
|
||||
# real-world game - defaults to game.id if unset; the notes
|
||||
# themselves live as dated entries in
|
||||
# ~/.config/runi/notes/<key>/<uuid>.md
|
||||
|
||||
[saves]
|
||||
path = "" # save location for backup/restore - relative to the prefix's
|
||||
# drive_c/, or an absolute path; the GUI can suggest paths
|
||||
# from PCGamingWiki, but the user confirms what is stored here
|
||||
```
|
||||
|
||||
The GUI presents each Gamescope width/height pair as one editable resolution
|
||||
selector with common presets. New games added through the GUI use the primary
|
||||
monitor's resolution for both output and render when it can be detected, while
|
||||
`refresh_rate` and `fps_limit` remain `0`. Existing configurations and the
|
||||
on-disk schema keep the separate integer fields shown above. The GUI's Display
|
||||
Mode selector maps Fullscreen to `fullscreen = true`, Borderless Window to
|
||||
`borderless = true`, and Windowed to both fields being false.
|
||||
|
||||
The GUI places MangoHud before Gamescope in Wrappers. Its header toggle maps to
|
||||
`enabled`, its editable preset slider maps to `fps_limit`, and `config_file`
|
||||
remains hidden under Advanced until explicitly expanded.
|
||||
|
||||
## Global config
|
||||
|
||||
`~/.config/runi/config.toml`:
|
||||
|
||||
```toml
|
||||
[defaults]
|
||||
backend = "umu"
|
||||
proton = "auto"
|
||||
|
||||
[paths]
|
||||
extra_proton_search_paths = []
|
||||
prefixes_root = "" # empty = ~/.local/share/runi/prefixes; see below
|
||||
|
||||
[covers]
|
||||
auto_download = true
|
||||
steamgriddb_api_key = "" # optional; STEAMGRIDDB_API_KEY overrides this value
|
||||
|
||||
[library]
|
||||
view = "grid" # grid|list
|
||||
card_size = 80 # 50..100; fourth mark by default, maps 120px to 240px
|
||||
|
||||
[ai_research]
|
||||
enabled = false # opt-in community HDR research; requests are still explicit
|
||||
model = "gpt-5.6-terra" # managed Runi default
|
||||
reasoning = "medium" # managed Runi default
|
||||
```
|
||||
|
||||
### `[paths].prefixes_root`
|
||||
|
||||
Where per-game Wine prefixes live. Empty - the default - keeps them under the
|
||||
data root at `~/.local/share/runi/prefixes`. Set it to move a prefix
|
||||
collection that has outgrown that disk; `~` is expanded.
|
||||
|
||||
Three things worth knowing before setting it:
|
||||
|
||||
- **It must be absolute.** A relative value is ignored, and `runi doctor`
|
||||
says so rather than letting it pass. Relative would resolve against the
|
||||
working directory, and the launcher deliberately sets that to the game's
|
||||
own folder - Wine prefixes would land inside the games.
|
||||
- **Changing it moves nothing.** A game with no explicit `[runtime].prefix`
|
||||
derives its prefix from this root, so the games whose prefixes stayed
|
||||
behind would get a fresh empty one on next launch. Move the directories
|
||||
across yourself; `runi doctor` reports any left behind
|
||||
(`prefixes_root_unmoved`) until you do.
|
||||
- **A per-game `[runtime].prefix` still wins.** This is only the default
|
||||
root for games that have not been pointed somewhere specific.
|
||||
|
||||
Store installations keep their own prefixes under
|
||||
`~/.local/share/runi/store-prefixes/`, and each one persists its path
|
||||
absolutely, so this setting does not affect them.
|
||||
|
||||
### `[launch].engine = "steam"`
|
||||
|
||||
For a game **Steam launches**, not Runi. Steam builds the command, and Runi
|
||||
contributes only what survives someone else owning the launch: the wrapper
|
||||
prefix (gamescope, MangoHud, linuwux, `taskset`) and the environment. Put
|
||||
this in the game's Steam launch options:
|
||||
|
||||
```
|
||||
runi wrap <game-id> %command%
|
||||
```
|
||||
|
||||
Steam substitutes the command it built for `%command%`, and `runi wrap`
|
||||
prepends Runi's stages to it. Verify before launching anything for real:
|
||||
|
||||
```bash
|
||||
runi wrap <game-id> --dry-run -- /usr/bin/proton run /path/game.exe
|
||||
```
|
||||
|
||||
What still works: OptiScaler (both providers), frame generation, Proton FSR
|
||||
tweaks, `WINEDLLOVERRIDES`, MangoHud, gamescope, linuwux and CPU affinity.
|
||||
|
||||
What has no effect, because Steam already decided it: `[runtime].backend`,
|
||||
`[runtime].proton` and `[runtime].prefix` — Steam picks the compat tool and
|
||||
uses `steamapps/compatdata/<appid>`. `runi config` says so when you set one
|
||||
of them, and the GUI greys them out. `[runtime].cpu_affinity` is **not** in
|
||||
that list: `taskset` is an argv prefix, so it survives.
|
||||
|
||||
Also given up: Runi's own log capture and `latest.log`, auxiliary launch
|
||||
actions, and the exit code — all of which belong to whoever owns the process.
|
||||
|
||||
#### If the first launch seems to hang
|
||||
|
||||
Adding launch options often coincides with the game's *first* launch under a
|
||||
different Proton build, and that first launch migrates the Wine prefix and
|
||||
compiles shaders. On slow storage this takes minutes, with Steam showing
|
||||
nothing but "Launching" throughout. It looks exactly like a broken wrapper.
|
||||
|
||||
Do not A/B it against a plain `mangohud %command%` and conclude from the
|
||||
result: by the time you try the second option, the first attempt has already
|
||||
paid the migration, so the comparison is a cold start against a warm one.
|
||||
That mistake cost an hour during this feature's own validation.
|
||||
|
||||
Steam's own log is where the truth is:
|
||||
|
||||
```bash
|
||||
tail -f ~/.steam/steam/logs/console-linux.txt
|
||||
```
|
||||
|
||||
`Proton: Upgrading prefix from ... to ...` means it is working, not stuck.
|
||||
And because Steam hides a wrapper's stderr, a shell script that logs `"$@"`
|
||||
and then `exec`s `runi wrap ... "$@"` is the fastest way to see what Steam
|
||||
actually passed and what Runi said about it.
|
||||
|
||||
`config.toml` is written with user-only (`0600`) permissions because it may
|
||||
contain the SteamGridDB API key. Automatically downloaded covers live under
|
||||
`~/.cache/runi/covers/`; imported custom covers are normalized and copied
|
||||
to `~/.local/share/runi/covers/custom/<game.uid>/cover.jpg`. Custom covers
|
||||
therefore do not depend on the original selected image remaining in place.
|
||||
Validated identity lookups are cached for seven days under
|
||||
`~/.cache/runi/identity/`; Runtime can force a fresh revalidation.
|
||||
Local INI discovery is bounded to the installation, ignores symlinks and large
|
||||
files, requires nearby Steam API evidence, and never persists an AppID until
|
||||
Steam confirms that it belongs to a game.
|
||||
|
||||
The OpenAI API key is deliberately absent from this schema. Preferences stores
|
||||
it in the system keyring, can keep it only for the current process, or reads
|
||||
`OPENAI_API_KEY`. Community HDR findings contain no secret and live in
|
||||
`~/.local/share/runi/knowledge.sqlite3`.
|
||||
|
||||
## Field notes
|
||||
|
||||
- **`game.id`** must be unique — it's the config filename and the default
|
||||
prefix folder name. For Steam titles it's `steam-<appid>`; otherwise a
|
||||
slugified name/filename. Never the literal `umu-default`. Uniqueness is
|
||||
enforced, not assumed: `register_game()` refuses an id that already exists,
|
||||
so registering a second copy of a game can never replace the first.
|
||||
A second copy is offered `<slug>-<build version>` read from the executable
|
||||
(`mortal-shell-ii-cl92120`), never a numeric counter; when no build version
|
||||
tells the copies apart, Runi asks for a discriminator rather than
|
||||
inventing one. The build version is *not* stored as a config field and is
|
||||
not stable across an in-place game patch — the id is a name, not a
|
||||
fingerprint.
|
||||
- **Parallel installations** of the same game share `identity.*` (and
|
||||
therefore the PGID-keyed notes, artwork and compatibility knowledge) but
|
||||
keep separate Wine prefixes, settings, environment overrides, logs, save
|
||||
backups and mod receipts.
|
||||
- **`game.installation_id`** is a separate UUID4 generated once for a concrete
|
||||
installation. Unlike `game.id` (a human-typed/derived slug), it is
|
||||
what Runi uses internally to detect "has this game's install
|
||||
directory been replaced externally" (stamped as a small marker file,
|
||||
`<game_dir>/.runi-installation-id`, checked by `core/config.py`'s
|
||||
`install_marker_matches()`/`runi doctor`). Not meant to be hand-edited.
|
||||
- **`identity.runi_game_id`** is Runi's opaque canonical identity,
|
||||
shared by separate installations and resolved from catalog aliases.
|
||||
- **`identity.ubisoft_store_product_id`** identifies the commercial Ubisoft
|
||||
Store edition used to resolve official boxshots. It is an edition alias and
|
||||
never replaces `runi_game_id`; local catalog entries persist it under
|
||||
`aliases.ubisoft_store`.
|
||||
- **`identity.umu_gameid`** is the value forwarded as umu's `GAMEID` env var.
|
||||
`umu-<appid>` for Steam titles (so community protonfixes key correctly),
|
||||
`umu-default` otherwise. Independently editable if you later find the
|
||||
specific umu-ID for a GOG/Epic title in the community `umu-database`.
|
||||
- **`identity.steam_appid`** is independent of installation source. It is used
|
||||
for Steam CDN artwork, Steam-aware save discovery and recipe matching.
|
||||
- **`identity.steamgriddb_game_id`** is written only after an exact automatic
|
||||
match or an explicit artwork selection. It never implies a Steam AppID or
|
||||
UMU identity.
|
||||
- **`game.display_name`** is the user-facing name and the artwork search term.
|
||||
It is editable and is not treated as a stable game identity.
|
||||
- **`game.platform`** records the original/content platform. A port from
|
||||
GameCube to Linux therefore keeps `platform = "gamecube"`.
|
||||
- **`game.port_target_os`** records the OS of a community port's executable.
|
||||
The Library derives Windows Ports/Linux Ports from it; it is not a launch
|
||||
backend. `classification_source = "manual"` protects a player correction
|
||||
from later catalog reconciliation.
|
||||
- **`runtime.proton = "auto"`** selects the newest installed GE-Proton
|
||||
itself (umu backend only); for the native backend, `proton` must name an
|
||||
already-discovered/installed `ProtonVersion`.
|
||||
- The GUI presents `runtime.backend` as **Launch engine** and separates it from
|
||||
**Runner family** and **Version**. The family is derived from the concrete
|
||||
`runtime.proton` name, so existing TOML and CLI usage remain valid. Curated
|
||||
families are GE-Proton, UMU-Proton, Proton-CachyOS, Proton-EM and Steam-managed Proton
|
||||
Experimental; other discovered tools remain available under Advanced.
|
||||
- **Gamescope refresh and FPS cap are independent.** `refresh_rate` controls
|
||||
the nested display exposed to the game; `fps_limit` applies an optional cap.
|
||||
- **`runtime.cpu_affinity`** wraps the complete launch chain with
|
||||
`taskset --cpu-list` without invoking a shell. This supports older games
|
||||
that fail when they detect or execute across too many CPU cores.
|
||||
- **`[gamescope].filter` vs `[gamescope].scaler`** are independent axes
|
||||
(gamescope's `-F` and `-S` flags) — filter is the upscale algorithm, scaler
|
||||
is how the image is fit into the output window.
|
||||
- **`[env]`** is the escape hatch: anything not modeled as a named field
|
||||
(custom `DXVK_HUD`, `PROTON_LOG`, etc.) goes here and is passed through
|
||||
as-is.
|
||||
- **`[lossless_scaling]`** configures frame generation through a separately
|
||||
installed lsfg-vk layer and a user-owned Steam copy of Lossless Scaling.
|
||||
Runi detects both dependencies and never writes lsfg-vk's global config
|
||||
or distributes `Lossless.dll`. Disabled means no Runi override, so an
|
||||
external lsfg-vk profile can still apply. See
|
||||
[lossless-scaling.md](lossless-scaling.md).
|
||||
- **`[hdr]`** is the single per-game source of truth for native HDR. Enabling
|
||||
it requires Gamescope to be configured already, adds `--hdr-enabled`, enables
|
||||
the Gamescope WSI layer and selects the runner-supported Proton/DXVK HDR
|
||||
variable. Runi only inspects the desktop HDR state; it never changes a
|
||||
monitor or compositor setting. Raw `DXVK_HDR`, `ENABLE_GAMESCOPE_WSI`,
|
||||
`PROTON_ENABLE_HDR` and `PROTON_USE_HDR` entries conflict with managed HDR.
|
||||
See [hdr.md](hdr.md).
|
||||
- **`[logging].output`** picks where a game's process output goes:
|
||||
`console` (default — inherits the terminal, same as always) or `file`
|
||||
(redirected to `~/.local/share/runi/logs/<installation-id>/<timestamp>.log`, with
|
||||
a `latest.log` symlink kept pointing at the most recent run). Never both at
|
||||
once. `runi run <id> --log`/`--console` transiently override this for
|
||||
a single run without touching the saved config.
|
||||
- **`[customize].applied_profile`** tracks which named "customize" profile
|
||||
(community upscaler/mod workaround) is currently applied, if any — see
|
||||
[customize-profiles.md](customize-profiles.md). Set/cleared by
|
||||
`runi customize apply|remove`, not meant to be hand-edited.
|
||||
- **`[notes].key`** is the only thing actually stored in the game's own
|
||||
config — the entries live under
|
||||
`~/.config/runi/notes/<key>/<uuid>.md` (`key` defaults to the PGID
|
||||
if unset). Each entry has a date, title, and Markdown body. The GUI can
|
||||
relink a re-added game to an existing collection. Notes are deliberately kept
|
||||
*outside* the game's own config so notes survive `runi remove` —
|
||||
set the *same* key on two different registrations (e.g. after a
|
||||
reinstall creates a new `game.id`) to relate their notes. A deliberately
|
||||
lightweight starting point rather than a designed findings/compatibility
|
||||
schema — see roadmap.md (source repository).
|
||||
- **`[saves].path`** is where a game's save files live, for
|
||||
`runi saves backup|restore|list`. The GUI can query PCGamingWiki,
|
||||
resolve common Windows paths against the Wine prefix, and present candidates;
|
||||
the user must confirm the stored path. Relative paths resolve against the Wine prefix's
|
||||
`drive_c/` (most saves live under a Windows user-profile path there);
|
||||
absolute paths are used as-is.
|
||||
|
||||
Every field above (plus its type, default, and description) is also
|
||||
queryable straight from the CLI — see `runi schema` below.
|
||||
|
||||
## Compatibility boundary
|
||||
|
||||
Schema v8 is written for new/edited registrations. Schema-v2 through v7
|
||||
registrations
|
||||
remain readable as Windows executable/Proton launches and are not rewritten by
|
||||
read-only operations. Their first later save creates a version-labelled backup,
|
||||
for example `.toml.v2.bak`, before writing v8. Schema v1 and unknown future
|
||||
schemas remain rejected.
|
||||
|
||||
## The `--json` output contract
|
||||
|
||||
`--json` is an interface, not a convenience: the Decky plugin reads Runi by
|
||||
running these commands and parsing stdout, so the shape is the coupling
|
||||
between the two. Three properties hold everywhere.
|
||||
|
||||
**Every document is an object.** Listings carry their payload under a named
|
||||
key rather than being a bare array, which leaves room beside it:
|
||||
|
||||
```json
|
||||
{ "games": [ ... ] } // list, steam-scan
|
||||
{ "sections": [ ... ] } // schema
|
||||
{ "versions": [ ... ] } // versions
|
||||
{ "findings": [ ... ] } // doctor
|
||||
```
|
||||
|
||||
**A failure carries the exception type**, because `core` raises typed
|
||||
exceptions precisely so a caller can tell one from another, and a sentence
|
||||
gives a program nothing to branch on:
|
||||
|
||||
```json
|
||||
{ "error": { "type": "GameNotFoundError", "message": "no such game: minos" } }
|
||||
```
|
||||
|
||||
**A setting that will not take effect says so in the document.** `runi config
|
||||
--json` reports both what changed and what that change cannot do, instead of
|
||||
sending the warning to stderr where a JSON consumer never reads it:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "minos", "updated": ["runtime.proton"], "ineffective": ["runtime.proton"],
|
||||
"notes": [{ "section": "runtime", "fields": ["backend", "proton", "prefix"],
|
||||
"engine": "steam",
|
||||
"reason": "Steam picks the compat tool and owns the prefix" }],
|
||||
"config": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
`--edit` is refused under `--json`: it opens an interactive editor, which a
|
||||
program has no use for.
|
||||
|
||||
## CLI reference
|
||||
|
||||
- `runi add [<exe>] [--id ID] [--name NAME] [--store local|steam|gog|egs] [--steam-appid N] [--backend umu|native] [--proton NAME|auto] [--args ...]`
|
||||
- `runi list [--json]`
|
||||
- `runi versions [--refresh] [--backend umu|native|all] [--json]`
|
||||
- `runi versions list-remote --source ge-proton|umu-proton|proton-cachyos|proton-em`
|
||||
- `runi versions install <source> [<version>|latest] [--json]`
|
||||
- `runi versions remove <name>`
|
||||
- `runi run <id> [--dry-run] [--verbose] [--debug] [--log|--console]`
|
||||
- `runi config <id> [--set section.key=value] [--edit] [--json]`
|
||||
- `runi steam-scan [--json]`
|
||||
- `runi optiscaler install <id> [--dll dxgi.dll] [--version latest]`
|
||||
- `runi optiscaler configure <id> [--provider auto|manual|ge-integrated] [--dll dxgi.dll] [--version default] [--fsr4]`
|
||||
- `runi optiscaler status <id>`
|
||||
- `runi optiscaler migrate <id> --to manual|ge-integrated [--dll dxgi.dll] [--version default] [--fsr4]`
|
||||
- `runi optiscaler remove <id>`
|
||||
- `runi remove <id> [--keep-prefix]`
|
||||
- `runi schema [--section runtime|gamescope|mangohud|linuwux|proton_tweaks|optiscaler|lossless_scaling|logging|customize|notes|saves] [--json]`
|
||||
- `runi customize list [--json]`
|
||||
- `runi customize apply <id> <profile-name-or-path> [--json]`
|
||||
- `runi customize remove <id> [--json]`
|
||||
- `runi mangohud-preset list [--json]`
|
||||
- `runi mangohud-preset save <name> (--from-file <path> | --from-game <id>) [--description TEXT] [--json]`
|
||||
- `runi mangohud-preset apply <name> <id> [--json]`
|
||||
- `runi mangohud-preset remove <id> [--json]`
|
||||
- `runi prefix open <id>`
|
||||
- `runi lossless-scaling [--json]` — whether frame generation is usable
|
||||
- `runi lossless-scaling install [--version TAG] [--json]` — install the
|
||||
lsfg-vk Vulkan layer (never `Lossless.dll`); refuses if a layer of that
|
||||
name is already registered. See [lossless-scaling.md](lossless-scaling.md)
|
||||
- `runi lossless-scaling remove [--json]` — remove the layer runi installed
|
||||
- `runi bundle export <id> --output <file.pfy>`
|
||||
- `runi bundle inspect <file.pfy>`
|
||||
- `runi bundle verify <file.pfy>`
|
||||
- `runi bundle import <file.pfy> [--exe <path>] [--as <new-id>]`
|
||||
- `runi saves backup <id> [--json]`
|
||||
- `runi saves list <id> [--json]`
|
||||
- `runi saves restore <id> <backup-file> [--json]`
|
||||
- `runi saves import-ludusavi <id> <backup-folder> [--preview] [--no-set-save-path] [--json]`
|
||||
- `runi gui` — launches the PySide6 GUI (optional `[gui]` extra)
|
||||
- `runi doctor [--fix] [--json]` — audits the registry against the
|
||||
real filesystem: missing exes, stale OptiScaler/customize state, an
|
||||
install-directory marker mismatch, orphaned prefix/save-backup/log
|
||||
directories. `--fix` auto-clears the safe findings (stale state, marker
|
||||
mismatch) - never deletes an orphaned directory
|
||||
- `runi doctor remove-orphan <path> [--yes] [--json]` — permanently
|
||||
deletes one orphaned directory (the exact path as printed by
|
||||
`runi doctor`); confirms first (showing total size) unless `--yes`
|
||||
- `runi notes <id> [--edit] [--json]` — view/edit a game's findings
|
||||
(stored outside its own config, see `[notes].key` above)
|
||||
|
||||
See [user-flows.md](user-flows.md) for these in context, and
|
||||
[customize-profiles.md](customize-profiles.md) for the profile TOML format.
|
||||
@@ -0,0 +1,208 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# Controller support and Steam integration
|
||||
|
||||
This document records the controller research behind Runi's input
|
||||
implementation. Observed facts belong here; future product work belongs in
|
||||
`roadmap.md`.
|
||||
|
||||
## Current architecture
|
||||
|
||||
Runi detects the 2026 Steam Controller at startup and on hotplug through
|
||||
SDL3, with evdev as a legacy fallback. During a direct game session it maps the
|
||||
SDL state either to an Xbox 360-shaped `uinput` device or to separate virtual
|
||||
keyboard and mouse devices, then temporarily grabs the controller's desktop
|
||||
keyboard/mouse event nodes. Launcher navigation is locked while the game owns
|
||||
the controller. The Xbox path was validated with DiRT Showdown: Wine sees the
|
||||
virtual device as XInput and the game accepts it.
|
||||
|
||||
The direct USB path was validated on real `28de:1302` hardware: launcher
|
||||
navigation worked, a Direct game received one controller, Steam's desktop
|
||||
mouse/keyboard output stayed suppressed during the session, and launcher
|
||||
control returned after the game exited. The wireless puck uses `28de:1304`
|
||||
through the same bridge and suppression path.
|
||||
|
||||
Every Play action asks for one of two modes:
|
||||
|
||||
- **Runi Direct — Recommended** never requires Steam. It automatically
|
||||
selects either SDL3 → `uinput` → Wine XInput or a per-game Keyboard & Mouse
|
||||
profile. The launch dialog can override that choice for one session.
|
||||
- **Steam Input** uses a deterministic non-Steam shortcut. The shortcut runs
|
||||
`runi steam-launch <installation-id>`; that command stays alive until
|
||||
the game exits so Steam does not fall back to its Desktop layout. The local
|
||||
bridge is not started in this mode, avoiding duplicate controllers.
|
||||
|
||||
Steam is detected in native and Flatpak locations. The most recently used
|
||||
local account is selected in v1. Runi preserves unknown VDF fields, backs
|
||||
up `shortcuts.vdf`, writes atomically, tags only its own shortcut, and derives
|
||||
a stable shortcut AppID from `runi-installation-id`. Steam must be closed
|
||||
while a shortcut is created, repaired or removed.
|
||||
|
||||
Controller troubleshooting is explicit rather than permanently verbose.
|
||||
Overview → Technology → **Run Controller Diagnostic** launches the Direct path
|
||||
with temporary Wine HID/XInput tracing, counts SDL3-to-uinput changes, checks
|
||||
whether the virtual device is observable in the launched process tree, and
|
||||
stores a per-installation stage report. UMU's launcher output and Proton's
|
||||
separate `steam-<appid>.log` are correlated because the Wine HID/XInput trace
|
||||
is written to the latter. Normal launches do not enable these trace channels.
|
||||
|
||||
## Keyboard & Mouse profiles
|
||||
|
||||
Games without native controller support can use a Runi-owned profile.
|
||||
The left stick emits WASD, the right trackpad controls the mouse with a
|
||||
right-stick fallback, and buttons, triggers and paddles emit configured
|
||||
keyboard or mouse actions. Gyro and haptics are not part of this first profile
|
||||
version.
|
||||
|
||||
Bundled profiles live in `runi/controller_profiles`. User changes are
|
||||
stored in `~/.config/runi/controller-profiles/<pgid>.toml`; the filename
|
||||
uses the canonical Runi Game ID, so a profile survives removal and
|
||||
re-import of an installation. Config → Controller edits the persistent method,
|
||||
pointer tuning and bindings. Reset removes only the user override and reveals
|
||||
the bundled profile again.
|
||||
|
||||
The Thing That Happened is the first curated Keyboard & Mouse profile. Its
|
||||
mapping is based on the game's own key-binding screen: triggers become aim and
|
||||
shoot, face buttons cover jump/crouch/use/reload, shoulders cycle weapons,
|
||||
D-pad selects or holsters weapons, rear buttons cover grenade/fire mode and
|
||||
flashlight/night vision, and Quick Access opens the map. The Guide button
|
||||
remains reserved.
|
||||
|
||||
## Steam Input is not XInput
|
||||
|
||||
Some games ask Valve's native action API for named digital and analog actions
|
||||
instead of polling XInput. Technology Scan detects embedded interface names
|
||||
such as `SteamInput006` and recommends Steam mode, but Direct remains available
|
||||
with a warning.
|
||||
|
||||
Digimon Story Time Stranger is the reference compatibility case. Its
|
||||
executable requests `SteamInput006`. The Runi virtual Xbox reaches Wine
|
||||
correctly, and the Wine trace confirms `VID_045E&PID_028E`, a `WINEXINPUT`
|
||||
device and an XInput controller opened at index 0. Finding action-set files is
|
||||
configuration evidence, not proof that the provider consumed input.
|
||||
|
||||
The inspected installation initially contained a 7.3 MB `steam_api64.dll`
|
||||
from `gbe_fork`, built on 27 November 2025, plus action manifests named
|
||||
`InGameControls.txt` and `MenuControls.txt`. Updating the provider to the
|
||||
verified Windows x64 VS2022 regular build from `gbe_fork 2026-05-30` did not by
|
||||
itself fix input. A temporary debug build produced `STEAM_LOG_*.log`, which
|
||||
showed that the provider repeatedly found one connected controller, while the
|
||||
game requested these current named actions:
|
||||
|
||||
```text
|
||||
Square Cross Circle Triangle L1 R1 L2 R2 Select Start L3 R3
|
||||
Up Right Down Left L_Analog_controls R_Analog_controls
|
||||
```
|
||||
|
||||
The bundled manifests used different action names. Nearly every lookup
|
||||
therefore returned handle `0`; only `Select` matched by accident and was
|
||||
incorrectly bound to `A`. The game made no `GetActionSetHandle` or
|
||||
`ActivateActionSet` calls, so the provider retained its lexicographically first
|
||||
action set. The validated fix was to add
|
||||
`steam_settings/controller/00_Default.txt` so the compatible set becomes the
|
||||
default:
|
||||
|
||||
```ini
|
||||
Square=X
|
||||
Cross=A
|
||||
Circle=B
|
||||
Triangle=Y
|
||||
L1=LBUMPER
|
||||
R1=RBUMPER
|
||||
L2=DLTRIGGER
|
||||
R2=DRTRIGGER
|
||||
Select=BACK
|
||||
Start=START
|
||||
L3=LSTICK
|
||||
R3=RSTICK
|
||||
Up=DUP
|
||||
Right=DRIGHT
|
||||
Down=DDOWN
|
||||
Left=DLEFT
|
||||
L_Analog_controls=LJOY=joystick_move
|
||||
R_Analog_controls=RJOY=joystick_move
|
||||
```
|
||||
|
||||
The regular provider build was restored after tracing. Direct mode was then
|
||||
validated end-to-end without Steam:
|
||||
Steam Controller → SDL3 → uinput Xbox 360 → Wine XInput → `gbe_fork` named
|
||||
actions → game. This is a game/build-specific compatibility record, not a
|
||||
generic mapping to install into unrelated games.
|
||||
|
||||
Runi models this as a **local Steam API provider**, not as a special case
|
||||
for one project. Detection is capability-based and conservative: an ordinary
|
||||
`steam_api64.dll` is not enough because official games ship it too. Embedded
|
||||
provider markers, the adjacent configuration layout, exported interfaces and
|
||||
controller action sets are normalized by provider adapters. Goldberg original,
|
||||
Goldberg-derived forks, RUNE-style configuration and unknown local providers
|
||||
all produce the same capability report; the launcher never branches on a
|
||||
release-group name.
|
||||
|
||||
The original Goldberg emulator documents a SteamController/SteamInput bridge
|
||||
that accepts XInput and maps named actions through files in
|
||||
`steam_settings/controller`. A complete original-style configuration can
|
||||
therefore prefer Runi's Direct virtual Xbox path. Goldberg-derived forks
|
||||
may instead expose a supported non-Steam takeover setting in
|
||||
`configs.main.ini`. When that exact capability is detected, Steam mode may set
|
||||
`disable_steamoverlaygameid_env_var=1`, preserving
|
||||
`configs.main.ini.runi-backup`. Unknown providers are reported but never
|
||||
modified. Runi does not replace or rename Steam API DLLs.
|
||||
|
||||
A second Digimon installation supplied a useful independent validation. It
|
||||
contains the same game executable and original Steam API payload as the GBE
|
||||
installation, but uses `steam_emu.ini` from RUNE. That configuration declares
|
||||
`SteamInput006`, an enabled controller, a `controls` action set and explicit
|
||||
Xbox mappings. Runi therefore reports `steam_input_via_xinput` and prefers
|
||||
Direct without modifying the provider. The same configuration also exposes
|
||||
its per-prefix save location under
|
||||
`users/Public/Documents/Steam/RUNE/1984270`. RUNE is implemented as one
|
||||
adapter; future layouts can add an adapter while recommendations remain driven
|
||||
by observed capabilities.
|
||||
|
||||
## Projects inspected
|
||||
|
||||
- [Steam Controller Desktop Input Toggle](https://github.com/GloriousEggroll/steam-controller-desktop-input)
|
||||
demonstrates that Steam's internal controller API can persistently select
|
||||
its empty Desktop action-set configuration for direct USB (`28de:1302`) and
|
||||
wireless-dongle (`28de:1304`) connections. Runi uses those transport
|
||||
identities for its session-scoped Direct bridge, but does not integrate the
|
||||
tool: the inspected repository has no explicit license, is very new, and
|
||||
requires an unauthenticated local CEF debugging endpoint plus undocumented
|
||||
Steam UI APIs. Any future integration needs an opt-in security review,
|
||||
verified restore semantics and crash recovery; Node.js and CEF remote
|
||||
debugging are not Runi runtime requirements.
|
||||
- [SteamlessController](https://github.com/ddeverill/SteamlessController)
|
||||
validates the same architecture on Windows: raw Steam Controller HID,
|
||||
lizard-mode suppression and a virtual Xbox through ViGEm. Useful future
|
||||
ideas include its documented 2026 HID reports, heartbeat, reverse rumble,
|
||||
multiple devices, back buttons, trackpads, gyro and diagnostic probes.
|
||||
- [SISR](https://github.com/Alia5/SISR) redirects Steam Input configurations to
|
||||
system-level virtual devices. On Linux it currently requires Steam, CEF
|
||||
remote debugging, USBIP and a VIIPER system service; it is inspiration, not
|
||||
a Runi dependency.
|
||||
- reWASD is a proprietary Windows product and is useful only as UX inspiration
|
||||
for per-game profiles and remapping.
|
||||
- [Steam Shortcut Manager](https://github.com/CorporalQuesadilla/Steam-Shortcut-Manager/wiki/Steam-Shortcuts-Documentation)
|
||||
documents `shortcuts.vdf`, CRC-based IDs and `steam://rungameid` URLs. Its
|
||||
2018 byte parser is not reused; Runi uses Python's `vdf` package and
|
||||
preserves modern and unknown fields.
|
||||
- Heroic and Lutris do not provide their own Steam Input implementation. They
|
||||
run normally without Steam and optionally create non-Steam shortcuts. Their
|
||||
reported failure mode—launcher bootstrap exits while the game remains
|
||||
open—informed Runi's blocking `steam-launch` command.
|
||||
- `sc-controller` remains design input for profiles, gyro, haptics and a
|
||||
virtual keyboard, not a runtime dependency.
|
||||
|
||||
## Known limitations in v1
|
||||
|
||||
- Only the Steam Controller is a supported physical controller.
|
||||
- Xbox Direct exposes the standard Xbox control set. Keyboard & Mouse profiles
|
||||
can use rear buttons and the right trackpad; gyro, rumble and per-mode layers
|
||||
are not yet forwarded.
|
||||
- Steam mode needs a locally installed Steam client and a signed-in user.
|
||||
- Account selection is automatic when several local accounts exist.
|
||||
- A non-Steam shortcut cannot make every replaced or incompatible local Steam
|
||||
API provider communicate with the real Steam client. Known behavior is
|
||||
selected from reported capabilities; unknown providers remain advisory.
|
||||
- No Steam API DLL, driver, SISR, VIIPER or reWASD component is distributed.
|
||||
@@ -0,0 +1,164 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# Customize profiles
|
||||
|
||||
Community workarounds for unofficial/leaked upscaler builds (new FSR
|
||||
versions, DLSS/XeSS swaps, and similar) recur — a "customize profile" is a
|
||||
named, reusable recipe for one of these, so applying (and cleanly reverting)
|
||||
one doesn't mean hand-editing files each time.
|
||||
|
||||
Validated for real: FSR 4.1.1 INT8 on non-RDNA4 hardware, applied to a real
|
||||
game via OptiScaler, checksum-verified to reproduce a manually-confirmed
|
||||
working in-game result exactly. The equivalent non-OptiScaler ("native")
|
||||
path was also investigated for real, in depth, and does **not** work on
|
||||
this hardware/Proton build — see "Follow-up investigation" below for the
|
||||
full root cause (it's not just a per-game quirk: a specific vkd3d-proton
|
||||
compile-time flag is missing). OptiScaler is the correct tool for forcing
|
||||
an upscaler backend/version a game wouldn't otherwise offer, not merely a
|
||||
fallback.
|
||||
|
||||
## Where profiles live
|
||||
|
||||
`~/.config/runi/profiles/<name>.toml`. `runi customize apply <id> <name>`
|
||||
looks there by name; you can also pass a direct file path instead of a name
|
||||
for a one-off profile you don't want to save centrally.
|
||||
|
||||
## Format
|
||||
|
||||
```toml
|
||||
# ~/.config/runi/profiles/fsr411-workaround.toml
|
||||
[profile]
|
||||
name = "fsr411-workaround"
|
||||
description = "Community FSR 4.1.1 INT8 workaround for non-RDNA4 GPUs (via OptiScaler)"
|
||||
|
||||
[[files]]
|
||||
src = "/home/user/Downloads/FidelityFX-Samples-v2.3.0-prebuilt/Samples/Upscalers/FidelityFX_FSR/dx12/x64/Release/amd_fidelityfx_upscaler_dx12.dll"
|
||||
dest = "amd_fidelityfx_upscaler_dx12.dll" # relative to the game's exe directory
|
||||
|
||||
[[ini_edits]]
|
||||
file = "OptiScaler.ini" # relative to the game dir; errors if missing
|
||||
[ini_edits.set]
|
||||
Fsr4Update = "true"
|
||||
|
||||
[env]
|
||||
FSR4_UPGRADE = "1" # only matters on a non-OptiScaler path; harmless otherwise
|
||||
```
|
||||
|
||||
- **`[[files]]`** — each entry copies `src` (a local path; leaked/unofficial
|
||||
builds have no stable official URL to automate) to `dest`, relative to the
|
||||
game's own exe directory. Goes through the same `core/backup.py` safety
|
||||
net every other game-file write in Runi uses — a colliding existing
|
||||
file is backed up first, never silently overwritten.
|
||||
- **`[[ini_edits]]`** — patches specific `key=value` lines in a target ini
|
||||
file (also relative to the game dir), leaving every other line/comment
|
||||
untouched. The target file must already exist (e.g. OptiScaler must
|
||||
already be installed) — this errors rather than silently skipping, since
|
||||
that usually means a prerequisite step was missed.
|
||||
- **`[env]`** — merged into the game's own `[env]` (overwrites, since
|
||||
applying a profile is a deliberate action). Only the keys a profile sets
|
||||
are removed again on `customize remove` — other env vars you've set
|
||||
yourself (e.g. `WINEDLLOVERRIDES` from an OptiScaler install) are left
|
||||
alone.
|
||||
|
||||
A profile that only needs the non-OptiScaler path (relying on a game's own
|
||||
native upscaler support rather than OptiScaler's proxy) simply omits
|
||||
`[[ini_edits]]` entirely — there's no conditional branching inside one
|
||||
profile; write two profiles if you want both paths available.
|
||||
|
||||
### The non-OptiScaler (native) path is not universally reliable
|
||||
|
||||
Tried for real on Gothic 1 Remake, immediately after the OptiScaler path
|
||||
above was confirmed working: swapping `amd_fidelityfx_upscaler_dx12.dll` and
|
||||
setting `FSR4_UPGRADE=1` **with OptiScaler removed had no effect** — the
|
||||
in-game menu still showed the game's original FSR 3.1.4, not 4.1.1.
|
||||
`PROTON_LOG=1` (`runi run <id> --debug --log`) confirmed why: the game
|
||||
never loaded `amd_fidelityfx_upscaler_dx12.dll` as a module at all, even
|
||||
after several minutes of actual play — strongly suggesting this title's
|
||||
FidelityFX integration is compiled directly into the `.exe` rather than
|
||||
deployed as the separate, swappable DLL some other games/the reference
|
||||
FidelityFX SDK samples use.
|
||||
|
||||
**OptiScaler works regardless of how a game is built** because it
|
||||
intercepts at the DXGI/D3D12 layer itself (hijacking `dxgi.dll`) and loads
|
||||
its own upscaler module unconditionally — it doesn't depend on the target
|
||||
game choosing to dynamically load that DLL by name. The non-OptiScaler path
|
||||
only works for games that already do that dynamic loading themselves.
|
||||
|
||||
**Practical takeaway**: prefer the OptiScaler path by default. Before
|
||||
relying on a non-OptiScaler profile for a new game, verify the target DLL
|
||||
actually gets loaded — `runi run <id> --debug --log`, play for a
|
||||
minute, then check the Proton debug log
|
||||
(`~/.local/share/runi/logs/<id>/steam-*.log`) for the DLL's filename
|
||||
in a `loaddll` trace line. If it never appears, the swap won't do anything
|
||||
for that game, no matter what env vars are set.
|
||||
|
||||
### Follow-up investigation: why the non-OptiScaler path can't work here at all
|
||||
|
||||
Pushed further using AMD's own official FidelityFX SDK sample app
|
||||
(`FidelityFX_FSR.exe`, which ships natively alongside the swappable DLL) as
|
||||
a clean, decoupled test case, fully rendering and running (its media assets
|
||||
had to be fetched separately via its bundled `MediaDelivery.exe` tool first —
|
||||
the "prebuilt" download didn't include them). Along the way this also
|
||||
surfaced and fixed a real, general bug: Runi wasn't setting the
|
||||
launched process's working directory to the game's own folder (see
|
||||
`docs/architecture.md`'s cwd design point) — the sample couldn't even parse
|
||||
its own bundled config file until that was fixed. That fix is unrelated to
|
||||
FSR specifically and helps any game relying on relative-path resource
|
||||
loading.
|
||||
|
||||
With the sample actually running: `amd_fidelityfx_upscaler_dx12.dll`
|
||||
genuinely loaded, and the GPU was confirmed non-RDNA4 (AMD Strix Halo/RDNA
|
||||
3.5) — exactly the target hardware for this workaround. But the sample's
|
||||
own "Override FSR Version" UI only offered FSR 2.3/3.1 — **no FSR4 entry at
|
||||
all**, regardless of the newer DLL being loaded. The downloaded
|
||||
`amdxcffx64.dll` was never loaded by anything either (checked the debug log
|
||||
directly) — it appears to target a real Windows AMD driver install
|
||||
(replacing a system32 file that only exists there), which doesn't map onto
|
||||
Wine at all.
|
||||
|
||||
The actual root cause: vkd3d-proton (the D3D12-to-Vulkan layer Proton uses)
|
||||
already has its own internal FSR4 support and vendor-hacks, but the INT8/
|
||||
older-GPU emulation path that would make a non-RDNA4 GPU qualify is **not
|
||||
enabled by default and requires building vkd3d-proton from source with
|
||||
specific compile flags** — it's not a runtime env var or a droppable file,
|
||||
it's a compile-time choice baked into a specific Proton build. The
|
||||
FidelityFX SDK's own hardware capability query correctly (from its
|
||||
perspective) declined to offer FSR4 without that underlying capability.
|
||||
|
||||
**Conclusion**: OptiScaler isn't a workaround-of-last-resort here — it's
|
||||
the actually-correct tool for this job. It intercepts the game's upscaler
|
||||
calls itself and has its own explicit force-override setting
|
||||
(`Fsr4Update=true`), so it never depends on the game's own auto-detection,
|
||||
the FidelityFX SDK's own UI, or a specific vkd3d-proton build having a
|
||||
particular compile-time flag. Replicating what OptiScaler does without
|
||||
OptiScaler would mean rebuilding OptiScaler itself — not reasonable scope
|
||||
for Runi (same philosophy as wrapping `umu-run` instead of
|
||||
reimplementing its runtime). **Use the OptiScaler path for any game where
|
||||
you need to force an upscaler backend/version the game or SDK wouldn't
|
||||
otherwise offer.**
|
||||
|
||||
## Commands
|
||||
|
||||
- `runi customize list [--json]` — profiles found in
|
||||
`~/.config/runi/profiles/`.
|
||||
- `runi customize apply <id> <profile-name-or-path> [--json]` —
|
||||
applies a profile. **If a different profile is already applied to that
|
||||
game, it's fully reverted first** (via its own tracked manifest), then the
|
||||
new one is applied — no simultaneous stacking.
|
||||
- `runi customize remove <id> [--json]` — reverts whatever the
|
||||
currently-applied profile changed: restores backed-up files exactly,
|
||||
removes files the profile added, and clears the env vars it set.
|
||||
|
||||
## How it composes with OptiScaler
|
||||
|
||||
Nothing here is OptiScaler-specific — the mechanism is generic (file copies
|
||||
+ ini edits + env vars against any game directory). It happens that the
|
||||
proven example edits `OptiScaler.ini`, since that's what the real workaround
|
||||
needed. A profile for the non-OptiScaler path (native FSR4 support some
|
||||
games already ship) would have no `[[ini_edits]]` at all, just the file
|
||||
copies and `FSR4_UPGRADE=1`.
|
||||
|
||||
See architecture.md (source repository) for the design
|
||||
rationale, and [config-schema.md](config-schema.md) for the `[customize]`
|
||||
per-game config section this feature adds.
|
||||
@@ -0,0 +1,237 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# Canonical feature catalogue
|
||||
|
||||
This document is the canonical inventory of Runi's implemented product
|
||||
capabilities. It answers **what Runi can do today**; it is not a roadmap,
|
||||
a configuration-field reference or an implementation history.
|
||||
|
||||
- **GUI** means the capability is available from `runi gui`.
|
||||
- **CLI** means it has a public command-line entry point.
|
||||
- **Automatic** means the core applies it as part of registration, inspection
|
||||
or launch rather than exposing a separate control.
|
||||
|
||||
Exact per-game and global configuration fields remain authoritative in
|
||||
[the configuration schema](config-schema.md). Planned, deferred and rejected
|
||||
ideas belong in the roadmap (source repository), not in this catalogue.
|
||||
|
||||
## Library and application experience
|
||||
|
||||
| Capability | Surface | Current behavior |
|
||||
|---|---|---|
|
||||
| Launcher-first game library | GUI | Grid and list views, title search, store/collection filters, feature badges, readiness state and running-state controls. |
|
||||
| Per-game cockpit | GUI | Overview, Runtime, HDR, Controller, OptiScaler, Lossless Scaling, Logs, Notes and Saves tabs. Windows-only tools are hidden where they do not apply; an applied legacy recipe temporarily exposes a rollback-only Recipe tab. |
|
||||
| Game collections and classification | GUI, CLI | Distinguishes PC games, Windows ports and Linux-port classification while preserving the original platform. Custom original-platform labels are supported. Linux ports are classification-only in the current release. |
|
||||
| Controller navigation | GUI | A supported Steam Controller can navigate the library, dialogs and game tabs. Launcher input is locked while a game session owns the controller. |
|
||||
| Appearance and placement | GUI | System, light, dark and Breeze theme choices; optional primary-monitor placement where the desktop environment supports it. |
|
||||
| Responsive long-running work | GUI | Runner installation, cover refresh, OptiScaler installation, save backup and other long operations run outside the UI thread. |
|
||||
| Single-instance local intents | GUI, Automatic | A bounded user-local channel can focus the existing window and queue an add-game request, including requests from Dolphin. It is not a daemon or general remote-control API. |
|
||||
|
||||
## Game registration, discovery and identity
|
||||
|
||||
| Capability | Surface | Current behavior |
|
||||
|---|---|---|
|
||||
| Standalone Windows game registration | GUI, CLI | Registers a selected PE executable with name, store, launch arguments, backend and runner choices. Invalid targets and DLLs are rejected; ambiguous utilities produce reviewable warnings and nearby executable alternatives. |
|
||||
| Steam library scan and import | GUI, CLI | Reads installed Steam libraries and imports a selected AppID without modifying Steam's compatibility-tool selection. Runtime/tool AppManifests can still appear in scan results. |
|
||||
| GOG and Epic registration | GUI, CLI | Installed executables can be registered manually and labelled as GOG or Epic. Automatic Heroic/Legendary discovery is not implemented. |
|
||||
| Ubisoft Connect shared store | GUI | Installs or imports Ubisoft Connect in one managed shared prefix, opens it for sign-in/installation, scans local Ubisoft records, imports reviewed games and launches them by deeplink. Runner changes apply to the shared installation, and process monitoring covers the real game lifecycle and optional cloud-sync wait. |
|
||||
| Dolphin integration | GUI, CLI | Optional, user-scoped **Add to Runi** service-menu action for Windows executables. Installation, status and removal are available in Preferences and under `desktop-integration dolphin`. |
|
||||
| Launch-target detection | GUI, CLI, Automatic | Detects the primary executable, install root, useful auxiliary actions and known launch-provider shapes during registration. Supported providers are executable, Xenia, RPCS3 and Ubisoft; commands can run through Proton or directly on the host as appropriate. |
|
||||
| Multiple launch actions | GUI, CLI | A registration can expose Play plus configuration, emulator or tool actions with their own executable, arguments, working directory and wrapper policy. CLI selection uses `run --action`. |
|
||||
| Canonical game identity | GUI, Automatic | Separates the local registration ID, canonical Runi Game ID and installation UUID. Identity evidence can include Steam, UMU, Ubisoft and SteamGridDB identifiers and records confidence/source. |
|
||||
| Reinstall recognition | GUI, CLI, Automatic | Install markers and archived retired configuration allow compatible state to be reconnected after a reinstall, or deliberately replaced with a fresh installation identity. |
|
||||
| Parallel installations | GUI, CLI | Two copies of the same game register side by side. A colliding registration is offered an id suffixed with the build version read from the executable, never a numeric counter, and asks for a discriminator when no build version tells the copies apart. Both share identity, notes and artwork; prefix, settings, logs, save backups and mod receipts stay separate. |
|
||||
| Moved-installation recognition | GUI, CLI | A game folder that was moved or renamed is identified by its install marker and repointed, keeping its prefix, saves, settings and mod receipts, instead of being registered a second time. |
|
||||
| Curated compatibility defaults | Automatic | Exact canonical games can receive narrowly scoped, safe launch defaults from the bundled compatibility catalogue. Explicit user launch options take precedence. |
|
||||
|
||||
See [game identity](game-identity.md), [launch providers](launch-providers.md),
|
||||
[Ubisoft Connect](ubisoft-connect.md) and [user flows](user-flows.md).
|
||||
|
||||
## Runners, prefixes and launch composition
|
||||
|
||||
| Capability | Surface | Current behavior |
|
||||
|---|---|---|
|
||||
| UMU backend | GUI, CLI | Wraps `umu-run`, supplies the UMU Game ID and uses its Steam Runtime handling. |
|
||||
| Native Proton backend | GUI, CLI | Drives a compatible installed Proton build directly without routing the launch through Steam. Builds that declare a required Steam Runtime are excluded from native use. |
|
||||
| Per-game runner selection | GUI, CLI | Stores an explicit Proton build or automatically chooses an installed GE-Proton build. |
|
||||
| Runner discovery | GUI, CLI | Discovers Steam official Proton, GE-Proton, UMU Proton, Proton-CachyOS, Proton-EM and custom local builds. |
|
||||
| Runner catalogue and lifecycle | GUI, CLI | Lists installed and remote builds, installs supported release sources, and removes unused builds. Removal is refused while a registered game references the build. |
|
||||
| Managed or custom Wine prefixes | GUI, CLI | Uses an XDG-managed per-game prefix by default or a user-selected prefix override. Prefix folders can be opened directly. |
|
||||
| Per-game launch environment | GUI, CLI | Persists executable arguments, working directory, arbitrary environment variables, UMU identity, CPU affinity and runner/backend selection in plain TOML. |
|
||||
| Safe command composition | GUI, CLI, Automatic | Produces an argument vector without shell interpolation and composes runtime, graphics, environment and action layers in a deterministic order. |
|
||||
| Dry-run preview | GUI, CLI | Shows the exact environment and command without launching the game. CLI output can be human-readable or JSON. |
|
||||
| Launch lifecycle | GUI, CLI | GUI launches are detached and observable so Play/Stop and running-state locking remain responsive; CLI launches return the process exit status. |
|
||||
| Debug and logging controls | GUI, CLI | Normal, verbose and Proton-debug launch modes; console or per-installation file output; a stable `latest.log`; and an in-app Logs viewer. CLI flags can override saved logging for one launch. |
|
||||
| LinUwUx compatibility wrapper | GUI, CLI | Optional `linuwux` LD_PRELOAD wrapper prefix for Reflex/DenuvOwO-style anti-cheat/DRM loaders under Proton/UMU. Runi only toggles presence and an optional debug flag; linuwux's own fingerprinting, variant resolution and WINEDLLOVERRIDES merging are untouched. `runi doctor` flags a non-blocking advisory if the selected Proton build is not GE-Proton/Proton-CachyOS, the only builds linuwux upstream documents as supported. Combining it with gamescope is refused outright (`IncompatibleWrapperError`) — confirmed crashing on real hardware. MangoHud works alongside it via `MANGOHUD=1` instead of MangoHud's own `LD_PRELOAD` wrapper, which does collide. |
|
||||
| MangoHud presets | GUI, CLI | Named, reusable MangoHud overlay configs saved by Runi (`~/.config/runi/mangohud-presets/`) and applied to a chosen game's `[mangohud].config_file` in one command. Ships one bundled preset (`horizontal-complete`). Deliberately per-game only — never writes the global `~/.config/MangoHud/MangoHud.conf`, which third-party tools like GOverlay/MangoJuice commonly manage themselves. |
|
||||
|
||||
See architecture (source repository), [launch providers](launch-providers.md)
|
||||
and [the configuration schema](config-schema.md).
|
||||
|
||||
## Display, performance and scaling
|
||||
|
||||
| Capability | Surface | Current behavior |
|
||||
|---|---|---|
|
||||
| Gamescope | GUI, CLI | Per-game output/render resolution, FSR/NIS/linear/nearest filter, scaler mode, fullscreen/borderless mode, refresh rate and frame-rate cap. The GUI groups each resolution into an editable preset selector, pairs rate preset sliders with custom numeric input, and initializes new games from the primary monitor when available. |
|
||||
| MangoHud | GUI, CLI | Per-game overlay, frame-rate limit and optional configuration file. The GUI provides a prominent header toggle, an editable preset slider for the limit and an Advanced area for the configuration path. When Gamescope is active, Runi composes MangoHud through Gamescope's supported `--mangoapp` path. |
|
||||
| Proton fullscreen FSR | GUI, CLI | Enables Proton's built-in fullscreen FSR hack and strength independently of Gamescope. |
|
||||
| CPU affinity | GUI, CLI | Applies a Linux CPU-list restriction to the complete launch chain through `taskset`. |
|
||||
| Native HDR | GUI, CLI, Automatic | Enables native game HDR through an already-configured Gamescope session, verifies the matching WSI layer, Vulkan capability and KDE output state where available, and selects a runner-supported Proton/DXVK flag. Confirmed failures block launch; an unverifiable desktop produces an explicit warning. Runi never changes desktop or monitor HDR/WCG settings. |
|
||||
| HDR display and OS guidance | GUI, Automatic | Reads KDE output calibration metadata and presents peak luminance, reference white, colour profile, gamut, RGB range and bit depth as read-only calibration guidance. |
|
||||
| Community HDR settings research | GUI | Optional, disabled-by-default OpenAI research initiated only by the player. It performs at most three searches across an audited source allowlist and can add one author-restricted HDR Game Analysis guide with at most four public images. It returns cited structured slider values, exposes conflicts and never applies settings automatically. |
|
||||
| Persistent HDR knowledge | GUI, Automatic | Stores the latest three successful research runs per canonical game in SQLite, shares them across registrations and keeps stale results visible after 30 days. Failed refreshes do not replace successful evidence. |
|
||||
| OptiScaler provider management | GUI, CLI, Automatic | The GUI offers two mutually-exclusive paths: capability-probed Runtime managed support and a pinned, checksum-verified FSR 4.1.1 INT8 quality-first installation. The INT8 path retains reversible game-file backups and owned Wine overrides; runtime setups use runner variables and prefix tracking without placing proxy DLLs beside the game. Provider changes require an explicit, receipt-backed migration. Generic manual installation remains available through the CLI for compatibility. |
|
||||
| Lossless Scaling frame generation | GUI, CLI configuration | Detects a supported `lsfg-vk` Vulkan layer and the user's Steam-owned Lossless Scaling installation, then enables per-game LSFG frame generation with multiplier, flow scale, performance mode and supported version-specific options. It does not modify the global lsfg-vk profile. |
|
||||
| Feature compatibility guidance | GUI | Reports missing scaling dependencies and relevant Gamescope, HDR, Vulkan and anti-cheat cautions. OptiScaler upscaling and LSFG frame generation can be composed, but enabling frame generation in both technologies is unsupported. |
|
||||
|
||||
Lossless Scaling on Windows includes upscaling, but Runi's Linux
|
||||
integration currently configures only LSFG frame generation through lsfg-vk.
|
||||
See [Lossless Scaling](lossless-scaling.md) and the display pipeline in
|
||||
architecture (source repository).
|
||||
OptiScaler provider selection, migration and runner ownership are documented in
|
||||
[OptiScaler providers](optiscaler.md).
|
||||
|
||||
## Controllers and Steam Input
|
||||
|
||||
| Capability | Surface | Current behavior |
|
||||
|---|---|---|
|
||||
| Steam Controller detection | GUI, Automatic | Detects supported 2026 Steam Controller USB and wireless-puck transports at startup and on hotplug through SDL3, with a legacy evdev fallback. Other physical controller families are not supported. |
|
||||
| Runi Direct Xbox mode | GUI | Maps the physical controller through SDL3 to a virtual Xbox 360-shaped `uinput` device for Wine/XInput games, without requiring Steam. Desktop mouse/keyboard event nodes are temporarily grabbed during the session to prevent duplicate input. |
|
||||
| Runi Direct Keyboard & Mouse mode | GUI | Per-game mapping for games without native controller support, including pointer tuning, keyboard/mouse bindings, rear buttons and right-trackpad mouse input. Gyro, rumble and mode layers are not implemented. |
|
||||
| Curated and user controller profiles | GUI | Bundled profiles can be overridden per canonical game and reset without changing the bundled definition. Profiles survive registration removal and re-import. |
|
||||
| Steam Input launch mode | GUI, Automatic | Creates and maintains a deterministic Runi-owned non-Steam shortcut, including stable AppID, launch options, tags and artwork. Steam must be closed while the shortcut is created, repaired or removed. |
|
||||
| Steam API provider inspection | GUI, Automatic | Detects supported Goldberg-derived, RUNE-style and unknown local Steam API provider capabilities. It can recommend Direct or Steam Input and apply only a narrowly supported, backed-up compatibility setting; it never replaces Steam API DLLs. |
|
||||
| Controller diagnostics | GUI | Runs explicit SDL/uinput/Wine HID and XInput tracing, correlates runtime logs and stores a per-installation stage report. Normal launches do not enable the diagnostic trace. |
|
||||
|
||||
See [controller support](controller-support.md) for hardware requirements,
|
||||
validated paths and current limits.
|
||||
|
||||
## Technology analysis and legacy recipes
|
||||
|
||||
| Capability | Surface | Current behavior |
|
||||
|---|---|---|
|
||||
| Bounded local Technology Scan | GUI, Automatic | Inventories graphics APIs, upscalers/frame generation, engines, middleware, anti-cheat, DRM and Steam Input evidence without treating a filename match as certainty. Upscaler versions are read from the binary's PE version resource, because one filename covers several versions. |
|
||||
| PCGamingWiki enrichment | GUI | Adds sourced compatibility evidence in the background. SteamDB remains an explicit external link because it has no supported public API. |
|
||||
| Runtime technology diagnostic | GUI | An explicit diagnostic launch can observe loaded runtime modules. It is never run silently. |
|
||||
| Evidence and confidence model | GUI, Automatic | Retains source, observation time and confidence for findings and ties installation-specific evidence to the installation identity. |
|
||||
| Legacy recipe preservation | GUI, Automatic | New games do not expose recipe discovery or application. When an existing installation has an applied receipt, Runi temporarily exposes **Recipe (Legacy)** so its exact rollback remains available. |
|
||||
| Recipe engine and asset cache | Automatic | Existing declarative definitions, checksum-validated private assets and transactional receipts remain readable for compatibility and rollback. No remote catalogue or new GUI application flow is exposed. |
|
||||
| Advanced Customize profiles | CLI | Applies named file-copy and INI-edit workaround profiles, records the previous state and restores it precisely. This remains the advanced/manual path for community modifications. |
|
||||
|
||||
See [Technology Scan](technology-scan.md), the retained recipe
|
||||
subsystem (source repository) and [Customize profiles](customize-profiles.md).
|
||||
|
||||
## Artwork, metadata and preferences
|
||||
|
||||
| Capability | Surface | Current behavior |
|
||||
|---|---|---|
|
||||
| Steam artwork fallback | GUI, Automatic | Uses exact Steam identity to retrieve portrait library artwork from Steam's CDN. |
|
||||
| SteamGridDB artwork | GUI | Supports API-key testing, exact candidate review, manual cover selection and optional automatic downloads for missing covers. |
|
||||
| Ubisoft artwork | GUI, Automatic | Uses confirmed Ubisoft Store product identity to resolve official box art. |
|
||||
| Custom covers and cache controls | GUI | Keeps user-selected covers separate from automatically downloaded cache; can refresh missing covers or clear only automatic artwork. |
|
||||
| Cover and appearance preferences | GUI | Persists artwork settings, theme and window-placement behavior globally rather than per game. |
|
||||
|
||||
## Saves, notes, portability and recovery
|
||||
|
||||
| Capability | Surface | Current behavior |
|
||||
|---|---|---|
|
||||
| Save-path discovery and confirmation | GUI | Queries PCGamingWiki for candidate save locations, resolves Windows paths inside the prefix and lets the user confirm or enter a path manually. |
|
||||
| Save backup and restore | GUI, CLI | Creates timestamped per-installation backups, lists them and restores a selected backup for a configured save path. |
|
||||
| Persistent game notes | GUI, CLI | Stores Markdown findings outside the registration TOML. A canonical/shared notes key can preserve or relate notes across registrations. |
|
||||
| Retired installation archive | Automatic | Removing a game archives its last configuration by installation identity so compatible state can later be reconnected. |
|
||||
| Portable prefix bundles | CLI | Exports `.pfy` bundles containing the Wine prefix and selected Runi state, inspects metadata without extraction, verifies format and SHA-256 payloads, and imports transactionally without overwriting existing registrations or prefixes. Replacement and GUI bundle workflows are not implemented. |
|
||||
| Registry/filesystem Doctor | GUI, CLI | Detects missing executables, install-marker mismatches, stale OptiScaler/Customize state and orphaned prefix/log/save-backup directories. Safe findings can be fixed; orphan deletion is a separate exact-path operation with confirmation. |
|
||||
| Safe game removal | GUI, CLI | Unregisters a game and archives its configuration. The GUI keeps game files and the Wine prefix by default; its disk-deletion option removes both only after verifying the installation ownership marker and warns about local saves. Ubisoft games instead open the store client's confirmed uninstall flow without deleting the shared prefix. The CLI can preserve a normal game's prefix with `--keep-prefix`. |
|
||||
|
||||
See [portable prefix bundles](prefix-bundles.md), [game identity](game-identity.md)
|
||||
and the Doctor behavior recorded in the implementation snapshot (source repository).
|
||||
|
||||
## Configuration, automation and safety
|
||||
|
||||
| Capability | Surface | Current behavior |
|
||||
|---|---|---|
|
||||
| Plain TOML configuration | GUI, CLI | One versioned file per game plus a small global configuration; files are human-editable and use standard XDG locations. |
|
||||
| Schema-driven Runtime editor | GUI | Builds controls from the same model metadata used by the CLI schema, reducing drift between UI and stored configuration. |
|
||||
| Self-describing schema | CLI | `runi schema` reports every configurable field, type, default, choices and description, with section filtering. |
|
||||
| Configuration editing | CLI | Reads full game configuration, applies dotted `section.key=value` updates or opens the file in `$EDITOR`. |
|
||||
| JSON automation output | CLI | State and query commands that expose structured results support `--json`; dry-run JSON contains the final environment and argument vector. Interactive launch, editor, desktop-integration and GUI entry points remain text-only. |
|
||||
| Versioned migrations and backups | Automatic | Migrates older configuration schemas on load/save and preserves the previous file before the first write in a newer schema. |
|
||||
| Atomic and ownership-aware writes | Automatic | Managed configuration, Steam shortcut, integration and state writes use bounded ownership markers, backups or atomic publication as appropriate. |
|
||||
| Game-file rollback boundary | Automatic | OptiScaler, Customize and recipes record owned changes and preserve pre-existing files instead of treating a game directory as disposable. |
|
||||
|
||||
## Complete public CLI surface
|
||||
|
||||
This table is an index, not a replacement for `runi <command> --help`.
|
||||
The hidden `steam-launch` implementation command is intentionally excluded
|
||||
because it is owned by managed Steam shortcuts rather than being a user-facing
|
||||
workflow.
|
||||
|
||||
| Command family | Operations |
|
||||
|---|---|
|
||||
| `add` | Register a standalone, Steam or classified Windows-port installation; reconnect archived state or start fresh. |
|
||||
| `list` | List registered games. |
|
||||
| `run` | Launch Play or an auxiliary action; preview, change verbosity and override logging for one run. |
|
||||
| `config` | Read or edit per-game TOML and set individual schema fields. |
|
||||
| `schema` | Inspect the configurable model. |
|
||||
| `steam-scan` | Scan installed Steam AppManifests. |
|
||||
| `versions` | List installed builds. |
|
||||
| `versions list-remote` | List releases available from a supported source. |
|
||||
| `versions install` / `versions remove` | Manage runner builds with in-use protection. |
|
||||
| `optiscaler install` / `configure` / `status` / `migrate` / `remove` | Manage, inspect and migrate reversible per-game OptiScaler providers. `install` remains the explicit manual compatibility command. |
|
||||
| `hdr status` | Inspect native HDR prerequisites, output state and warnings for one game. |
|
||||
| `customize list` / `customize apply` / `customize remove` | Manage advanced community workaround profiles. |
|
||||
| `bundle export` / `bundle inspect` / `bundle verify` / `bundle import` | Manage portable prefix bundles. |
|
||||
| `desktop-integration dolphin status\|install\|remove` | Manage the Dolphin service-menu action. |
|
||||
| `prefix open` | Open a game's Wine prefix. |
|
||||
| `saves backup` / `saves list` / `saves restore` | Manage save backups. |
|
||||
| `notes` | View or edit persistent game notes. |
|
||||
| `doctor` / `doctor remove-orphan` | Audit managed state, apply safe fixes and explicitly remove one reported orphan. |
|
||||
| `remove` | Unregister a game with prefix-retention control. |
|
||||
| `gui` | Start the optional PySide6 application. |
|
||||
|
||||
## Optional dependencies and service boundaries
|
||||
|
||||
| Dependency or service | Required for |
|
||||
|---|---|
|
||||
| `umu-run` | UMU launch backend. |
|
||||
| An installed Proton build | Native backend and explicit per-game runner selection. |
|
||||
| PySide6 | GUI only; the base CLI remains usable without it. |
|
||||
| Steam | Steam scan, Steam-owned Proton discovery and optional Steam Input launch mode. |
|
||||
| Gamescope | Gamescope display pipeline. |
|
||||
| Wayland HDR session, Gamescope WSI layer and HDR-capable Vulkan stack | Per-game native HDR. KDE output state is verified when `kscreen-doctor` is available. |
|
||||
| MangoHud | MangoHud overlay when used without Gamescope; Gamescope must itself support `--mangoapp` for the combined path. |
|
||||
| OptiScaler release access | OptiScaler installation; game ownership and compatibility remain the user's responsibility. |
|
||||
| `lsfg-vk` plus Lossless Scaling from Steam | Per-game Lossless Scaling frame generation. |
|
||||
| SDL3, `libevdev` and `/dev/uinput` access | Runi Direct Steam Controller input. |
|
||||
| SteamGridDB API | Community artwork search and automatic non-Steam cover downloads; exact Steam CDN artwork does not require a key. |
|
||||
| PCGamingWiki access | Online technology enrichment and save-location candidates; local scans and manual save paths continue to work offline. |
|
||||
|
||||
## Explicitly not implemented
|
||||
|
||||
The following commonly confused items are not current capabilities:
|
||||
|
||||
- automatic GOG, Epic, Heroic or Legendary library discovery;
|
||||
- writing Steam's own per-game `CompatToolMapping` selection;
|
||||
- live shortcut creation through Steam's undocumented `addnonsteamgame` URL;
|
||||
- general support for physical controllers other than the Steam Controller;
|
||||
- Lossless Scaling upscaling on Linux (the integration is LSFG only);
|
||||
- a GUI for prefix bundle export/import or bundle replacement;
|
||||
- whole-application state backup and restore;
|
||||
- Runi Help, an OpenAI diagnostic agent or a shared online compatibility
|
||||
catalogue;
|
||||
- a daemon or broad remote-control API;
|
||||
- native Linux-port execution beyond the current classification model.
|
||||
|
||||
See the roadmap (source repository) for the status and rationale of future work.
|
||||
|
||||
## Maintenance rule
|
||||
|
||||
Every change that adds, removes or materially changes a product capability must
|
||||
update this catalogue in the same commit. It must also update `CHANGELOG.md` and
|
||||
the relevant specialist documentation when user behavior, requirements,
|
||||
persistence, compatibility or rollback semantics change.
|
||||
|
||||
Roadmap proposals must not be listed as implemented. Conversely, once a feature
|
||||
is shipped, its roadmap entry should link here or be moved into an implementation
|
||||
history section so the two sources cannot contradict each other.
|
||||
@@ -0,0 +1,37 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# Game and installation identity
|
||||
|
||||
Runi keeps three identifiers with different lifetimes:
|
||||
|
||||
| Field | Scope | Example |
|
||||
|---|---|---|
|
||||
| `game.id` | Local registration and CLI name | `doom-the-dark-ages` |
|
||||
| `identity.runi_game_id` | Canonical game across stores and reinstalls | `pgid_…` |
|
||||
| `game.installation_id` | One concrete installation | UUID4 |
|
||||
|
||||
The canonical game catalog is bundled with Runi and extended locally in
|
||||
`~/.config/runi/game-catalog/`. Entries map a PGID to strong aliases such
|
||||
as Steam, GOG, Epic and UMU IDs, plus executable names used as weaker evidence.
|
||||
Titles are never automatic identity evidence.
|
||||
|
||||
Game recipes, notes, compatibility knowledge and custom artwork use PGID.
|
||||
Receipts, manifests, logs, save backups and recipe drafts use installation ID.
|
||||
This allows two installations to share knowledge without sharing mutable state
|
||||
or rollback data.
|
||||
|
||||
Removing a registration archives its last configuration under
|
||||
`~/.local/share/runi/retired-installations/<installation-id>/`. If the
|
||||
same install directory is added again, Runi can reconnect that archived
|
||||
state after validating the marker and canonical game identity. Starting fresh
|
||||
creates a new installation ID and leaves the retired state untouched.
|
||||
|
||||
Schema v2/v3 `game.uid` and `.runi-uid` remain readable. Schema v4 writes
|
||||
`installation_id` and `.runi-installation-id`; the previous config is
|
||||
backed up before its first v4 save.
|
||||
|
||||
When a locally learned PGID is later promoted into the bundled catalogue, the
|
||||
bundled entry becomes authoritative for canonical metadata and collection
|
||||
placement while retaining the same PGID. This preserves references held by
|
||||
existing installations, artwork and future compatibility knowledge.
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# Native HDR
|
||||
|
||||
Runi can enable native HDR for one Windows game without changing KDE,
|
||||
KWin, the monitor, or global Gamescope configuration. The player remains
|
||||
responsible for enabling HDR/WCG on the intended desktop output before launch.
|
||||
|
||||
## Requirements
|
||||
|
||||
- a Wayland session with HDR enabled on the intended output;
|
||||
- Gamescope already enabled and configured for the game under **Runtime**;
|
||||
- a Gamescope build exposing `--hdr-enabled`;
|
||||
- a Gamescope WSI Vulkan layer matching the game's PE architecture;
|
||||
- a Vulkan stack exposing `VK_EXT_swapchain_colorspace`;
|
||||
- a game with native HDR support and HDR enabled in its own settings.
|
||||
|
||||
The HDR tab reports **Ready**, **Unverified**, or **Not ready**. Confirmed
|
||||
missing prerequisites block an HDR launch. If Runi cannot query the
|
||||
desktop output state, it reports Unverified and permits an explicit launch;
|
||||
this is a warning, not proof that the display pipeline is correct. Mixed
|
||||
HDR/SDR outputs and a non-primary HDR output are also reported.
|
||||
|
||||
Runi deliberately does not enable Gamescope automatically. Existing
|
||||
output and render resolutions are per-game choices, so silently activating the
|
||||
wrapper could select an inappropriate mode.
|
||||
|
||||
## Display and OS guidance
|
||||
|
||||
The HDR tab also shows the calibration metadata that KDE exposes for the
|
||||
selected output, including peak luminance, reference white, colour profile,
|
||||
gamut preference, RGB range and bit depth where available. These values are
|
||||
read-only guidance: Runi does not change KWin, KScreen or monitor state.
|
||||
|
||||
Use the display peak as the starting point for an in-game **Max Nits** control
|
||||
and the display reference white as the starting point for an in-game **Paper
|
||||
White** control. The game remains authoritative because slider names and tone
|
||||
mapping differ between engines. A correct transport can still look wrong when
|
||||
the game's midpoint, contrast or saturation is poorly calibrated.
|
||||
|
||||
## Optional community game settings
|
||||
|
||||
When explicitly enabled in Preferences, Runi can make a bounded OpenAI
|
||||
Responses API request to research HDR sliders for the current game. Research
|
||||
is never automatic: the player must click **Research Settings** or **Refresh**
|
||||
and approve the privacy/cost preview. The request sends only the canonical game
|
||||
title, Steam AppID when known, platform and optional game version. When a
|
||||
matching HDR Game Analysis entry exists, its public guide text and up to four
|
||||
public images are also included in the OpenAI request; the approval dialog
|
||||
discloses this additional image-input cost before every request.
|
||||
|
||||
The researcher uses `gpt-5.6-terra` with medium reasoning, at most three web
|
||||
searches, structured output and an audited allowlist:
|
||||
|
||||
- PCGamingWiki;
|
||||
- KoKlusz HDR Gaming Database discussions;
|
||||
- `r/HDR_Den`;
|
||||
- `r/OLED_Gaming`;
|
||||
- HDR Game Analysis, through a dedicated author-restricted adapter.
|
||||
|
||||
The HDR Game Analysis adapter reads one audited index through FxTwitter,
|
||||
matches only PC entries against the canonical game title, follows at most
|
||||
three links owned by `HDRgameAnalysis` and supplies at most four guide images.
|
||||
It does not add `x.com` to the web-search domain allowlist. Canonical source
|
||||
validation accepts only `https://x.com/HDRgameAnalysis/status/<id>` links, and
|
||||
an adapter or mirror outage degrades to the remaining sources instead of
|
||||
failing the research request.
|
||||
|
||||
Returned values keep the sources' original numbers and citations. Runi
|
||||
may show a local display-specific suggestion beside Max Nits or Paper White,
|
||||
but never applies either value to the game. Conflicting or insufficient
|
||||
evidence is shown as such instead of being converted into a false consensus.
|
||||
|
||||
Successful findings are stored by canonical Runi Game ID in
|
||||
`~/.local/share/runi/knowledge.sqlite3`, shared by registrations of the
|
||||
same game and retained after removal/re-import. The latest three successful
|
||||
runs are kept. Results become stale after 30 days but remain readable; a failed
|
||||
refresh never replaces the latest successful result.
|
||||
|
||||
The feature is disabled by default and requires the `ai` optional dependency.
|
||||
The OpenAI API key is read from the system keyring, `OPENAI_API_KEY`, or
|
||||
session-only memory. It is never written to Runi configuration, logs, the
|
||||
knowledge database, backups or Git.
|
||||
|
||||
## Launch composition
|
||||
|
||||
When `[hdr].enabled = true`, the main Play action receives:
|
||||
|
||||
- Gamescope `--hdr-enabled`;
|
||||
- `ENABLE_GAMESCOPE_WSI=1`;
|
||||
- `PROTON_ENABLE_HDR=1` when the selected runner advertises that path, or
|
||||
`DXVK_HDR=1` as the compatibility fallback.
|
||||
|
||||
Auxiliary actions do not inherit HDR. Raw `[env]` values for the managed HDR
|
||||
keys are rejected to avoid two competing sources of truth. Use
|
||||
`runi hdr status <game-id> --json` to inspect the resolved pipeline and
|
||||
`runi run <game-id> --dry-run --json` to inspect the final launch.
|
||||
|
||||
## Lossless Scaling
|
||||
|
||||
The HDR tab is authoritative when Lossless Scaling frame generation is also
|
||||
enabled. Runi maps the same value to the lsfg-vk 1.x HDR shader mode.
|
||||
lsfg-vk 2.x has no equivalent separate control, so the UI reports that limit
|
||||
instead of inventing one.
|
||||
|
||||
## Validation
|
||||
|
||||
A valid acceptance pass uses a game known to expose native PC HDR, checks a
|
||||
disabled control launch, enables Gamescope and HDR, reviews the dry-run, then
|
||||
confirms both the in-game HDR option and the display's HDR indication. A
|
||||
successful launch alone is not sufficient evidence of correct HDR output.
|
||||
|
||||
DOOM: The Dark Ages is the current acceptance target. Atomic Heart is not used
|
||||
for this check because its PC HDR path depends on external AutoHDR/mod behavior
|
||||
rather than providing the clean native control required by this test.
|
||||
|
||||
The DOOM acceptance pass completed on 2026-08-11 with GE-Proton11-5,
|
||||
Gamescope 3.16.25, RADV and KDE Wayland. Gamescope exposed an
|
||||
`A2B10G10R10` swapchain using `HDR10_ST2084`, reported HDR formats to the
|
||||
client and ran the display at 120 Hz. The validated display calibration was
|
||||
1015-nit peak and 240-nit reference white. The game's defaults were not a
|
||||
useful calibration: the confirmed starting values were 1000 max nits, 20.30
|
||||
mid point, 1.00 brightness, 0.90 contrast, 240 paper white and 1.00 saturation.
|
||||
@@ -0,0 +1,126 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# Launch providers, emulated games and auxiliary actions
|
||||
|
||||
## Status
|
||||
|
||||
This describes the implemented provider model for Windows executables,
|
||||
wrapper bundles, emulated games and shared storefront clients.
|
||||
|
||||
## Architecture
|
||||
|
||||
Launch construction has three independent dimensions:
|
||||
|
||||
```text
|
||||
Launch target -> provider -> execution engine -> optional wrappers -> process
|
||||
```
|
||||
|
||||
- A **target** is Play, Settings, Open Emulator or another explicit action.
|
||||
- A **provider** builds the program invocation: direct executable, Xenia,
|
||||
RPCS3 or Ubisoft Connect.
|
||||
- An **engine** runs that invocation: Proton/UMU, direct Proton or the Linux
|
||||
host. `host` is deliberately distinct from the legacy runtime backend named
|
||||
`native`, which means direct Proton.
|
||||
- Gamescope, MangoHud and CPU affinity wrap Play by default. Utility actions do
|
||||
not inherit graphical wrappers.
|
||||
|
||||
Examples:
|
||||
|
||||
| Use case | Provider | Engine |
|
||||
|---|---|---|
|
||||
| Existing Windows game/wrapper | executable | Proton/UMU or direct Proton |
|
||||
| Xenia with an ISO | xenia | Proton |
|
||||
| RPCS3 with a PS3 game directory | rpcs3 | host |
|
||||
| Windows Settings tool | executable | inherited Proton engine |
|
||||
| Open emulator UI | executable/emulator UI | provider's engine |
|
||||
| Ubisoft game deeplink | ubisoft | shared Proton prefix |
|
||||
|
||||
## Persistence and compatibility
|
||||
|
||||
Schema v3 added `game.platform`, `game.install_root`, `[launch]` and repeated
|
||||
`[[actions]]`. Schema v5 adds `[store_binding]` and store-specific identity
|
||||
aliases. Schema v7 separates a port's original platform from its target OS
|
||||
and stores optional Lossless Scaling frame-generation settings. Schema v8 adds
|
||||
explicit OptiScaler provider and migration state. Schema v9 adds managed native
|
||||
HDR state and migrates the legacy Lossless Scaling HDR option. Schema-v2 files remain
|
||||
readable and map to Windows, direct
|
||||
executable provider and Proton engine. Merely reading/listing does not rewrite
|
||||
them; the first later save creates a `.toml.v2.bak` and writes v9.
|
||||
|
||||
Every existing v2 game must produce the same argv, environment and working
|
||||
directory after loading through the new model. Recipes, prefix bundles,
|
||||
Doctor, saves, artwork, notes and configuration edits remain compatible.
|
||||
|
||||
## Library hierarchy
|
||||
|
||||
The primary navigation is platform, followed by a scoped store filter:
|
||||
|
||||
```text
|
||||
All | PC | Windows Ports | Linux Ports | Xbox 360 | PlayStation 3
|
||||
All stores | Steam | GOG | Epic | Ubisoft | Local
|
||||
```
|
||||
|
||||
Only populated platform/store values appear. Search and sort apply after both
|
||||
filters. Store and platform remain independent; emulator is neither. An Xbox
|
||||
360 ISO imported from disk is initially `platform = "xbox360"`,
|
||||
`store = "local"`. Disc/digital format may become a separate future field.
|
||||
|
||||
Port collections are derived from `port_target_os`, while `platform` retains
|
||||
the original/content platform. A Windows port keeps Proton, Windows Technology
|
||||
Scan, recipes and OptiScaler. The first curated example is Gears of War 2 -
|
||||
Hollow (`xbox360 -> windows`); see its
|
||||
investigation (source repository). Linux Ports is structural in
|
||||
this phase: ELF/AppImage import and host launch remain future work.
|
||||
|
||||
## Add/import experience
|
||||
|
||||
Selecting a file or folder runs conservative local detection and shows the
|
||||
platform, evidence, confidence and additional actions before registration.
|
||||
Recognized Xenia bundles use the included wrapper by default and offer direct
|
||||
Xenia as an alternative; an existing wrapper is never silently replaced.
|
||||
`Settings.exe` and the Xenia UI become Settings and Open Emulator actions using
|
||||
the same prefix.
|
||||
|
||||
RPCS3 imports read `PS3_GAME/PARAM.SFO`, resolve title/Title ID and build a host
|
||||
launch without Proton. A later library importer can batch-select games from an
|
||||
RPCS3 installation. Runi detects existing emulators or accepts a custom
|
||||
path; the first phase does not install/update them.
|
||||
|
||||
## Process and safety rules
|
||||
|
||||
- Process tracking keys auxiliary work by `(game_id, action_id)`; Play/Stop
|
||||
remains tied only to the primary target.
|
||||
- Settings and Open Emulator are unavailable while Play owns the same prefix.
|
||||
- Relative target paths resolve below the explicit install root. Importers do
|
||||
not create arbitrary command strings.
|
||||
- Providers own argument ordering and validation.
|
||||
- Adapter-suggested save paths require user confirmation; no backup starts
|
||||
automatically.
|
||||
- Multi-disc metadata may be retained, but interactive disc switching is a
|
||||
later phase; existing wrappers continue owning it.
|
||||
|
||||
## Shared storefront sessions
|
||||
|
||||
Ubisoft games reference a versioned store installation instead of owning a
|
||||
Wine prefix. The shared installation owns the prefix, launcher path and Proton
|
||||
runner; changing its runner updates every linked game. Play sends
|
||||
`uplay://launch/<id>/0` to `upc.exe` and monitors the detected game executable,
|
||||
not merely the short-lived deeplink process. After that executable exits,
|
||||
Runi allows 30 seconds for cloud sync before closing the managed session;
|
||||
**Keep Open** cancels automatic closure. Concurrent games in the same store
|
||||
prefix are refused, removal of one game never removes the shared prefix, and a
|
||||
per-game prefix bundle cannot include it. See [ubisoft-connect.md](ubisoft-connect.md).
|
||||
|
||||
## Delivery and acceptance
|
||||
|
||||
1. Schema-v3 compatibility and golden launch-plan regression tests.
|
||||
2. Provider/engine boundary with executable and host execution.
|
||||
3. Auxiliary action model, CLI and GUI process tracking.
|
||||
4. Xenia and RPCS3 providers plus conservative detection.
|
||||
5. Platform -> Store library filters and reviewed Add flow.
|
||||
6. Real acceptance: existing registered Windows titles, Lost Odyssey wrapper,
|
||||
direct Xenia and one RPCS3-native title.
|
||||
|
||||
No phase is accepted while an existing game's launch plan changes
|
||||
unintentionally.
|
||||
@@ -0,0 +1,116 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# Lossless Scaling
|
||||
|
||||
Runi can configure [lsfg-vk](https://github.com/PancakeTAS/lsfg-vk)
|
||||
frame generation for each registered game. Lossless Scaling itself provides
|
||||
both upscaling and frame generation on Windows; the Linux Vulkan layer exposes
|
||||
the LSFG frame-generation path, not the application's scaling algorithms.
|
||||
|
||||
## Ownership and dependencies
|
||||
|
||||
Runi never purchases or installs Lossless Scaling, and never copies or
|
||||
distributes `Lossless.dll`. That file is proprietary, paid, and shipped inside
|
||||
a Steam application: the player buys it, Runi finds it.
|
||||
|
||||
Runi *does* install the **layer**. The earlier rule said it would not, but
|
||||
that rule bundled two claims together and only one of them was about
|
||||
licensing; the other was consistency with how `gamescope` and `mangohud` are
|
||||
treated. lsfg-vk is free software published as a GitHub release, and
|
||||
`core/install/installer.py` already downloads and checksum-verifies
|
||||
third-party binaries - Runi had simply never installed this particular one.
|
||||
See "Installing the layer" below.
|
||||
|
||||
The **Lossless Scaling** game tab becomes editable only when Runi finds:
|
||||
|
||||
- a supported lsfg-vk 1.x or 2.x Vulkan layer and its shared library; and
|
||||
- Steam AppID `993090` in a native, external, or Flatpak Steam library, with a
|
||||
readable `Lossless.dll` in that installation.
|
||||
|
||||
Use **Recheck** after installing either dependency externally. If a dependency
|
||||
disappears while a game is enabled, the enable switch remains available so the
|
||||
saved integration can be turned off; launches otherwise fail with an actionable
|
||||
missing-dependency error.
|
||||
|
||||
## Installing the layer
|
||||
|
||||
```bash
|
||||
runi lossless-scaling # what is present, what is missing
|
||||
runi lossless-scaling install # fetch and register the layer
|
||||
runi lossless-scaling remove # undo, if runi installed it
|
||||
```
|
||||
|
||||
The desktop GUI's **Lossless Scaling** tab shows the same thing and offers an
|
||||
**Install lsfg-vk** button, which appears only when no layer is registered.
|
||||
|
||||
Runi installs the release's layer-only artifact - about 700 KB, against 74 MB
|
||||
for the bundle that also ships a configuration UI Runi has no use for, since
|
||||
it writes the layer's settings as environment variables itself. Two files are
|
||||
written, both user-level, neither needing root:
|
||||
|
||||
| File | Destination |
|
||||
|---|---|
|
||||
| `liblsfg-vk.so` | `~/.local/lib/` |
|
||||
| `VkLayer_LS_frame_generation.json` | `$XDG_DATA_HOME/vulkan/implicit_layer.d/` |
|
||||
|
||||
Three details are not obvious:
|
||||
|
||||
- **The manifest is rewritten on install.** The shipped one names its library
|
||||
as the bare filename `liblsfg-vk.so`, which the Vulkan loader resolves
|
||||
against the system's shared-object search path - not against the manifest's
|
||||
own directory, and `~/.local/lib` is not on that path. Runi substitutes the
|
||||
absolute path of the library it just installed. Left alone, the layer would
|
||||
register and then fail to load.
|
||||
- **The download is verified against the digest the GitHub API publishes for
|
||||
the asset**, because this project ships no checksum file of its own - unlike
|
||||
every Proton source, which publishes a sibling `.sha512sum`. Verification is
|
||||
required rather than best-effort: this is a shared library that every Vulkan
|
||||
application on the machine will load. An asset with no published digest is
|
||||
refused.
|
||||
- **Runi refuses to register a second layer of the same name.** If lsfg-vk is
|
||||
already installed - most often from your distribution's packages, in
|
||||
`/usr/share/vulkan/implicit_layer.d/` - Runi reports where that one came
|
||||
from instead of adding its own. Two manifests claiming one layer name is a
|
||||
real failure mode, not untidiness. For the same reason `remove` only ever
|
||||
deletes what Runi itself installed; a package's copy belongs to the package
|
||||
manager.
|
||||
|
||||
Prereleases are skipped: the 2.x line is published only as one, and it drives
|
||||
a different set of environment variables. Pass `--version <tag>` to select a
|
||||
specific release.
|
||||
|
||||
## Per-game behavior
|
||||
|
||||
The tab controls the displayed-frame multiplier, motion-estimation flow scale,
|
||||
performance model and the lsfg-vk 1.x experimental present-mode option.
|
||||
Native HDR is managed only in the dedicated **HDR** tab and is the source of
|
||||
truth for launch composition. lsfg-vk 1.x receives its HDR shader mode
|
||||
automatically; 2.x has no equivalent separate control and reports that limit
|
||||
instead. The legacy `hdr_mode` field remains only as a schema migration mirror.
|
||||
|
||||
For lsfg-vk 1.x, Runi uses its documented environment-only mode. This
|
||||
avoids editing `~/.config/lsfg-vk/conf.toml` and avoids process-name matching.
|
||||
The equivalent environment interface is used for 2.x. Dedicated settings take
|
||||
precedence over duplicate LSFG values in the game's generic `[env]` section.
|
||||
|
||||
When the integration is disabled, Runi adds no enable or disable variable.
|
||||
Profiles maintained externally with `lsfg-vk-ui` can therefore continue to
|
||||
apply. Only the main Play action receives the per-game environment; settings
|
||||
tools and other auxiliary actions do not.
|
||||
|
||||
## Compatibility notes
|
||||
|
||||
- The final graphics path must use Vulkan. DirectX 11/12 games normally reach
|
||||
Vulkan through DXVK or VKD3D-Proton, while native OpenGL-only games do not.
|
||||
- Generated frames increase displayed frame rate, not game simulation or input
|
||||
sampling. Start with a stable base frame rate and evaluate latency and visual
|
||||
artifacts in motion.
|
||||
- Gamescope may not honor the VSync behavior expected by lsfg-vk. Verify frame
|
||||
pacing and latency when both are enabled.
|
||||
- Native HDR should only be enabled for an active HDR game and desktop pipeline.
|
||||
- Vulkan-layer injection may be rejected by anti-cheat. Runi warns when
|
||||
anti-cheat evidence exists and never enables Lossless Scaling automatically.
|
||||
|
||||
The current installation state and warnings are advisory; the running game is
|
||||
the final compatibility test.
|
||||
@@ -0,0 +1,114 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# OptiScaler providers
|
||||
|
||||
Runi exposes two explicit player-facing paths. **Runtime managed** is
|
||||
recommended when the selected runner passes the complete setup, redirection and
|
||||
configuration probe. **FSR 4.1.1 INT8** installs one pinned, validated nightly
|
||||
pipeline beside the game executable. They are mutually exclusive.
|
||||
|
||||
## Providers and ownership
|
||||
|
||||
### GE-Proton Integrated
|
||||
|
||||
The integrated provider is available only when the selected runner implements
|
||||
the complete OptiScaler contract. Runi does not infer support from the
|
||||
runner name or version alone. It inspects the installed runner for setup,
|
||||
dispatcher, DLL-redirection and structured-configuration support.
|
||||
|
||||
At launch Runi supplies the dedicated `PROTON_USE_OPTISCALER`,
|
||||
`PROTON_OPTISCALER_NAME` and, when needed, `PROTON_OPTISCALER_CONFIG` variables.
|
||||
`PROTON_FSR4_UPGRADE` enables the runner-managed FSR 4 path. GE-Proton owns the
|
||||
download cache and files placed in the Wine prefix; disabling or migrating the
|
||||
provider does not delete those runner-owned files.
|
||||
|
||||
### FSR 4.1.1 INT8
|
||||
|
||||
The quality-first path downloads official nightly `nightly-20260817`, verifies
|
||||
the complete archive plus the loader and FSR 4.1.1 upscaler hashes, installs a
|
||||
selected proxy DLL beside the game executable, and explicitly sets
|
||||
`Fsr4ForceModel=2`. The validation watermark is enabled by default for the
|
||||
first test and can be disabled in Advanced before installation. It owns only
|
||||
the files it installs and its corresponding `WINEDLLOVERRIDES` token, so removal
|
||||
restores previous files and prunes only empty directories it created.
|
||||
|
||||
Generic stable manual injection remains available to the CLI and existing
|
||||
configurations for compatibility, but it is no longer a third GUI choice.
|
||||
|
||||
The OptiScaler tab audits the pinned loader, nested FSR 4.1.1 DLL and required
|
||||
INT8 INI selection separately. A missing or changed component is reported as an
|
||||
incomplete installation rather than silently falling back.
|
||||
|
||||
The integrated provider does not require player-supplied DLLs: the compatible
|
||||
runner owns provisioning. If the runtime probe fails, its card is unavailable
|
||||
and the pinned INT8 path remains explicit rather than being selected silently.
|
||||
|
||||
## Selection and migration
|
||||
|
||||
The stored provider preference and the provider already applied are separate:
|
||||
|
||||
- **Runtime managed** requires a successful capability probe and is unavailable
|
||||
otherwise.
|
||||
- **FSR 4.1.1 INT8** always uses the pinned, reversible game-directory provider.
|
||||
- Legacy Auto and generic Manual preferences remain readable for compatibility,
|
||||
but new GUI configuration does not create them.
|
||||
|
||||
Changing runners can change Auto's resolution. Runi blocks Play when the
|
||||
resolved provider no longer matches the applied provider; it never silently
|
||||
places or removes DLLs during launch. Use the migration action in the game tab
|
||||
or the CLI:
|
||||
|
||||
```bash
|
||||
runi optiscaler status <game-id>
|
||||
runi optiscaler migrate <game-id> --to ge-integrated --fsr4
|
||||
runi optiscaler migrate <game-id> --to manual --version latest
|
||||
```
|
||||
|
||||
Migration is explicit, transactional and receipt-backed. A manual recipe is
|
||||
removed through its normal rollback path before Integrated is configured, and
|
||||
its source, parameters and retained unrelated settings are recorded so the
|
||||
manual setup can be reconstructed if migration fails. Migration in the other
|
||||
direction disables the integrated launch path before installing the reversible
|
||||
manual provider.
|
||||
|
||||
Raw integrated-provider variables in `[env]` are rejected because they bypass
|
||||
ownership and can activate both paths. Play is also blocked if manual tracking
|
||||
files coexist with an integrated selection.
|
||||
|
||||
## FSR 4 behavior
|
||||
|
||||
Runtime-managed FSR 4 is not an alias for the pinned FSR 4.1.1 INT8 path. The
|
||||
runner manifest chooses a compatible component set and may combine different
|
||||
component versions. The INT8 path is pinned to the exact tested nightly and
|
||||
checksums, so Runi does not claim that both methods are byte-for-byte or
|
||||
performance equivalent.
|
||||
|
||||
For a detected DirectX 12 game, the integrated setup selects the DX12 FSR 3.1
|
||||
input path before requesting the FSR 4 upgrade. DirectX 11 and Vulkan use their
|
||||
corresponding DX12-interoperability input. The Vulkan-to-DX12 FSR 4 recipe path
|
||||
requires Proton 11 or newer.
|
||||
|
||||
In games with a DLSS input, select DLSS in the game and use `Insert` to inspect
|
||||
the OptiScaler overlay. The overlay is the authoritative check for the
|
||||
intercepted input, selected output and loaded versions.
|
||||
|
||||
## Status and troubleshooting
|
||||
|
||||
The OptiScaler tab and `runi optiscaler status <game-id>` distinguish
|
||||
disabled, configured, ready, inconsistent and migration-required states.
|
||||
For the manual provider, JSON status also includes an `fsr411` audit with its
|
||||
own state and the missing or invalid components.
|
||||
`runi doctor` additionally reports stale manual tracking, duplicate raw
|
||||
provider variables and provider migrations required after runner changes.
|
||||
|
||||
If a GUI process that was already open reports that schema v9 is unsupported,
|
||||
restart Runi so the running Python process loads the updated application
|
||||
code. Editable `pipx` installations already point at the checkout and do not
|
||||
need to be reinstalled for source changes.
|
||||
|
||||
The integrated path was validated on a real GE-Proton11-5 installation. Kusan:
|
||||
City of Wolves confirmed provisioning, cached reuse, DLL redirection and the
|
||||
disabled control. Atomic Heart then confirmed the complete gameplay path after
|
||||
migration from the manual recipe: the overlay loaded, DLSS input was
|
||||
intercepted, FSR 4 output worked and gameplay remained stable.
|
||||
@@ -0,0 +1,359 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# Portable prefix bundles
|
||||
|
||||
## Status and objective
|
||||
|
||||
The first delivery is implemented: versioned export, inspection, full verification
|
||||
and non-destructive transactional import are available from the CLI and shared
|
||||
core. Replacement, optional components and GUI integration remain future phases.
|
||||
|
||||
The Wine prefix is the primary artefact, but a useful recovery package must also
|
||||
record enough Runi configuration and runtime metadata to restore that prefix
|
||||
on another Linux installation, including a Steam Deck, without assuming identical
|
||||
filesystem paths.
|
||||
|
||||
The intended CLI is:
|
||||
|
||||
```console
|
||||
runi bundle export bionic-commando-rearmed \
|
||||
--output bionic-commando-rearmed.pfy
|
||||
|
||||
runi bundle inspect bionic-commando-rearmed.pfy
|
||||
runi bundle verify bionic-commando-rearmed.pfy
|
||||
|
||||
runi bundle import bionic-commando-rearmed.pfy \
|
||||
--exe "/path/to/bcr.exe"
|
||||
```
|
||||
|
||||
## Bundle contents
|
||||
|
||||
A bundle has this logical layout:
|
||||
|
||||
```text
|
||||
manifest.toml
|
||||
checksums.sha256
|
||||
game/config.toml
|
||||
prefix/
|
||||
notes/ # optional
|
||||
cover/ # optional
|
||||
recipe-state/ # optional, when applicable
|
||||
```
|
||||
|
||||
`manifest.toml` records:
|
||||
|
||||
- bundle format version and creating Runi version;
|
||||
- creation time, source operating system and architecture;
|
||||
- game ID and UID;
|
||||
- backend and runtime GameID;
|
||||
- Proton name, source and build fingerprint;
|
||||
- original executable path, for information only;
|
||||
- included components;
|
||||
- total uncompressed size and file count;
|
||||
- external requirements and compatibility warnings.
|
||||
|
||||
`checksums.sha256` covers every payload file. It detects corruption but is not an
|
||||
authenticity mechanism: signatures belong to a later phase if bundles are ever
|
||||
published or exchanged through an untrusted channel.
|
||||
|
||||
The following are excluded by default:
|
||||
|
||||
- the game installation;
|
||||
- Proton itself;
|
||||
- logs and caches;
|
||||
- installers or recipe assets without clear redistribution rights;
|
||||
- saves stored outside the prefix.
|
||||
|
||||
These exclusions keep the bundle focused, avoid licensing problems and prevent
|
||||
machine-local or disposable data from silently becoming portable state.
|
||||
|
||||
## Archive format
|
||||
|
||||
The payload must use TAR rather than ZIP. Wine prefixes contain symbolic links,
|
||||
permissions and other Unix filesystem metadata which ZIP does not reliably round
|
||||
trip.
|
||||
|
||||
The first format version uses:
|
||||
|
||||
- the `.pfy` extension;
|
||||
- a versioned manifest independent of its container compression;
|
||||
- TAR with gzip as the baseline available in Python 3.11's standard library.
|
||||
|
||||
Zstandard can be added later as another encoding without changing the logical
|
||||
bundle format. Import must identify the actual format from its header, not trust
|
||||
the filename alone.
|
||||
|
||||
## Current CLI
|
||||
|
||||
```console
|
||||
runi bundle export <game-id> --output game.pfy
|
||||
runi bundle inspect game.pfy
|
||||
runi bundle verify game.pfy
|
||||
runi bundle import game.pfy [--exe /new/path/game.exe] [--as new-id]
|
||||
```
|
||||
|
||||
Export refuses an existing destination and an apparently active prefix. `--force`
|
||||
only overrides the active-prefix check; it never overwrites a destination. Import
|
||||
refuses existing registrations and prefixes. There is deliberately no `--replace`
|
||||
until the preservation-and-rollback phase is implemented.
|
||||
|
||||
## Consistent export
|
||||
|
||||
Export is a snapshot operation, not an ordinary recursive copy:
|
||||
|
||||
1. Load the registered game and resolve its prefix.
|
||||
2. Confirm that the prefix exists and passes basic structural checks.
|
||||
3. Detect Wine/Proton processes using that prefix and refuse while it is active.
|
||||
`--force` can explicitly override this consistency protection.
|
||||
4. Estimate required space and create the archive as a temporary sibling of the
|
||||
requested destination.
|
||||
5. Archive filesystem entries without following symlinks.
|
||||
6. Calculate the manifest and payload hashes.
|
||||
7. Re-open and verify the completed temporary archive.
|
||||
8. Flush it and atomically rename it to the final destination.
|
||||
|
||||
A failed or interrupted export must never appear under the requested final name.
|
||||
Runi launches should take an advisory per-prefix lock; the process scan also
|
||||
covers games started outside the current Runi process. This reduces races,
|
||||
although a generic filesystem snapshot cannot guarantee application consistency
|
||||
when an uncooperative external Wine process is still writing.
|
||||
|
||||
## Inspection and verification
|
||||
|
||||
`bundle inspect` reads only metadata and reports contents, requirements, sizes and
|
||||
compatibility information without extracting the prefix.
|
||||
|
||||
`bundle verify` validates:
|
||||
|
||||
- container readability and format version;
|
||||
- required manifest fields;
|
||||
- unique, normalized archive paths;
|
||||
- declared size and file-count limits;
|
||||
- payload membership against the manifest;
|
||||
- every SHA-256 digest.
|
||||
|
||||
Both commands must work without a registered game or installed Proton version.
|
||||
|
||||
## Transactional import
|
||||
|
||||
Import is split into four explicit stages:
|
||||
|
||||
```text
|
||||
open -> validate -> stage -> publish
|
||||
```
|
||||
|
||||
1. Open and fully validate the bundle before modifying managed state.
|
||||
2. Extract into a newly-created staging directory inside the destination prefix
|
||||
root, so the eventual rename remains on the same filesystem.
|
||||
3. Validate the staged prefix and construct the destination configuration.
|
||||
4. Atomically rename the staged prefix into place.
|
||||
5. Persist the game configuration last.
|
||||
|
||||
On failure, only staging data is removed. Existing configuration and prefixes are
|
||||
left untouched.
|
||||
|
||||
Archive extraction is implemented entry by entry. It rejects absolute paths, `..`
|
||||
traversal, device nodes, FIFOs, duplicate paths, member paths descending through
|
||||
symlinks, unsafe hardlinks, unexpected entry types and excessive expansion.
|
||||
Symlink targets themselves are preserved without following them: Wine prefixes
|
||||
legitimately contain external mappings such as `dosdevices/z:` pointing at `/`.
|
||||
All directories and regular files are created before symlinks, so archive writes
|
||||
cannot traverse those mappings during extraction.
|
||||
The standard library's unrestricted `extractall()` is not an acceptable trust
|
||||
boundary.
|
||||
|
||||
## Conflict and replacement policy
|
||||
|
||||
The default is non-destructive:
|
||||
|
||||
```text
|
||||
existing registration -> abort
|
||||
existing prefix -> abort
|
||||
occupied game ID -> require --as <new-id>
|
||||
```
|
||||
|
||||
An explicit future `--replace` mode must:
|
||||
|
||||
1. validate and stage the incoming bundle completely;
|
||||
2. export or otherwise preserve the current registered state;
|
||||
3. move the current prefix aside rather than delete it;
|
||||
4. publish the incoming prefix and configuration;
|
||||
5. roll back the old state if publication fails.
|
||||
|
||||
The safety snapshot's location and retention policy must be visible to the user.
|
||||
No overwrite flag may turn validation off.
|
||||
|
||||
## Portability rules
|
||||
|
||||
Machine-specific absolute paths cannot be restored blindly:
|
||||
|
||||
- the prefix is imported under the destination machine's configured
|
||||
`prefixes_root`;
|
||||
- the imported `runtime.prefix` is cleared when the bundle represented a managed
|
||||
prefix, allowing normal XDG resolution on the destination;
|
||||
- the original executable path is treated only as a candidate;
|
||||
- `--exe` supplies the new path when the original executable is unavailable;
|
||||
- a default working directory tied to the old executable is recalculated;
|
||||
- explicitly independent working directories require confirmation or replacement;
|
||||
- the required Proton build is matched by name, source and fingerprint;
|
||||
- a missing Proton build leaves a valid imported environment in a clear
|
||||
"restored but not ready" state with concrete remediation instructions.
|
||||
|
||||
Import must not perform speculative rewrites inside the Wine registry. Any future
|
||||
registry migration must be narrow, declarative and auditable because an incorrect
|
||||
rewrite can destroy the state the bundle exists to preserve.
|
||||
|
||||
Game UID handling must be explicit. A normal recovery keeps the original UID. An
|
||||
import using `--as` creates a distinct registration and therefore needs a new UID,
|
||||
while retaining the source UID in import provenance rather than pretending both
|
||||
registrations are the same object.
|
||||
|
||||
## Core architecture
|
||||
|
||||
This is a separate subsystem from `core/backup.py`. That existing module protects
|
||||
individual files modified inside a game installation; it does not create or
|
||||
restore complete environment snapshots.
|
||||
|
||||
Proposed package:
|
||||
|
||||
```text
|
||||
src/runi/core/bundles/
|
||||
__init__.py
|
||||
models.py # BundleManifest and BundleComponent
|
||||
archive.py # safe TAR writing and extraction
|
||||
integrity.py # SHA-256 generation and verification
|
||||
compatibility.py # destination readiness and Proton matching
|
||||
exporter.py # snapshot orchestration
|
||||
importer.py # staging, publication and rollback
|
||||
|
||||
src/runi/cli/commands/bundle.py
|
||||
```
|
||||
|
||||
The core API should remain presentation-independent:
|
||||
|
||||
```python
|
||||
export_bundle(game, destination, options, on_progress=None)
|
||||
inspect_bundle(source)
|
||||
verify_bundle(source, on_progress=None)
|
||||
import_bundle(source, options, on_progress=None)
|
||||
```
|
||||
|
||||
Core functions raise typed `RuniError` subclasses and never print, prompt or
|
||||
exit. CLI and GUI adapters own user interaction. Long export, hash and extraction
|
||||
operations report through the existing progress callback abstraction.
|
||||
|
||||
## Delivery phases
|
||||
|
||||
1. ✅ Versioned manifest, safe archive primitives, export, inspect and verify.
|
||||
2. ✅ Transactional import with no replacement support.
|
||||
3. ✅ Cross-machine executable override and Proton fingerprint/readiness reporting.
|
||||
4. Explicit `--replace` with automatic preservation and rollback.
|
||||
5. Required game notes plus optional custom cover and recipe-state components.
|
||||
6. GUI integration and a visible bundle/history view.
|
||||
7. Cryptographic signing only if an untrusted sharing channel creates that need.
|
||||
|
||||
The implemented first delivery remains deliberately narrow: a reliable prefix plus
|
||||
configuration round trip through `export`, `verify` and non-destructive `import`.
|
||||
|
||||
## GUI design checkpoint (recorded, not approved for implementation)
|
||||
|
||||
Do not start the bundle GUI until its workflow and the remaining core contracts
|
||||
below have been decided. The CLI implementation is not, by itself, a sufficient
|
||||
UX specification for multi-gigabyte interactive operations.
|
||||
|
||||
### Export workflow
|
||||
|
||||
Decide whether export is exposed in the game context menu, Overview, or both; the
|
||||
default filename and destination; which optional components can be selected; how
|
||||
estimated size and free space are shown; and how an active prefix is handled. The
|
||||
GUI should probably not expose the consistency-bypassing `--force` option in its
|
||||
first version.
|
||||
|
||||
Prefix, game configuration and game notes remain mandatory for any bundle
|
||||
presented as a complete per-game backup. Custom cover and recipe state are
|
||||
candidates for explicit optional components once supported by the core. Until
|
||||
the implemented bundle format carries notes, the UI and documentation must
|
||||
describe it as a prefix/config portability bundle rather than a complete backup
|
||||
of the game's Runi knowledge.
|
||||
|
||||
### Import workflow
|
||||
|
||||
Import belongs to the library/Add Game flow because the game may not be registered
|
||||
yet. The proposed wizard is:
|
||||
|
||||
1. Select a `.pfy` file.
|
||||
2. Verify structure and integrity.
|
||||
3. Review game identity, creation date, size and required Proton build.
|
||||
4. Select or confirm the executable on the destination machine.
|
||||
5. Resolve the destination ID and paths.
|
||||
6. Review the complete operation.
|
||||
7. Import transactionally.
|
||||
8. Show readiness and any missing requirements.
|
||||
|
||||
The initial GUI should resolve an occupied ID with only **Import as Copy** or
|
||||
**Cancel**. It must not offer Replace until core replacement has automatic
|
||||
preservation and rollback.
|
||||
|
||||
### Suggested entry points
|
||||
|
||||
- Game context menu and/or Overview: **Export Bundle…**
|
||||
- Library **Add Game** menu: **Import Bundle…**
|
||||
- A global utility action: **Inspect/Verify Bundle…**
|
||||
|
||||
Inspect/Verify does not need to appear inside a registered game's Overview because
|
||||
it operates on an arbitrary bundle file, not on that registration.
|
||||
|
||||
### Progress and cancellation prerequisites
|
||||
|
||||
The current core reports phase-level progress only. Before GUI work, extend its
|
||||
contract to provide file/byte totals and cooperative cancellation for hashing,
|
||||
compression, verification and extraction. Cancellation must clean staging and
|
||||
temporary archives, and the UI must prevent application shutdown during the small
|
||||
atomic publication window.
|
||||
|
||||
All long-running work must use the existing background worker mechanism; no bundle
|
||||
operation may block the Qt event loop.
|
||||
|
||||
### Completion and readiness view
|
||||
|
||||
After import, show the restored prefix, selected executable, backend, disk usage
|
||||
and Proton state (available, missing or fingerprint mismatch). Useful next actions
|
||||
are Run, Dry-run, Open Prefix Folder, Open Game Folder and Install/Select Proton.
|
||||
|
||||
Integrity and authenticity must use different language: a bundle whose internal
|
||||
checksums pass is **integrity verified**, not a trusted or endorsed bundle. Invalid,
|
||||
incompatible and unsigned/unknown-origin states must remain distinguishable.
|
||||
|
||||
### Deliberate first-GUI scope
|
||||
|
||||
The first GUI should read and write user-selected `.pfy` files only. A managed
|
||||
bundle library/history is deferred because it introduces retention, cleanup,
|
||||
removable-storage and storage-policy decisions.
|
||||
|
||||
Before implementation, close these prerequisites in order:
|
||||
|
||||
1. Detailed core progress.
|
||||
2. Cooperative cancellation.
|
||||
3. Required notes support and decisions for optional cover and recipe-state
|
||||
components.
|
||||
4. A richer import readiness/result model.
|
||||
5. Final conflict and Import-as-Copy semantics.
|
||||
6. An agreed import-wizard wireframe.
|
||||
|
||||
## Required tests
|
||||
|
||||
- Round-trip symlinks, permissions, hidden files, Unicode names and empty dirs.
|
||||
- Export/import a synthetic prefix under different XDG roots.
|
||||
- Detect a one-byte corruption, truncated archive and manifest mismatch.
|
||||
- Reject traversal, absolute paths, escaping symlinks/hardlinks, device entries,
|
||||
duplicate members and decompression/resource abuse.
|
||||
- Leave no final archive after interrupted export.
|
||||
- Leave no registration or final prefix after interrupted import.
|
||||
- Preserve existing state on every conflict and failure path.
|
||||
- Exercise ID, UID, config and prefix conflicts, including `--as` semantics.
|
||||
- Report missing or fingerprint-mismatched Proton builds without corrupting the
|
||||
imported environment.
|
||||
- Confirm JSON CLI output and progress callbacks remain stable.
|
||||
- Use the known-good Bionic Commando Rearmed prefix as a manual acceptance test,
|
||||
including restoration onto a Steam Deck or an equivalent clean environment.
|
||||
@@ -0,0 +1,103 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# Technology Scan
|
||||
|
||||
Technology Scan builds an evidence-based inventory for each game. Its guiding
|
||||
rule is:
|
||||
|
||||
> Local analysis and PCGamingWiki are automatic; the inventory is complete;
|
||||
> Overview contains the expandable summary; runtime observation is explicit.
|
||||
> Recipe recommendations are a consequence of the inventory, not a separate
|
||||
> scanner.
|
||||
|
||||
## Evidence sources
|
||||
|
||||
The local scan stays inside the registered installation root. It inspects
|
||||
filenames, directory conventions, Windows PE imports, the PE version resource of
|
||||
known upscaler and frame-generation DLLs, and a bounded set of small text logs. It recognises graphics APIs, upscalers and frame generation, engines,
|
||||
audio/physics middleware, anti-cheat and DRM, and compatibility-loader
|
||||
markers (LinUwUx). It does not scan the full Wine
|
||||
prefix, follow external symlinks or unpack large game archives such as `.pak`
|
||||
files; those would introduce unrelated evidence, especially in shared prefixes.
|
||||
|
||||
Community evidence comes from the official PCGamingWiki MediaWiki API. Runi
|
||||
queries by a known store identity before trying an exact canonical title,
|
||||
identifies itself with a project user-agent, observes the service rate limit and
|
||||
caches responses for seven days. A network failure never blocks registration or
|
||||
launch.
|
||||
|
||||
SteamDB has no public API and explicitly disallows automated scraping. Runi
|
||||
therefore provides only an **Open in SteamDB** link for manual research.
|
||||
|
||||
A filename identifies a technology *family*, never its version. One filename
|
||||
is shared across versions - `amd_fidelityfx_upscaler_dx12.dll` is the FidelityFX
|
||||
SDK 2.x name for FSR 3.1 and FSR 4.x alike - so the version is read from the
|
||||
binary's own version resource and reported in the finding's version field, with
|
||||
the vendor's full build string kept in the evidence line. A missing `pefile`, a
|
||||
binary without a version resource or a non-numeric version all degrade to a
|
||||
finding with no version rather than to a guess.
|
||||
|
||||
For an FSR 4 capable upscaler DLL the scan also records which model weights are
|
||||
embedded (`fp8`, `int8`). That is evidence, not a verdict: it is what decides
|
||||
whether a game's own FSR 4 path can run on a given GPU generation at all.
|
||||
Runi does not compare it against the local hardware.
|
||||
|
||||
Files recorded in Runi's own OptiScaler and Customize manifests are ignored
|
||||
as evidence. Installing a recipe must not make the next scan claim that the
|
||||
technology originally belonged to the game.
|
||||
|
||||
LinUwUx-compatible-loader markers (`reflex.ini`/`reflex.dll`/`reflex64.dll`,
|
||||
`DenuvOwO.ini`/`DenuvOwO.dll`) are a dedicated `compat_shim` category, kept
|
||||
separate from `anti_cheat`/`drm` so a finding never affects
|
||||
`recommendations_for()`'s existing anti-cheat-gated OptiScaler suppression.
|
||||
`winmm.dll`/`version.dll` are deliberately excluded from this rule set even
|
||||
though LinUwUx's own detection also looks for them: both are generic
|
||||
proxy-DLL names also used by unrelated mods, including Runi's own
|
||||
OptiScaler manual-injection overrides, so a bare filename match on either
|
||||
risks a false positive. Finding a marker never enables `[linuwux].enabled`
|
||||
itself - it's informational only, the same detect-only philosophy as
|
||||
OptiScaler/Lossless Scaling.
|
||||
|
||||
## Confidence and identity
|
||||
|
||||
Every finding retains its source, locator and observation time. Confidence is:
|
||||
|
||||
- `confirmed`: directly imported by the primary executable or observed at runtime;
|
||||
- `likely`: filename, secondary binary, log signature or community evidence;
|
||||
- `unknown`: no reliable signal;
|
||||
- `conflicting`: authoritative signals disagree.
|
||||
|
||||
Community findings belong to the canonical Runi Game ID. Local files and
|
||||
runtime observations belong to the Installation ID, because another build or
|
||||
store release of the same game may differ.
|
||||
|
||||
DRM reported only by PCGamingWiki is presented as **possible DRM** because the
|
||||
community entry may describe an older build from before that DRM was removed.
|
||||
Local or runtime evidence takes precedence when both sources are available.
|
||||
|
||||
## Runtime scan
|
||||
|
||||
**Run Runtime Scan** performs one diagnostic launch with Proton DLL logging and
|
||||
observes the Linux process tree through `/proc`. It does not install or inject
|
||||
Special K. Collection ends when the launcher exits or when the player chooses
|
||||
**Finish Runtime Scan**. Only matched module/signature evidence is retained;
|
||||
the complete diagnostic log is not copied into the technology report.
|
||||
|
||||
Runtime observation can confirm that an FSR provider DLL loaded, but the FSR
|
||||
watermark remains the authority for the exact `FSR4-I8 Upscale 4.1.1` model.
|
||||
|
||||
## Recipe recommendations
|
||||
|
||||
- DirectX 12 plus a modular FidelityFX upscaler DLL found on disk or observed
|
||||
at runtime recommends **Native FSR 4.1.1 Upgrade**. Community evidence alone
|
||||
does not qualify: it names the family without proving the DLL is present.
|
||||
- Native Vulkan recommends **Generic OptiScaler + FSR 4.1.1** for DX12 interop.
|
||||
- DLSS, XeSS or older FSR without a confirmed native upgrade path recommends
|
||||
OptiScaler.
|
||||
- Ambiguous evidence leaves the choice to the player.
|
||||
- Detected anti-cheat suppresses automatic OptiScaler recommendation and shows
|
||||
an injection warning.
|
||||
|
||||
Recommendations never apply a recipe automatically. One recipe remains active
|
||||
per installation.
|
||||
@@ -0,0 +1,90 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# Ubisoft Connect integration
|
||||
|
||||
## User flow
|
||||
|
||||
Use **+ → Ubisoft Connect** for the shortest path, or **Tools → Stores** to
|
||||
return to the same manager later.
|
||||
|
||||
1. Choose **Install Automatically** to download the official Ubisoft Connect
|
||||
installer and run its silent install into a dedicated managed prefix, or
|
||||
choose **Import Existing Prefix**. Imported prefixes remain user-owned.
|
||||
2. Open Ubisoft Connect. The real client UI appears for login and game
|
||||
installation; Runi neither stores credentials nor copies login tokens.
|
||||
3. Close the client. Runi scans its local Wine registry, configuration
|
||||
catalogue and local launcher storage, then shows every detected game for
|
||||
review. Nothing is imported without that review.
|
||||
4. Imported titles appear under platform **PC** and store **Ubisoft**. Play
|
||||
uses the Ubisoft deeplink directly; routine game launches do not require
|
||||
navigating the store UI.
|
||||
|
||||
The integration is launcher-light, not fully headless: authentication, account
|
||||
security prompts, updates and game installation still belong to Ubisoft
|
||||
Connect. Once authenticated, a normal game launch only sends the deeplink.
|
||||
|
||||
## Shared prefix and runner
|
||||
|
||||
One Ubisoft installation owns one dedicated prefix. Every linked Ubisoft game
|
||||
stores only a `[store_binding]` reference to it. The manager's **Change Runner**
|
||||
action therefore changes the runtime for all linked games together. Runi
|
||||
does not mix standalone Windows games into this prefix.
|
||||
|
||||
This ownership boundary has deliberate safeguards:
|
||||
|
||||
- only one game may be active in the shared prefix at a time;
|
||||
- deleting a game never deletes the shared prefix;
|
||||
- the integration cannot be removed while games still reference it;
|
||||
- per-game `.pfy` export refuses shared store prefixes;
|
||||
- Doctor reports a missing binding, prefix, launcher or provider mismatch;
|
||||
- a managed prefix is deleted only through a separate explicit confirmation;
|
||||
- an imported external prefix is never deleted by Runi.
|
||||
|
||||
Removing a linked game from the GUI removes only its Runi registration by
|
||||
default. An optional **Open Ubisoft Connect to uninstall the game** action sends
|
||||
`uplay://uninstall/<install-id>` to the launcher in the shared prefix. Ubisoft
|
||||
Connect remains responsible for the confirmation and file removal; Runi
|
||||
never deletes the game's directory directly. If that flow cannot be opened,
|
||||
the registration is kept so the operation can be retried.
|
||||
|
||||
## Launch and cloud sync lifecycle
|
||||
|
||||
Play resolves the store installation and runs `upc.exe
|
||||
uplay://launch/<ubisoft-id>/0` through its selected Proton runner. The bootstrap
|
||||
process is not considered the game. Runi watches the executable names
|
||||
recorded during discovery and scopes them to the shared prefix when process
|
||||
environment data is available.
|
||||
|
||||
Once a real game process has been seen and then exits, the library shows a
|
||||
30-second cloud-sync countdown. At the end Runi closes the launch session.
|
||||
Choose **Keep Open** to leave Ubisoft Connect running and release the game card
|
||||
immediately. If no game process is ever detected, Runi does not guess that
|
||||
cloud sync has begun; the user can stop the session explicitly.
|
||||
|
||||
After a session closes, local data is scanned in the background. The review
|
||||
dialog appears only when genuinely new games are found.
|
||||
|
||||
Cover discovery asks the Ubisoft Store for the exact master product first and
|
||||
uses its official vertical `boxshot`. The resolved `ecommerceMasterId` is kept
|
||||
as `identity.ubisoft_store_product_id`; it identifies the installed store
|
||||
edition without replacing the canonical Runi game identity. If the store
|
||||
has no unique exact product, Runi tries an exact SteamGridDB match and then
|
||||
an exact Steam Store match. The horizontal Ubisoft Connect background is not
|
||||
used as a cover, and vague title matches are never accepted merely to fill a
|
||||
card.
|
||||
|
||||
## Local identities
|
||||
|
||||
Each registration keeps two different identities:
|
||||
|
||||
- `game.installation_id`: the UUID for this concrete Runi registration;
|
||||
- `identity.runi_game_id`: Runi's canonical game identity, reused
|
||||
across reinstalls and store aliases.
|
||||
|
||||
Ubisoft's launch, space and installation IDs are aliases attached to the
|
||||
canonical identity. Removing and re-adding a title can therefore reconnect to
|
||||
the same game identity without depending on its old config filename.
|
||||
The Ubisoft Store product ID is a separate edition-level alias: Standard,
|
||||
Deluxe and other commercial editions may have different product IDs while
|
||||
sharing the same canonical game.
|
||||
@@ -0,0 +1,475 @@
|
||||
<!-- Generated from the Runi source repository at v0.4.0. -->
|
||||
<!-- Edit the original there, not this copy. -->
|
||||
|
||||
# User flows
|
||||
|
||||
See [config-schema.md](config-schema.md) for the exact fields referenced
|
||||
below, and architecture.md (source repository) for how `run` resolves all of
|
||||
this internally.
|
||||
|
||||
## 1. First run on a fresh machine
|
||||
|
||||
`runi versions` auto-discovers whatever's already installed (Steam's
|
||||
official Proton if Steam is present; nothing else yet). To get GE-Proton:
|
||||
`runi versions list-remote --source ge-proton` shows installable
|
||||
releases, `runi versions install ge-proton latest` downloads, verifies
|
||||
checksum, extracts into `compatibilitytools.d` with a progress bar, and now
|
||||
shows up in `runi versions`.
|
||||
|
||||
## 2. Add and run a standalone `.exe` (no storefront)
|
||||
|
||||
```
|
||||
runi add ~/Games/MyIndieGame/game.exe --name "My Indie Game"
|
||||
runi run my-indie-game
|
||||
```
|
||||
|
||||
Exe path validated, `game.id = my-indie-game` (unique slug),
|
||||
`identity.umu_gameid = umu-default` (shared fallback, no game-specific fixes — expected for an
|
||||
unknown title). `run` resolves backend=umu (the global default), builds
|
||||
`PROTONPATH`/`WINEPREFIX`/`GAMEID=umu-default`, execs `umu-run`. First launch
|
||||
creates the Wine prefix.
|
||||
|
||||
## 3. Add a second build of a game you already have
|
||||
|
||||
Keeping two builds installed at once — a working one and a newer one you want
|
||||
to test — is a normal thing to do. Both derive the same id, so Runi
|
||||
resolves it instead of overwriting:
|
||||
|
||||
```
|
||||
runi add ~/Games/Mortal.Shell.II.v92120-P2P/.../MortalShell2-Win64-Shipping.exe \
|
||||
--name "Mortal Shell II"
|
||||
|
||||
added mortal-shell-ii-cl92120 (Mortal Shell II)
|
||||
note: a different copy of 'Mortal Shell II' is already registered as
|
||||
mortal-shell-ii; this one is registered alongside it (build version cl92120,
|
||||
from pe-file-version)
|
||||
shared with the other copy: game identity, notes, artwork
|
||||
kept separate: Wine prefix, settings, environment, logs, save backups, mod receipts
|
||||
```
|
||||
|
||||
The suffix is the build version read from the executable, so the id says which
|
||||
copy it is. When the two copies report the same version — common for engines
|
||||
that stamp their own version rather than the game's — there is nothing to tell
|
||||
them apart and Runi asks instead of guessing:
|
||||
|
||||
```
|
||||
Error: 'Ascend to ZERO' is already registered as ascend-to-zero and no build
|
||||
version tells the two copies apart; re-run with --id <unique-id> to add it as a
|
||||
separate installation
|
||||
```
|
||||
|
||||
In the GUI this is one dialog with the proposed id pre-filled and editable,
|
||||
showing the raw version string it came from.
|
||||
|
||||
**If you moved or renamed the folder instead**, that is not a second copy —
|
||||
Runi recognises the installation by the marker inside it and offers to
|
||||
repoint the existing registration, keeping its prefix, saves, settings and
|
||||
mods:
|
||||
|
||||
```
|
||||
Error: this installation is already registered as 'Mortal Shell II'
|
||||
(mortal-shell-ii); it appears to have moved
|
||||
```
|
||||
|
||||
## 4. Add a Steam game with a different Proton version than Steam's own choice
|
||||
|
||||
```
|
||||
runi steam-scan
|
||||
runi add --steam-appid 292030 --backend native --proton GE-Proton9-27
|
||||
```
|
||||
|
||||
`steam-scan` lists installed titles with their AppIDs. `add --steam-appid`
|
||||
resolves title/exe path automatically from the scan (no manual path-typing),
|
||||
sets `game.id = steam-292030`, `identity.steam_appid = 292030`, and
|
||||
`identity.umu_gameid = umu-292030` (so
|
||||
protonfixes still key correctly even though this game runs outside Steam's
|
||||
own launch path). `runi versions` would have already flagged
|
||||
`GE-Proton9-27` as safe for `native` (no `⚠ needs steamrt`), so `add` doesn't
|
||||
need to warn here.
|
||||
|
||||
In the GUI, Runtime separates **Launch Engine** from **Runner family** and
|
||||
**Version**. UMU Automatic selects the newest installed GE-Proton; Direct
|
||||
Proton selects an explicit installed build.
|
||||
|
||||
Manual game creation asks for the executable and suggests Display Name from a
|
||||
local Steam manifest, PE ProductName/FileDescription, a cleaned installation
|
||||
folder or finally the executable filename. The player may edit the suggestion.
|
||||
Runi then resolves Steam/UMU identity in the background from a
|
||||
game-provided `steam_appid.txt`, corroborated AppID metadata in bounded local
|
||||
INI files, an exact local Steam manifest match, or an exact Steam Store title
|
||||
match. Local AppIDs are validated directly through Steam app details, so an
|
||||
imperfect folder title does not prevent identification. Ambiguous matches
|
||||
require confirmation; unavailable or failed validation leaves `umu-default`. Runtime shows the
|
||||
canonical title, source, confidence and validation timestamp, retains editable
|
||||
technical fields and offers explicit revalidation.
|
||||
Settings → Runners manages GE-Proton, UMU-Proton, Proton-CachyOS and experimental
|
||||
Proton-EM releases. Proton Experimental is detected but remains Steam-managed.
|
||||
Installed versions show manager and usage badges. Selecting a games badge
|
||||
expands the titles configured to use that exact version; removal remains
|
||||
disabled until those games are moved to another Runtime version.
|
||||
Selecting an already installed release changes the action to **Installed**.
|
||||
New builds are installed transactionally, so Runi never extracts over an
|
||||
existing runner or exposes a partially extracted build to Steam/UMU.
|
||||
|
||||
### Add from Dolphin
|
||||
|
||||
On KDE Plasma, enable **Settings → Desktop Integration → Show “Add to
|
||||
Runi” in Dolphin**. Right-click one local Windows `.exe` and choose
|
||||
**Add to Runi**. Dolphin sends the path to the already-running GUI, or
|
||||
starts it when necessary, and the normal Add Game review opens.
|
||||
|
||||
The local candidate check is shared by Dolphin, the GUI and `runi add`.
|
||||
Malformed files and DLLs are blocked. A valid PE whose role is unclear, or a
|
||||
file that resembles a settings/installer utility, is shown with evidence and
|
||||
ranked alternatives but may still be added deliberately. Selecting a game
|
||||
already in the library focuses its existing card instead of creating a
|
||||
duplicate. Requests received while a game or modal task is active remain in a
|
||||
small in-memory queue.
|
||||
|
||||
The same entry point is available for desktop integration testing:
|
||||
|
||||
```bash
|
||||
runi gui --add-file /path/to/game.exe
|
||||
runi desktop-integration dolphin status
|
||||
runi desktop-integration dolphin install
|
||||
runi desktop-integration dolphin remove
|
||||
```
|
||||
|
||||
## 5. Add a GOG/Epic title
|
||||
|
||||
```
|
||||
runi add ~/Games/SomeGogGame/game.exe --store gog --name "Some Game"
|
||||
```
|
||||
|
||||
Manual registration in v1 (no Heroic/Legendary auto-scan yet — see
|
||||
roadmap.md (source repository)); `STORE=gog` is still forwarded so protonfixes keyed
|
||||
by store apply if the umu-database has an entry for it.
|
||||
|
||||
## 6. Add games from Ubisoft Connect
|
||||
|
||||
In the GUI choose **+ → Ubisoft Connect**. Install the client automatically or
|
||||
import an existing Ubisoft prefix, open it to log in/install games, then close
|
||||
it. Review the locally detected titles and import the selected ones. Later,
|
||||
**Play** sends the Ubisoft deeplink directly; **Tools → Stores** reopens client
|
||||
management. See [ubisoft-connect.md](ubisoft-connect.md) for the shared-prefix,
|
||||
runner, process-monitoring and cloud-sync rules.
|
||||
|
||||
## 7. Tweak display settings on a stuttery game
|
||||
|
||||
```
|
||||
runi config steam-292030 \
|
||||
--set gamescope.enabled=true \
|
||||
--set gamescope.output_width=2560 --set gamescope.output_height=1440 \
|
||||
--set gamescope.render_width=1920 --set gamescope.render_height=1080 \
|
||||
--set gamescope.filter=fsr \
|
||||
--set mangohud.enabled=true
|
||||
|
||||
runi run steam-292030 --dry-run
|
||||
```
|
||||
|
||||
`--dry-run` prints the fully composed command so you can verify before
|
||||
committing:
|
||||
|
||||
```
|
||||
gamescope -W 2560 -H 1440 -w 1920 -h 1080 -F fsr --mangoapp -- umu-run witcher3.exe
|
||||
```
|
||||
|
||||
Note MangoHud folded into gamescope's own `--mangoapp` flag rather than
|
||||
becoming a separate wrapper stage (gamescope's nested compositor doesn't
|
||||
support the plain MangoHud overlay). Drop `--dry-run` to launch for real.
|
||||
|
||||
## 8. Install OptiScaler for a game with a weak native upscaler
|
||||
|
||||
```
|
||||
runi optiscaler install steam-292030 --dll dxgi.dll
|
||||
runi run steam-292030
|
||||
```
|
||||
|
||||
Downloads the release, backs up any `dxgi.dll` the game already shipped,
|
||||
copies OptiScaler's files alongside the game exe, sets
|
||||
`env.WINEDLLOVERRIDES = "dxgi=n,b"` and `[optiscaler]` state in the config.
|
||||
`run` launches normally; in-game `Insert` opens the OptiScaler overlay to pick
|
||||
an upscaler backend.
|
||||
|
||||
```
|
||||
runi optiscaler remove steam-292030
|
||||
```
|
||||
|
||||
Restores the original file from the recorded manifest and clears the env
|
||||
override.
|
||||
|
||||
## 9. Manage Proton versions over time
|
||||
|
||||
```
|
||||
runi versions install ge-proton latest
|
||||
runi versions remove GE-Proton9-20
|
||||
```
|
||||
|
||||
The second command fails with `VersionInUseError` listing which games still
|
||||
reference that version; re-point those games first
|
||||
(`runi config <id> --set runtime.proton=GE-Proton9-27`), then removal
|
||||
succeeds.
|
||||
|
||||
## 10. Clean up
|
||||
|
||||
```
|
||||
runi remove my-indie-game
|
||||
```
|
||||
|
||||
The CLI deletes the registration and its Wine prefix by default; pass
|
||||
`--keep-prefix` to preserve the prefix. In the GUI, removing only the
|
||||
registration is the safe default. A separate checkbox can permanently delete
|
||||
the marked game installation and its prefix, and warns that local saves and
|
||||
settings stored in either location are also deleted. Runi refuses this
|
||||
disk deletion when the installation ownership marker does not match. The
|
||||
originally selected folder remains the deletion unit even when the executable
|
||||
is nested deeply; recognized Unreal packages include the sibling `Engine`
|
||||
directory rather than only `Binaries/Win64`. Existing Runi save backups
|
||||
are kept.
|
||||
|
||||
For Ubisoft games, the shared prefix is always preserved. The GUI can open
|
||||
Ubisoft Connect's own uninstall confirmation before removing the Runi
|
||||
registration; it never deletes the Ubisoft game directory itself.
|
||||
|
||||
## 11. Recipes, and what replaced them
|
||||
|
||||
**There is no Recipe tab, and adding a game no longer offers one.** Recipes
|
||||
asked the player to assemble a setup from required items, recommended items,
|
||||
supplied asset files and a review step - which is a great deal of deciding for
|
||||
someone who wanted to press Play. The OptiScaler tab now narrows the same
|
||||
ground to two explicit choices with automatic selection between them, so the
|
||||
flow that used to live here is [flow 8](#8-install-optiscaler-for-a-game-with-a-weak-native-upscaler).
|
||||
|
||||
What remains is rollback. A game that had a recipe applied *before* the
|
||||
retirement still carries its receipt, and shows a **Recipe (Legacy)** entry
|
||||
for as long as that receipt exists, so the transaction can still be reverted.
|
||||
Nothing creates a new one.
|
||||
|
||||
For an upscaler build with no official source to automate - a leaked FSR
|
||||
package, a DLSS/XeSS swap - the supported path is a customize profile, which
|
||||
is [flow 13](#13-apply-a-community-upscaler-workaround-manually-advanced).
|
||||
|
||||
## 12. Known launch compatibility
|
||||
|
||||
When Runi recognises an exact canonical game with a verified launch
|
||||
workaround, it applies the safe default during registration or before launch
|
||||
and shows a short notification. Explicit user launch options take precedence.
|
||||
|
||||
The first verified case is Pathogenic's Windows build on Linux: its Godot 4.7
|
||||
D3D12 path crashed in RADV while creating a VKD3D compute pipeline, while the
|
||||
same executable ran correctly with `--rendering-driver vulkan`. This argument
|
||||
is scoped to Pathogenic's PGID; it is not inferred for other Godot games.
|
||||
|
||||
## 13. Apply a community upscaler workaround manually (advanced)
|
||||
|
||||
Real example, validated end-to-end: enabling a leaked FSR 4.1.1 INT8 build
|
||||
on non-RDNA4 hardware, for a game with OptiScaler already installed.
|
||||
|
||||
```
|
||||
runi optiscaler install my-game --dll dxgi.dll
|
||||
```
|
||||
|
||||
Write `~/.config/runi/profiles/fsr411-workaround.toml` once (see
|
||||
[customize-profiles.md](customize-profiles.md) for the full format):
|
||||
|
||||
```toml
|
||||
[profile]
|
||||
name = "fsr411-workaround"
|
||||
description = "Community FSR 4.1.1 INT8 workaround for non-RDNA4 GPUs"
|
||||
|
||||
[[files]]
|
||||
src = "/home/user/Downloads/FidelityFX-Samples-v2.3.0-prebuilt/.../amd_fidelityfx_upscaler_dx12.dll"
|
||||
dest = "amd_fidelityfx_upscaler_dx12.dll"
|
||||
|
||||
[[ini_edits]]
|
||||
file = "OptiScaler.ini"
|
||||
[ini_edits.set]
|
||||
Fsr4Update = "true"
|
||||
```
|
||||
|
||||
```
|
||||
runi customize apply my-game fsr411-workaround
|
||||
runi run my-game
|
||||
```
|
||||
|
||||
In-game, press `Insert` for the OptiScaler overlay and select FSR4 → the
|
||||
INT8 model. If a newer leaked build shows up later, just edit the profile's
|
||||
`[[files]]` source paths and re-`apply` — the previous version is reverted
|
||||
automatically before the new one is applied.
|
||||
|
||||
```
|
||||
runi customize remove my-game
|
||||
```
|
||||
|
||||
Restores the pre-customize state exactly (the stock OptiScaler files,
|
||||
`Fsr4Update` back to its original value) — OptiScaler itself stays installed.
|
||||
|
||||
## 14. Enable LinUwUx for a Reflex/DenuvOwO-protected game
|
||||
|
||||
```
|
||||
runi config my-game --set linuwux.enabled=true
|
||||
runi run my-game --dry-run
|
||||
```
|
||||
|
||||
`--dry-run` confirms the wrapper is in place:
|
||||
|
||||
```
|
||||
linuwux umu-run MyGame.exe
|
||||
```
|
||||
|
||||
Drop `--dry-run` to launch for real. If the Technology Scan (GUI Overview
|
||||
tab, or `runi doctor`) already flagged a "Compat Shim: LinUwUx (Reflex)"
|
||||
or "LinUwUx (DenuvOwO)" finding for this game, that's an informational hint
|
||||
only — Runi never enables the wrapper automatically.
|
||||
|
||||
`[linuwux].debug=true` adds `LINUWUX_DEBUG=1` for troubleshooting; leave it
|
||||
off for normal play, since it produces heavy `sigsys redirect` logging.
|
||||
|
||||
`[linuwux].enabled` cannot be combined with `[gamescope].enabled` —
|
||||
confirmed crashing on real hardware (the gamescope session dies before the
|
||||
game process ever starts), not just untested. `runi run`/`--dry-run`
|
||||
refuse with `IncompatibleWrapperError` up front rather than launching into a
|
||||
crash; disable gamescope first if you need linuwux.
|
||||
|
||||
`[mangohud].enabled` works fine alongside linuwux — Runi activates it
|
||||
with `MANGOHUD=1` alone instead of mangohud's usual `LD_PRELOAD` wrapper
|
||||
(which does collide), confirmed working on real hardware. No extra
|
||||
configuration needed; just enable both.
|
||||
|
||||
If you want a custom MangoHud layout via `[mangohud].config_file`, MangoHud's
|
||||
own `core_load` option (per-core CPU load/frequency) is worth knowing about:
|
||||
bisected on real hardware (Monster Hunter Wilds) that enabling `core_load`
|
||||
forces MangoHud back to a vertical layout even with `horizontal` set - a
|
||||
per-core breakdown doesn't fit a horizontal row. Every other stat option
|
||||
(`cpu_stats`, `gpu_stats`, `ram`, `vram`, `frame_timing`, etc.) composes fine
|
||||
with `horizontal`; only `core_load` is the exception. This is MangoHud's own
|
||||
behavior, unrelated to Runi or linuwux specifically.
|
||||
|
||||
That exact layout ships as a bundled preset - see flow 15 below - so you
|
||||
don't have to rebuild it by hand:
|
||||
`runi mangohud-preset apply horizontal-complete <id>`.
|
||||
|
||||
LinUwUx upstream documents only GE-Proton and Proton-CachyOS as supported
|
||||
(official Valve Proton is not). If `[runtime].proton` resolves to anything
|
||||
else, `runi doctor` surfaces a non-blocking advisory finding rather
|
||||
than refusing to launch:
|
||||
|
||||
```
|
||||
runi doctor
|
||||
```
|
||||
|
||||
Requires `linuwux` to already be on `PATH` (installed via upstream's own
|
||||
`install.sh`/`build.sh --install` — see
|
||||
[github.com/brcly/linuwux-runtime](https://github.com/brcly/linuwux-runtime));
|
||||
Runi never installs or manages it.
|
||||
|
||||
## 15. Save and apply a named MangoHud preset
|
||||
|
||||
```
|
||||
runi mangohud-preset list
|
||||
```
|
||||
|
||||
Shows every available preset — bundled (shipped with Runi) plus any
|
||||
you've saved locally. Runi ships one bundled preset out of the box,
|
||||
`horizontal-complete`, the exact layout validated in flow 14 above:
|
||||
|
||||
```
|
||||
runi mangohud-preset apply horizontal-complete my-game
|
||||
runi config my-game --set mangohud.enabled=true
|
||||
```
|
||||
|
||||
This writes the preset's content to a per-game file
|
||||
(`~/.local/share/runi/mangohud/<id>.conf`) and sets
|
||||
`[mangohud].config_file` to it — the same effect as building the file by
|
||||
hand and pointing `config_file` at it, just one command. `runi run
|
||||
my-game --dry-run` shows the resolved `MANGOHUD_CONFIGFILE` to confirm.
|
||||
|
||||
To save your own config (built by hand, or already applied to a game you're
|
||||
happy with) as a reusable preset:
|
||||
|
||||
```
|
||||
runi mangohud-preset save my-layout --from-game my-game --description "my favorite layout"
|
||||
# or, from an arbitrary file:
|
||||
runi mangohud-preset save my-layout --from-file ~/some/MangoHud.conf
|
||||
```
|
||||
|
||||
Saved presets are plain TOML at
|
||||
`~/.config/runi/mangohud-presets/<name>.toml`; a local preset with the
|
||||
same name as a bundled one takes priority, so you can override
|
||||
`horizontal-complete` itself by saving your own version under that name.
|
||||
|
||||
```
|
||||
runi mangohud-preset remove my-game
|
||||
```
|
||||
|
||||
Clears `[mangohud].config_file` (falling back to MangoHud's own default
|
||||
resolution order) and deletes the applied per-game file.
|
||||
|
||||
**Deliberately per-game only** — there is no "apply globally" (writing
|
||||
`~/.config/MangoHud/MangoHud.conf`). That file is commonly managed by
|
||||
third-party tools like GOverlay/MangoJuice; a second tool silently writing
|
||||
over it (even with a backup taken first) creates an ongoing "who owns this
|
||||
file" conflict with no clean resolution, confirmed real on a machine already
|
||||
running MangoJuice. If you want a preset's layout system-wide, apply it to
|
||||
each game you care about, or use GOverlay/MangoJuice's own global-config
|
||||
tooling directly.
|
||||
|
||||
## 16. Import a Ludusavi save backup into a game's prefix
|
||||
|
||||
CLI-only, low-key utility — reads a [Ludusavi](https://github.com/mtkennerly/ludusavi)
|
||||
backup folder (`mapping.yaml` + `drive-C/`, the "simple" non-zip format)
|
||||
directly and copies the files into the game's own Wine prefix. No
|
||||
dependency on the `ludusavi` binary itself.
|
||||
|
||||
```
|
||||
runi saves import-ludusavi my-game /path/to/ludusavi/backups/My\ Game --preview
|
||||
```
|
||||
|
||||
`--preview` shows exactly what would be copied — source → destination →
|
||||
size — without writing anything. The prefix must already exist (launch the
|
||||
game for real at least once first); otherwise this refuses rather than
|
||||
half-creating one.
|
||||
|
||||
```
|
||||
runi saves import-ludusavi my-game /path/to/ludusavi/backups/My\ Game
|
||||
```
|
||||
|
||||
Copies every recorded file, verifying each one's SHA1 against what
|
||||
`mapping.yaml` recorded (catches SMB/network corruption, not just logic
|
||||
bugs), and — unless `--no-set-save-path` is passed — updates
|
||||
`[saves].path` to the backup's own save location, so Runi's own
|
||||
`saves backup`/`restore` also point at the right place afterward. If the
|
||||
computed destination already has files in it, the *current* state is
|
||||
backed up first via the normal `saves backup` mechanism, so an import is
|
||||
always reversible.
|
||||
|
||||
Two real things this surfaced, worth knowing if a result looks surprising:
|
||||
|
||||
- **Path casing and shared folders.** Ludusavi records the source Windows
|
||||
machine's own path casing (`Users`, capitalized) and username segment
|
||||
(e.g. `Users/cvarandas/...`). Wine's real prefix directory is lowercase
|
||||
(`users`) with its own account name (`steamuser` in every
|
||||
runi-managed prefix) — a blind copy would create a bogus sibling
|
||||
directory the game never sees. The importer resolves both against what's
|
||||
actually on disk. It also knows `Users/Public/...` is a real, shared
|
||||
folder, not a personal account to remap — confirmed by a real game (The
|
||||
Sinking City 2) whose save data legitimately lives partly under `Public`.
|
||||
- **A RUNE/crack config's declared save path and a Ludusavi backup's
|
||||
actual files can legitimately disagree.** The Sinking City 2's RUNE
|
||||
crack (`steam_emu.ini`) declares saves at
|
||||
`users/Public/Documents/Steam/RUNE/2825860` — that's likely where RUNE
|
||||
stores its *own* Steam-emulation state (fake achievements/cloud markers),
|
||||
not necessarily the engine's native save files, which for this game
|
||||
turned out to be `AppData/Local/TSC2/Saved` (confirmed by both the real
|
||||
backup content and an actual game launch). The importer prints a note
|
||||
when this happens (reusing Runi's existing RUNE detection as a
|
||||
cross-check) but always imports to where the backup's files really are.
|
||||
|
||||
**Runi cannot verify a backup matches the installed game's specific
|
||||
version/release** — Ludusavi's `mapping.yaml` records hashes for the save
|
||||
data itself, not the source machine's game build. The safety backup above
|
||||
is the actual mitigation; confirm compatibility the same way it was
|
||||
confirmed here — launch the game and check.
|
||||
|
||||
Only the simple (non-zip) backup format is supported; a zip-format backup
|
||||
folder is refused with a clear error rather than silently mishandled.
|
||||
Reference in New Issue
Block a user