From 1f3dc40c07219267e0761cfb6fab97ac07165ca0 Mon Sep 17 00:00:00 2001 From: Daniel Lynch Date: Fri, 18 Sep 2026 02:15:10 -0400 Subject: [PATCH] =?UTF-8?q?docs+build:=20hygiene=20pass=20=E2=80=94=20clos?= =?UTF-8?q?e=20leak=20risk,=20de-drift=20docs,=20fix=20build=20prereqs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- .gitignore | 4 + HOST.md | 72 +++++++++++++---- README.md | 18 +++-- TESTING.md | 135 ++++++++++++++++++++----------- docs/research/RECON.md | 26 +++--- packaging/build_openxr_loader.sh | 8 +- packaging/steamframe_patches.sh | 18 ++++- scripts/fetch_deps.sh | 21 +++-- shim/README.md | 123 ++++++++++++++++------------ shim/build_android.sh | 13 +-- shim/gen_stubs.sh | 4 +- shim/src/core.c | 21 +++-- shim/src/stubs.c | 2 +- 13 files changed, 304 insertions(+), 161 deletions(-) diff --git a/.gitignore b/.gitignore index 17606eb..c1a3022 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,7 @@ packaging/libs/ *.zip *.tar.gz save_backup/ + +# Session/lane scratch (personal assistant-lane config — never public) +CLAUDE.md +scratch_obj/ diff --git a/HOST.md b/HOST.md index fab045c..9cfebaf 100644 --- a/HOST.md +++ b/HOST.md @@ -1,7 +1,15 @@ # Target host platform — Steam Frame -Researched 2026-06-23. Answers "can the dumped APK run on Steam Frame, and do we -need Android given SteamOS is Linux?" +Researched 2026-06-23; **corrected 2026-09-18 against the shipped hardware and Lepton's +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. 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 Waydroid on x86 PCs; Frame is ARM so it doesn't apply). Walkabout Mini Golf (Quest title) already cited running on it. -- **Monado** = open OpenXR runtime, runs on **Linux AND Android**, Vulkan - compositor using VK_KHR_external_memory_fd / external_semaphore_fd (matches our - Vulkan-renderer finding). +- **SteamVR** is the OpenXR runtime apps see inside Lepton, bind-mounted in from the host + (not Monado — see the correction above). It advertises the `XR_FB_foveation` family, + `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 ``` Steam Frame (SteamOS / Arch Linux, ARM64) └─ Lepton (AOSP/Waydroid container, native ARM64) └─ 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 — a valid entitlement is the user's responsibility; see README "Legal / scope") ``` ## Why the shim IS the project Meta ended VrApi support 2022-08-31; OpenXR is the only supported Quest API and -Valve's whole stack is OpenXR (Monado). So: -- OpenXR Quest games -> Lepton+Monado likely run them with little/no work. +Valve's whole stack is OpenXR (SteamVR on Frame). So: +- 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 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. -## Remaining real unknowns (gated on Frame shipping ~summer 2026) -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.) -2. Can a SIDELOADED app reach the runtime + compositor (perms across the Waydroid - boundary)? -3. **Likely the real technical crux:** sharing Vulkan swapchain images from inside - the Lepton container out to the host Monado/Frame compositor - (VK_KHR_external_memory_fd across the container GPU boundary). May be moot if - Monado's compositor runs inside the container. +## Status of the old unknowns (resolved 2026-09-18) + +Steam Frame shipped **2026-09-14** and Lepton is open source (MIT for the tool), so the three +2026-06 unknowns are answered: + +1. **Does Lepton expose an OpenXR runtime to apps inside the container?** Yes — SteamVR, via + the bind-mounted `active_runtime.json` and `vrclient.so` described above. +2. **Can a sideloaded app reach the runtime + compositor?** Yes. Lepton documents adb + 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. diff --git a/README.md b/README.md index c5783f0..9ec0108 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 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 -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 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 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 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, @@ -44,11 +44,15 @@ of it to the `ovrp_*` ABI the game expects. ## Build ```sh -scripts/fetch_deps.sh # OpenXR + Vulkan headers (Apache-2.0), Android NDK -shim/build_android.sh # -> shim/build/arm64/libOVRPlugin.so +scripts/fetch_deps.sh # OpenXR + Vulkan headers (Apache-2.0), NDK, JDK, build-tools +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 -gets them). A host x86-64 build is also supported for compile-validation. +`fetch_deps.sh` pins the header versions; override with `OPENXR_TAG` / `VULKAN_HEADERS_TAG`. + +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) diff --git a/TESTING.md b/TESTING.md index 9477dbd..a8a96bf 100644 --- a/TESTING.md +++ b/TESTING.md @@ -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 `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 -> 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; @@ -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 accepts the Application context vs requiring the real Activity, and the PreInitialize3-creates-instance-before-activity ordering. -3. **Manifest** — add the OpenXR usage declarations Meta's runtime expects - (`` already there; - add OpenXR ``/intent bits per Meta's OpenXR mobile docs). +3. **Manifest** — no change needed in the end. RE4 VR is already a shipping Quest VR app + and declares ``; + `packaging/inspect_manifest.sh` reports any gaps. 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 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 logcat | grep -iE 'xrr|OVRPlugin|openxr' # watch the [xrr] logs ``` -Expected first-run signal: instance/session create succeed in logcat; if the frame -loop spins and the projection layer submits, you get an image (even if poses/input -are rough). Known rough edges on first run: depth (Unsupported), input (controllers -return NotYetImplemented), the [VERIFY-ON-HW] swapchain index lockstep. +`packaging/repack.sh` now does the repack, align and re-sign in one step; the manual +sequence above is kept only to show what it does. -## 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 -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: +## Path B — desktop OpenXR harness (fast iteration) — BUILT + +`tools/desktop-harness/` drives the `ovrp_*` sequence against a real OpenXR runtime with no +headset and no RE4: PreInitialize3 -> Initialize5 -> SetupLayer -> [WaitToBeginFrame -> BeginFrame4 -> GetNodePoseState3 -> EndFrame4] xN -> Shutdown2 -and asserts each returns ovrpSuccess. This exercises the real OpenXR calls without -RE4 or hardware. Build: +asserting each returns `ovrpSuccess`. Build and run: ``` -# inside the Linux arm64 env, with Monado + openxr loader installed: -cc -std=c11 -Iinclude -Ithird_party/openxr tests/harness.c \ - src/*.c -lopenxr_loader -lvulkan -o harness # needs a Vulkan device/headless -XR_RUNTIME_JSON=/path/to/monado/openxr_monado-dev.json ./harness +shim/build_host.sh # -> build/host/{libOVRPlugin.so,harness} +tools/desktop-harness/run.sh # headless against monado-service ``` -Note: Initialize5 needs real Vulkan handles; for a pure-logic smoke test the harness -can pass a headless VkInstance/Device (or we add a "no-gfx" build flag that skips -xrCreateSession to test the non-rendering calls first). +Needs libvulkan and an OpenXR loader (Debian: `libvulkan-dev libopenxr-loader1 +libopenxr-dev`); without the loader `build_host.sh` compiles the objects and stops. + +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) -Same arm64 shim `.so`, but the APK runs under **Lepton** (Valve's Waydroid/AOSP) and -the OpenXR runtime is **Monado**. Once Frame ships: NDK build -> repack -> sideload -into Lepton -> the open question is whether Lepton exposes the OpenXR loader + -Vulkan swapchain sharing across the container (see HOST.md unknowns). Path A having -worked makes this mostly a packaging/runtime-plumbing exercise. +Same arm64 shim `.so`, but the APK runs under **Lepton** (Valve's Waydroid fork, now open +source) and the OpenXR runtime inside the container is **SteamVR** — Lepton's +`vendor/etc/openxr/1/active_runtime.json` names `steamvr` and points at a host-mounted +`vrclient.so`. (Earlier notes here and in `HOST.md` assumed Monado; that was wrong.) + +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): -- Session lifecycle: instance/system/session create, event-driven state machine. -- Frame loop: xrWaitFrame/Begin/EndFrame, predicted display time. -- Poses: xrLocateViews (eyes) + xrLocateSpace (head). -- Swapchains: xrCreateSwapchain from ovrpLayerDesc, enumerate VkImages, per-frame - acquire/wait/release, real XrCompositionLayerProjection submit. -- 20 OpenXR functions; 239/239 ovrp_ symbols. +**Path A is done and field-verified.** RE4 VR boots and is playable on a Quest 2 through the +shim on Meta's OpenXR runtime: stereo rendering, head and controller tracking, buttons, +grips, haptics, save loading. The frame-pacing "ghost" that dominated development is fixed +(game-thread pacing; see `docs/research/ghost-fix-2026-06-27.md`). Shipped and verified: -NOT yet (the to-do list before a meaningful Quest run): -- arm64/Android NDK build (host build only so far). -- [ANDROID-TODO] JavaVM/activity -> XrInstanceCreateInfoAndroidKHR + xrInitializeLoaderKHR. -- Input: controller/hand action sets (GetControllerState4 returns NotYetImplemented). -- Depth layer (SetupLayerDepth -> Unsupported). -- Map ovrp_GetInstance/DeviceExtensionsVk -> xrGetVulkan*ExtensionsKHR (so the app - creates its VkInstance/Device with the runtime's required extensions). -- [VERIFY-ON-HW] swapchain index lockstep assumption. +- Session lifecycle, event-driven state machine, Android instance handshake + (`src/android_init.c`). +- Frame loop with `xrWaitFrame` on the game thread and a FIFO frameState handoff to the + render thread. +- Poses via `xrLocateViews` and `xrLocateSpace`; `LOCAL_FLOOR` when the game asks for floor + level. +- Swapchains from `ovrpLayerDesc`, per-frame acquire/wait/release, real projection-layer + 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 -1. Install Android NDK; cross-build the shim to arm64 (proves it builds for target). -2. Stand up the Monado-sim Linux arm64 VM on the Mac + run tests/harness.c (Path B - smoke test of the non-gfx calls). -3. Then start the [ANDROID-TODO] instance handshake (gates the real Quest run). +**Open / known gaps:** +- Depth layer submission is built but gated off (`debug.re4vr.depth`); Meta's runtime accepts + but does not use plain KHR depth for reprojection. +- Swapchain index lockstep with UE's own `TextureStage` is assumed, not enforced — a + 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`. diff --git a/docs/research/RECON.md b/docs/research/RECON.md index 0ca7eb5..1e368b1 100644 --- a/docs/research/RECON.md +++ b/docs/research/RECON.md @@ -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 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 -> entitlement-**stub** approach in a few places (§4, the wiring summary, the decision tree) -> that was **not** carried into the project — entitlement handling is out of scope and the -> shim ships **no circumvention code** (see the README's scope section). Those passages are -> kept as a record of the original investigation, not as instructions. +> **Editor's note (2026-06, revised 2026-09):** this is a historical recon document. Its +> original text proposed an entitlement-stub approach in a few places (§4, the wiring +> summary, the decision tree). That was **not** carried into the project: entitlement +> handling is out of scope and the shim ships **no circumvention code** (see the README's +> 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 | | `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 | - [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.** ovrp_* is the documented Unity-shared API (OVR_Plugin.h); modern Meta OVRPlugin has an OpenXR backend = reference impl / prior art. -- => **Stub the ovr_* Platform SDK** (libovrplatformloader.so) — entitlement/account, - just return OK; don't reimplement. +- => **Leave the ovr_* Platform SDK alone** (`libovrplatformloader.so`). Entitlement and + 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 > imports of `libUE4.so` (it may dynamically link the runtime): @@ -187,12 +190,13 @@ device: Quest 2 serial (codename hollywood) **Decision tree:** - **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. - **VrApi** → next phase: scope a VrApi reimplementation/translation shim (much larger). Reassess whether the project is worth it vs. waiting/UEVR. -- **Either way** → `libovrplatformloader.so` entitlement bypass is a required - Ghidra patch (legal for your own copy). +- **Either way** → `libovrplatformloader.so` is left as shipped. Entitlement handling is + 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.) --- diff --git a/packaging/build_openxr_loader.sh b/packaging/build_openxr_loader.sh index a203c51..b81ab5c 100755 --- a/packaging/build_openxr_loader.sh +++ b/packaging/build_openxr_loader.sh @@ -4,14 +4,18 @@ set -euo pipefail 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; } +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" if [ ! -d "$SRC" ]; then echo "downloading OpenXR-SDK source..." 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" - mv "$ROOT/tools/OpenXR-SDK-main" "$SRC" + mv "$ROOT/tools/OpenXR-SDK-${OPENXR_TAG}" "$SRC" fi GEN="Unix Makefiles"; command -v ninja >/dev/null && GEN="Ninja" diff --git a/packaging/steamframe_patches.sh b/packaging/steamframe_patches.sh index 194296f..8b181e9 100755 --- a/packaging/steamframe_patches.sh +++ b/packaging/steamframe_patches.sh @@ -16,12 +16,22 @@ # in.apk : shim-repacked APK (default: out/re4vr-shim.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. set -euo pipefail 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; } -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}" OUT="${2:-$PKG/out/re4vr-steamframe.apk}" @@ -54,7 +64,7 @@ rm -rf "$WORK"; mkdir -p "$WORK" DEC="$WORK/dec" 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). mapfile -t GA < <(find "$DEC" -path '*/com/epicgames/ue4/GameActivity.smali' \ @@ -90,7 +100,7 @@ if [ -f "$MAN" ]; then fi echo "== apktool build ==" -apktool b -o "$WORK/unsigned.apk" "$DEC" >/dev/null +"${APKTOOL[@]}" b -o "$WORK/unsigned.apk" "$DEC" >/dev/null echo "== align + sign ==" "$BT/zipalign" -f -p 4 "$WORK/unsigned.apk" "$WORK/aligned.apk" diff --git a/scripts/fetch_deps.sh b/scripts/fetch_deps.sh index 9cbb1db..b8d306a 100644 --- a/scripts/fetch_deps.sh +++ b/scripts/fetch_deps.sh @@ -3,22 +3,27 @@ # the Android NDK). Run once after cloning. set -euo pipefail 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" VK="$ROOT/shim/third_party/vulkan/vulkan" -echo "== OpenXR headers (Apache-2.0) ==" +echo "== OpenXR headers (Apache-2.0, $OPENXR_TAG) ==" 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 curl -fsSL "$OXRBASE/$h" -o "$OXR/$h" done -echo "== Vulkan headers (Apache-2.0) ==" +echo "== Vulkan headers (Apache-2.0, $VULKAN_HEADERS_TAG) ==" 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 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 curl -fsSL "$VVBASE/$f" -o "$VK/vk_video/$f" done @@ -44,5 +49,9 @@ echo "== apktool (manifest tooling) ==" [ -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" -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." diff --git a/shim/README.md b/shim/README.md index b56e3f9..db34bd2 100644 --- a/shim/README.md +++ b/shim/README.md @@ -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 -backed by OpenXR (Monado) instead of Meta's libvrapi.so. See ../HOST.md, ../SHIM-SCOPE.md. +Drop-in replacement for RE4 VR's `libOVRPlugin.so`. It re-exports the `ovrp_*` C ABI the +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 -- 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) -``` -cc -std=c11 -Wall -shared -fPIC -fvisibility=hidden -Iinclude src/stubs.c src/core.c \ - -o build/libOVRPlugin.so -``` -Status: builds; exports exactly 239 ovrp_ symbols (1:1 with shim_surface.txt, 0 -missing/extra); dlopen+dlsym verified. This is the SKELETON — it loads and resolves, -it does not yet drive VR (core fns are TODO). +- `include/ovrplugin_shim.h` — the `ovrp_*` C ABI: types, structs, core prototypes. + Independently written against the observed ABI, with `_Static_assert`s pinning the + binary-verified struct sizes so a layout mistake fails the build rather than the device. +- `src/xr_runtime.{c,h}` — the OpenXR engine: instance/session lifecycle, the frame loop, + swapchains, layer composition, foveation, perf levels. +- `src/vk_session.c` — Vulkan graphics binding, image enumeration, the tile-memory flush. +- `src/layers.c` — layer setup and eye layer geometry. +- `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 -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.) +Roughly 48 distinct OpenXR entry points are used. -## Modules (current) -- 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. +## Build -## Done so far -Session lifecycle, frame loop (xrWaitFrame/Begin/EndFrame), poses (xrLocateViews/ -Space), swapchains (xrCreateSwapchain from ovrpLayerDesc, acquire/wait/release, -XrCompositionLayerProjection submit). Vulkan binding from Initialize5 args [VERIFIED]. +**The deployable artifact** is Android arm64 — the game runs inside Horizon OS or Lepton: + +```sh +../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.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) -1. NDK arm64 build (host build is validation only). -2. [ANDROID-TODO] JavaVM/activity -> XrInstanceCreateInfoAndroidKHR + xrInitializeLoaderKHR. -3. Input action sets (controllers/hands); depth layer; GetVulkan*ExtensionsKHR mapping. -4. Repack APK (real entitlement — you own it); test on Quest 2. +`debug.re4vr.passthru=1` hands 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 behaviour 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. + +## 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. diff --git a/shim/build_android.sh b/shim/build_android.sh index 810c0a2..a177f1a 100755 --- a/shim/build_android.sh +++ b/shim/build_android.sh @@ -27,11 +27,12 @@ mkdir -p "$SHIM/build/arm64" # 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/). +# 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)" -if [ -n "$PATCHELF" ]; then - "$PATCHELF" --add-needed libopenxr_loader.so "$SHIM/build/arm64/libOVRPlugin.so" - echo "added NEEDED libopenxr_loader.so" -else - echo "WARN: patchelf not found — add 'libopenxr_loader.so' as NEEDED before packaging" -fi +[ -n "$PATCHELF" ] || { echo "ERROR: patchelf not found. It is required to add"; \ + echo " 'libopenxr_loader.so' as NEEDED; without it the shim cannot resolve the"; \ + echo " OpenXR loader at runtime. Install patchelf and re-run."; exit 1; } +"$PATCHELF" --add-needed libopenxr_loader.so "$SHIM/build/arm64/libOVRPlugin.so" +echo "added NEEDED libopenxr_loader.so" echo "built: $SHIM/build/arm64/libOVRPlugin.so" diff --git a/shim/gen_stubs.sh b/shim/gen_stubs.sh index 443c538..092394c 100755 --- a/shim/gen_stubs.sh +++ b/shim/gen_stubs.sh @@ -1,5 +1,5 @@ #!/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 # name per SHIM-SCOPE.md buckets. Functions prototyped in the header (real sigs) # 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. { -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 "log.h"' echo diff --git a/shim/src/core.c b/shim/src/core.c index 20a1406..10a5353 100644 --- a/shim/src/core.c +++ b/shim/src/core.c @@ -211,31 +211,38 @@ OVRP_EXPORT ovrpResult ovrp_GetSystemDisplayFrequency2(float *outFreq) { /* per-frame app-state getters the loading loop polls (all (ovrpBool* out)->result) */ OVRP_EXPORT ovrpResult ovrp_GetAppHasVrFocus2(ovrpBool *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) { 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) { 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) { 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) { 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) { 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) { 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 */ OVRP_EXPORT ovrpResult ovrp_SetupDistortionWindow3(unsigned int flags) { diff --git a/shim/src/stubs.c b/shim/src/stubs.c index ec36746..69b86c2 100644 --- a/shim/src/stubs.c +++ b/shim/src/stubs.c @@ -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 "log.h"