Files
DroidDeck/docs/development/windows-components-sources.md
The412Banner d1b0ac4df1 Windows components: picked automatically by each game's source (#554)
Each game's Windows components now come from the list its own source
trusts, and the ones that list names turn on by themselves. The user's own
on/off switches always win; each automatic row says why it is on.

- Steam games: the Steamworks Shared redistributables Steam installs with
  the game (appmanifest SharedDepots, else the client's appinfo.vdf).
  Steam keeps its install scripts marked as already run.
- GOG: the build manifest's dependencies. Epic: the manifest's
  prerequisite (Unreal's maps to VC++ and DirectX). Amazon: fuel.json
  PostInstall. Older GOG/Epic installs get their list fetched once with the
  saved sign-in; game files are never downloaded again.
- Custom games (the + button, Add all, migrated folders): one shared Steam
  match per game, also used for artwork. An appid from the game's files
  turns components on; a name-only match stays Recommended; no match falls
  back to the folder scan.
- Steam app info for apps the client never saw comes from an anonymous
  Steam PICS lookup (CM websocket, anonymous logon, product info), cached
  per appid; the user's Steam sign-in is never used.
- Missing components download in the background on one low-priority
  worker that pauses during sessions and waits on battery saver, heat or a
  metered network unless the user asked. A game Steam installs during a
  session is readied right away so it can be played without ending the
  session; the launch waits up to 10 s only for a component still on its
  way.
- Page: "On for this game" with reasons, a quiet "Checking what this game
  needs..." only for games never worked out, rows that ease in, pieces that
  need a download greyed out while offline. The intro names no other app.
- The folder scan skips Unity's *_Data/Managed (Mono, not .NET Framework).
2026-10-10 21:15:56 -04:00

117 lines
7.9 KiB
Markdown

# Windows components by source
A game's Windows components (VC++ runtimes, DirectX's d3dx9, XNA, PhysX...) are picked for it by
itself, from the list its source keeps. The source badge (Steam, GOG, Epic Games, Amazon Games,
Custom) decides whose list is trusted (`WinCompSources`):
| Source | List | On by itself |
| --- | --- | --- |
| Steam | The Steamworks Shared redistributables Steam installs with the game: the appmanifest's `SharedDepots`, else the client's app info (`SteamAppInfo`) | yes |
| GOG | The build manifest's `dependencies`, kept in the sidecar at install (`extra.dependencies`) | yes |
| Epic Games | The manifest's prerequisite (`PrereqName`, `PrereqPath`), kept in the sidecar at install | yes, for the mapped parts |
| Amazon Games | fuel.json's `PostInstall` installers | yes, for the mapped parts |
| Custom, appid in its files | Steam's list for that app (`SteamMatch` certainty `FILES`: steam_appid.txt, also under steam_settings/ or a parent; an emulator ini; an appmanifest) | yes |
| Custom, matched by name | Steam's list for that app (certainty `NAME`, a store search with the same name) | no, Recommended |
| Custom, no match | the folder scan (`DependencyDetector`) | no, Recommended |
The folder scan is offered as Recommended for every game.
## Steam's data
Never scraped from a website. `SteamAppInfo` reads the Steam client's own app-info cache in the
runtime (`Steam/appcache/appinfo.vdf`, versions 27-29): the app's depots that point at app 228980
(Steamworks Shared) and its name. Each appid's answer is kept in `files/steam-appinfo.json`, so it
works offline; an app the client has not seen is looked for again after a week. A Steam game with
its appmanifest beside it uses the manifest first. `droiddeck-seed-redists` still marks Steam's
install scripts for those redistributables as already run, so the client never runs the real
installers.
## One Steam match per game
`SteamMatch` keeps, per added game (by its folder), `{appId, certainty FILES|NAME|NONE, source}`
in `files/steam-matches.json`. The art fetch (`AddedGameArt`) and the components read and write
the same record; an earlier build's art lookup (`added-art/<appid>/steam-appid`) is taken over as
a name match. A miss is tried again after a week.
## Mapping
All in `WinCompSources`:
- GOG ids: `MSVC<year>[_x64]` → `vcredist<year>` (2017 → the 2015 family, as the folder scan),
`DirectX*`/`DX9`/`D3DX*` → `d3dx9`, `DOTNET<n>` → `dotnet35`...`dotnet48`, `PhysX` → `physx`,
`OpenAL` → `oalinst`, `XNA3`/`XNA31` → `xna31`, other `XNA*` → `xna40`, `XLive*`/`GFWL*` →
`XLiveRedist`, `XACT*` → `xact`.
- Epic: Unreal's `UE4PrereqSetup`/`UEPrereqSetup` (or a "UE4/UE5/Unreal Engine Prerequisites" name)
→ `vcredist2022` + `d3dx9`; any other installer by its path, as the folder scan names it.
- Amazon: each `PostInstall` command by its path, as the folder scan names it (Unreal's
prerequisite as above).
- Steam: Steamworks Shared depots 228981-228990 (`SteamRedists.DEPOTS`).
Anything that maps to nothing is listed under "Also listed" with its own name. A mapped component
that needs a Windows installer here, or is not in the catalog, stays Recommended with its status.
## On by itself, and the user's switches
All of it runs on one background thread (`AutoComponents`), low priority (the runtime helpers an
install starts run under `nice -n 19` and `ionice -c3`), one job at a time from one queue: a game's
list (worked out again only when the files it is read from change, by modification time, and kept
in `files/wincomp-findings.json`, so a page opens on it at once) and a component's download (each
component once, for every game that wants it). What the user asked for goes first: the page of a
game (on a metered network too), a store install or an add. A game's list is queued when it is
installed, added or edited, when its page opens, when it is launched from DroidDeck (the launch
itself only takes what is on already), and for every Steam game once per app start, ten seconds
after the launcher is up (Steam installs them without a word to DroidDeck). The queue waits while
a session runs, and a download also waits for a validated network (the page then greys what is not
downloaded yet, "Available when online", and follows the network live); what nobody asked for also waits on battery saver, thermal status moderate or
worse, or a metered network. A component turns on for its games when its download lands, for the
next launch, and an open page shows the progress and reads the picks again. One log line per
queued and per finished download.
`wincomponents.json` keeps, per game,
`auto` (each with its reason) and `user` (each switch the user pressed, on or off) beside `games`,
the effective list the launch reads (still version 1). A user switch always wins over an automatic
pick, either way, and a new automatic list never touches it. Picks saved before this build count
as the user's own.
The page shows "On for this game" with each reason ("From GOG's list for this game", "Steam
installs VC++ 2015 with <game> (app N)"), then Recommended, then "Also listed".
## Older store installs
A GOG or Epic game installed before its sidecar kept the store's list gets it once, on the worker
(`StoreListBackfill`): GOG's build manifest for the installed build (`dependencies`), Epic's
manifest (prerequisite name and path), fetched with the saved sign-in - the manifest alone, never a
game file - and kept in the sidecar as an install keeps it. Fresh installs now keep the keys even
when empty. Without a sign-in or when the store refuses: skipped quietly, tried again a week later;
offline is no try. Like any work nobody asked for it waits for an unmetered network, unless the
game's page is open.
## Apps the client never saw
A Steam app the client in the runtime never saw (not owned, never viewed: a Custom game's
steam_appid.txt) is missing from its `appinfo.vdf`. `SteamPics` asks Steam for it anonymously:
Steam's public CM list (`ISteamDirectory/GetCMListForConnect`), a WebSocket connection to a
connection manager (`wss://<cm>/cmsocket/`, the transport Steam's web clients use), an anonymous
logon (an AnonUser SteamID, no account, nothing of the user's Steam sign-in), a
`ClientPICSAccessTokenRequest` and a `ClientPICSProductInfoRequest` (at most 25 apps each, all in
one connection), the app KeyValues read for depots with `depotfromapp` 228980 and the name, and a
logoff. Kotlin only, no library (TLS from the platform, a minimal WebSocket and protobuf reader).
Answers go into `steam-appinfo.json` like the client's (a miss is asked again after a week).
Order for an appid: the client's `appinfo.vdf` first (free), then the anonymous lookup. It runs on
the components' worker: one batch for every game in the start/after-session pass (before their
lists), or the one app of a game whose page is open; never during a session, and as work nobody
asked for only on an unmetered, favourable device. Log: `steam pics: N apps, M with shared redists`.
## A game installed during a session
`SessionInstalls` looks at the Steam libraries' appmanifests every five seconds while a session
runs (a manifest re-read only when its time changes); a game whose StateFlags turn to 4 (fully
installed) becomes one job allowed to run in the session (`AutoComponents.queueInSession`): its
list (local appmanifest, appinfo.vdf, the kept PICS answer; one small anonymous lookup only for an
app nothing knows), its automatic picks written to `wincomponents.json` (the runtime's copy too) and
only its missing components downloaded, at low priority. It waits while a game runs in the session
(Steam's reaper `SteamLaunch AppId=`, or Proton's `waitforexitandrun`). All other work stays
paused. While its components download, the app lists them per game in the runtime's
`~/.config/droiddeck/wincomponents-pending.json`; `droiddeck-wincomponents` at launch waits up to
10 s for those (no wait when nothing is pending), then applies what is there: log
`wincomp: <appid> waited Nms for <ids>` or `... launched without <ids> (not ready)`.