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).
This commit is contained in:
The412Banner authored and GitHub committed 2026-10-10 21:15:56 -04:00
1 parent d45933e559
commit a2e596a200
35 files changed
+3810 -146

No files matched your search

+54 -1
View File
@@ -137,7 +137,7 @@ class WinComponentsTest(unittest.TestCase):
self.pick("openal")
globals_ = GAME_ENV["win_components"].__globals__
real = runpy.run_path(str(BIN / "droiddeck-wincomponents"))
real["apply"].__defaults__ = (self.selection, self.store)
real["apply"].__defaults__ = (self.selection, self.store, self.selection.parent / "no-pending.json", None)
with mock.patch.dict(globals_, {"runpy": mock.Mock(run_path=lambda _path: real)}):
self.assertEqual(GAME_ENV["win_components"](str(self.compat) + "/", "2571976725"), "openal32=n,b")
self.assertEqual(GAME_ENV["win_components"]("", "2571976725"), "")
@@ -166,5 +166,58 @@ class WinComponentsTest(unittest.TestCase):
self.assertEqual(MODULE["overrides_value"](["a", "b=n", "a=b", "bad;name", "c=x"]), "b=n;a=b")
class PendingWaitTest(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
root = Path(self.tmp.name)
self.store = root / "store"
self.pending = root / "pending.json"
self.slept = []
def tearDown(self):
self.tmp.cleanup()
def install(self, cid):
folder = self.store / cid
folder.mkdir(parents=True, exist_ok=True)
(folder / "component.json").write_text(json.dumps({"id": cid, "overrides": []}))
def clock(self):
return len(self.slept) * 0.5
def wait(self, timeout=10.0, on_sleep=None):
def sleep(seconds):
self.slept.append(seconds)
if on_sleep:
on_sleep(len(self.slept))
return MODULE["wait_for_pending"]("1262580", self.pending, self.store, timeout, sleep, self.clock)
def test_nothing_pending_means_no_wait(self):
self.assertIsNone(self.wait())
self.pending.write_text(json.dumps({"games": {"999": ["d3dx9"]}}))
self.assertIsNone(self.wait())
self.install("d3dx9")
self.pending.write_text(json.dumps({"games": {"1262580": ["d3dx9"]}}))
self.assertIsNone(self.wait())
self.assertEqual([], self.slept)
def test_waits_until_the_component_lands(self):
self.pending.write_text(json.dumps({"games": {"1262580": ["vcredist2019_dll"]}}))
note = self.wait(on_sleep=lambda n: self.install("vcredist2019_dll") if n == 3 else None)
self.assertEqual(3, len(self.slept))
self.assertEqual("wincomp: 1262580 waited 1500ms for vcredist2019_dll", note)
def test_gives_up_cleanly_after_the_timeout(self):
self.pending.write_text(json.dumps({"games": {"1262580": ["vcredist2019_dll", "d3dx9"]}}))
self.install("d3dx9")
note = self.wait(timeout=2.0)
self.assertEqual(4, len(self.slept))
self.assertEqual("wincomp: 1262580 launched without vcredist2019_dll (not ready)", note)
def test_a_damaged_pending_file_is_no_wait(self):
self.pending.write_text("{not json")
self.assertIsNone(self.wait())
if __name__ == "__main__":
unittest.main()