docs(design): trim shipped plans, consolidate cluster, add index
Much of design/ described work that has since shipped. Trim each doc to
its durable rationale + still-open items (the code is the source of truth
for shipped detail; git history holds the full originals).
- Shipped plans -> status stubs: stats-capture, gamestream-host-plan,
apple-stage2-presenter, windows-service.
- Trimmed completed-out / open-kept: implementation-plan, hdr-pipeline,
host-latency, gpu-contention (fixed stale status table), game-library,
linux-setup (fixed m0->spike + stale zero-copy claim),
session-aware-host-followups, windows-client-bootstrap,
windows-dualsense-{scoping,game-detection}, windows-virtual-display,
security-review (per-finding status table; #12 still open),
apollo-comparison (shipped backlog collapsed to one-liners).
- Windows-host cluster consolidated: windows-host.md -> redirect into
windows-host-rewrite.md (whose stale scorecard is corrected -- goal1 is
merged, M4 done); windows-secure-desktop.md archived (now a fallback
behind IDD-push primary).
- Kept evergreen: ci.md, gamescope-multiuser.md, windows-build-and-packaging.md.
- New design/README.md: per-doc status table + consolidated open-items
roll-up so nothing is tracked in only one buried doc.
- Repoint 5 code comments to the archived secure-desktop doc path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+66
-143
@@ -1,6 +1,11 @@
|
||||
# Game library: more game stores
|
||||
|
||||
Status: **design / not started** · Author research: web-backed, adversarially verified (2026-06-26).
|
||||
> **Status:** Phases 1–4 SHIPPED — 6 `LibraryProvider` impls (Steam, Lutris, Heroic, Epic, GOG,
|
||||
> Xbox) in [`crates/punktfunk-host/src/library.rs`](../crates/punktfunk-host/src/library.rs)
|
||||
> (1869 lines; commits `5f8c6b6` Lutris+Heroic, `b657452` Epic+GOG, `aed0bf0` Xbox, `5acc12d` shared
|
||||
> art cache, `7e9023f` GameStream/Windows+non-gamescope launch wiring, `203ad80` web store badges).
|
||||
> Phases 5–6 (the remaining 6 providers + the `/library/art` endpoint) are **pending**. This doc is
|
||||
> trimmed to design rationale + open items; the shipped code is the source of truth.
|
||||
|
||||
Goal: extend the unified game library so it enumerates and launches titles from more stores —
|
||||
on **Windows** Xbox / Game Pass, Epic, EA app (and GOG / Ubisoft / Battle.net / Amazon);
|
||||
@@ -8,11 +13,10 @@ on **Linux** Heroic (Epic+GOG+Amazon), Lutris, and a `.desktop`/Flatpak catch-al
|
||||
|
||||
---
|
||||
|
||||
## 1. Where the extension point already is
|
||||
## 1. The extension point
|
||||
|
||||
The library lives in [`crates/punktfunk-host/src/library.rs`](../crates/punktfunk-host/src/library.rs)
|
||||
and is already a plug-in system — its own doc comment names these exact targets. Adding a store is
|
||||
a new `LibraryProvider`, not a rewrite.
|
||||
The library lives in [`library.rs`](../crates/punktfunk-host/src/library.rs) and is a plug-in system:
|
||||
adding a store is a new `LibraryProvider`, not a rewrite.
|
||||
|
||||
```rust
|
||||
pub trait LibraryProvider {
|
||||
@@ -21,21 +25,11 @@ pub trait LibraryProvider {
|
||||
}
|
||||
pub struct GameEntry { id: String /* "<store>:<localid>" */, store, title, art: Artwork, launch: Option<LaunchSpec> }
|
||||
pub struct Artwork { portrait, hero, logo, header: Option<String> } // URLs the CLIENT fetches
|
||||
pub struct LaunchSpec{ kind: String, value: String } // today: "steam_appid" | "command"
|
||||
pub struct LaunchSpec{ kind: String, value: String }
|
||||
```
|
||||
|
||||
Today: `SteamProvider` (reads local `.acf` / `.vdf` files — **no API key, no network**) plus a
|
||||
user-curated `custom` store. `all_games()` merges them; `launch_command(id)` resolves a
|
||||
store-qualified id **against the host's own library** and maps the `LaunchSpec` to a shell command,
|
||||
with injection guards (`steam_appid` is validated digits-only; the client never sends a raw command).
|
||||
|
||||
**The "read the launcher's own on-disk files, no auth" approach is the gold standard we replicate per store.**
|
||||
|
||||
Surfaces touched by adding stores:
|
||||
- `library.rs` — new providers (the bulk of the work is small per store).
|
||||
- [`mgmt.rs`](../crates/punktfunk-host/src/mgmt.rs) `:1138` — serves `/library`; OpenAPI-generated TS client picks up new stores as data.
|
||||
- [`web/src/sections/Library/view.tsx`](../web/src/sections/Library/view.tsx) — the grid; **store badge is hard-coded** steam-vs-custom, needs generalizing per `game.store`.
|
||||
- Launch wiring: [`punktfunk1.rs`](../crates/punktfunk-host/src/punktfunk1.rs) `:573` (native) and [`gamestream/stream.rs`](../crates/punktfunk-host/src/gamestream/stream.rs) `:122` (Moonlight).
|
||||
**The "read the launcher's own on-disk files, no auth" approach is the gold standard we replicate per
|
||||
store.** Launcher-need-not-be-running unless noted.
|
||||
|
||||
> The legacy GameStream `apps.json` ([`gamestream/apps.rs`](../crates/punktfunk-host/src/gamestream/apps.rs))
|
||||
> is a **separate** Moonlight surface (session recipes: compositor + nested command) and stays as-is.
|
||||
@@ -103,12 +97,12 @@ Steam gets free CDN art keyed by appid. Most stores don't. Layered ladder, degra
|
||||
<operator key>`, **off by default**) to fill gaps. Not no-auth; never blocks listing.
|
||||
6. **None** → existing title-only card.
|
||||
|
||||
**New endpoint:** `GET /library/art/<entryId>/<slot>` (slot ∈ `portrait|hero|logo|header`) on `mgmt.rs`.
|
||||
It resolves `entryId` in the host library to a **known on-disk absolute path** (never interpolates raw
|
||||
client input into a filesystem path), sanitizes the slot, rejects `..`, streams the bytes with the right
|
||||
content-type. Reserve `data:` URLs for tiny logos only (don't bloat the catalog JSON that crosses the
|
||||
control plane). See open question on whether this GET bypasses the mgmt bearer (images are non-sensitive
|
||||
and the streaming client connects over punktfunk/1, not the bearer-gated REST).
|
||||
**New endpoint (still pending):** `GET /library/art/<entryId>/<slot>` (slot ∈ `portrait|hero|logo|header`)
|
||||
on `mgmt.rs`. It resolves `entryId` in the host library to a **known on-disk absolute path** (never
|
||||
interpolates raw client input into a filesystem path), sanitizes the slot, rejects `..`, streams the bytes
|
||||
with the right content-type. Reserve `data:` URLs for tiny logos only (don't bloat the catalog JSON that
|
||||
crosses the control plane). See open question on whether this GET bypasses the mgmt bearer (images are
|
||||
non-sensitive and the streaming client connects over punktfunk/1, not the bearer-gated REST).
|
||||
|
||||
---
|
||||
|
||||
@@ -141,7 +135,7 @@ The one net-new surface is `GET /library/art` — covered in §2b (id-resolved p
|
||||
|
||||
---
|
||||
|
||||
## 4. New `LaunchSpec` kinds
|
||||
## 4. `LaunchSpec` kinds
|
||||
|
||||
| kind | value holds | maps to |
|
||||
|---|---|---|
|
||||
@@ -160,45 +154,31 @@ The one net-new surface is `GET /library/art` — covered in §2b (id-resolved p
|
||||
|
||||
## 5. Per-store provider catalog
|
||||
|
||||
Confidence is **after** adversarial web-verification (research → verify). All enumeration is no-auth,
|
||||
local, launcher-need-not-be-running unless noted.
|
||||
All enumeration is no-auth, local. Confidence is **after** adversarial web-verification.
|
||||
|
||||
### Linux
|
||||
### Shipped (phases 1–4)
|
||||
|
||||
#### Lutris — P0, effort M, confidence **high**
|
||||
- **Enumerate:** read-only `rusqlite` open of `pga.db`
|
||||
(`$XDG_DATA_HOME/lutris` | `~/.local/share/lutris` | `~/.var/app/net.lutris.Lutris/data/lutris`).
|
||||
`SELECT id, slug, name, runner FROM games WHERE installed=1`. Optionally LEFT JOIN
|
||||
`games_categories`/`categories` to drop the `.hidden` category. Open `mode=ro`/`immutable=1` (Lutris
|
||||
holds it open). `installed=1` matters — the DB also lists owned-but-not-installed rows.
|
||||
- **Launch:** `lutris_id` → `lutris lutris:rungameid/<id>` (execs the game; most nesting-friendly).
|
||||
One-time on-box check that `games.id` == the `rungameid` int.
|
||||
- **Artwork:** **local** JPEGs keyed by slug — `coverart/<slug>.jpg` (→ portrait), `banners/<slug>.jpg`
|
||||
(→ header) under `~/.local/share/lutris` (0.5.18+), with `~/.cache/lutris` (≤0.5.17) and the Flatpak
|
||||
cache as fallbacks. Needs the `/library/art` endpoint. hero/logo stay None.
|
||||
- **Notes:** highest-confidence new store. A `runner=='steam'` row can duplicate `SteamProvider` — dedup
|
||||
is a nicety. Verify bundled-SQLite is fine for deb/rpm/flatpak.
|
||||
| store | OS | enumerate | launch kind | art |
|
||||
|---|---|---|---|---|
|
||||
| **Steam** | both | local `.acf`/`.vdf` | `steam_appid` | Steam CDN (client-direct) |
|
||||
| **Lutris** | Linux | read-only `pga.db` (`installed=1`) | `lutris_id` | local JPEGs (needs `/library/art`, still pending) |
|
||||
| **Heroic** | Linux | `store_cache/{legendary,gog,nile}_library.json` | `heroic` | free public CDN (`art_*`) |
|
||||
| **Epic** | Windows | `…\Manifests\*.item` | `epic` | local `catcache.bin` keyImages |
|
||||
| **GOG** | Windows | registry + `goggame-<id>.info` | `gog` (direct-exe) | `api.gog.com/products/<id>?expand=images` |
|
||||
| **Xbox / Game Pass** | Windows | `XboxGames\*\Content\MicrosoftGame.config` + AppRepository PFN | `aumid` | unofficial `displaycatalog` lookup |
|
||||
|
||||
#### Heroic — P0, effort M, confidence **high** (one provider = Epic + GOG + Amazon, art free)
|
||||
- **Enumerate:** parse `~/.config/heroic/store_cache/{legendary,gog,nile}_library.json` (Flatpak:
|
||||
`~/.var/app/com.heroicgameslauncher.hgl/config/heroic/...`). Data key is `"library"` (legendary/nile)
|
||||
or `"games"` (gog); ignore `__timestamp.*` siblings. Filter `is_installed==true` **and** cross-check
|
||||
`install.install_path` exists (works around the gog `is_installed` bug, Heroic #2691). Fall back to
|
||||
`legendaryConfig/legendary/installed.json` etc. when a cache file is absent.
|
||||
*(Heroic uses `legendaryConfig/legendary`, **not** the standalone `~/.config/legendary`.)*
|
||||
- **Launch:** `heroic` → `heroic --no-gui "heroic://launch?appName=<app>&runner=<runner>"` (argv, no shell).
|
||||
`--no-gui` does the suppression; the `gui=false` query param is **inert/fabricated** — drop it.
|
||||
**Ship enumeration+art first, gate launch:** Heroic is single-instance Electron — if already running it
|
||||
forwards the URI and **exits**, which (as gamescope's foreground child) would tear the session down while
|
||||
the game runs **outside** gamescope, uncaptured. Also Electron needs a display — fine nested in gamescope,
|
||||
not in a bare headless context.
|
||||
- **Artwork:** **free** — `art_square` → portrait, `art_cover` → header, `art_background`||`art_cover` →
|
||||
hero, `art_logo` → logo are already public Epic/GOG/Amazon CDN URLs. Skip non-`http(s)` values
|
||||
(sideloaded `file://` art). No host endpoint.
|
||||
- **Notes:** do **not** also build separate Linux GOG/Amazon providers — native Linux GOG Galaxy doesn't
|
||||
exist; Heroic is the canonical Linux path for those.
|
||||
The hard-won corrections folded into these (keep when revisiting): Epic uses Playnite's **exclusion**
|
||||
filter (skip `UE_`, DLC `addons` w/o `addons/launchable`), builds the namespace:catalog:app **triple** when
|
||||
ids exist else **falls back to the bare `appName` URI** (don't set launch=None); GOG launches the
|
||||
**direct exe** (dodges Galaxy cold-start/anti-cheat); Xbox **reads** the PackageFamilyName from the
|
||||
`AppRepository\Packages\<PackageFullName>` dir name (**never** hash the publisher), scans `XboxGames` rather
|
||||
than parse the undocumented `.GamingRoot`, and UWP `aumid` activation is load-bearing on the interactive
|
||||
user token; Heroic `gui=false` is inert (`--no-gui` does it) and single-instance Electron forwards-and-exits
|
||||
(launch was gated). Misses pure-UWP (non-GDK) Store games under ACL-locked `WindowsApps` — accepted for v1.
|
||||
|
||||
#### Desktop (`.desktop` + Flatpak) — P1, effort M, confidence medium (universal catch-all)
|
||||
### Remaining providers (phases 5–6)
|
||||
|
||||
#### Desktop (`.desktop` + Flatpak) — Linux, P1, effort M, confidence medium (universal catch-all)
|
||||
- **Enumerate:** scan `{/var/lib/flatpak/exports/share/applications,
|
||||
~/.local/share/flatpak/.../applications, /usr/share/applications, /usr/local/share/applications,
|
||||
~/.local/share/applications}/*.desktop`. Require `Type=Application` + `Categories` contains `Game`; skip
|
||||
@@ -210,7 +190,7 @@ local, launcher-need-not-be-running unless noted.
|
||||
App icons are low-res, not box art (acceptable header fallback).
|
||||
- **Notes:** run **last** and dedup by install path / drop ids already surfaced by Steam/Heroic/Lutris.
|
||||
|
||||
#### itch.io — P3, effort S, confidence medium (Linux + Windows)
|
||||
#### itch.io — Linux + Windows, P3, effort S, confidence medium
|
||||
- **Enumerate:** read-only `rusqlite` of `butler.db` (`~/.config/itch/db/butler.db`; Flatpak
|
||||
`io.itch.itch`; Windows `%AppData%\itch\db`, per-user). JOIN `caves`→`games`. **Key on `cave.ID`** (a
|
||||
game can have multiple caves; install location + verdict are per-cave). Read game title / `cover_url`;
|
||||
@@ -220,53 +200,7 @@ local, launcher-need-not-be-running unless noted.
|
||||
`flavor==native` (html/jar/love need itch's runtime — fall back to custom).
|
||||
- **Artwork:** **free** — `games.cover_url` is a public itch CDN URL.
|
||||
|
||||
### Windows
|
||||
|
||||
#### Epic Games Store — P1, effort M, confidence medium (cleanest Windows store to validate the launch wiring)
|
||||
- **Enumerate:** read `C:\ProgramData\Epic\EpicGamesLauncher\Data\Manifests\*.item` (JSON; machine-wide,
|
||||
SYSTEM-readable, launcher need not run). Read `DisplayName`, `AppName`, `CatalogNamespace`,
|
||||
`CatalogItemId`, `InstallLocation`, `LaunchExecutable`, `MainGameAppName`, `AppCategories`. Iterate the
|
||||
dir (filename is a random GUID).
|
||||
**Use Playnite's EXCLUSION filter, not a positive `games` filter:** skip `AppName` starting `UE_`; skip
|
||||
DLC only when `AppCategories` has `addons` && **not** `addons/launchable`; require `InstallLocation`
|
||||
exists. (The first-pass positive filter `games + MainGameAppName==AppName` can drop legit games.)
|
||||
- **Launch:** `epic` → Spawn `EpicGamesLauncher.exe` + `com.epicgames.launcher://apps/<ns>%3A<cat>%3A<app>?action=launch&silent=true`.
|
||||
Build the **triple** only when both namespace and CatalogItemId are present; otherwise **fall back to the
|
||||
bare `appName` URI (don't set launch=None)** — bare still works in Playnite today, it's just less robust.
|
||||
CatalogItemId is **not** present in every `.item` — verify on a real box.
|
||||
- **Artwork:** **free** — base64-decode + parse `Data\Catalog\catcache.bin`, index by catalogItemId, map
|
||||
keyImages `DieselGameBoxTall`→portrait, `DieselGameBox`→hero, `DieselGameBoxLogo`→logo. None on miss.
|
||||
- **Notes:** `.item` + `catcache.bin` are community-RE'd; `silent=true` may not suppress a cold-start
|
||||
launcher window.
|
||||
|
||||
#### GOG — P1, effort M, confidence medium
|
||||
- **Enumerate:** registry `HKLM\SOFTWARE\WOW6432Node\GOG.com\Games\<id>` (PATH/GAMENAME/gameID/EXE) or
|
||||
Uninstall `<id>_is1` keys with `Publisher=='GOG.com'` (exclude `GOGPACK*`). Parse
|
||||
`<PATH>\goggame-<id>.info` for `playTasks[isPrimary && type=='FileTask']` → exe/args/workingDir.
|
||||
- **Launch:** `gog` → **direct-exe** Spawn (no Galaxy dependency, dodges cold-start/anti-cheat). Optional
|
||||
fallback: `GalaxyClient.exe /launchViaAutostart /gameId=<id> /command=runGame /path="<dir>"` (note the
|
||||
`/launchViaAutostart` token; `goggalaxy://openGameView/<id>` only **opens the page**, doesn't launch).
|
||||
- **Artwork:** **free** — public no-auth `GET https://api.gog.com/products/<id>?expand=images` →
|
||||
`images.logo2x`/`verticalCover`/`background`; cache resolved URLs. (`goggame-.info` carries no art; the
|
||||
Galaxy `galaxy-2.0.db` is undocumented/locked — avoid.)
|
||||
|
||||
#### Xbox / Microsoft Store / Game Pass — P1, effort **L**, confidence medium (big Game Pass value, most plumbing)
|
||||
- **Enumerate:** probe each fixed drive for an `XboxGames` dir (default `C:\XboxGames`; the `.GamingRoot`
|
||||
binary layout is **undocumented** — just scan, don't depend on parsing it). For each
|
||||
`<Title>\Content\MicrosoftGame.config` (**presence = it's a GDK game**, the game-vs-app signal) read
|
||||
`ShellVisuals.DefaultDisplayName` (title), `<StoreId>` (12-char BigId, the art key), `Identity Name`,
|
||||
`<Executable Id="Game">` (the AppId). **Read the PackageFamilyName from the
|
||||
`C:\ProgramData\Microsoft\Windows\AppRepository\Packages\<PackageFullName>` directory name** (strip
|
||||
`_Version_Arch_~_PublisherHash`) — **never compute the PFN by hashing the publisher**. AUMID = `PFN!AppId`.
|
||||
- **Launch:** `aumid` → `explorer.exe shell:AppsFolder\<AUMID>` into the interactive session. **UWP
|
||||
activation fails from SYSTEM/session-0 — the interactive user token is load-bearing.**
|
||||
- **Artwork:** one **unofficial** no-auth lookup
|
||||
`displaycatalog.mp.microsoft.com/v7.0/products/<StoreId>?market=US&languages=en-us&fieldsTemplate=Details`,
|
||||
map `Images[]` ImagePurpose Poster→portrait / SuperHeroArt→hero / Logo→logo / BoxArt→header; cache to
|
||||
the config dir, degrade to no-art offline. Not a stable contract.
|
||||
- **Notes:** misses pure-UWP (non-GDK) Store games under the ACL-locked `WindowsApps` — accept for v1.
|
||||
|
||||
#### Ubisoft Connect — P2, effort S, confidence medium
|
||||
#### Ubisoft Connect — Windows, P2, effort S, confidence medium
|
||||
- **Enumerate:** registry `HKLM\SOFTWARE\WOW6432Node\Ubisoft\Launcher\Installs\<gameId>` (both reg views),
|
||||
read `InstallDir`; title = install-dir leaf folder (primary) else the `Uplay Install <gameId>` Uninstall
|
||||
`DisplayName`.
|
||||
@@ -274,7 +208,7 @@ local, launcher-need-not-be-running unless noted.
|
||||
- **Notes:** smallest effort once the Windows URI-launch wiring exists; hive+scheme unchanged across the
|
||||
Origin→EA migration.
|
||||
|
||||
#### Amazon Games — P2, effort S, confidence medium
|
||||
#### Amazon Games — Windows, P2, effort S, confidence medium
|
||||
- **Enumerate:** read-only `rusqlite` of
|
||||
`%LocalAppData%\Amazon Games\Data\Games\Sql\GameInstallInfo.sqlite`:
|
||||
`SELECT Id,ProductTitle,InstallDirectory FROM DbSet WHERE Installed=1`. **Per-user path** — the SYSTEM
|
||||
@@ -282,7 +216,7 @@ local, launcher-need-not-be-running unless noted.
|
||||
- **Launch:** `amazon` → `amazon-games://play/<Id>` (impersonate-token ShellExecute; no clean exe-argv form).
|
||||
- **Artwork:** `ProductIconUrl`/`ProductLogoUrl` columns when present, else none.
|
||||
|
||||
#### Battle.net — P2, effort **L**, confidence medium (high catalog value: WoW/Diablo IV/Overwatch 2/CoD)
|
||||
#### Battle.net — Windows, P2, effort **L**, confidence medium (high catalog value: WoW/Diablo IV/Overwatch 2/CoD)
|
||||
- **Enumerate:** hand-roll a ~4-field protobuf decode of `C:\ProgramData\Battle.net\Agent\product.db`
|
||||
(`product_install{ uid, product_code, settings.install_path, cached_product_state.base_product_state.installed }`).
|
||||
Registry fallback: Uninstall keys whose `UninstallString` matches `Battle.net.exe --uid=<uid>`.
|
||||
@@ -292,7 +226,7 @@ local, launcher-need-not-be-running unless noted.
|
||||
`battlenet://<code>` URI, which only hands off). **Artwork:** none → title-only.
|
||||
- **Notes:** the protobuf + name map + no-art make it L; pin the `.proto` and decode defensively.
|
||||
|
||||
#### EA app — P2, effort M, confidence medium (most closed/fragile — ship last)
|
||||
#### EA app — Windows, P2, effort M, confidence medium (most closed/fragile — ship last)
|
||||
- **Enumerate:** registry `HKLM\SOFTWARE\WOW6432Node\{EA Games,Origin Games}\<id>` (Install Dir /
|
||||
DisplayName), parse `<dir>\__Installer\installerdata.xml` for the **full** `<contentIDs>` list +
|
||||
`<gameTitle locale='en_US'>`. Registry under-reports for EA-app (vs legacy Origin) installs — known
|
||||
@@ -308,26 +242,24 @@ local, launcher-need-not-be-running unless noted.
|
||||
|
||||
---
|
||||
|
||||
## 6. Suggested structure & phasing
|
||||
## 6. Structure & phasing
|
||||
|
||||
**Structure.** Split `library.rs` → a `library/` dir before it balloons:
|
||||
`mod.rs` (trait, wire types, `LaunchAction`, custom CRUD, `all_games`, `resolve_launch`,
|
||||
**Structure (still pending refactor).** Split the 1869-line `library.rs` → a `library/` dir before it
|
||||
balloons further: `mod.rs` (trait, wire types, `LaunchAction`, custom CRUD, `all_games`, `resolve_launch`,
|
||||
`launch_command`/`launch_title`), `steam.rs`, one file per provider, `art.rs` (ArtResolver +
|
||||
displaycatalog/gog-api/steamgriddb helpers), `win_util.rs` (HKLM subkey enumerator, read-only SQLite
|
||||
opener, tiny read-only XML reader). New deps: `rusqlite` (bundled, read-only) for lutris/itch/amazon DBs;
|
||||
`roxmltree`/`quick-xml` for the Windows manifests; registry via the `windows` crate's
|
||||
opener, tiny read-only XML reader). Deps in play: `rusqlite` (bundled, read-only) for lutris/itch/amazon
|
||||
DBs; `roxmltree`/`quick-xml` for the Windows manifests; registry via the `windows` crate's
|
||||
`Win32_System_Registry` feature (no new crate). Avoid `prost` — hand-roll the ~4 Battle.net fields.
|
||||
|
||||
| Phase | Deliverable | Files |
|
||||
|---|---|---|
|
||||
| **1 — Foundation** (no new stores) | Split `library.rs` → `library/`; add `LaunchAction` + `resolve_launch`; factor `windows/interactive.rs::spawn_in_active_session` out of `wgc_relay.rs`; make `set_launch_command` real on Windows; wire `launch_title` at session-start post-capture; add `win_util.rs` + deps | `library/{mod,steam,launch,art,win_util}.rs`; `windows/interactive.rs` (new); `capture/windows/wgc_relay.rs`; `punktfunk1.rs:573`; `gamestream/stream.rs:122`; `vdisplay.rs:57`; `main.rs`; `Cargo.toml` |
|
||||
| **2 — Linux Lutris + Heroic + art endpoint** (P0) | `LutrisProvider`, `HeroicProvider` (art free); `GET /library/art/<id>/<slot>` for Lutris local JPEGs; wire into `all_games()`; unit tests for new `resolve_launch` arms + guards | `library/{lutris,heroic,art}.rs`; `library/mod.rs`; `mgmt.rs:1138` + new route |
|
||||
| **3 — Windows Epic + GOG** (P1) | `EpicProvider` (.item + catcache art), `GogProvider` (registry + .info + api.gog.com art); validate `windows/interactive.rs` end-to-end on the RTX box | `library/{epic,gog,win_util,art,launch}.rs` |
|
||||
| **4 — Xbox / Game Pass** (P1) | `XboxProvider` (XboxGames scan + MicrosoftGame.config + AppRepository PFN + aumid launch) + displaycatalog art with caching/offline degrade | `library/{xbox,art,launch}.rs` |
|
||||
| **5 — Linux Desktop catch-all + easy Windows URI stores** (P1/P2) | `DesktopProvider` (last + dedup, icons via `/library/art`), `UplayProvider`, `AmazonProvider` (+ per-user-profile-under-SYSTEM helper) | `library/{desktop,uplay,amazon,win_util,art}.rs` |
|
||||
| **6 — Remaining + opt-in enrichment** (P2/P3) | `BattleNetProvider` (hand-rolled protobuf + code→name map), `EaAppProvider`, `ItchProvider`; Rockstar/Bottles → custom; optional SteamGridDB v2 behind an operator key | `library/{battlenet,eaapp,itch,art,mod}.rs` |
|
||||
|
||||
Also generalize the web console store badge (`web/src/sections/Library/view.tsx`) to render per `game.store`.
|
||||
- **Phases 1–4 — DONE:** launch abstraction + Windows interactive-session spawn; Steam/Lutris/Heroic
|
||||
providers + Linux art; Epic/GOG providers; Xbox / Game Pass provider; shared art warmer + cache; web
|
||||
store badge generalized per `game.store`.
|
||||
- **Phase 5 — future:** Linux Desktop catch-all (last + dedup, icons via `/library/art`), Ubisoft
|
||||
(`UplayProvider`), Amazon (`AmazonProvider` + per-user-profile-under-SYSTEM helper); land the
|
||||
`GET /library/art/<id>/<slot>` endpoint that Lutris/Desktop local art still needs.
|
||||
- **Phase 6 — future:** Battle.net (hand-rolled protobuf + code→name map), EA app, itch.io; Rockstar/
|
||||
Bottles → custom; optional SteamGridDB v2 enrichment behind an operator key.
|
||||
|
||||
---
|
||||
|
||||
@@ -357,21 +289,12 @@ Also generalize the web console store badge (`web/src/sections/Library/view.tsx`
|
||||
|
||||
---
|
||||
|
||||
## 8. Verification notes (what the adversarial pass corrected)
|
||||
## Open items (what's left)
|
||||
|
||||
First-pass research was web-re-checked; corrections folded into §5 above:
|
||||
- **Epic:** bare-`AppName` URI is **not** universally removed (Playnite still uses it) — build the triple
|
||||
when ids exist, fall back to bare; use Playnite's **exclusion** filter, not a positive `games` filter.
|
||||
- **EA:** a single offerId no longer launches — emit the **full** comma-joined contentID list; registry
|
||||
under-reports for EA-app installs.
|
||||
- **Battle.net:** `battlenet://<code>` only hands off — use `Battle.net.exe --exec="launch <code>"`.
|
||||
- **Xbox:** **read** the PFN from the AppRepository dir name, don't hash the publisher; `.GamingRoot`
|
||||
layout is undocumented — just scan `XboxGames`.
|
||||
- **Heroic:** `gui=false` is inert (`--no-gui` does it); single-instance Electron forwards-and-exits →
|
||||
gate launch.
|
||||
- **Lutris:** open the DB read-only; `lutris -l -j` fallback is fragile (single-instance D-Bus forwarding).
|
||||
- **SteamGridDB:** v1 is deprecated — use v2 (`/api/v2`, Bearer key).
|
||||
|
||||
**Not web-confirmable / needs on-box validation:** every Windows launch path (each launcher's argv
|
||||
handling, foreground grab, secure-desktop behavior), all registry keys / DB schemas against a live box,
|
||||
and `rusqlite` packaging.
|
||||
- **6 remaining providers:** Desktop/Flatpak (Linux), itch.io (Linux+Windows), Ubisoft Connect, Amazon
|
||||
Games, Battle.net, EA app (recipes in §5).
|
||||
- **`GET /library/art/<entryId>/<slot>` mgmt endpoint** — still missing; Lutris local JPEGs (and the future
|
||||
Desktop icons) have no public URL without it.
|
||||
- **Refactor `library.rs` (1869 lines) into a `library/` directory** (structure in §6).
|
||||
- The **8 open questions** in §7.
|
||||
- **Optional SteamGridDB v2 enrichment** behind an operator key (off by default; never blocks listing).
|
||||
|
||||
Reference in New Issue
Block a user