mirror of
https://github.com/daniel-lynch/ovrplugin-openxr-shim.git
synced 2026-10-06 01:00:05 +02:00
docs+build: hygiene pass — close leak risk, de-drift docs, fix build prereqs
Repo hygiene round following a full review. No shim behaviour changes. Leak risk: - .gitignore: ignore CLAUDE.md (personal assistant-lane config, was one `git add -A` away from a public commit) and scratch_obj/. Docs vs. reality: - shim/README.md: rewritten. It described a pre-implementation skeleton with "core fns are TODO stubs returning -1005", three mutually inconsistent stub counts, and four completed milestones listed as open. Now carries the verified breakdown: 438/438 exports = 371 generated stubs + 46 core + 7 layers + 2 Vulkan queries + 12 passthru trampolines. - TESTING.md: dropped the self-contradicting "NOT yet" block (5 of 6 items were done or misstated, and contradicted the same file 45 lines above). Path B now points at tools/desktop-harness, which exists, instead of the orphaned shim/tests/harness.c. Path A prereqs marked as the record they are. - HOST.md: corrected the runtime assumption. The OpenXR runtime inside Lepton is SteamVR (vendor/etc/openxr/1/active_runtime.json -> vrclient.so), not Monado. Favourable: SteamVR emulates Oculus Touch by default and advertises the XR_FB_foveation family, so the existing input and foveation paths should carry over. The old "remaining unknowns" are resolved by Lepton's published source and replaced with the items to check before a first Frame boot. - README.md: same runtime correction. - docs/research/RECON.md: the four passages prescribing an entitlement NOP/stub/bypass are corrected in place rather than merely disclaimed by the top banner, which they contradicted. Build correctness: - shim/build_android.sh: missing patchelf is now fatal. It warned and exited 0, producing a .so that cannot resolve the OpenXR loader at runtime. - scripts/fetch_deps.sh + packaging/build_openxr_loader.sh: pin the OpenXR and Vulkan header versions (were tracking `main`), overridable via OPENXR_TAG / VULKAN_HEADERS_TAG; require cmake for the loader build. - packaging/steamframe_patches.sh: use the apktool.jar that fetch_deps.sh downloads. Its prereq check demanded an `apktool` binary on PATH that the documented setup never provides, so it could not run after a clean setup. - shim/gen_stubs.sh: it reads all_exports.txt, not shim_surface.txt; comment and emitted banner corrected. stubs.c regenerated (banner line only). - shim/src/core.c: split seven `if (out) ...; return ...;` one-liners. Host build now compiles with zero warnings, down from seven. Verified: host build 0 warnings; gen_stubs.sh output identical on regeneration; bash -n clean on all edited scripts; pinned header/tarball URLs return 200 and the tag tarball extracts to the expected directory name. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
574f41a0e4
commit
1f3dc40c07
13 files changed
+304
-161
No files matched your search
@@ -34,3 +34,7 @@ packaging/libs/
|
|||||||
*.zip
|
*.zip
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
save_backup/
|
save_backup/
|
||||||
|
|
||||||
|
# Session/lane scratch (personal assistant-lane config — never public)
|
||||||
|
CLAUDE.md
|
||||||
|
scratch_obj/
|
||||||
@@ -1,7 +1,15 @@
|
|||||||
# Target host platform — Steam Frame
|
# Target host platform — Steam Frame
|
||||||
|
|
||||||
Researched 2026-06-23. Answers "can the dumped APK run on Steam Frame, and do we
|
Researched 2026-06-23; **corrected 2026-09-18 against the shipped hardware and Lepton's
|
||||||
need Android given SteamOS is Linux?"
|
published source.** Answers "can the dumped APK run on Steam Frame, and do we need Android
|
||||||
|
given SteamOS is Linux?"
|
||||||
|
|
||||||
|
> **Correction (2026-09-18):** the 2026-06 research assumed the OpenXR runtime inside Lepton
|
||||||
|
> would be **Monado**. It is **SteamVR**. Lepton ships
|
||||||
|
> `vendor/etc/openxr/1/active_runtime.json` naming runtime `steamvr`
|
||||||
|
> (`VALVE_runtime_is_steamvr: true`) and pointing at a host-mounted
|
||||||
|
> `/data/steamvr/runtime/bin/androidarm64/vrclient.so`. Sections below are updated; treat any
|
||||||
|
> remaining "Monado" reference in older docs as superseded.
|
||||||
|
|
||||||
## Do we need the Android side? YES.
|
## Do we need the Android side? YES.
|
||||||
The game is an **Android binary**, not a Linux one — ARM64==ARM64 does NOT bridge:
|
The game is an **Android binary**, not a Linux one — ARM64==ARM64 does NOT bridge:
|
||||||
@@ -18,34 +26,64 @@ The game is an **Android binary**, not a Linux one — ARM64==ARM64 does NOT bri
|
|||||||
run **native ARM64, no emulation** (the "Waydroid needs x86" caveat is about
|
run **native ARM64, no emulation** (the "Waydroid needs x86" caveat is about
|
||||||
Waydroid on x86 PCs; Frame is ARM so it doesn't apply). Walkabout Mini Golf
|
Waydroid on x86 PCs; Frame is ARM so it doesn't apply). Walkabout Mini Golf
|
||||||
(Quest title) already cited running on it.
|
(Quest title) already cited running on it.
|
||||||
- **Monado** = open OpenXR runtime, runs on **Linux AND Android**, Vulkan
|
- **SteamVR** is the OpenXR runtime apps see inside Lepton, bind-mounted in from the host
|
||||||
compositor using VK_KHR_external_memory_fd / external_semaphore_fd (matches our
|
(not Monado — see the correction above). It advertises the `XR_FB_foveation` family,
|
||||||
Vulkan-renderer finding).
|
`XR_FB_swapchain_update_state`, `XR_META_foveation_eye_tracked` and
|
||||||
|
`XR_EXT_eye_gaze_interaction`, and it presents Frame controllers as **emulating Oculus
|
||||||
|
Touch** by default (falling back Frame profile -> generic -> Touch). Both facts are
|
||||||
|
favourable: our Touch bindings and our FB-foveation path should work unchanged.
|
||||||
|
- Lepton also mounts the host's graphics stack into the container (mesa/turnip/zink, gralloc
|
||||||
|
`minigbm_msm`) plus host Vulkan layers including a foveated-rendering injector and a
|
||||||
|
renderpass optimizer.
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
```
|
```
|
||||||
Steam Frame (SteamOS / Arch Linux, ARM64)
|
Steam Frame (SteamOS / Arch Linux, ARM64)
|
||||||
└─ Lepton (AOSP/Waydroid container, native ARM64)
|
└─ Lepton (AOSP/Waydroid container, native ARM64)
|
||||||
└─ RE4 VR APK (unmodified bionic Android binary)
|
└─ RE4 VR APK (unmodified bionic Android binary)
|
||||||
├─ libUE4.so → [SHIM libOVRPlugin] → OpenXR → Monado → Frame compositor (Vulkan)
|
├─ libUE4.so → [SHIM libOVRPlugin] → OpenXR → SteamVR (vrclient.so) → Frame compositor
|
||||||
└─ ovr_* Platform SDK → out of scope (no entitlement code ships in this repo —
|
└─ ovr_* Platform SDK → out of scope (no entitlement code ships in this repo —
|
||||||
a valid entitlement is the user's responsibility; see README "Legal / scope")
|
a valid entitlement is the user's responsibility; see README "Legal / scope")
|
||||||
```
|
```
|
||||||
|
|
||||||
## Why the shim IS the project
|
## Why the shim IS the project
|
||||||
Meta ended VrApi support 2022-08-31; OpenXR is the only supported Quest API and
|
Meta ended VrApi support 2022-08-31; OpenXR is the only supported Quest API and
|
||||||
Valve's whole stack is OpenXR (Monado). So:
|
Valve's whole stack is OpenXR (SteamVR on Frame). So:
|
||||||
- OpenXR Quest games -> Lepton+Monado likely run them with little/no work.
|
- OpenXR Quest games -> Lepton+SteamVR likely run them with little/no work.
|
||||||
- VrApi games (RE4 VR) -> won't: Lepton/AOSP will never ship Meta's proprietary
|
- VrApi games (RE4 VR) -> won't: Lepton/AOSP will never ship Meta's proprietary
|
||||||
libvrapi.so, so the unmodified game finds no VR runtime. The OVRPlugin->OpenXR
|
libvrapi.so, so the unmodified game finds no VR runtime. The OVRPlugin->OpenXR
|
||||||
shim is exactly what bridges a dead-API VrApi game to Frame's OpenXR stack.
|
shim is exactly what bridges a dead-API VrApi game to Frame's OpenXR stack.
|
||||||
|
|
||||||
## Remaining real unknowns (gated on Frame shipping ~summer 2026)
|
## Status of the old unknowns (resolved 2026-09-18)
|
||||||
1. Does Lepton expose an OpenXR loader+runtime to apps INSIDE the container?
|
|
||||||
(Almost certainly yes for the OpenXR-Quest-game use case; ride on it.)
|
Steam Frame shipped **2026-09-14** and Lepton is open source (MIT for the tool), so the three
|
||||||
2. Can a SIDELOADED app reach the runtime + compositor (perms across the Waydroid
|
2026-06 unknowns are answered:
|
||||||
boundary)?
|
|
||||||
3. **Likely the real technical crux:** sharing Vulkan swapchain images from inside
|
1. **Does Lepton expose an OpenXR runtime to apps inside the container?** Yes — SteamVR, via
|
||||||
the Lepton container out to the host Monado/Frame compositor
|
the bind-mounted `active_runtime.json` and `vrclient.so` described above.
|
||||||
(VK_KHR_external_memory_fd across the container GPU boundary). May be moot if
|
2. **Can a sideloaded app reach the runtime + compositor?** Yes. Lepton documents adb
|
||||||
Monado's compositor runs inside the container.
|
sideloading (`lepton install_app`, or `adb install` against the container), and its own
|
||||||
|
installer pushes an adjacent `obb/` directory into the app's data — which matters for us,
|
||||||
|
since RE4 VR ships its assets as OBBs.
|
||||||
|
3. **Vulkan swapchain sharing across the container GPU boundary** — a non-issue by design:
|
||||||
|
Lepton mounts the host graphics drivers into the container rather than proxying them.
|
||||||
|
|
||||||
|
### New items to check before a first boot attempt
|
||||||
|
|
||||||
|
- **Page alignment (check this first).** Our shim's ELF LOAD segments align at 4 KB and
|
||||||
|
`repack.sh` runs `zipalign -p 4`. Valve's Unreal docs reference a 16 KB page-alignment
|
||||||
|
requirement on this platform. If the Frame kernel uses 16 KB pages, the library will not
|
||||||
|
load, and it would present as an unexplained launch failure. Fix is
|
||||||
|
`-Wl,-z,max-page-size=16384` at link time plus `zipalign -P 16`.
|
||||||
|
- **The Build spoof in `packaging/steamframe_patches.sh` is confirmed necessary.** Lepton sets
|
||||||
|
`ro.product.manufacturer=Valve` and `ro.product.model=Lepton`, so UE's Oculus-HMD gate is
|
||||||
|
false without it and our shim is never called.
|
||||||
|
- **Swapchain usage flags.** `setup_layer` requests only COLOR_ATTACHMENT and SAMPLED. Meta
|
||||||
|
over-provisions; a spec-following runtime does not.
|
||||||
|
- **Refresh rate.** 72 Hz is hardcoded in two places; Frame runs 72/90/120/144.
|
||||||
|
- **`UECommandLine.txt`.** Lepton's installer pushes one into `/data/steam_app`, which may be
|
||||||
|
a route for the UE streaming CVars that the baked-commandline dead end blocked on Quest.
|
||||||
|
Unverified, but cheap to try.
|
||||||
|
- **App SDK level.** RE4 VR is `minSdkVersion 25` / `targetSdkVersion 29`, arm64-v8a only.
|
||||||
|
Lepton rejects APKs whose SDK level is *higher* than the container's, so a low target should
|
||||||
|
be fine — but it is untested.
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
A from-scratch reimplementation of Meta's `libOVRPlugin.so` on top of **OpenXR**,
|
A from-scratch reimplementation of Meta's `libOVRPlugin.so` on top of **OpenXR**,
|
||||||
so legacy VrApi/OVRPlugin-based Meta Quest titles can run on standard OpenXR
|
so legacy VrApi/OVRPlugin-based Meta Quest titles can run on standard OpenXR
|
||||||
runtimes (Monado, and eventually Valve's **Steam Frame** under Lepton).
|
runtimes — Valve's **Steam Frame** (SteamVR inside Lepton), Monado, and others.
|
||||||
|
|
||||||
**Status:** *Resident Evil 4 VR* boots and is playable on a Quest 2 through this
|
**Status:** *Resident Evil 4 VR* boots and is playable on a Quest 2 through this
|
||||||
shim — stereo rendering, head + controller tracking, buttons, grips, haptics, and
|
shim — stereo rendering, head + controller tracking, buttons, grips, haptics, and
|
||||||
@@ -12,12 +12,12 @@ save loading all work. (Developed as a preservation / interoperability experimen
|
|||||||
|
|
||||||
Quest's `libOVRPlugin.so` is the C shim Unreal/Unity games call to talk to Meta's
|
Quest's `libOVRPlugin.so` is the C shim Unreal/Unity games call to talk to Meta's
|
||||||
VR runtime. Meta deprecated the underlying VrApi in 2022 and the whole modern stack
|
VR runtime. Meta deprecated the underlying VrApi in 2022 and the whole modern stack
|
||||||
(incl. Steam Frame's Monado) is OpenXR-only, so VrApi-era titles have no runtime on
|
(incl. Steam Frame's SteamVR) is OpenXR-only, so VrApi-era titles have no runtime on
|
||||||
non-Meta OpenXR platforms. This project re-exports the `ovrp_*` C API backed by
|
non-Meta OpenXR platforms. This project re-exports the `ovrp_*` C API backed by
|
||||||
OpenXR instead, as a **drop-in replacement** `libOVRPlugin.so`:
|
OpenXR instead, as a **drop-in replacement** `libOVRPlugin.so`:
|
||||||
|
|
||||||
```
|
```
|
||||||
game (libUE4.so) ──ovrp_* C API──> [THIS SHIM] ──OpenXR──> runtime (Monado / Meta / …)
|
game (libUE4.so) ──ovrp_* C API──> [THIS SHIM] ──OpenXR──> runtime (SteamVR / Meta / Monado / …)
|
||||||
```
|
```
|
||||||
|
|
||||||
It implements the OpenXR instance/session lifecycle, the Vulkan graphics binding,
|
It implements the OpenXR instance/session lifecycle, the Vulkan graphics binding,
|
||||||
@@ -44,11 +44,15 @@ of it to the `ovrp_*` ABI the game expects.
|
|||||||
## Build
|
## Build
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
scripts/fetch_deps.sh # OpenXR + Vulkan headers (Apache-2.0), Android NDK
|
scripts/fetch_deps.sh # OpenXR + Vulkan headers (Apache-2.0), NDK, JDK, build-tools
|
||||||
shim/build_android.sh # -> shim/build/arm64/libOVRPlugin.so
|
shim/build_android.sh # -> shim/build/arm64/libOVRPlugin.so
|
||||||
|
packaging/build_openxr_loader.sh # -> packaging/libs/arm64/libopenxr_loader.so
|
||||||
```
|
```
|
||||||
Needs: Android NDK (r27c), a JDK, and the OpenXR/Vulkan headers (the fetch script
|
`fetch_deps.sh` pins the header versions; override with `OPENXR_TAG` / `VULKAN_HEADERS_TAG`.
|
||||||
gets them). A host x86-64 build is also supported for compile-validation.
|
|
||||||
|
From your distro you also need **`patchelf`** (the Android build fails without it — the shim
|
||||||
|
cannot resolve the OpenXR loader) and **`cmake`** (for the loader build). A host x86-64 build
|
||||||
|
is also supported for compile-validation and the desktop harness: `shim/build_host.sh`.
|
||||||
|
|
||||||
## Use (with your own dumped game)
|
## Use (with your own dumped game)
|
||||||
|
|
||||||
|
|||||||
+88
-47
@@ -25,7 +25,7 @@ Idea: build the shim as an Android arm64 `.so`, drop it into the RE4 APK in plac
|
|||||||
of the real `libOVRPlugin.so`, sideload, run. Our shim calls the Quest's own
|
of the real `libOVRPlugin.so`, sideload, run. Our shim calls the Quest's own
|
||||||
`libopenxr_loader` -> Meta's OpenXR runtime.
|
`libopenxr_loader` -> Meta's OpenXR runtime.
|
||||||
|
|
||||||
Prereqs to do first (these are the currently-open work items):
|
Prereqs (all done — kept as the record of what Path A needed):
|
||||||
1. **NDK arm64 build** of the shim — DONE. `shim/build_android.sh` -> NDK r27c ->
|
1. **NDK arm64 build** of the shim — DONE. `shim/build_android.sh` -> NDK r27c ->
|
||||||
build/arm64/libOVRPlugin.so (aarch64, 438/438 drop-in, NEEDED libopenxr_loader).
|
build/arm64/libOVRPlugin.so (aarch64, 438/438 drop-in, NEEDED libopenxr_loader).
|
||||||
2. **Instance handshake** — DONE. src/android_init.c: JNI_OnLoad captures the JavaVM;
|
2. **Instance handshake** — DONE. src/android_init.c: JNI_OnLoad captures the JavaVM;
|
||||||
@@ -34,9 +34,9 @@ Prereqs to do first (these are the currently-open work items):
|
|||||||
Application-context reflection fallback. [VERIFY-ON-HW] whether Meta's runtime
|
Application-context reflection fallback. [VERIFY-ON-HW] whether Meta's runtime
|
||||||
accepts the Application context vs requiring the real Activity, and the
|
accepts the Application context vs requiring the real Activity, and the
|
||||||
PreInitialize3-creates-instance-before-activity ordering.
|
PreInitialize3-creates-instance-before-activity ordering.
|
||||||
3. **Manifest** — add the OpenXR usage declarations Meta's runtime expects
|
3. **Manifest** — no change needed in the end. RE4 VR is already a shipping Quest VR app
|
||||||
(`<uses-feature android:name="android.hardware.vr.headtracking">` already there;
|
and declares `<uses-feature android:name="android.hardware.vr.headtracking">`;
|
||||||
add OpenXR `<meta-data>`/intent bits per Meta's OpenXR mobile docs).
|
`packaging/inspect_manifest.sh` reports any gaps.
|
||||||
4. **Entitlement** — on Quest you OWN RE4 and the Quest has the real Meta Horizon
|
4. **Entitlement** — on Quest you OWN RE4 and the Quest has the real Meta Horizon
|
||||||
platform service, so leave the ORIGINAL libovrplatformloader.so untouched and only
|
platform service, so leave the ORIGINAL libovrplatformloader.so untouched and only
|
||||||
swap libOVRPlugin.so. Logged into the owning account, the real ovr_Entitlement check
|
swap libOVRPlugin.so. Logged into the owning account, the real ovr_Entitlement check
|
||||||
@@ -55,61 +55,102 @@ cp shim/build/arm64/libOVRPlugin.so apk/lib/arm64-v8a/libOVRPlugin.so
|
|||||||
adb install -r re4vr-shim.apk # or push OBB + sideload
|
adb install -r re4vr-shim.apk # or push OBB + sideload
|
||||||
adb logcat | grep -iE 'xrr|OVRPlugin|openxr' # watch the [xrr] logs
|
adb logcat | grep -iE 'xrr|OVRPlugin|openxr' # watch the [xrr] logs
|
||||||
```
|
```
|
||||||
Expected first-run signal: instance/session create succeed in logcat; if the frame
|
`packaging/repack.sh` now does the repack, align and re-sign in one step; the manual
|
||||||
loop spins and the projection layer submits, you get an image (even if poses/input
|
sequence above is kept only to show what it does.
|
||||||
are rough). Known rough edges on first run: depth (Unsupported), input (controllers
|
|
||||||
return NotYetImplemented), the [VERIFY-ON-HW] swapchain index lockstep.
|
|
||||||
|
|
||||||
## Path B — Monado simulated (fast iteration, works on the Mac)
|
First-run signal to look for: instance and session create succeeding in logcat, then the
|
||||||
|
frame loop spinning and the projection layer submitting. (Historically the rough edges on a
|
||||||
|
first run were depth, input and the swapchain index lockstep; input and rendering are now
|
||||||
|
working — see "Current state" below.)
|
||||||
|
|
||||||
Monado has a simulated/headless HMD driver — no real headset. Run it in a Linux
|
## Path B — desktop OpenXR harness (fast iteration) — BUILT
|
||||||
arm64 VM (UTM/QEMU on Apple Silicon), point an OpenXR loader at it, and run the
|
|
||||||
harness (tests/harness.c) which drives the ovrp_* sequence:
|
`tools/desktop-harness/` drives the `ovrp_*` sequence against a real OpenXR runtime with no
|
||||||
|
headset and no RE4:
|
||||||
PreInitialize3 -> Initialize5 -> SetupLayer -> [WaitToBeginFrame -> BeginFrame4 ->
|
PreInitialize3 -> Initialize5 -> SetupLayer -> [WaitToBeginFrame -> BeginFrame4 ->
|
||||||
GetNodePoseState3 -> EndFrame4] xN -> Shutdown2
|
GetNodePoseState3 -> EndFrame4] xN -> Shutdown2
|
||||||
and asserts each returns ovrpSuccess. This exercises the real OpenXR calls without
|
asserting each returns `ovrpSuccess`. Build and run:
|
||||||
RE4 or hardware. Build:
|
|
||||||
```
|
```
|
||||||
# inside the Linux arm64 env, with Monado + openxr loader installed:
|
shim/build_host.sh # -> build/host/{libOVRPlugin.so,harness}
|
||||||
cc -std=c11 -Iinclude -Ithird_party/openxr tests/harness.c \
|
tools/desktop-harness/run.sh # headless against monado-service
|
||||||
src/*.c -lopenxr_loader -lvulkan -o harness # needs a Vulkan device/headless
|
|
||||||
XR_RUNTIME_JSON=/path/to/monado/openxr_monado-dev.json ./harness
|
|
||||||
```
|
```
|
||||||
Note: Initialize5 needs real Vulkan handles; for a pure-logic smoke test the harness
|
Needs libvulkan and an OpenXR loader (Debian: `libvulkan-dev libopenxr-loader1
|
||||||
can pass a headless VkInstance/Device (or we add a "no-gfx" build flag that skips
|
libopenxr-dev`); without the loader `build_host.sh` compiles the objects and stops.
|
||||||
xrCreateSession to test the non-rendering calls first).
|
|
||||||
|
Worth adding when chasing a portability bug: run it with `VK_LAYER_KHRONOS_validation` and
|
||||||
|
the OpenXR core-validation API layer enabled. The harness currently passes without them, so
|
||||||
|
it does not yet catch usage-flag or queue-family mistakes that a strict runtime would reject.
|
||||||
|
|
||||||
|
Note: `shim/tests/harness.c` is the original superseded smoke test; its build line is stale
|
||||||
|
and no script references it.
|
||||||
|
|
||||||
## Path C — Steam Frame (the target)
|
## Path C — Steam Frame (the target)
|
||||||
|
|
||||||
Same arm64 shim `.so`, but the APK runs under **Lepton** (Valve's Waydroid/AOSP) and
|
Same arm64 shim `.so`, but the APK runs under **Lepton** (Valve's Waydroid fork, now open
|
||||||
the OpenXR runtime is **Monado**. Once Frame ships: NDK build -> repack -> sideload
|
source) and the OpenXR runtime inside the container is **SteamVR** — Lepton's
|
||||||
into Lepton -> the open question is whether Lepton exposes the OpenXR loader +
|
`vendor/etc/openxr/1/active_runtime.json` names `steamvr` and points at a host-mounted
|
||||||
Vulkan swapchain sharing across the container (see HOST.md unknowns). Path A having
|
`vrclient.so`. (Earlier notes here and in `HOST.md` assumed Monado; that was wrong.)
|
||||||
worked makes this mostly a packaging/runtime-plumbing exercise.
|
|
||||||
|
What that buys us: SteamVR presents Frame controllers as emulating Oculus Touch by default,
|
||||||
|
so the shim's Touch bindings should bind; and it advertises the `XR_FB_foveation` family plus
|
||||||
|
`XR_META_foveation_eye_tracked`, so the existing foveation path survives. Lepton also mounts
|
||||||
|
the host's mesa/turnip/zink and gralloc into the container, which answers the old
|
||||||
|
`HOST.md` unknown about sharing swapchain images across the container boundary.
|
||||||
|
|
||||||
|
Steam Frame shipped 2026-09-14. Flow: NDK build -> `repack.sh` -> `steamframe_patches.sh`
|
||||||
|
(the Build spoof is required — Lepton reports `ro.product.manufacturer=Valve`) -> sideload
|
||||||
|
via adb into a Lepton container. Path A having worked makes this mostly packaging and
|
||||||
|
runtime-plumbing, but see `HOST.md` for the specific items to check first.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Current state (what's ready to test vs not)
|
## Current state
|
||||||
|
|
||||||
WIRED & building (host x86-64, validation only):
|
**Path A is done and field-verified.** RE4 VR boots and is playable on a Quest 2 through the
|
||||||
- Session lifecycle: instance/system/session create, event-driven state machine.
|
shim on Meta's OpenXR runtime: stereo rendering, head and controller tracking, buttons,
|
||||||
- Frame loop: xrWaitFrame/Begin/EndFrame, predicted display time.
|
grips, haptics, save loading. The frame-pacing "ghost" that dominated development is fixed
|
||||||
- Poses: xrLocateViews (eyes) + xrLocateSpace (head).
|
(game-thread pacing; see `docs/research/ghost-fix-2026-06-27.md`). Shipped and verified:
|
||||||
- Swapchains: xrCreateSwapchain from ovrpLayerDesc, enumerate VkImages, per-frame
|
|
||||||
acquire/wait/release, real XrCompositionLayerProjection submit.
|
|
||||||
- 20 OpenXR functions; 239/239 ovrp_ symbols.
|
|
||||||
|
|
||||||
NOT yet (the to-do list before a meaningful Quest run):
|
- Session lifecycle, event-driven state machine, Android instance handshake
|
||||||
- arm64/Android NDK build (host build only so far).
|
(`src/android_init.c`).
|
||||||
- [ANDROID-TODO] JavaVM/activity -> XrInstanceCreateInfoAndroidKHR + xrInitializeLoaderKHR.
|
- Frame loop with `xrWaitFrame` on the game thread and a FIFO frameState handoff to the
|
||||||
- Input: controller/hand action sets (GetControllerState4 returns NotYetImplemented).
|
render thread.
|
||||||
- Depth layer (SetupLayerDepth -> Unsupported).
|
- Poses via `xrLocateViews` and `xrLocateSpace`; `LOCAL_FLOOR` when the game asks for floor
|
||||||
- Map ovrp_GetInstance/DeviceExtensionsVk -> xrGetVulkan*ExtensionsKHR (so the app
|
level.
|
||||||
creates its VkInstance/Device with the runtime's required extensions).
|
- Swapchains from `ovrpLayerDesc`, per-frame acquire/wait/release, real projection-layer
|
||||||
- [VERIFY-ON-HW] swapchain index lockstep assumption.
|
submit, layer z-order fix so splash quads composite above the eye layer.
|
||||||
|
- Input action sets for Touch controllers plus haptics (`src/xr_input.c`).
|
||||||
|
- Vulkan extension queries mapped to `xrGetVulkan*ExtensionsKHR` (`src/vk_session.c`).
|
||||||
|
- CPU/GPU perf levels forwarded via `XR_EXT_performance_settings`; game-driven foveation.
|
||||||
|
- 438/438 `ovrp_` symbols; around 48 OpenXR entry points.
|
||||||
|
|
||||||
## Pick-up-tomorrow shortlist
|
**Open / known gaps:**
|
||||||
1. Install Android NDK; cross-build the shim to arm64 (proves it builds for target).
|
- Depth layer submission is built but gated off (`debug.re4vr.depth`); Meta's runtime accepts
|
||||||
2. Stand up the Monado-sim Linux arm64 VM on the Mac + run tests/harness.c (Path B
|
but does not use plain KHR depth for reprojection.
|
||||||
smoke test of the non-gfx calls).
|
- Swapchain index lockstep with UE's own `TextureStage` is assumed, not enforced — a
|
||||||
3. Then start the [ANDROID-TODO] instance handshake (gates the real Quest run).
|
mismatch is logged but not corrected.
|
||||||
|
- Residual `XR_FRAME_DISCARDED` hiccups from the frameState ring dropping its oldest entry
|
||||||
|
under overflow instead of applying back-pressure.
|
||||||
|
- In-game black near load zones is a level-streaming / memory stall, mitigated rather than
|
||||||
|
fixed. See `docs/handoffs/HANDOFF-2026-06-27.md`.
|
||||||
|
- Several `debug.re4vr.*` paths are documented dead ends kept for reference.
|
||||||
|
|
||||||
|
## Next: Path C (Steam Frame)
|
||||||
|
|
||||||
|
Frame shipped 2026-09-14, so this is the live front. Highest-value items before a first boot
|
||||||
|
attempt, in order:
|
||||||
|
1. **Page alignment.** The shim's ELF segments and the repacked APK are 4 KB-aligned
|
||||||
|
(`zipalign -p 4`); Valve's Unreal docs reference a 16 KB page-alignment requirement. If
|
||||||
|
the Frame kernel uses 16 KB pages the library will not load. Check first: it is a
|
||||||
|
`-Wl,-z,max-page-size=16384` plus `zipalign -P 16` fix, but it would present as a
|
||||||
|
mystery launch failure.
|
||||||
|
2. **Swapchain usage flags.** `setup_layer` requests only COLOR_ATTACHMENT and SAMPLED.
|
||||||
|
Meta over-provisions mutable, broadly-usable images; a spec-following runtime gives you
|
||||||
|
exactly what you asked for, and UE 4.25 needs a mutable format for its linear UNORM view.
|
||||||
|
3. **Refresh rate.** 72 Hz is hardcoded in `ovrp_GetSystemDisplayFrequency2` and in the
|
||||||
|
frame-budget constant. Frame runs 72/90/120/144. Derive it from
|
||||||
|
`predictedDisplayPeriod`.
|
||||||
|
4. **Session events.** Only READY and STOPPING are handled, so focus loss and quit from the
|
||||||
|
Steam overlay never reach the game.
|
||||||
|
5. **The per-frame GPU flush-wait.** It exists because Meta's compositor does not sync
|
||||||
|
against our submit. A/B it on Frame with `debug.re4vr.noflushwait`.
|
||||||
+15
-11
@@ -10,11 +10,12 @@ Legal posture: dump-your-own only. We extract from hardware *you own* running a
|
|||||||
copy *you own*. **Nothing here gets redistributed** — only patches/shims you
|
copy *you own*. **Nothing here gets redistributed** — only patches/shims you
|
||||||
author, applied by people who dump their own copy. Same model as ReXGlue.
|
author, applied by people who dump their own copy. Same model as ReXGlue.
|
||||||
|
|
||||||
> **Editor's note (2026-06):** this is a historical recon document. It explores an
|
> **Editor's note (2026-06, revised 2026-09):** this is a historical recon document. Its
|
||||||
> entitlement-**stub** approach in a few places (§4, the wiring summary, the decision tree)
|
> original text proposed an entitlement-stub approach in a few places (§4, the wiring
|
||||||
> that was **not** carried into the project — entitlement handling is out of scope and the
|
> summary, the decision tree). That was **not** carried into the project: entitlement
|
||||||
> shim ships **no circumvention code** (see the README's scope section). Those passages are
|
> handling is out of scope and the shim ships **no circumvention code** (see the README's
|
||||||
> kept as a record of the original investigation, not as instructions.
|
> scope section). Those passages have since been **corrected in place** so the document no
|
||||||
|
> longer reads as instructions; the correction is noted inline where it applies.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -98,7 +99,7 @@ Interpret the result:
|
|||||||
|-----------------------------------|-----------------------------------------------------|------------|
|
|-----------------------------------|-----------------------------------------------------|------------|
|
||||||
| `libopenxr_loader.so` | ✅ Standard OpenXR — Steam Frame provides a runtime; you translate vendor extensions. | Tractable |
|
| `libopenxr_loader.so` | ✅ Standard OpenXR — Steam Frame provides a runtime; you translate vendor extensions. | Tractable |
|
||||||
| `libvrapi.so` | ⚠️ Proprietary Meta VrApi — must reimplement/shim the runtime. | Hard |
|
| `libvrapi.so` | ⚠️ Proprietary Meta VrApi — must reimplement/shim the runtime. | Hard |
|
||||||
| `libovrplatformloader.so` | Present either way — this is the **entitlement check** to NOP/stub. | Required patch |
|
| `libovrplatformloader.so` | Present either way — the platform/entitlement library. **Left untouched** by this project (out of scope). | Not patched |
|
||||||
| `libUE4.so` (or split into modules)| The Unreal runtime itself — the host you'll be hooking. | n/a |
|
| `libUE4.so` (or split into modules)| The Unreal runtime itself — the host you'll be hooking. | n/a |
|
||||||
|
|
||||||
- [x] **RESULT — runtime is:** ☑ **VrApi** (legacy OVRPlugin path). Confirmed
|
- [x] **RESULT — runtime is:** ☑ **VrApi** (legacy OVRPlugin path). Confirmed
|
||||||
@@ -117,8 +118,10 @@ libUE4.so --(NEEDED + ovr_* Platform SDK, 132 refs)--> libovrplatformloader.so
|
|||||||
- => **PORT SEAM = reimplement libOVRPlugin.so (ovrp_* on OpenXR), drop libvrapi.**
|
- => **PORT SEAM = reimplement libOVRPlugin.so (ovrp_* on OpenXR), drop libvrapi.**
|
||||||
ovrp_* is the documented Unity-shared API (OVR_Plugin.h); modern Meta OVRPlugin
|
ovrp_* is the documented Unity-shared API (OVR_Plugin.h); modern Meta OVRPlugin
|
||||||
has an OpenXR backend = reference impl / prior art.
|
has an OpenXR backend = reference impl / prior art.
|
||||||
- => **Stub the ovr_* Platform SDK** (libovrplatformloader.so) — entitlement/account,
|
- => **Leave the ovr_* Platform SDK alone** (`libovrplatformloader.so`). Entitlement and
|
||||||
just return OK; don't reimplement.
|
account handling are **out of scope**: the original library is kept and its real check
|
||||||
|
runs unchanged. (The original recon note here proposed stubbing it; that was *not*
|
||||||
|
carried into the project — see the README's scope section.)
|
||||||
|
|
||||||
> If you want to double-check beyond filename presence, extract and inspect
|
> If you want to double-check beyond filename presence, extract and inspect
|
||||||
> imports of `libUE4.so` (it may dynamically link the runtime):
|
> imports of `libUE4.so` (it may dynamically link the runtime):
|
||||||
@@ -187,12 +190,13 @@ device: Quest 2 serial <redacted-serial> (codename hollywood)
|
|||||||
|
|
||||||
**Decision tree:**
|
**Decision tree:**
|
||||||
- **OpenXR** → next phase: map which Meta OpenXR vendor extensions the binary
|
- **OpenXR** → next phase: map which Meta OpenXR vendor extensions the binary
|
||||||
requests, plan the OpenXR→Steam-Frame-runtime shim + entitlement stub.
|
requests, plan the OpenXR→Steam-Frame-runtime shim.
|
||||||
This is the "weekend-of-shimming" branch.
|
This is the "weekend-of-shimming" branch.
|
||||||
- **VrApi** → next phase: scope a VrApi reimplementation/translation shim
|
- **VrApi** → next phase: scope a VrApi reimplementation/translation shim
|
||||||
(much larger). Reassess whether the project is worth it vs. waiting/UEVR.
|
(much larger). Reassess whether the project is worth it vs. waiting/UEVR.
|
||||||
- **Either way** → `libovrplatformloader.so` entitlement bypass is a required
|
- **Either way** → `libovrplatformloader.so` is left as shipped. Entitlement handling is
|
||||||
Ghidra patch (legal for your own copy).
|
out of scope for this project and no bypass is part of it; on Quest the platform's real
|
||||||
|
check runs unchanged. (The original recon note assumed a patch here; superseded.)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -4,14 +4,18 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_env.sh"
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_env.sh"
|
||||||
[ -n "${NDK:-}" ] && [ -d "$NDK" ] || { echo "NDK not found. Set \$ANDROID_NDK or run scripts/fetch_deps.sh"; exit 1; }
|
[ -n "${NDK:-}" ] && [ -d "$NDK" ] || { echo "NDK not found. Set \$ANDROID_NDK or run scripts/fetch_deps.sh"; exit 1; }
|
||||||
|
command -v cmake >/dev/null || { echo "cmake not found — install it (and optionally ninja) to build the loader"; exit 1; }
|
||||||
|
# Keep this in step with OPENXR_TAG in scripts/fetch_deps.sh so the loader and the headers
|
||||||
|
# the shim compiles against are the same OpenXR version.
|
||||||
|
OPENXR_TAG="${OPENXR_TAG:-release-1.1.50}"
|
||||||
SRC="$ROOT/tools/OpenXR-SDK"
|
SRC="$ROOT/tools/OpenXR-SDK"
|
||||||
|
|
||||||
if [ ! -d "$SRC" ]; then
|
if [ ! -d "$SRC" ]; then
|
||||||
echo "downloading OpenXR-SDK source..."
|
echo "downloading OpenXR-SDK source..."
|
||||||
curl -fsSL -o "$ROOT/tools/oxrsdk.tgz" \
|
curl -fsSL -o "$ROOT/tools/oxrsdk.tgz" \
|
||||||
"https://github.com/KhronosGroup/OpenXR-SDK/archive/refs/heads/main.tar.gz"
|
"https://github.com/KhronosGroup/OpenXR-SDK/archive/refs/tags/${OPENXR_TAG}.tar.gz"
|
||||||
tar xzf "$ROOT/tools/oxrsdk.tgz" -C "$ROOT/tools"
|
tar xzf "$ROOT/tools/oxrsdk.tgz" -C "$ROOT/tools"
|
||||||
mv "$ROOT/tools/OpenXR-SDK-main" "$SRC"
|
mv "$ROOT/tools/OpenXR-SDK-${OPENXR_TAG}" "$SRC"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
GEN="Unix Makefiles"; command -v ninja >/dev/null && GEN="Ninja"
|
GEN="Unix Makefiles"; command -v ninja >/dev/null && GEN="Ninja"
|
||||||
|
|||||||
@@ -16,12 +16,22 @@
|
|||||||
# in.apk : shim-repacked APK (default: out/re4vr-shim.apk)
|
# in.apk : shim-repacked APK (default: out/re4vr-shim.apk)
|
||||||
# out.apk : patched output (default: out/re4vr-steamframe.apk)
|
# out.apk : patched output (default: out/re4vr-steamframe.apk)
|
||||||
#
|
#
|
||||||
# Prereqs: apktool (https://apktool.org), plus the Android build-tools used by repack.sh.
|
# Prereqs: tools/apktool.jar (scripts/fetch_deps.sh) or apktool on PATH, plus a JDK and
|
||||||
|
# the Android build-tools used by repack.sh.
|
||||||
# NOTE: prepared for the non-Quest bring-up; not yet validated on Steam Frame hardware.
|
# NOTE: prepared for the non-Quest bring-up; not yet validated on Steam Frame hardware.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_env.sh"
|
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_env.sh"
|
||||||
[ -n "${BT:-}" ] && [ -d "$BT" ] || { echo "Android build-tools not found. Run scripts/fetch_deps.sh"; exit 1; }
|
[ -n "${BT:-}" ] && [ -d "$BT" ] || { echo "Android build-tools not found. Run scripts/fetch_deps.sh"; exit 1; }
|
||||||
command -v apktool >/dev/null || { echo "apktool not found — install it (https://apktool.org) to run the smali/manifest patches"; exit 1; }
|
# Prefer the apktool.jar that scripts/fetch_deps.sh downloads (same as inspect_manifest.sh);
|
||||||
|
# fall back to an `apktool` wrapper on PATH.
|
||||||
|
if [ -f "${APKTOOL_JAR:-}" ]; then
|
||||||
|
APKTOOL=("${JAVA_HOME:+$JAVA_HOME/bin/}java" -jar "$APKTOOL_JAR")
|
||||||
|
elif command -v apktool >/dev/null; then
|
||||||
|
APKTOOL=(apktool)
|
||||||
|
else
|
||||||
|
echo "apktool not found — run scripts/fetch_deps.sh (fetches tools/apktool.jar) or install"
|
||||||
|
echo "apktool on PATH (https://apktool.org) to run the smali/manifest patches"; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
IN="${1:-$PKG/out/re4vr-shim.apk}"
|
IN="${1:-$PKG/out/re4vr-shim.apk}"
|
||||||
OUT="${2:-$PKG/out/re4vr-steamframe.apk}"
|
OUT="${2:-$PKG/out/re4vr-steamframe.apk}"
|
||||||
@@ -54,7 +64,7 @@ rm -rf "$WORK"; mkdir -p "$WORK"
|
|||||||
DEC="$WORK/dec"
|
DEC="$WORK/dec"
|
||||||
|
|
||||||
echo "== apktool decode =="
|
echo "== apktool decode =="
|
||||||
apktool d -f -o "$DEC" "$IN" >/dev/null
|
"${APKTOOL[@]}" d -f -o "$DEC" "$IN" >/dev/null
|
||||||
|
|
||||||
# Locate UE's GameActivity smali (ue4 or unreal namespace, across smali_classesN dirs).
|
# Locate UE's GameActivity smali (ue4 or unreal namespace, across smali_classesN dirs).
|
||||||
mapfile -t GA < <(find "$DEC" -path '*/com/epicgames/ue4/GameActivity.smali' \
|
mapfile -t GA < <(find "$DEC" -path '*/com/epicgames/ue4/GameActivity.smali' \
|
||||||
@@ -90,7 +100,7 @@ if [ -f "$MAN" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo "== apktool build =="
|
echo "== apktool build =="
|
||||||
apktool b -o "$WORK/unsigned.apk" "$DEC" >/dev/null
|
"${APKTOOL[@]}" b -o "$WORK/unsigned.apk" "$DEC" >/dev/null
|
||||||
|
|
||||||
echo "== align + sign =="
|
echo "== align + sign =="
|
||||||
"$BT/zipalign" -f -p 4 "$WORK/unsigned.apk" "$WORK/aligned.apk"
|
"$BT/zipalign" -f -p 4 "$WORK/unsigned.apk" "$WORK/aligned.apk"
|
||||||
|
|||||||
+15
-6
@@ -3,22 +3,27 @@
|
|||||||
# the Android NDK). Run once after cloning.
|
# the Android NDK). Run once after cloning.
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||||
|
|
||||||
|
# Pinned upstream versions. Bump deliberately, not implicitly: tracking `main` means a
|
||||||
|
# clone six months from now builds against different headers than the one that was tested.
|
||||||
|
OPENXR_TAG="${OPENXR_TAG:-release-1.1.50}"
|
||||||
|
VULKAN_HEADERS_TAG="${VULKAN_HEADERS_TAG:-v1.4.321}"
|
||||||
OXR="$ROOT/shim/third_party/openxr/openxr"
|
OXR="$ROOT/shim/third_party/openxr/openxr"
|
||||||
VK="$ROOT/shim/third_party/vulkan/vulkan"
|
VK="$ROOT/shim/third_party/vulkan/vulkan"
|
||||||
|
|
||||||
echo "== OpenXR headers (Apache-2.0) =="
|
echo "== OpenXR headers (Apache-2.0, $OPENXR_TAG) =="
|
||||||
mkdir -p "$OXR"
|
mkdir -p "$OXR"
|
||||||
OXRBASE="https://raw.githubusercontent.com/KhronosGroup/OpenXR-SDK/main/include/openxr"
|
OXRBASE="https://raw.githubusercontent.com/KhronosGroup/OpenXR-SDK/$OPENXR_TAG/include/openxr"
|
||||||
for h in openxr.h openxr_platform.h openxr_platform_defines.h; do
|
for h in openxr.h openxr_platform.h openxr_platform_defines.h; do
|
||||||
curl -fsSL "$OXRBASE/$h" -o "$OXR/$h"
|
curl -fsSL "$OXRBASE/$h" -o "$OXR/$h"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "== Vulkan headers (Apache-2.0) =="
|
echo "== Vulkan headers (Apache-2.0, $VULKAN_HEADERS_TAG) =="
|
||||||
mkdir -p "$VK/vk_video"
|
mkdir -p "$VK/vk_video"
|
||||||
VKBASE="https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vulkan"
|
VKBASE="https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/$VULKAN_HEADERS_TAG/include/vulkan"
|
||||||
for h in vulkan_core.h vk_platform.h; do curl -fsSL "$VKBASE/$h" -o "$VK/$h"; done
|
for h in vulkan_core.h vk_platform.h; do curl -fsSL "$VKBASE/$h" -o "$VK/$h"; done
|
||||||
printf '#ifndef VULKAN_H_\n#define VULKAN_H_\n#include "vk_platform.h"\n#include "vulkan_core.h"\n#endif\n' > "$VK/vulkan.h"
|
printf '#ifndef VULKAN_H_\n#define VULKAN_H_\n#include "vk_platform.h"\n#include "vulkan_core.h"\n#endif\n' > "$VK/vulkan.h"
|
||||||
VVBASE="https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/main/include/vk_video"
|
VVBASE="https://raw.githubusercontent.com/KhronosGroup/Vulkan-Headers/$VULKAN_HEADERS_TAG/include/vk_video"
|
||||||
for f in $(grep -oE 'vk_video/[a-zA-Z0-9_]+\.h' "$VK/vulkan_core.h" | sed 's|vk_video/||' | sort -u); do
|
for f in $(grep -oE 'vk_video/[a-zA-Z0-9_]+\.h' "$VK/vulkan_core.h" | sed 's|vk_video/||' | sort -u); do
|
||||||
curl -fsSL "$VVBASE/$f" -o "$VK/vk_video/$f"
|
curl -fsSL "$VVBASE/$f" -o "$VK/vk_video/$f"
|
||||||
done
|
done
|
||||||
@@ -44,5 +49,9 @@ echo "== apktool (manifest tooling) =="
|
|||||||
[ -f "$ROOT/tools/apktool.jar" ] || curl -fsSL -o apktool.jar \
|
[ -f "$ROOT/tools/apktool.jar" ] || curl -fsSL -o apktool.jar \
|
||||||
"https://github.com/iBotPeaches/Apktool/releases/download/v2.10.0/apktool_2.10.0.jar"
|
"https://github.com/iBotPeaches/Apktool/releases/download/v2.10.0/apktool_2.10.0.jar"
|
||||||
|
|
||||||
echo "done. Now: shim/build_android.sh (and packaging/repack.sh for an installable APK)."
|
echo
|
||||||
|
echo "Also needed, not fetched here: patchelf (build_android.sh) and cmake"
|
||||||
|
echo "(packaging/build_openxr_loader.sh). Install them from your distro."
|
||||||
|
echo "done. Now: shim/build_android.sh, packaging/build_openxr_loader.sh,"
|
||||||
|
echo "then packaging/repack.sh for an installable APK."
|
||||||
echo "NOTE: URLs are Linux-x86_64; on macOS swap the NDK/JDK/build-tools archives."
|
echo "NOTE: URLs are Linux-x86_64; on macOS swap the NDK/JDK/build-tools archives."
|
||||||
+72
-51
@@ -1,61 +1,82 @@
|
|||||||
# libOVRPlugin.so shim (OVRPlugin v1.51 -> OpenXR)
|
# libOVRPlugin.so shim (OVRPlugin v1.51 ABI -> OpenXR)
|
||||||
|
|
||||||
Drop-in replacement for RE4 VR's libOVRPlugin.so that re-exports the ovrp_* symbols
|
Drop-in replacement for RE4 VR's `libOVRPlugin.so`. It re-exports the `ovrp_*` C ABI the
|
||||||
backed by OpenXR (Monado) instead of Meta's libvrapi.so. See ../HOST.md, ../SHIM-SCOPE.md.
|
game calls and implements it on **OpenXR + Vulkan** instead of Meta's `libvrapi.so`.
|
||||||
|
|
||||||
|
**Status: working.** RE4 VR boots and is playable on a Quest 2 through this shim on Meta's
|
||||||
|
OpenXR runtime — stereo rendering, head and controller tracking, buttons, grips, haptics,
|
||||||
|
and save loading. Steam Frame is the next target; see `../HOST.md`.
|
||||||
|
|
||||||
|
## Export surface
|
||||||
|
|
||||||
|
The artifact exports **438/438** `ovrp_*` symbols, matching `../analysis/all_exports.txt`
|
||||||
|
exactly (0 missing, 0 extra), so any `dlsym` of any `ovrp_` resolves even for the ~199
|
||||||
|
entry points RE4 never references.
|
||||||
|
|
||||||
|
| Source | Exports | What |
|
||||||
|
|---|---|---|
|
||||||
|
| `src/stubs.c` (generated) | 371 | 103 Unsupported (-1004), 67 no-op Success (0), 201 NotYetImplemented (-1005) |
|
||||||
|
| `src/core.c` | 46 | lifecycle, frame loop, poses, controller, system info |
|
||||||
|
| `src/layers.c` | 7 | `SetupLayer` / `GetLayerTexture2` / stage count / eye layer desc |
|
||||||
|
| `src/vk_session.c` | 2 | the Vulkan instance/device extension queries |
|
||||||
|
| `src/passthru.c` | 12 | trampolines for calls the game makes that need no real impl |
|
||||||
|
|
||||||
|
`gen_stubs.sh` regenerates `src/stubs.c` from `../analysis/all_exports.txt`; functions
|
||||||
|
prototyped in the header are skipped there (67 of them) and implemented in `core.c`.
|
||||||
|
Regeneration is deterministic — re-running it on a clean tree produces no diff.
|
||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
- include/ovrplugin_shim.h — OVRPlugin v1.51 C ABI (types + core protos; self-checking
|
|
||||||
_Static_asserts on binary-verified struct sizes).
|
|
||||||
- gen_stubs.sh — generates src/stubs.c from ../analysis/shim_surface.txt.
|
|
||||||
- src/stubs.c (generated) — 224 stubs: 101 Unsupported(-1004), 32 no-op Success(0),
|
|
||||||
91 NotYetImplemented(-1005).
|
|
||||||
- src/core.c — the 15 header-prototyped core fns (frame loop/init/poses/
|
|
||||||
controller/system), currently TODO stubs returning -1005 with outputs zeroed.
|
|
||||||
|
|
||||||
## Build (host, for validation)
|
- `include/ovrplugin_shim.h` — the `ovrp_*` C ABI: types, structs, core prototypes.
|
||||||
```
|
Independently written against the observed ABI, with `_Static_assert`s pinning the
|
||||||
cc -std=c11 -Wall -shared -fPIC -fvisibility=hidden -Iinclude src/stubs.c src/core.c \
|
binary-verified struct sizes so a layout mistake fails the build rather than the device.
|
||||||
-o build/libOVRPlugin.so
|
- `src/xr_runtime.{c,h}` — the OpenXR engine: instance/session lifecycle, the frame loop,
|
||||||
```
|
swapchains, layer composition, foveation, perf levels.
|
||||||
Status: builds; exports exactly 239 ovrp_ symbols (1:1 with shim_surface.txt, 0
|
- `src/vk_session.c` — Vulkan graphics binding, image enumeration, the tile-memory flush.
|
||||||
missing/extra); dlopen+dlsym verified. This is the SKELETON — it loads and resolves,
|
- `src/layers.c` — layer setup and eye layer geometry.
|
||||||
it does not yet drive VR (core fns are TODO).
|
- `src/xr_input.c` — action sets and controller/haptics mapping.
|
||||||
|
- `src/android_init.c` — JavaVM/activity capture, `xrInitializeLoaderKHR`, Android instance
|
||||||
|
create-info chaining.
|
||||||
|
- `src/passthru.c` — the author-only A/B diagnostic (see below).
|
||||||
|
- `src/stubs.c` — generated.
|
||||||
|
|
||||||
## Real target build (deployable) — TODO
|
Roughly 48 distinct OpenXR entry points are used.
|
||||||
Must be aarch64 / Android (bionic) since it runs inside Lepton. Use the Android NDK:
|
|
||||||
```
|
|
||||||
$NDK/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android29-clang \
|
|
||||||
-std=c11 -shared -fPIC -fvisibility=hidden -Iinclude src/stubs.c src/core.c \
|
|
||||||
-o build/arm64/libOVRPlugin.so
|
|
||||||
```
|
|
||||||
(host build only proves the C + symbol coverage; NDK build is the artifact that
|
|
||||||
replaces the real lib in the APK.)
|
|
||||||
|
|
||||||
## Modules (current)
|
## Build
|
||||||
- include/ovrplugin_shim.h — OVRPlugin v1.51 ABI (types, structs, core protos).
|
|
||||||
- src/stubs.c (generated) — 221 stubs (unsupported/no-op/TODO).
|
|
||||||
- src/core.c — lifecycle + frame loop + poses (-> xr_runtime).
|
|
||||||
- src/xr_runtime.{h,c} — OpenXR engine: session, frame loop, swapchains.
|
|
||||||
- src/vk_session.c — Vulkan-typed: xrCreateSession binding + image enum.
|
|
||||||
- src/layers.c — ovrp_SetupLayer / GetLayerTexture2 / StageCount.
|
|
||||||
- tests/harness.c — Path B smoke test (see ../TESTING.md).
|
|
||||||
Build adds: -Ithird_party/openxr -Ithird_party/vulkan, and src/{core,xr_runtime,
|
|
||||||
vk_session,layers,stubs}.c. 20 OpenXR fns used; 239/239 ovrp_ exported.
|
|
||||||
|
|
||||||
## Done so far
|
**The deployable artifact** is Android arm64 — the game runs inside Horizon OS or Lepton:
|
||||||
Session lifecycle, frame loop (xrWaitFrame/Begin/EndFrame), poses (xrLocateViews/
|
|
||||||
Space), swapchains (xrCreateSwapchain from ovrpLayerDesc, acquire/wait/release,
|
```sh
|
||||||
XrCompositionLayerProjection submit). Vulkan binding from Initialize5 args [VERIFIED].
|
../scripts/fetch_deps.sh # OpenXR + Vulkan headers, NDK, JDK, build-tools
|
||||||
|
./build_android.sh # -> build/arm64/libOVRPlugin.so
|
||||||
|
```
|
||||||
|
Needs the NDK (r27c) and `patchelf` (to add `NEEDED libopenxr_loader.so`). Then
|
||||||
|
`../packaging/repack.sh` swaps it into your own dumped APK.
|
||||||
|
|
||||||
|
**Host build** is compile-validation plus the desktop OpenXR harness, not a shipping
|
||||||
|
artifact:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
./build_host.sh # -> ../build/host/{libOVRPlugin.so,harness}
|
||||||
|
../tools/desktop-harness/run.sh
|
||||||
|
```
|
||||||
|
Needs libvulkan and an OpenXR loader; without the loader it builds the objects and stops.
|
||||||
|
|
||||||
## Debug: passthru forwarding (author-only diagnostic)
|
## Debug: passthru forwarding (author-only diagnostic)
|
||||||
`debug.re4vr.passthru=1` makes the shim hand the whole OVRPlugin session to a real,
|
|
||||||
SONAME-patched `libOVRPlugin_real.so` if one is present in the APK — an A/B harness for
|
|
||||||
diffing native runtime behavior against the shim. **No such binary is included or
|
|
||||||
distributed by this repo**; you would have to place one from your own device dump, and
|
|
||||||
without it the flag is inert. All other `debug.re4vr.*` props are likewise diagnostics.
|
|
||||||
|
|
||||||
## Next (see ../TESTING.md for the test plan)
|
`debug.re4vr.passthru=1` hands the whole OVRPlugin session to a real, SONAME-patched
|
||||||
1. NDK arm64 build (host build is validation only).
|
`libOVRPlugin_real.so` if one is present in the APK — an A/B harness for diffing native
|
||||||
2. [ANDROID-TODO] JavaVM/activity -> XrInstanceCreateInfoAndroidKHR + xrInitializeLoaderKHR.
|
runtime behaviour against the shim. **No such binary is included or distributed by this
|
||||||
3. Input action sets (controllers/hands); depth layer; GetVulkan*ExtensionsKHR mapping.
|
repo**; you would have to place one from your own device dump, and without it the flag is
|
||||||
4. Repack APK (real entitlement — you own it); test on Quest 2.
|
inert. All other `debug.re4vr.*` props are likewise diagnostics.
|
||||||
|
|
||||||
|
## Known gaps
|
||||||
|
|
||||||
|
- Depth layer submission is gated behind `debug.re4vr.depth` and off by default; Meta's
|
||||||
|
runtime accepts but does not use plain KHR depth for reprojection.
|
||||||
|
- Input binds `/interaction_profiles/oculus/touch_controller` only.
|
||||||
|
- Several `debug.re4vr.*` paths are documented dead ends kept for reference (copy-ring,
|
||||||
|
render-ahead, submithook); see `../docs/research/` for what each one ruled out.
|
||||||
|
|
||||||
|
See `../docs/research/SHIM-SCOPE.md` for how the surface was scoped, `../TESTING.md` for the
|
||||||
|
test plan, and `../docs/README.md` for the research trail.
|
||||||
@@ -27,11 +27,12 @@ mkdir -p "$SHIM/build/arm64"
|
|||||||
|
|
||||||
# the xr* symbols resolve against libopenxr_loader.so at runtime; declare the dep so
|
# the xr* symbols resolve against libopenxr_loader.so at runtime; declare the dep so
|
||||||
# the dynamic linker loads it (bundle libopenxr_loader.so in the APK lib/arm64-v8a/).
|
# the dynamic linker loads it (bundle libopenxr_loader.so in the APK lib/arm64-v8a/).
|
||||||
|
# Without this the shim's xr* symbols never resolve at runtime, so a build that skipped it
|
||||||
|
# is unusable — fail loudly rather than emitting a broken .so.
|
||||||
PATCHELF="$(command -v patchelf || true)"
|
PATCHELF="$(command -v patchelf || true)"
|
||||||
if [ -n "$PATCHELF" ]; then
|
[ -n "$PATCHELF" ] || { echo "ERROR: patchelf not found. It is required to add"; \
|
||||||
"$PATCHELF" --add-needed libopenxr_loader.so "$SHIM/build/arm64/libOVRPlugin.so"
|
echo " 'libopenxr_loader.so' as NEEDED; without it the shim cannot resolve the"; \
|
||||||
echo "added NEEDED libopenxr_loader.so"
|
echo " OpenXR loader at runtime. Install patchelf and re-run."; exit 1; }
|
||||||
else
|
"$PATCHELF" --add-needed libopenxr_loader.so "$SHIM/build/arm64/libOVRPlugin.so"
|
||||||
echo "WARN: patchelf not found — add 'libopenxr_loader.so' as NEEDED before packaging"
|
echo "added NEEDED libopenxr_loader.so"
|
||||||
fi
|
|
||||||
echo "built: $SHIM/build/arm64/libOVRPlugin.so"
|
echo "built: $SHIM/build/arm64/libOVRPlugin.so"
|
||||||
+2
-2
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# Generate shim/src/stubs.c from analysis/shim_surface.txt.
|
# Generate shim/src/stubs.c from analysis/all_exports.txt (the FULL 438-export set).
|
||||||
# Each ovrp_ function gets a stub returning a sensible ovrpResult, classified by
|
# Each ovrp_ function gets a stub returning a sensible ovrpResult, classified by
|
||||||
# name per SHIM-SCOPE.md buckets. Functions prototyped in the header (real sigs)
|
# name per SHIM-SCOPE.md buckets. Functions prototyped in the header (real sigs)
|
||||||
# are SKIPPED here and live in core.c instead.
|
# are SKIPPED here and live in core.c instead.
|
||||||
@@ -36,7 +36,7 @@ ovrp_GetLayerTextureFoveation ovrp_GetControllerHapticsDesc2 "
|
|||||||
# original return constant when passthru is off, and tail-call the real lib when it's on.
|
# original return constant when passthru is off, and tail-call the real lib when it's on.
|
||||||
|
|
||||||
{
|
{
|
||||||
echo '/* AUTO-GENERATED by gen_stubs.sh from analysis/shim_surface.txt. Do not edit by hand. */'
|
echo '/* AUTO-GENERATED by gen_stubs.sh from analysis/all_exports.txt. Do not edit by hand. */'
|
||||||
echo '#include "ovrplugin_shim.h"'
|
echo '#include "ovrplugin_shim.h"'
|
||||||
echo '#include "log.h"'
|
echo '#include "log.h"'
|
||||||
echo
|
echo
|
||||||
|
|||||||
+14
-7
@@ -211,31 +211,38 @@ OVRP_EXPORT ovrpResult ovrp_GetSystemDisplayFrequency2(float *outFreq) {
|
|||||||
/* per-frame app-state getters the loading loop polls (all (ovrpBool* out)->result) */
|
/* per-frame app-state getters the loading loop polls (all (ovrpBool* out)->result) */
|
||||||
OVRP_EXPORT ovrpResult ovrp_GetAppHasVrFocus2(ovrpBool *out) {
|
OVRP_EXPORT ovrpResult ovrp_GetAppHasVrFocus2(ovrpBool *out) {
|
||||||
PT_FWD(ovrp_GetAppHasVrFocus2, out);
|
PT_FWD(ovrp_GetAppHasVrFocus2, out);
|
||||||
if (out) *out = ovrpBool_True; return ovrpSuccess; /* we have focus */
|
if (out) *out = ovrpBool_True;
|
||||||
|
return ovrpSuccess; /* we have focus */
|
||||||
}
|
}
|
||||||
OVRP_EXPORT ovrpResult ovrp_GetAppShouldQuit2(ovrpBool *out) {
|
OVRP_EXPORT ovrpResult ovrp_GetAppShouldQuit2(ovrpBool *out) {
|
||||||
PT_FWD(ovrp_GetAppShouldQuit2, out);
|
PT_FWD(ovrp_GetAppShouldQuit2, out);
|
||||||
if (out) *out = ovrpBool_False; return ovrpSuccess;
|
if (out) *out = ovrpBool_False;
|
||||||
|
return ovrpSuccess;
|
||||||
}
|
}
|
||||||
OVRP_EXPORT ovrpResult ovrp_GetUserPresent2(ovrpBool *out) {
|
OVRP_EXPORT ovrpResult ovrp_GetUserPresent2(ovrpBool *out) {
|
||||||
PT_FWD(ovrp_GetUserPresent2, out);
|
PT_FWD(ovrp_GetUserPresent2, out);
|
||||||
if (out) *out = ovrpBool_True; return ovrpSuccess; /* headset worn */
|
if (out) *out = ovrpBool_True;
|
||||||
|
return ovrpSuccess; /* headset worn */
|
||||||
}
|
}
|
||||||
OVRP_EXPORT ovrpResult ovrp_GetAppShouldRecenter2(ovrpBool *out) {
|
OVRP_EXPORT ovrpResult ovrp_GetAppShouldRecenter2(ovrpBool *out) {
|
||||||
PT_FWD(ovrp_GetAppShouldRecenter2, out);
|
PT_FWD(ovrp_GetAppShouldRecenter2, out);
|
||||||
if (out) *out = ovrpBool_False; return ovrpSuccess;
|
if (out) *out = ovrpBool_False;
|
||||||
|
return ovrpSuccess;
|
||||||
}
|
}
|
||||||
OVRP_EXPORT ovrpResult ovrp_GetAppShouldRecreateDistortionWindow2(ovrpBool *out) {
|
OVRP_EXPORT ovrpResult ovrp_GetAppShouldRecreateDistortionWindow2(ovrpBool *out) {
|
||||||
PT_FWD(ovrp_GetAppShouldRecreateDistortionWindow2, out);
|
PT_FWD(ovrp_GetAppShouldRecreateDistortionWindow2, out);
|
||||||
if (out) *out = ovrpBool_False; return ovrpSuccess;
|
if (out) *out = ovrpBool_False;
|
||||||
|
return ovrpSuccess;
|
||||||
}
|
}
|
||||||
OVRP_EXPORT ovrpResult ovrp_GetSystemMultiViewSupported2(ovrpBool *out) {
|
OVRP_EXPORT ovrpResult ovrp_GetSystemMultiViewSupported2(ovrpBool *out) {
|
||||||
PT_FWD(ovrp_GetSystemMultiViewSupported2, out);
|
PT_FWD(ovrp_GetSystemMultiViewSupported2, out);
|
||||||
if (out) *out = ovrpBool_True; return ovrpSuccess; /* Quest supports multiview */
|
if (out) *out = ovrpBool_True;
|
||||||
|
return ovrpSuccess; /* Quest supports multiview */
|
||||||
}
|
}
|
||||||
OVRP_EXPORT ovrpResult ovrp_GetAppHasInputFocus(ovrpBool *out) {
|
OVRP_EXPORT ovrpResult ovrp_GetAppHasInputFocus(ovrpBool *out) {
|
||||||
PT_FWD(ovrp_GetAppHasInputFocus, out);
|
PT_FWD(ovrp_GetAppHasInputFocus, out);
|
||||||
if (out) *out = ovrpBool_True; return ovrpSuccess;
|
if (out) *out = ovrpBool_True;
|
||||||
|
return ovrpSuccess;
|
||||||
}
|
}
|
||||||
/* the OpenXR session IS our display; nothing extra to set up */
|
/* the OpenXR session IS our display; nothing extra to set up */
|
||||||
OVRP_EXPORT ovrpResult ovrp_SetupDistortionWindow3(unsigned int flags) {
|
OVRP_EXPORT ovrpResult ovrp_SetupDistortionWindow3(unsigned int flags) {
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
/* AUTO-GENERATED by gen_stubs.sh from analysis/shim_surface.txt. Do not edit by hand. */
|
/* AUTO-GENERATED by gen_stubs.sh from analysis/all_exports.txt. Do not edit by hand. */
|
||||||
#include "ovrplugin_shim.h"
|
#include "ovrplugin_shim.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user