476 Commits
Author SHA1 Message Date
Alex Southwell bcdd9d0830 Merge pull request #99: Queue Android and Quest installs
Queue Android and Quest installs; prepare Frame Control 0.4.7
v0.4.7
2026-10-15 13:32:54 +11:00
saphid ddd2d1c295 Merge landed Quest and Meta library stack 2026-10-15 13:10:26 +11:00
Alex Southwell 8f2e34a40e Merge pull request #93: My Meta library (Experimental)
My Meta library (Experimental): install and test Quest games you own
2026-10-15 13:10:18 +11:00
Alex Southwell 351230fcad Merge pull request #92: Import from my Quest
Import from my Quest: copy games you own off a USB-connected Quest
2026-10-15 13:10:04 +11:00
saphid 5b35e9cc5d Merge current main and prepare Frame Control 0.4.7 2026-10-15 13:09:59 +11:00
saphidandClaude Opus 5.5 671c4c06e7 Install queue: only the install showing its progress hides the bar
Removing a waiting import hid the progress of the one still running.
Found by GPT-6.1 Sol review round 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:43:18 +11:00
saphidandClaude Opus 5.5 443813a088 Install queue: Android store, Quest and Meta installs wait their turn
Installs from the Android store, Import from my Quest and My Meta library
now go into one server-side queue and run one after another, instead of
being refused while another runs. The page shows the queue with Remove
for installs that haven't started; each queued install runs only on the
headset it was queued for.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:38:53 +11:00
saphidandClaude Opus 5.5 92a68285b5 Meta library: show sizes first, warn about left-out game data, count apps
From a live test with a real account (71 owned entries):
- the size was cut off behind the package name; sizes now come first;
- a required data file Frame Control can't place as an OBB (The Climb's
  1.8 GB main OBB) was never mentioned; the row now says it won't be
  installed;
- the count said games, but Meta's list includes apps and environments.
Problems and warnings get their own coloured line; the verdict colours
now win over the row's grey text (they were grey before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 20:07:36 +11:00
saphidandClaude Opus 5.5 5878c45ddb Meta library: count PERMANENT entitlements as owned
A real account's entitlements all came back with active_state PERMANENT
(92 records, 71 of them Quest apps), and the library only accepted ACTIVE,
so it showed no games. Both now count as owned; inactive and expired
entitlements are still left out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 19:47:23 +11:00
Alex Southwell ba0ac6022b Merge pull request #96 from saphid/feat/frame-control-center
Add comprehensive Steam Frame Control Center
2026-10-14 18:51:07 +11:00
saphid 8b9bd2119c Add searchable Steam Frame Control Center with live metrics 2026-10-14 18:40:59 +11:00
saphidandClaude Opus 5.5 d773babaa3 Meta sign-in: allow Instagram, and say where a stopped redirect went
A live sign-in with a real Meta account goes auth.oculus.com -> auth.meta.com
-> www.facebook.com -> www.instagram.com -> auth.meta.com. The window only
allowed oculus.com, meta.com and facebook.com, so it stopped the Instagram
step and sat blank. instagram.com is now allowed (same HTTPS-only checks;
the cookie is still read only from oculus.com). A page change the window
stops is remembered by host only, so closing the stuck window says why; it
is forgotten once the window loads a page again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:39:59 +11:00
Alex Southwell a8888cb2fc Merge pull request #94 from saphid/feat/telemetry-timezone
Usage statistics: send a country (from the time zone setting), and show the notice again when it changes
v0.4.6
2026-10-14 13:22:27 +11:00
saphidandClaude Opus 5.5 1f51cc89d2 Frame Control 0.4.6
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:14:46 +11:00
saphidandClaude Opus 5.5 bcdb4c3b78 Usage statistics: no country for clock-rule zones, and Johnston Atoll is UM
From review: EET and WET link to Athens and Lisbon but name clock rules,
not Greece or Portugal, so they and the other rule names (CET, EST5EDT, …)
send nothing. Pacific/Johnston has left zone.tab, which made its Honolulu
group look only American; it's set to UM by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 13:01:46 +11:00
saphidandClaude Opus 5.5 e9b02d61e7 Usage statistics: don't give an old zone name the country of the zone it now links to
From review: tzdata links an old name to any zone with the same clocks,
even in another country (Africa/Asmera, Eritrea, links to Africa/Nairobi).
An old name now gets a country only when every zone sharing its target is
in that country, or when it's a hand-checked old name for a place listed in
zone.tab. The rest (UTC, CET, GMT, …) send nothing. Every zone Node's ICU
reports has an entry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:55:18 +11:00
saphidandClaude Opus 5.5 02419ffbd4 Usage statistics: send a country, not the time zone; only the notice itself counts as seen
From review: the time zone setting could hold any text (a TZ like
home/alex/private passed the name check), so the app now looks the zone up
in a table made from the tz database (scripts/timezones.py) and sends only
its two-letter country; anything not in the table sends nothing.

