mirror of
https://github.com/saphid/frame-control.git
synced 2026-10-06 12:00:37 +02:00
Apps without a banner or screenshot show their icon on a blurred wash of
itself, or a VR or app glyph, rather than a giant letter. Every card says
whether it will run on the Frame (works, should install, not checked yet,
won't run), in place of "Free" on every card.
The featured app isn't repeated in the rows below, and no row repeats
another. Its buttons stay in the app: Install on the Frame opens the details
with the install under way, and Details opens them. It's chosen from apps
that can run, with real artwork, VR first.
The stray "Browse SideQuest" link goes; the Sources dialog lists SideQuest
with a link to its website instead of a switch that did nothing. The "All
sources" drop-down goes too, as the Sources dialog already chooses them.
The copy is plain and British ("Apps and games for the Frame", no "Find
your next favorite"), and Games and Android point at each other.
Two functions were each declared twice across the page's scripts, so the
later one silently replaced the earlier everywhere: the store's details
dialog opened empty (the Devices tab's renderDetail ran instead), and Home's
Desktop view never found the headset's panels (the panel switcher's loader
ran instead). They're renamed, and a test
fails on any top-level name declared twice.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
145 lines
10 KiB
Markdown
145 lines
10 KiB
Markdown
# SideQuest and Frame Control
|
|
|
|
Researched 2026-09-28. SideQuest is both a Quest discovery website and a desktop
|
|
sideloading/device-management app. Its Quest labels are **not** evidence that a
|
|
game works on Lepton: inspect the APK for arm64/OpenXR, Android API requirements,
|
|
VrApi and Meta services (see [VR APKs](vr-apks.md)).
|
|
|
|
## Features worth borrowing
|
|
|
|
Desktop evidence is the public [SideQuest source at af2ac70](https://github.com/SideQuestVR/SideQuest/tree/af2ac7043db122bca3c8db18f2b58f1660e9befb),
|
|
especially [ADB operations](https://github.com/SideQuestVR/SideQuest/blob/af2ac7043db122bca3c8db18f2b58f1660e9befb/desktop-app/src/app/adb-client.service.ts),
|
|
[drag and drop](https://github.com/SideQuestVR/SideQuest/blob/af2ac7043db122bca3c8db18f2b58f1660e9befb/desktop-app/src/app/drag-and-drop.service.ts),
|
|
and the [legacy repository index](https://github.com/SideQuestVR/SideQuest/blob/af2ac7043db122bca3c8db18f2b58f1660e9befb/desktop-app/src/app/packages/package.service.ts).
|
|
Website evidence: [SideQuest](https://sidequestvr.com/) and its public Angular
|
|
bundle `main-4MMXZRXL.js`, inspected locally without browser automation.
|
|
No SideQuest implementation code was copied.
|
|
|
|
| SideQuest feature | Frame Control before this change | Borrow? / effort |
|
|
|---|---|---|
|
|
| Store descriptions, screenshots, banners, trailers, ratings | F-Droid names, icons, compatibility verdicts and reports; no equivalent rich VR store | Yes, from authorised sources; medium. Search and library workers own presentation/artwork. |
|
|
| OBB expansion-file install | APK-only install | **Implemented helper and CLI**, medium. Essential for games whose assets are separate from the APK. |
|
|
| App-data backup/restore | Persistent instances and optional keep-data uninstall, no portable save archive | **Implemented private-data helper and CLI**, medium. Back up before updates or experiments. |
|
|
| File manager (list, upload, download, remove) | General Send to Frame, no Android file browser | Useful later, medium; requires clear instance selection and scoped paths. |
|
|
| Installed-app management (launch, uninstall, backup) | List, launch, stop, remove, probe | Already mostly covered. Backup added here. |
|
|
| Update notices / account library | Compatible-version lookup; no source-aware installed update notices | Useful later, medium; needs original version code and source identity recorded on install. |
|
|
| Custom repositories | Built-in F-Droid catalogue and compatible-version indexes | Separate user-repos worker. Legacy SideQuest source has a fixed SideQuestRepos index; arbitrary current custom-repo support was not verified. |
|
|
| Drag-and-drop APK/OBB install | APK drag-and-drop already works | OBB backend added here; future UI can call it. UI drop wiring is not included. |
|
|
| Tags, price, headset filters, reviews | Text search and Lepton verdicts, not Quest headset metadata | Useful, medium; search worker owns filters. Keep source headset claims distinct from tested Frame compatibility. |
|
|
| Screenshot/video capture and streaming | Frame screenshots/VR capture already present | Reuse existing tools; do not port Quest capture commands. |
|
|
| Device settings and ADB utilities | Frame/Android display settings, SSH and own-instance tools | Borrow selectively; Quest CPU/GPU presets and wireless-ADB setup do not map directly to Lepton. |
|
|
|
|
Priority: expansion files, then save backup/restore. Rich discovery and update
|
|
notices follow once a permitted metadata source and source/version persistence
|
|
are available. This patch deliberately exposes CLI/backend operations, leaving
|
|
shared UI, install(), Steam artwork and launch behavior to sibling work.
|
|
|
|
## SideQuest as a source: page-only
|
|
|
|
[Terms](https://sidequestvr.com/terms), “Prohibited Activities”, (i) prohibits
|
|
copying/distributing/disclosing the Service including automated or non-automated
|
|
“scraping”; (xi) prohibits content access through means other than those provided
|
|
or authorised by the Service; (xii) prohibits bypassing access restrictions.
|
|
The terms describe downloading developer-posted games through the Service, but
|
|
do not establish permission for this third-party API integration.
|
|
|
|
[robots.txt](https://sidequestvr.com/robots.txt) requests a three-second crawl
|
|
delay and disallows `/search/`, `/user/*` and `/sideload/*`. Robots permission
|
|
would not override the terms. The API host's robots request returned HTTP 403;
|
|
a request for the first shared website JS chunk also returned 403. No bypass,
|
|
account token, cookies, browser session or private endpoint was used.
|
|
|
|
The homepage publishes `https://api.sidequestvr.com` and
|
|
`https://cdn.sidequestvr.com`. The website bundle calls `searchApps(...)` and
|
|
`getApp(id, null)`; their actual HTTP search/detail routes could not be established
|
|
from the retrieved bundle. Do not invent endpoints. The open-source desktop
|
|
[install flow](https://github.com/SideQuestVR/SideQuest/blob/af2ac7043db122bca3c8db18f2b58f1660e9befb/electron/app.ts)
|
|
POSTs `{token: ...}` to `/install-from-key`. It consumes
|
|
`data.apps[].urls[]`, with `provider` values including `APK`, `OBB`,
|
|
`Github Release` and `Mod`, and `link_url`. This is a website-issued install-key
|
|
flow, not evidence of an anonymous download API. It is not implemented here.
|
|
|
|
`ui/apk_sources/sidequest.py` implements the shared interface conservatively:
|
|
|
|
- `sources()` marks SideQuest `page_only` and explains why.
|
|
- `search()` raises a user-readable `SourceError` with the browse URL (zero
|
|
limit returns no rows). It does not invent app results or report a false
|
|
“no matching games”. The aggregate search UI should surface this source error.
|
|
In the app, Discover doesn't link to it; the **Sources** dialog lists SideQuest
|
|
with a link to its website instead of an on/off switch.
|
|
- `details()` accepts a numeric listing id and returns its canonical page link,
|
|
`downloadable: False`, empty versions/tags/headsets and the `images` shape
|
|
`{icon: None, banner: None, screenshots: []}`. Name is explicitly a listing id;
|
|
unknown facts, including free/VR status, stay `None`.
|
|
- `download()` refuses with that page link. Paid/external listings cannot be
|
|
downloaded by this adapter either. No downloads means no verification claim.
|
|
|
|
The JSON fixture records policy evidence, **not a purported live app response**.
|
|
No listing metadata, artwork URLs, or OBB download URLs were scraped.
|
|
The requested real SideQuest → OpenXR APK → `frame_android.py info` test is
|
|
**blocked by the terms**, and was not performed. No alternate source is silently
|
|
substituted. A future integration needs SideQuest's permission or an expressly
|
|
supported third-party API, plus recorded search/detail/download fixtures,
|
|
free/direct-download classification, and size/hash verification. A calculated
|
|
local SHA-256 alone must not be called publisher verification.
|
|
|
|
## OBB files
|
|
|
|
```sh
|
|
python3 ui/frame_android.py install-obb org.example.game main.42.org.example.game.obb
|
|
python3 ui/frame_android.py install-obb org.example.game main.42.org.example.game.obb patch.42.org.example.game.obb
|
|
```
|
|
|
|
Install the APK first. The named instance must already be running; the helper
|
|
never launches an app or uses Lepton Development. It requires standard
|
|
`main|patch.<versionCode>.<package>.obb` filenames and nonempty files, validates
|
|
the entire batch before transfer, streams each file through SSH into that
|
|
instance, checks its SHA-256 **inside Android**, then renames it into
|
|
`/sdcard/Android/obb/<package>/`. `verified: True` here means transfer integrity
|
|
against the local input, not publisher authentication. Publication is atomic per
|
|
file, not for the whole batch; retry after a partial batch failure. Existing OBBs
|
|
with different version codes remain. The filename version must match the game;
|
|
the current install metadata does not expose its version code for comparison.
|
|
Restart the game yourself after the transfer if it cached missing expansion data.
|
|
|
|
Both read-only SSH attempts to the Frame timed out. Therefore the exact
|
|
host-side `/sdcard` mapping and persistence of expansion data were **not verified**.
|
|
`compatdata/<instance>/internal/<package>` is documented as `/data/data/<package>`;
|
|
it must not be mistaken for `/sdcard`. Using Android's path avoids guessing a
|
|
host layout, but device verification across restart/update is still required.
|
|
No OBB file was installed on the Frame during this work.
|
|
|
|
## Private app-data backups
|
|
|
|
```sh
|
|
python3 ui/frame_android.py stop org.example.game
|
|
python3 ui/frame_android.py backup-data org.example.game ./game-save.tar.gz
|
|
python3 ui/frame_android.py restore-data org.example.game ./game-save.tar.gz
|
|
```
|
|
|
|
Keep the instance stopped throughout either operation; do not launch it from
|
|
Steam concurrently. The remote guard fails if Podman cannot enumerate containers
|
|
or reports that instance running. The helpers use `podman unshare` to read/write
|
|
Android's mapped ownership without changing the live data's permissions.
|
|
|
|
The archive covers **only** `compatdata/<instance>/internal/<package>`, not the
|
|
APK, external `/sdcard/Android/data`, OBBs, keystore, or the full Android snapshot.
|
|
It contains a package/instance manifest and regular files/directories. Backups
|
|
are private (0600), validated before publication, and never overwrite an existing
|
|
backup. Keep them safe: app data can contain credentials and is not encrypted.
|
|
|
|
Restore checks the package and instance, rejects absolute/traversing/duplicate
|
|
paths, links and devices, caps files at 100,000 and content at 20 GiB, and validates
|
|
again on the Frame. It extracts into a separate directory, preserves numeric
|
|
ownership, ordinary modes and timestamps, then swaps the private-data directory.
|
|
Setuid/setgid bits are not restored. The previous directory remains beside it as
|
|
`.<package>.before-restore-<timestamp>`; the returned `previous` path identifies
|
|
it. This is an additional recovery copy, not an automatic deletion policy.
|
|
|
|
Locally verified: archive round trip including recovery copy, malformed archive
|
|
rejection, transfer command construction and failure handling. Not verified:
|
|
real Frame UID mappings/permissions, Android app-level recovery, live FUSE OBB
|
|
writes or persistence. Backups reject symlinks/special files; an app requiring
|
|
those needs a separately designed backup format. These CLI features still need
|
|
a real-device acceptance pass before being exposed as a polished UI workflow.
|