From bad59bfb5bd4038ad300930b2792518981896d50 Mon Sep 17 00:00:00 2001 From: spoopyghosty0 <336754034+spoopyghosty0@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:57:25 -0400 Subject: [PATCH] Steam library fallback (agent v56, GitHub #41): Play re-registers a devkit entry Steam forgot after a restart, the console-log appid comes from the current Steam session only, and art updates also reach the devkit entry --- CLAUDE.md | 2 +- agent/frameport_agent.py | 34 +++++++++++++++++++++++++--------- tests/test_agent.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 7527aa4..bb470ea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -514,7 +514,7 @@ lacks (ActivityNotFoundException) → imported by hand: libmain.so + game.json DATA/ → external files/WiiCompiledOpenXRVR/DATA, MOD/ → .../RetroRewind6 (decompiled with jadx). Lepton has no picker at all: a FramePort fix would be an injected picker activity. Generated cover/banner art used to suppress the Steam placeholder set (no Steam art at all) → fixed in artwork/steam.py. -**Steam library failures (GitHub #21/#30):** on some Frames Steam never loads FramePort's shortcuts.vdf entries (same SteamOS build and arm64 beta client as the dev Frame; IDs correct; file written before Steam starts): Play → console_log `GameAction [AppID ] … RequestingLicense → UpdatingAppInfo → LaunchApp failed with AppError_9` (= "Game configuration unavailable", Steam treats the id as a store app). Cause unknown; agent v42/43 diagnostics: `launch` returns `steam` (started / error + code / silent from console_log), `collect_diag` adds `steam_library` (real Steam dir, beta, Steam start vs vdf write time, entries per account, devkit games) + `steam-console.txt` (incl. Steam's `logs/shortcuts.previous.txt` if present; steamclient.so has "LoadShortcuts: rejecting attempt to load shortcuts: invalid account ID"). **Fallback (agent v43):** when Play gets AppError_9 the agent registers the game through Steam's devkit interface (what Valve's Devkit Management Tool does, MIT: `devkit-1 steam://devkit-1/<~/.steam/steam.token>/create-shortcut?response=&gameid=&directory=~/devkit-game` written to ~/.steam/steam.pipe; answer file / `.error`; `~/devkit-game//launch.sh` = link to the game's launch.sh, `-argv.json` ["launch.sh"] (relative to the folder), `-settings.json` {steam_play 0, compat_tool ""}). Steam adds it live (no restart), picks its own appid (read back from shortcuts.vdf by DevkitGameID), shows it as a normal non-Steam game named "Devkit Game: " (OpenVR 0, but VR works: owner-verified 2026-10-04), art copied to grid/*. Ids: letter first, then letters/digits/_ only (spaces, '-', leading digit → "missing/invalid arguments"). Later Plays use the devkit entry; uninstall (`devkit_unregister`, `delete-shortcut` live) and purge remove it. Verified on the dev Frame: register/launch/unregister; the AppError_9 trigger itself only in unit tests. GitHub #42 (two Steam accounts, neither MostRecent): Steam rewrote the active account's shortcuts.vdf without FramePort's entries right after starting, and added the devkit entry live without saving it, so `devkit_appid` (vdf only) failed ("Steam added the devkit entry but didn't save it"); agent v55 `devkit_appid_from_log` reads Steam's `sanitize shortcut app id "~/devkit-game//launch.sh": replacing 0 with N` console line. +**Steam library failures (GitHub #21/#30):** on some Frames Steam never loads FramePort's shortcuts.vdf entries (same SteamOS build and arm64 beta client as the dev Frame; IDs correct; file written before Steam starts): Play → console_log `GameAction [AppID ] … RequestingLicense → UpdatingAppInfo → LaunchApp failed with AppError_9` (= "Game configuration unavailable", Steam treats the id as a store app). Cause unknown; agent v42/43 diagnostics: `launch` returns `steam` (started / error + code / silent from console_log), `collect_diag` adds `steam_library` (real Steam dir, beta, Steam start vs vdf write time, entries per account, devkit games) + `steam-console.txt` (incl. Steam's `logs/shortcuts.previous.txt` if present; steamclient.so has "LoadShortcuts: rejecting attempt to load shortcuts: invalid account ID"). **Fallback (agent v43):** when Play gets AppError_9 the agent registers the game through Steam's devkit interface (what Valve's Devkit Management Tool does, MIT: `devkit-1 steam://devkit-1/<~/.steam/steam.token>/create-shortcut?response=&gameid=&directory=~/devkit-game` written to ~/.steam/steam.pipe; answer file / `.error`; `~/devkit-game//launch.sh` = link to the game's launch.sh, `-argv.json` ["launch.sh"] (relative to the folder), `-settings.json` {steam_play 0, compat_tool ""}). Steam adds it live (no restart), picks its own appid (read back from shortcuts.vdf by DevkitGameID), shows it as a normal non-Steam game named "Devkit Game: " (OpenVR 0, but VR works: owner-verified 2026-10-04), art copied to grid/*. Ids: letter first, then letters/digits/_ only (spaces, '-', leading digit → "missing/invalid arguments"). Later Plays use the devkit entry; uninstall (`devkit_unregister`, `delete-shortcut` live) and purge remove it. Verified on the dev Frame: register/launch/unregister; the AppError_9 trigger itself only in unit tests. GitHub #42 (two Steam accounts, neither MostRecent): Steam rewrote the active account's shortcuts.vdf without FramePort's entries right after starting, and added the devkit entry live without saving it, so `devkit_appid` (vdf only) failed ("Steam added the devkit entry but didn't save it"); agent v55 `devkit_appid_from_log` reads Steam's `sanitize shortcut app id "~/devkit-game//launch.sh": replacing 0 with N` console line. GitHub #41 (one account): same Steam behaviour, and every install restarts Steam, which forgets the never-saved devkit entry. Agent v56: the log fallback reads only the current session's console_log.txt; Play re-registers a devkit entry that gets AppError_9; art updates also write the devkit entry's grid art (`copy_grid_art`). Open: why some Frames' Steam never loads or saves shortcuts.vdf (single account too). **Exit game (GitHub #36, 2026-10-04):** `Apps.TerminateApp` (what Exit game calls; reachable for tests through Steam's CEF devtools on 127.0.0.1:8080, SharedJSContext, `Runtime.evaluate` — a stdlib websocket client is enough) closed both a normal shortcut and a devkit entry on the dev Frame within 1-3 s. SIGTERM to Steam's `reaper` alone left launch.sh + Lepton (setsid) + the container running (an earlier headset launch ran on for 87 min); SIGTERM to launch.sh cleans up in ~4 s. Agent v44: launch.sh's 2 s loop ends the game when its parent (the reaper) is gone; `upgrade_launchers` (from ensure_host_fixes) adds that to existing launchers in place. Which in-headset exit path fails is still unknown. Screenshots on the Frame: all under SteamVR's appid 250820 (`760/remote/250820/screenshots`, screenshots.vdf has creation time, no game). **SteamVR dashboard at game start (2026-10-04):** the dashboard (Resume game / controller / VR options) was open whenever a FramePort game started (also over Lepton's 2D launcher). Steam's UI exposes `SteamClient.OpenVR.VROverlay.{IsDashboardVisible,HideDashboard,ShowDashboard}` (CEF devtools, `steam_js` in the agent). Agent v44: launch.sh starts `_dashboard_worker` (waits for FrameBridge's first `pacing:` line in launch.log, then hides a visible dashboard for 25 s, at most 3 times; log `/dashboard.log`; opt out with env FRAMEPORT_KEEP_DASHBOARD=1). Verified on the dev Frame (dashboard shown → 4XVR launched via Steam → hidden ~5 s after the first frames); not yet seen in the headset. **Headset (ITR2, 2026-10-05): too late** — Steam shows its frame menu (`valve.steam.gamepadui.frame.menu`, vrwebhelper_systemui.txt `[PooledPopups] Showing`) ~0.3 s after the game's first submitted frame (FrameBridge `new layer:`), but the first `pacing:` summary comes ~8 s later; the owner had pressed Resume (`[HideDashboard] return_to_game`) before the worker looked. Agent v52: watches from the first `new layer:` line (incremental log reads, 0.5 s polls, 30 s window). Agent v53: stops for good once the player opens the dashboard with the controller (`toggle_dashboard_action` in vrwebhelper_systemui.txt since the worker started): v52 closed it 60 ms after each press, and the game, paused for it, stayed paused. **Space warp / ITR2 (2026-10-04):** Into The Radius 2 (UE5, `libUnreal.so` + OVRPlugin, Vulkan) uses Application SpaceWarp (extra swapchains `376x376 format=97` motion vectors + `format=129` depth); #35 reports flickering textures. OVRPort's `patch_disable_space_warp` has no effect on UE5, and hiding XR_FB_space_warp in FrameBridge's enumerate doesn't reach the game either: **OVRPort's dispatcher offers/enables XR_FB_space_warp itself**. FrameBridge `hide_space_warp` (per game, off) hides it and strips `XrCompositionLayerSpaceWarpInfoFB` (1000171000) from the projection views (log `hide_space_warp: removed space warp info`); the game still renders its MV swapchains. Test build installed on the dev Frame; flicker not yet checked in a headset. Heuristic: UE5 + OVRPlugin shows it as an option (off). `equirect_emul` is suggested for Unreal games whose graphics API isn't detected ("GLES or unknown"); on Vulkan it switches itself off (Myst/Riven/ITR2): harmless, but Unreal Vulkan detection is a known gap. diff --git a/agent/frameport_agent.py b/agent/frameport_agent.py index 3d5149e..9303682 100644 --- a/agent/frameport_agent.py +++ b/agent/frameport_agent.py @@ -36,7 +36,7 @@ import time import zlib from types import SimpleNamespace -AGENT_VERSION = 55 +AGENT_VERSION = 56 HOME = os.path.expanduser("~") STEAM = os.path.join(HOME, ".local/share/Steam") ANCHORS = os.path.join(HOME, "Applications/quest-frame") @@ -1135,7 +1135,16 @@ def remove_from_library(user, remove, result): def update_library(user, packages, result, shortcuts=True): """Add/update installed games' shortcuts + grid art in one Steam account (Steam is closed; shortcuts=False: only - the grid art of shortcuts that are already right, while Steam runs).""" + the grid art of shortcuts that are already right, while Steam runs). A game's devkit entry (the Play fallback) + gets the same art: on Frames where Steam ignores shortcuts.vdf it is the one in the library (GitHub #41).""" + for pkg in packages: + gameid = devkit_gameid(pkg) + appid = devkit_appid(gameid) if gameid else None + if appid: + try: + copy_grid_art(pkg, appid, [user]) + except OSError as exc: + result.setdefault("errors", []).append(f"{pkg}: devkit art: {exc}") vdf = os.path.join(STEAM, "userdata", user, "config/shortcuts.vdf") grid = os.path.join(os.path.dirname(vdf), "grid") os.makedirs(grid, exist_ok=True) @@ -1283,12 +1292,13 @@ def devkit_appid(gameid): def devkit_appid_from_log(gameid): """The appid Steam picked for a devkit game, from its console log ('sanitize shortcut app id "//launch.sh": - replacing 0 with N'). Steam adds the entry live but may save shortcuts.vdf only later (GitHub #42: a Frame with two - Steam accounts never had it on disk), so the log is the fallback. The newest line wins.""" + replacing 0 with N'). Steam adds the entry live but may never save it (GitHub #41/#42), so the log of the running + Steam session is the fallback (console_log.previous.txt is the session before, whose live entries are gone). The + newest line wins.""" exe = os.path.join(DEVKIT_GAMES, gameid, "launch.sh") pat = re.compile(r'sanitize shortcut app id "' + re.escape(exe) + r'": replacing \d+ with (\d+)') found = None - for name in ("console_log.previous.txt", "console_log.txt"): + for name in ("console_log.txt",): # this Steam session only: entries added live are gone after a restart try: with open(os.path.join(STEAM, "logs", name), errors="replace") as f: for line in f: @@ -1340,8 +1350,14 @@ def devkit_register(pkg): time.sleep(0.5) if not appid: raise AgentError("Steam added the devkit entry but didn't save it") + copy_grid_art(pkg, appid) + return appid + + +def copy_grid_art(pkg, appid, users=None): + """The game's Steam art (portrait, landscape, hero, logo) as grid/* in every (or the given) account.""" anchor = os.path.join(ANCHORS, pkg) - for user in steam_users(): + for user in users if users is not None else steam_users(): grid = os.path.join(STEAM, "userdata", user, "config/grid") os.makedirs(grid, exist_ok=True) for kind, suffix in (("portrait", "p"), ("landscape", ""), ("hero", "_hero"), ("logo", "_logo")): @@ -1351,7 +1367,6 @@ def devkit_register(pkg): if re.match(rf"^{appid}{re.escape(suffix)}\.", os.path.basename(old)): os.remove(old) shutil.copy(img, os.path.join(grid, f"{appid}{suffix}{os.path.splitext(img)[1]}")) - return appid def devkit_unregister(pkg, steam_running=True): @@ -1403,8 +1418,9 @@ def cmd_launch(args): raise AgentError(f"{NOT_IN_LIBRARY}: {dep.get('title') or pkg}") steam = steam_launch(appid, args.get("wait", 10)) out = {"package": pkg, "gameid": steam_gameid(appid), "title": dep.get("title"), "via": via, "steam": steam} - if via == "shortcut" and steam["result"] == "error" and steam.get("code") == 9 and args.get("fallback", True): - # Steam doesn't know the shortcut at all (it tried to license a store app with that id): devkit entry + if steam["result"] == "error" and steam.get("code") == 9 and args.get("fallback", True): + # Steam doesn't know the shortcut at all (it tried to license a store app with that id): devkit entry. A devkit + # entry Steam never saved is gone after a Steam restart (every install restarts it, GitHub #41): add it again try: appid = devkit_register(pkg) except AgentError as exc: diff --git a/tests/test_agent.py b/tests/test_agent.py index df315f6..6877c7a 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -1329,3 +1329,35 @@ def test_devkit_appid_from_steams_console_log(monkeypatch, tmp_path): f'[2026-10-05 15:29:14] sanitize shortcut app id "{exe}": replacing 0 with 3849978642, reason: k_unAppIdInvalid\n') assert a.devkit_appid("I_Am_Cat") == 3849978642 # nothing in shortcuts.vdf: the newest log line assert a.devkit_appid("Missing") is None + + +def test_devkit_appid_ignores_the_previous_steam_session(monkeypatch, tmp_path): + """Live devkit entries die with Steam (GitHub #41): an appid from console_log.previous.txt is stale.""" + a = load_agent(monkeypatch, tmp_path) + logs = Path(a.STEAM) / "logs" + logs.mkdir(parents=True, exist_ok=True) + exe = f"{a.DEVKIT_GAMES}/Batman/launch.sh" + (logs / "console_log.previous.txt").write_text(f'sanitize shortcut app id "{exe}": replacing 0 with 77, x\n') + (logs / "console_log.txt").write_text("Steam started\n") + assert a.devkit_appid("Batman") is None + + +def test_launch_registers_a_lost_devkit_entry_again(monkeypatch, tmp_path): + """After a Steam restart Steam forgot the (never saved) devkit entry: Play gets AppError_9 and registers it again.""" + a = load_agent(monkeypatch, tmp_path) + monkeypatch.setattr(a, "check_pkg", lambda p: p) + monkeypatch.setattr(a, "deployment", lambda p: {"appid": 111, "title": "Batman"}) + monkeypatch.setattr(a, "run", lambda *x, **k: subprocess.CompletedProcess([], 0, "", "")) + monkeypatch.setattr(a, "devkit_gameid", lambda p: "Batman") + monkeypatch.setattr(a, "devkit_appid", lambda g: 222) # the stale entry from before the restart + registered = [] + monkeypatch.setattr(a, "devkit_register", lambda p: registered.append(p) or 333) + launches = [] + + def launch(appid, wait=10): + launches.append(appid) + return {"result": "error", "code": 9} if appid == 222 else {"result": "started"} + monkeypatch.setattr(a, "steam_launch", launch) + out = a.cmd_launch({"package": "com.camouflaj.manta"}) + assert registered == ["com.camouflaj.manta"] and launches == [222, 333] + assert out["via"] == "devkit" and out["steam"]["result"] == "started"