Turning a setting on no longer counts as having seen the new notice, and
the country is taken out at send time until it has been, so it can't
leave early whatever order things happen in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:48:46 +11:00
saphidandClaude Opus 5.5 5db9631571 Usage statistics: send the time zone, and show the notice again when it lists something new
Opens, installs and updates carry the computer's time zone name (from
Electron, which knows it on every OS), so we can see roughly which
countries Frame Control is used in without ever seeing an address.

The first-run notice now has a version. Copies that saw the older notice
see it again, and keep sending their usual statistics, but leave the time
zone out until they've seen the new one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 12:41:19 +11:00
saphidandClaude Opus 5.5 c060df4b50 My Meta library: one Disconnect at a time, guarded from the confirm until the delete returns
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:17:39 +11:00
saphidandClaude Opus 5.5 c7125bc3de My Meta library: Sign in with Meta closes two sign-out races
- cancel_signin marks an in-flight attempt and, independently, deletes the
  account if that attempt saved it, so a cancel landing after the save but
  before the connect's cleanup still removes the token.
- Sign-out runs one at a time (a second call joins it) and refuses new
  sign-ins while it runs; it goes round until no window, connect or cancel is
  left. The page keeps Sign in with Meta off from pressing Disconnect until
  the server has deleted the token.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:10:59 +11:00
saphidandClaude Opus 5.5 9c33711e6f My Meta library: Sign in with Meta cancels only after its connect settles
When the sign-in window is closed, times out or is signed out of while a
connect is under way, the page is answered at once, but cancel-signin is
posted only after that connect settles (saved, refused or failed), so the
server always sees the cancel after the connect and deletes the token if
that connect saved it. Nothing sent means no cancel. Sign-out, Disconnect's
first step, waits for any such cancel (also from a window closed earlier)
before the page posts its disconnect.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:00:36 +11:00
saphidandClaude Opus 5.5 9d92396d49 My Meta library: Sign in with Meta cancels by in-flight attempt and saved owner
- frame_meta keeps only in-flight sign-in attempts (removed when their connect
  finishes) instead of a bounded list of cancelled ids, so other cancels can't
  push one out, and records which attempt saved the account. Cancelling an
  attempt being checked stops its save; cancelling the attempt that just saved
  deletes that token; a later sign-in's or a pasted token's account is never
  touched; unknown attempts change nothing.
- will-redirect applies the per-frame rule: frames may be redirected to any
  https: page, the window only to Meta's sites.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:54:38 +11:00
saphidandClaude Opus 5.5 be2eda8304 My Meta library: Sign in with Meta lets frames load any HTTPS page
Meta's login can embed captcha and CDN frames from other sites (Arkose Labs,
hCaptcha, reCAPTCHA, fbcdn), so frames inside the sign-in window may now go to
any https: address (or about:blank/about:srcdoc); other schemes (http:, data:,
blob:, javascript:, file:...) are still stopped. The window itself stays on
the Meta/Oculus/Facebook allow-list. Frames can't reach the cookie read or IPC.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:44:32 +11:00
saphidandClaude Opus 5.5 f5bed08931 My Meta library: Sign in with Meta drops abandoned attempts, holds frames to the list
Review fixes for the sign-in window:
- Each sign-in has a random attempt id. Closing, timing out or signing out
  stops cookie intake first, then tells the server (new cancel-signin action)
  not to save a connect from that attempt, even one still waiting on Meta
  (frame_meta.connect/cancel_signin; 409 from the server). Disconnect signs
  out of the window before deleting the token.
