Medal of Honor (com.Virtuos.R6DLM) keeps Epic's DownloaderActivity and log text but renames the delivery predicates
and OBBData, so expansionFilesDelivered wasn't found and frame.unreal_skip_obb_delivery was never offered. Detection
now also matches a boolean method in the package's DownloaderActivity, called from onCreate, that carries Epic's
"Checking for file : " log and calls the expansion-downloader helpers; the patch rewrites those too. ANALYSIS_VERSION
18 -> 19. Heuristics eval unchanged (10/66); no existing recipe affected.
The GLES-only stand-in left Vulkan games (UNDERDOGS: VkFormat 43, faces=6) with the runtime's refusal, so OVRPlugin
crashed in ovrp_EndFrame4. When there's no GLES context, the adapter now makes a cube-compatible VkImage (6 layers,
the requested format/size/mips, usage from the XR flags) with the app's device/queue and a private transient pool,
transitions it to COLOR_ATTACHMENT_OPTIMAL, and enumerates it as a Vulkan image; its cube layers are still dropped.
GLES behaviour is unchanged. Adapter rebuilt deterministically (both ABIs).
After "Waiting for app", a failure line or 20 s without a logcat line start FramePort's own reader, from the
launcher's start (logcat -T <epoch>) when the mirror never wrote, else from just before its last line. The reader
restarts from where it ended, stops when Lepton's lines come back, the container's log buffer is 16 MiB, and every
decision goes to logcat-keeper.log.
Merged main (agent 90, ANALYSIS_VERSION 17/18 after main's 15/16) and rebuilt the bridge, foveation and a32
libraries from source (deterministic, no local paths).
Fixes on top:
- a32 loader: apply DT_ANDROID_RELR relocations (Journey of the Gods crashed at start)
- VrApi bridge: vrapi_Get/SetTrackingTransform (floor-level tracking; Shadow Point's player was below the floor)
- a32: never submit an untracked default head pose (hold up to 100 ms, repair or drop); pose consistency per display
time; submit/tracking counters
- VrApi bridge: wait again when xrWaitFrame gives a display time in the refresh just submitted (the runtime rejected
the second frame with AlreadySubmitted; Journey of the Gods ran at ~120 fps on a 90 Hz display and the scene slid
and snapped back); pacing counters
- VrApi bridge: GLES re-entry polls events before reusing the kept session (a lost or exiting session is replaced, a
stopped one waits for READY)
- VrApi bridge: STAGE is the runtime's STAGE space (play-area centre and orientation, unchanged by recentering);
LOCAL_FLOOR keeps the head-based origin
- signer certificate: pick the certificate the SignerInfo names, not the first in the PKCS#7 block
- flat 32-bit apps keep the bridge (it isn't a VR-only fix)
- Frida gadget loads are stripped before translation
- launcher restart test fixed; native/.cache is no longer committed
- catalog: Shadow Point and Journey of the Gods work (headset, 2026-10-14; min_app 1.1.2)
Holding the Frame's menu button turned 32-bit (and other VrApi bridge)
games 90-120 degrees to the side. The bridge's app space is LOCAL plus
the head's yaw at the start; the runtime's recenter moves LOCAL to the
head, so that old yaw was applied again. On a LOCAL reference space
change (and on vrapi_RecenterPose, ignored before) the origin is taken
from the head again in the first frame after the change, and VrApi's
recenter count goes up. Owner-verified with Gunjack.
The upstream patches are regenerated against OVRPort 5e7df52 (they
were missing the FOV cache changes).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sports Scramble (a 32-bit Santa Cruz build: UE 4.21, OVRPlugin on VrApi 1.21)
now plays in the headset with both controllers.
a32 bridge:
- AAsset_getBuffer: the host buffer lies above 4 GiB, so the guest gets a
copy in its own memory, freed in AAsset_close (Unreal read its
UE4CommandLine.txt this way and aborted on the NULL it got before)
- AKeyEvent_getFlags and the remaining AConfiguration getters
- Touch controllers for Quest builds: apps that require 6DoF head tracking
(uses-feature android.hardware.vr.headtracking version 1) get
assets/fp_a32_headset.txt = "quest" at conversion; the bridge then passes
both Frame controllers through (input devices, caps, state, tracking,
vibration; struct tails per VrApi minor) and sends no synthetic touchpad
events. Gear VR / Go games keep the remote emulation. A32_TOUCH=1/0
overrides.
Analysis: detect.requires_6dof, headset "quest" for such 32-bit apps
(ANALYSIS_VERSION 13).
Agent 82: Unreal restarts the app once after its first-run shader precompile
(the "optimizing" screen): it starts its own launcher activity and exits, and
Lepton ends the container with the first process. Skipping the restart or
turning it off (r.ProgramBinaryCache.RestartAndroidAfterPrecompile=0) left
the game on that screen for good. launch.sh now starts Lepton once more when
the system log shows the app's own LAUNCHER start right before its
System.exit(0) (_restart_requested; first run kept as
launch-before-restart.log; FRAMEPORT_NO_RESTART=1 turns it off).
upgrade_launchers adds it to existing launchers. Crashes, quitting from the
game and Steam's Exit game don't match.
Catalog: Sports Scramble works (owner, headset, 2026-10-13).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gunjack (UE 4.10, Gear VR) has no separate reload or special-weapon input on
the touchpad: every swipe goes to UE4's Gesture_Flick, and
ATDPlayerController::OnFlick reads the flick angle. Below 45 or above 315
degrees (+X, a swipe towards the back of the Gear VR touchpad) it reloads;
135 to 225 degrees (-X, a swipe forward) fires the special weapon. Up and down
swipes do nothing. The bridge sent the stick's up/down as Y swipes, so in
Gunjack those directions did nothing.
For games that read their AInputQueue (NativeActivity), the right stick's
up/down now swipe along the touchpad's own X axis: up = swipe forward
(special weapon), down = swipe back (reload). Left/right are unchanged.
Unity games (AFFECTED, Spunky Dungeon) keep their mapping.
Rebuilt liba32bridge.so. Gunjack was rebuilt and installed on the dev Frame;
the headless launch test passes. Not yet tried in the headset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Gunjack (UE 4.10, Gear VR) imports no vrapi input functions: it reads the
Gear VR touchpad and back button as Android input events from its
AInputQueue (native_app_glue). The bridge delivered its controller events
through Activity.dispatchKeyEvent, which never reaches native code, so no
control worked at all.
input.c now queues synthetic events in front of the game's AInputQueue:
- AInputQueue_attachLooper (without a callback) registers the queue;
ALooper_pollAll/pollOnce return its ident while a synthetic event waits
(ALooper_wake ends a blocking poll), AInputQueue_getEvent hands it out,
finishEvent frees it.
- The AInputQueue/AInputEvent/AKeyEvent/AMotionEvent imports are written
by hand ("!" in imports.txt) and answer for synthetic events; real
events go to libandroid as before.
- Touch positions are scaled to the activity's window size
(native_activity.c records it).
Mapping (right controller, as for AFFECTED: The Manor): trigger = hold
the touchpad, A = tap, right stick = swipe, B / left menu = back. Unity
and other Java-delivered games are unchanged. A new input_start (VR
re-entry) ends the previous poll thread.
Also: the libandroid functions the host side calls directly are weak
references again, so the PC test harness links (it broke with the
AAsset calls of the signature loader); bridge/test/run.py passes.
Headless on the Frame: "controllers: the game reads its AInputQueue",
72 fps. Not yet verified in the headset.
Gunjack (CCP, UE 4.10, Gear VR) quit after loading because AndroidMain compares
GameActivity.AndroidThunkJava_GetSignature() with the original signing
certificate, and then crashed in the Frame's runtime when it entered VR again.
a32 bridge:
- frame.a32_bridge: original_signature() = Base64.DEFAULT(SHA-1(signing
certificate DER)) of the original APK (META-INF/*.RSA|DSA|EC), stored as
assets/fp_a32_signature.txt for libUE4.so games (convert(), shared with
bridge/convert.py)
- the bridge loads it at activity creation and answers guest calls on the
method ID of AndroidThunkJava_GetSignature()Ljava/lang/String; with it
(replaces the unused hex/OpenSSL prototype)
VrApi bridge: after loading, Unreal leaves VR mode and enters it again on the
same GL context; the first frame of a second OpenXR session there aborted in
vrclient (CSharedSemaphoreLinux::WaitFromGPU from the GL binding's
PreBeginFrame). vrapi_LeaveVrMode now keeps a running GLES session and
vrapi_EnterVrMode reuses it for the same context (another context still gets
a new session; Vulkan unchanged). upstream-patches/runtime.cpp.patch updated,
libvrapi.so and liba32bridge.so rebuilt.
Verified on the Frame: headless launch test passes (72 fps after re-entry);
owner-verified in the headset.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
32-bit-only APKs (Gear VR / Oculus Go games) are no longer "unsupported": the
recipe engine suggests frame.a32_bridge, which converts the APK with the
translator bridge (convert() shared with bridge/convert.py) and ships
liba32bridge.so / liba32rt.so / liba32stub.so (native/build.py --only a32).
FramePort:
- engine._a32: overport off (OVRPort is 64-bit only; the patch ships OVRPort's
loader chain + its 32-bit platform dispatcher, overport.ensure_runtime_lib),
only frame.a32_bridge/adapter/launcher of the frame patches, 64-bit-only
adapter settings dropped, device.text_input_window for Unity; such patches
are hidden in the UI
- analysis extra.headset (ANALYSIS_VERSION 9): quest / go / gearvr ->
"Gear VR" / "Oculus Go" platform + Steam tag; catalog `headset:` field
- expects_obb ignores Unreal's OBB-inside-the-APK (assets/main.obb.png)
- catalog: com.fallen.affected, net.mobilemischief.spunkydungeon
Bridge / translator:
- VrApi 1.31 + Unity 2019 (no-config EGL contexts, LOCAL_FLOOR/STAGE tracking
space), GL buffer mappings per buffer object (fixes a ~10 MB/s guest leak and
garbled vertices), Go controller mapped to the right Frame controller
- native_app_glue imports (ALooper/AInputQueue/AConfiguration/AAsset*) for
UE4 NativeActivity games (Gunjack: runs, then quits on its own APK signature
check, which FramePort doesn't defeat)
- debugging aids: A32_BREAK, A32_STRACE to logcat, lifecycle/LeaveVrMode logs
vrapi-bridge: honours LOCAL_FLOOR/STAGE, picks a window/RGBA8 ES3 config for
no-config contexts; upstream patch updated, libvrapi.so rebuilt.
Verified on the Frame: Spunky Dungeon (Unity 2019.2, OVRPlugin 1.46) plays at
72 fps; AFFECTED The Manor still runs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Agent 85: the game's container gets the host's /dev/input at /dev/fp-input (Lepton's /dev/input holds its own Wayland
input pipes) and the launcher's _gamepad_links worker keeps /dev/input/eventN links to Steam Input's virtual pads in
step while the game runs: controllers switched off and on again (Steam makes a new pad) work without a restart.
Android's EventHub adds and removes the links through inotify (dev Frame, uinput stand-in pad created after boot:
New device, Device removed, added again). Older launchers get the new gamepad line through upgrade_launchers.
The key layout also maps BTN_DPAD_* (a Steam Input layout that sends the D-pad as buttons, e.g. a stick set to
D-pad; the Frame's controllers have no D-pad of their own).
bHaptics' vest SDK asks for BLUETOOTH_CONNECT at start; on Lepton's Android 11 the permission screen takes the
foreground for a moment and the game pauses (Yupitergrad froze on its loading screen in the reporter's session).
PermissionUtils.requestBluetoothPermission returns at once (Dex.return_early). Suggested by analysis
bhaptics_permission (ANALYSIS_VERSION 14) and triage bhaptics-permission-prompt. On the dev Frame the prompt is gone;
whether it was the whole cause of the freeze is for the reporter to confirm.
Opt-in game setting (Picture -> Draw fades over the picture). Batman: Arkham Shadow fades by dimming its projection
layer with XR_KHR_composition_layer_color_scale_bias; with fade_quad=1 FrameBridge adds a black, alpha-blended quad
right after the dimmed layer (VIEW space, 1 m ahead), opacity = 1 - scale in 16 steps. One 8x8 swapchain, refilled
only when the step changes (Vulkan clear on the app's queue / GLES clear in its context); one swapchain per step hit
XR_ERROR_LIMIT_REACHED. fade_quad=2 also removes the layer's own scale. Device (headless, Batman): swapchain created,
level 16/16 on the first frame, no xrEndFrame errors, 72 fps.
The game showed a canvas being painted as its own compositor layer, which the Frame draws in front of everything.
PaintableMesh.HasCompositorLayerAvailable and ShouldUseCompositorOverlay return false (Cpp2IL), so the game's own
fallback draws the canvas in the scene (Klownicle's finding). Catalog recipe, PLAYBOOK row, test.
Bionic allows 128 pthread keys per process. Unity 6 takes about 40, and with Gecko, Firebase and the Frame's own
libraries Wander 0.3570 ran out: Firestore aborted at start ("cannot create thread specific key for
__cxa_get_globals()", #198, #202). Opt-in patch, suggested for Unity 6 with Firebase C++ or Gecko: libunity.so's
pthread_key_create/_delete and pthread_get/setspecific imports are renamed in place to fp_tkey_* (new
elf.rename_imports: undefined symbols only, shared names refused, versions set global) and libfp_tkeys.so
(native/tlskeys) is added to its DT_NEEDED; keys without a destructor become generation-tagged slots behind one
real key, everything else keeps real keys. Approach and the first helper by Klownicle (#198), reimplemented here.
Triage signature pthread-keys-exhausted, PLAYBOOK row, Wander's recipe updated to 0.3570.
- Agent 83: Valve's Lepton pastes the game's paths unquoted into shell commands (liblepton mounting.sh), so a game on
a card named "Steam Frame 512g" never got its internal storage (no saves, no SharedPreferences). The Quest launcher
now links the game folder to ~/.local/share/frameport/lepton-paths/<pkg> at every start and gives Lepton that path;
upgrade_launchers converts existing launchers, move and uninstall handle the link (#213, #214, #215, #221).
- frame.eos_keychain_loop (opt-in, offered by triage): Epic Online Services' resetKeychain returns at once, so a
keychain it can't write no longer loops forever (Beyond Sandbox, #214).
- Triage lepton-data-link-failed and eos-keychain-loop.
- CryEngine also from libAndroidLauncher.so + a .cryproject (Climb 1, #238); ANALYSIS_VERSION 12.
Epic's DownloaderActivity checks the OBB's name and size and, with the manifest's
bVerifyOBBOnStartUp=true, CRC-checks the whole file; its screens are invisible in Lepton,
so games like Contractors seem to hang at 'Displayed .../.DownloaderActivity'. The patch
flips that manifest boolean (axml.set_meta_data_bool, one 4-byte value); analysis
unreal_verify_obb (ANALYSIS_VERSION 11) suggests it. Triage unreal-obb-check-stuck (fatal)
fails such launch tests; triage keeps system_server's lines about the game's own activities.
Godot 4.2-4.4's Kotlin code casts getSystemService(CLIPBOARD_SERVICE) with a
non-null 'as ClipboardManager'; Lepton has no clipboard service, so the app
crashes with a NullPointerException in Godot.<init> (4.3/4.4) or on first use
(4.2's lazy property). In place in classes*.dex, matched exactly: the
Intrinsics.checkNotNull before that check-cast becomes nops (older kotlinc
branch forms: the branch is fixed), and Godot's getClipboard/hasClipboard/
setClipboard return at once (Dex.return_early gains String -> "").
Analysis field godot_clipboard (ANALYSIS_VERSION 10), triage
godot-no-clipboard, PLAYBOOK row, unit tests on synthetic dex. Checked against
godot-lib 4.1.4-4.7.2 from Maven Central and the 4.3/4.4.1 export templates:
4.2.2, 4.3.0, 4.4.1 patched (D8 re-reads the result), the others untouched.
Host OpenVR overlays are composited over Lepton (Quest) games on the Frame;
Windows overlay apps register through Proton's vrclient. Detect overlay apps
(bundled .vrmanifest with is_dashboard_overlay, or OpenVR + IVROverlay +
self-registration), patch pcvr.vr_overlay and the Linux entry fields
vr_overlay/vr_overlay_autostart, agent register/unregister/launch_vr_overlay
(IVRApplications through SteamVR's libopenvr_api.so; manifest with
binary_path_linux_arm, the only key the Frame's vrserver reads). Overlay apps
skip launch tests and the Linux Steam-parent watchdog and don't count as a
running game. Probe sources in native/vroverlay_probe.
Conflicts kept main's features with the site branch's wording: the uninstall
dialog's new delete-saves option, hardware video decoding and last-session help,
new Game settings (plain labels), agent 73 (main's 72 plus the Take screenshot
command from v66). Main's new texts follow docs/STYLE.md (for example, patch/setting
instead of fix). Pages rebuilds on docs changes and daily; the CNAME file is
gone (the domain is a Pages setting).
Lepton's Android only gets keyboard, pointer and touch from the Wayland seat. The opt-in patch (2D apps; suggested
when the manifest declares android.hardware.gamepad or LEANBACK_LAUNCHER, ANALYSIS_VERSION 9) makes the agent record
steam_gamepad in deployment.json and give the launcher a line that exports SDL's hint
(LEPTON_ENV_SDL_GAMECONTROLLER_ALLOW_STEAM_VIRTUAL_GAMEPAD) and puts FramePort's own Podman wrapper (agent/bin/podman,
written by ensure_host_fixes, independent of the video codec) first on PATH. For the game's podman run it bind-mounts
Steam's virtual pads (uinput, 28de, gamepad buttons) at /dev/input/eventN plus an Xbox 360 key layout, then hands on
to the next Podman after itself (the codec wrapper or Podman), so both wrappers chain in either order.
FRAMEPORT_NO_GAMEPAD=1 turns it off for one start; failures keep the arguments unchanged. Verified headless on the
dev Frame with a uinput stand-in pad: Android's EventHub opens it with our layout and dispatches BUTTON_A.