- will-frame-navigate holds every frame to the allow-list; a frame going
  elsewhere is stopped, never opened outside.
- The window is destroyed, not closed, and will-prevent-unload is overridden,
  so beforeunload can't keep it alive; sign-out clears the session only after
  the window is gone.
- Permission checks refused as well as requests; no developer tools.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:39:25 +11:00
saphid e144325375 Merge branch 'feat/quest-import' into feat/meta-library 2026-10-14 09:26:42 +11:00
saphidandClaude Opus 5.5 291b8ec295 My Meta library: Sign in with Meta in a Frame Control window
The desktop app can now open Meta's own login page (https://secure.oculus.com,
as BSManager's "Log in with Meta") in a window with its own session
(persist:meta-signin: sandboxed, context isolation, no preload or Node,
permissions refused). Navigation stays on https oculus.com, meta.com and
facebook.com; other links open in the system browser and other redirects are
stopped. The oc_ac_at cookie is read only from that session and only for
oculus.com, sent once per value through the server's existing connect path
(check_token, who am I, 0600 file), and the window closes. The page only gets
the server's answer, never the token. Disconnect also signs out of Meta in
Frame Control by clearing that session. Pasting a token stays as the fallback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:26:18 +11:00
saphidandClaude Opus 5.5 9697812984 Merge origin/main into feat/quest-import
One conflict, in ui/frame_android.py where main added COPY_MIN_RATE (size-scaled
copy timeouts) beside the install lock this branch made reentrant: both kept,
the lock stays an RLock. ui/index.html and ui/server.py merged on their own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:22:56 +11:00
saphidandClaude Opus 5.5 0fc6fbe596 My Meta library: find overlapping token matches so a partial match can't hide the real start
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:39:57 +11:00
saphidandClaude Opus 5.5 f2261bac1c My Meta library: find every redaction span in the original text and merge overlaps
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:34:03 +11:00
saphidandClaude Opus 5.5 7c45d0ff24 My Meta library: redact token-shaped values before the known token's encoded forms
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:23:08 +11:00
saphidandClaude Opus 5.5 19233fbf7d My Meta library: close the last gaps in "every accepted token is redacted"
- check_token refuses values with no lower-case letter (real tokens are mixed
  case), so the kept upper-case-constant shape can never hold an accepted
  token, even with a _SUFFIX appended.
- The token-shape pattern has no word boundary: a token is found after any
  character (_OC..., /OC..., libOC...), and its run takes every following
  token character, so an accepted token is always inside one match. Still
  one run per match, linear (timing tests unchanged).
- Whole-token redaction runs before access_token= and Authorization
  redaction, so OCaccess_token=123456 goes whole, prefix included.

The property test now also surrounds each accepted random token with random
printable-ASCII neighbours (and _FLAG, access_token=) and checks the output is
a start of the left side, one <redacted>, and an end of the right side.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:16:39 +11:00
saphidandClaude Opus 5.5 3ef978e232 My Meta library: one token character set for accepting and redacting
check_token accepted any printable character, more than the generic redaction
looked for, so an accepted value like OCAbcdef.GhijkLmnopQrstUvwx wasn't
redacted and could show as a library name. TOKEN_CHARS (letters, digits and
the base64 / URL-safe base64 characters + / = - _) and TOKEN_MIN (20) now
define both: check_token refuses anything else ("That doesn't look like a
Meta token"), including upper-case underscore constants the redaction keeps,
and the generic pattern is built from the same constants. The public tools
only describe tokens of letters and digits; widen TOKEN_CHARS in one place
if a real token needs more. Property test: 3000 random OC/FRL values over the
allowed set; every one check_token accepts is redacted in full in five
contexts and never shown as a library name. Dotted regression too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 19:04:47 +11:00
saphidandClaude Opus 5.5 8afa1113c9 My Meta library: redact digit-free token shapes too
check_token accepts tokens without digits (OCAbcdefGhijkLmnopQrstUvwx), but the
generic redaction kept OC/FRL runs that had none, so such a value in a log line
(dlopen failed: "<token>.so") was shown as the library name. The generic pattern
now redacts every run of the shape check_token accepts (OC or FRL prefix, 20+
characters of token characters), digits or not, still as one linear match.
Upper-case underscore constants (OCULUS_PLATFORM_SDK_VERSION) are kept, as are
short words, package names and runs too short to be a token. Tests for
digit-free redaction, the 20-character boundary, the excerpt, and identifiers
that must stay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:54:13 +11:00
saphidandClaude Opus 5.5 7fe7e504da My Meta library: never surface a failed log read; linear-time log and token scans
- A Frame error during a test (an ssh failure with empty stderr raises its
  stdout, which can be the game's log) becomes the fixed "couldn't read the
  game's log from the Frame" message, with no original text and no chained
  cause. run_test wraps every step; the /api/meta test job also maps any
  other exception to a fixed text, and the CLI and Install and test go
  through run_test. Close failures no longer echo their error text.
- Log scans are linear: signatures look at the first 4 KB of each line and
  every repetition followed by more required text is bounded; excerpts
  split the redacted line once and fullmatch only words of 64 characters or
  less; the generic OC/FRL token pattern matches each run once (no
  lookahead) and keeps it unless it has a digit. Timing tests: 100 KB
  adversarial lines through classify, redact and every excerpt each finish
  well under 200 ms (the old library and token patterns took 19 s and 9 s).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:38:46 +11:00
saphidandClaude Opus 5.5 f678b4c8a2 My Meta library: redact twice-encoded tokens; never show a cut-off token as a name
- _clean matches each token character literally, percent-encoded (%XX) or
  percent-encoded twice (%25XX), either letter case, hex in any case, still
  with 8+ character starts. This restores the double-encoding the port from
  feat/itch-purchases dropped; regression cases for double-encoded, recased
  and prefix forms.
- A shown log excerpt (library, extension, exception or signal name) is
  taken from the line after the whole line is redacted, never cut from
  something longer, and dropped unless it looks like a real identifier
  (64 characters at most, no 32+ alphanumeric run, no 16+ run mixing upper,
  lower and digits). Tested with the token, and the tail of a longer one,
  as a library name in a dlopen failure.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 18:21:38 +11:00
saphidandClaude Opus 5.5 33e4d2c092 My Meta library: fix review findings (token on redirects, addresses, redaction, budget, tests)
- Redirects off Meta's hosts are refused, before any lookup or connection,
  when the address carries the token in any form anywhere (host, path, query,
  fragment; literal, recased, percent-encoded, 8+ character starts) or an
  access_token parameter, instead of stripping only access_token.
- Connections resolve once, require every address to be public (an
  allow-list that refuses ff02::, fec0::, NAT64, 6to4, Teredo and the IPv4
  special blocks), connect to exactly a checked address with hostname-
  verified TLS within one shared deadline, and use no environment proxies.
- Redaction uses the itch branch's _clean (each character literal or
  percent-encoded in either case, 8+ character starts).
  These three mirror _web.public_address / resolve_public /
  _public_connection and _itch_account._clean / _carries_key on
  feat/itch-purchases, so the two can be unified when both merge.
- One byte budget per install (16 GB) counts every byte as it arrives,
  including files of unknown size; overflow stops and removes the file.
- Test causes are fixed texts; reports never carry log text. A short
  excerpt (extension, library, exception or signal name) is shown only,
  redacted before it is shortened.
- A running game must be confirmed closed before a test; a failed close
  after a hang is reported ("the game may still be running").
- A licence failure needs explicit failure evidence; a game open without a
  VR-session line is "inconclusive" and not reported to the compat DB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:59:19 +11:00
saphidandClaude Opus 5.5 621330d397 My Meta library (Experimental): install and test Quest games you own
Opt-in, off until you connect: paste your own Meta access token, which is
checked with a read-only "who am I" query and kept owner-only in
data_dir/meta-account.json. Lists the Quest apps the account owns, downloads
an owned app's APK and OBB from Meta's download server into the install
cache (size limits, declared-size and optional SHA-256 checks, cleaned up
after every install), and installs it through Import from my Quest's checks,
install and two-step data copy. Install and test runs the game, classifies
the outcome from its log (runs, runs with issues, quits, crashes, hangs) with
a plain-English cause, and records it through the existing compatibility
report path with source meta-store and the version code, shared only per the
Privacy settings.

The token goes only to graph.oculus.com and securecdn.oculus.com over
HTTPS, is dropped on redirects elsewhere, and is redacted (any case,
percent-encoded, before truncation) from every message. A disconnect wins
over a connect in flight and stops downloads. No licence-check bypass.
Protocol facts come from the public QAVS, OculusGraphQLApiLib, DownQuest and
BSManager sources (docs/meta-library.md); the code is our own.

UI: Android -> My Meta library. CLI: ui/frame_meta.py. API: /api/meta.
Tested offline with a fake Meta and fake Frame (tests/test_meta.py).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:32:28 +11:00
saphidandClaude Opus 5.5 15e14c0d47 Quest import: warn about Meta's licence check instead of refusing
A game linking libovrplatformloader.so now installs with a warning ("uses
Meta's licence check; it may quit, hang, or run without online features
on the Frame. Frame Control won't remove the check."): linking the SDK
doesn't mean the check is enforced (VRC.Quest.Security.1 has only been
Recommended since 2021). 32-bit-only and API > 30 are still refused. The
VrApi warning says "may not run". The data step (copy into the open game,
then close it) becomes add_game_data, for reuse by My Meta library.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:32:28 +11:00
Alex Southwell 141b95ddde Merge pull request #89 from saphid/feat/headset-telemetry-id
Frame Control 0.4.5: count headsets, lighter Comfort checks, sturdier APK installs
v0.4.5
2026-10-13 17:18:52 +11:00
saphidandClaude Opus 5.5 4ec2ff40c3 APK installs: decode the Frame's ssh output as UTF-8 on every OS
Windows decoded it with its code page, so the Windows CI saw 'ok ÿ' where
the test expects a replacement character, and real UTF-8 messages from the
headset came out garbled there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:07:12 +11:00
saphidandClaude Opus 5.5 39e819872a Privacy notice and setting: say headsets are counted, by a one-way hash
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 17:00:08 +11:00
saphidandClaude Opus 5.5 ee7951e8d0 Release 0.4.5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 16:55:41 +11:00
saphidandClaude Opus 5.5 77c6c286cd Split merge: accept 64-bit data descriptors on small streamed ZIP entries
Streaming writers (Python's unseekable ZipFile, force_zip64) write ZIP64
data descriptors even for small files, so the descriptor width can't be
taken from the final sizes. The reader now accepts the signed or unsigned,
32- or 64-bit layout that matches the central directory and fits before
the next entry, preferring one that ends exactly there, then the width the
local header's ZIP64 metadata implies. Format tests cover streamed ZIP64
stored and deflated entries.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:54:55 +11:00
saphidandClaude Opus 5.5 ca53b26701 Quest import: fix round-3 review findings (legacy versions, strict ZIP, closing)
- Installs made before records kept a version code no longer get replaced by
  an older Quest copy: the installed code is read from app.apk on the Frame
  (stdlib parser, only the manifest crosses the link); if it can't be read,
  replacing needs allowDowngrade. The data-copy guard uses the same lookup.
- Split members are checked against both ZIP headers: same local name and
  method, local sizes and checksum (or a validated data descriptor, with
  ZIP64 extra fields understood) equal to the central directory, no overlap
  with the next entry, and no trailing bytes after a deflate stream.
- The game is closed in a finally block, so a local OSError or Ctrl-C part-way
  through a copy still closes it; the original exception is kept and a failed
  close is reported separately.
- running_instances(strict=True) fails closed (a failed `podman ps` raises),
  used by the Quest import; other callers keep the lenient listing.
- The dumpsys version check reads only the package's own block.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:43:03 +11:00
saphidandClaude Opus 5.5 baec984449 Quest import: fix round-2 review findings (versions, locking, strict split reads)
- An older Quest copy no longer replaces a newer install on the Frame: installs
  now record versionCode, and a downgrade is refused unless allowed
  (--allow-downgrade, "allowDowngrade", or the panel's checkbox).
- The game-data copy only targets this package's own install record and
  instance at the Quest's exact version code, and checks Android's own
  dumpsys in that instance before copying.
- After the (possibly long) pulls the target is checked again, and the final
  checks, the transfer and closing the game hold the shared install lock,
  now reentrant so install_obb and install can be called under it.
- Closing the game is confirmed; a failed or incomplete close is reported
  instead of claiming the game was closed.
- Split members are read from their raw bytes: stored members must be exactly
  their declared size, deflated streams must end exactly there, then the
  checksum; forged sizes with prefix checksums are refused.
- A split's resource table counts as empty only if no type chunk defines any
  entry (bags included) and every chunk is understood.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:24:30 +11:00
saphidandClaude Opus 5.5 18e5053b64 Quest import: fix review findings (bounded split merge, no game launch, streamed data)
- Split merging only accepts splits holding lib/<abi>/*.so, assets/, their own
  signature/manifest and an empty resource table. Code (classes*.dex), other
  files, resource splits, feature splits and split manifests declaring
  components or libraries are refused before anything reaches the Frame.
- Merged bytes are bounded (512 MB per file, 1 GB in all) from the headers and
  again by bounded reads with a checksum, so a zip bomb or a forged header
  can't fill memory.
- Import never starts the game. The first import installs it; game data (OBB,
  Android/data) is copied by a second import while the person has the game
  open, never replacing an open game's APK, then the game is closed.
- Android/data is streamed as a tar straight into the SSH transfer, not
  staged; the space check counts split payloads uncompressed and data once.
- The panel shows small sizes in KB rather than "1 MB".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:02:42 +11:00
saphidandClaude Opus 5.5 7faf80fc2b Import from my Quest: copy games you own off a USB-connected Quest
New ui/frame_quest.py (CLI: devices | list | import), GET/POST /api/quest and
an Import from my Quest panel on the Android page. It finds a Quest on this
computer's adb (never Frame Control's own tunnelled serials), lists third-party
apps with split, OBB and Android/data details, pulls the APKs into a per-user
cache and checks them before copying anything else: Meta's Platform SDK
licence check (libovrplatformloader.so) and 32-bit-only builds are refused,
VrApi-only builds flagged. It then installs through the normal APK path, adds
OBBs with the existing helper and, if asked, Android/data files, and always
removes the cache. No licence-check bypass is bundled, called or linked.

Tests use a fake adb (tests/fakeadb.py). Docs: docs/quest-import.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:17:11 +11:00
saphidandClaude Opus 5.5 6ea20a2806 Android installs: accept split APKs by merging them into one APK
Lepton runs one APK per instance, so frame_android.install(base, splits=[...])
(and `frame_android.py install base.apk split.apk ...`) merges splits that add
native libraries or assets into the base, drops the manifest's isSplitRequired
flag, and re-signs with Frame Control's key. Density and language splits,
splits of another package and clashing entries are refused before anything
reaches the Frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 13:17:11 +11:00
saphidandClaude Opus 5.5 d456aa1f3d Take the #81 log scrubbing rework out of this release
Three review rounds kept finding ways that matching names, ssh operands and
user@host in one pass shows something the scrubber on main hid (URLs, home
folders, keys). The two #81 edge cases are minor; the scrubber goes back to
main's, and #81 stays open with the findings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 23:07:35 +11:00
saphidandClaude Opus 5.5 6c7b2ca22f Log scrubbing: match names in the text as it came, with user@host found alongside
Round 2 of the review: scrubbing the text before matching names stopped a
name the scrubber partly rewrote ("frame.local Jane Doe headset") from
matching whole, and cut the text before names were hidden. Instead the
shared scrubber's user@host patterns are found in the original text as
spans too, so a name can't take their @. A randomised comparison with the
scrubber on main (160,000 cases) found nothing it hid that this shows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 22:59:42 +11:00