# Native components FrameBridge includes automatic metadata retirement for the verified Meta XR Audio Wwise build; see [AUDIO_METADATA.md](../docs/AUDIO_METADATA.md) for its lifetime invariant and regression coverage. It uses the existing adapter installation. Sources of the prebuilt binaries in `../artifacts/` (committed, with `SHA256SUMS`). End users never compile anything; developers rebuild with `python native/build.py` (downloads NDK r27c, OpenXR headers at pinned commits, d8 into `native/.cache/`, git-ignored). The adapter, GL shim and platform compat rebuild byte-identically; the VrApi bridge embeds debug paths (`-g`) and the dex depends on the d8 version, so those differ in bytes only. | Dir | Artifact | What / why | |---|---|---| | `luajit/` | `arm64-v8a/libfp_luajit.so` | Pinned upstream LuaJIT with full-width lightuserdata (`frame.luajit_pointers`). Only the verified old Lua engine's shared public functions become imports; the original xLua API 105 bindings, LuaSocket helpers and `luaopen_perflib` remain in the game. No game library is bundled. Build: `python native/build.py --only luajit`; source revision + archive SHA-256 in `luajit/build.py`, MIT notices in `luajit/COPYRIGHT` and `artifacts/licenses/LuaJIT.txt`. `runtime.c` checks GC64 and the retained `xlua_gl` global-reference offset. `smoke.c` compares a real low-address allocation with the original library's own native static pointer; the unmodified library fails only the high-address case, the patched one round-trips both exactly. Startup verified on Frame; gameplay unverified. | | `adapter/` | `{arm64-v8a,armeabi-v7a}/libopenxr_loader_generic.so` | **FrameBridge**: replaces overport's generic loader (which is renamed `libopenxr_loader_original.so`) and fixes Steam Frame runtime gaps. Hooks xrCreateInstance, swapchains, xrEndFrame, spaces, xrPollEvent, … everything else is forwarded by `gen_forwarders.py`-generated tail calls. `scene_emu.c` = Meta scene/spatial-entity emulation; `flip_vk.c` = Vulkan blit for VERTICAL_FLIP quads. `session_fixes.c` = per-game session fixes, all off by default and hooked only when on (`layer_debug` diagnostics, `stable_local`, `focus_hold`, aim correction `aim_pitch/aim_yaw/aim_forward`, `refresh_rate`, `proximity_emul` = finger proximity (XR_FB_touch_controller_proximity, which the Frame lacks) from the capacitive touch inputs: OVRPlugin's `hand_thumb_proximity`/`hand_trigger_proximity` actions get extra Touch bindings); `input_diag.c` = controller-input diagnostics (setting `input_diag`, off by default and hooked only when on; logs suggested interaction profiles and the runtime's answers, each hand's current profile, functions the runtime lacks and failing input/haptics/perf calls, each line once; tested on the host by `tests/test_input_diag.py`); `layer_emul_gl.c` = `equirect_emul` (GLES sessions only): 360° equirect/equirect2 layers become one adapter projection layer, drawn by a worker thread with its own EGL context shared with the app's: each 360° image is converted to a cube map only when it changes, and the view is redrawn only after a 4° head turn (GL/EGL resolved with dlopen, so Vulkan games never load them); `layer_math.h` = their pose/face math, tested on the host by `tests/test_layer_emul.py`. `render_model.c` = XR_FB_render_model emulation (setting `controller_models`, off by default): serves `files/framebridge/controller_{left,right}.glb`, which the agent converts on the Frame from SteamVR's own Frame controller render models (never shipped by FramePort); tested on the host by `tests/test_render_model.py`. Settings: `libframe_settings.so` in the APK + `settings.conf`/`framebridge.conf` on the Frame. | | `vrapi-bridge/` | `arm64-v8a/libvrapi.so` | VrApi → OpenXR bridge from [Android-XR-Bridge/OVRPort](https://github.com/Android-XR-Bridge/OVRPort) `native/vrapi` at 5e7df52 (GPL-3.0, `LICENSE.upstream`; unchanged upstream as of OVRPort v1.2.5, which ships the unpatched code as its opt-in `patch_vrapi_openxr` in experimental CLI builds), with our changes in `upstream-patches/`: GLES sessions + GL texture swapchains, cylinder→quad layers, GL vertical flip, UNORM↔sRGB format twins, 30 s VR-mode deadline, VALID-only recenter, loading-icon layers skipped, `vrapi_PollEvent`/`RecenterPose`/`SetDisplayRefreshRate`/`GetSystemPropertyFloatArray` (BlazeRush imports them, GitHub #57), legacy compositor debug/volume frame flags accepted, full-rate Vulkan foveation images allocated as density attachments (The Climb, GitHub #238), diagnostics behind `-DOVP_GL_DIAG`. `upstream-patches/` reproduces these sources from the fork's `native/vrapi` (checked 2026-10-06 against its latest commit); upstream PRs Android-XR-Bridge/OVRPort#3-#8, tracked in GitHub #75. | | `platformcompat/` | `arm64-v8a/libovrplatformcompat.so` | Real `ovrMessageType_ToString` (same fork, `native/platform`). | | `langpack/` | `arm64-v8a/libfp_langpack.so` | Language packs for the Meta Platform SDK (patch `frame.langpacks`, opt-in). overport's platform loader is a dispatcher in front of Meta's own loader and answers `ovr_LanguagePack_GetCurrent/SetCurrent` with `return 0` (no reply ever). This library becomes the loader's first DT_NEEDED; the loader's own exports of the 31 functions it defines are made STB_LOCAL in `.dynsym` (`elf.hide_exports`, nothing moves), so symbol lookups reach the library first. It serves `.lang` files found in the game's OBB / app files (`$FRAMEPORT_LANGPACK_DIRS` overrides the search path) through `ovr_LanguagePack_*`, `ovr_AssetFile_GetList` (the loader's list + the packs) and `ovr_AssetFile_StatusById`; its own message queue is merged into `ovr_PopMessage`, and every handle that is not ours (an address-range check) goes to the loader's original function, whose address is read at run time from the loader's in-memory `.dynsym`. With patch `frame.asset_files` (marker byte `@FPASSETS@`, or env `FRAMEPORT_ASSET_FILES=1`) it also lists the data's `*.pak` content files as installed `default` asset files and answers `ovr_AssetFile_DownloadById` for them at once (a completed `DownloadUpdate`, then the result with the path). Host-tested against a stand-in loader (`tests/test_langpack.py`, plain `cc`, no NDK); the bionic/headset side is unverified. | | `glshim/` | `arm64-v8a/libglshim.so` | Mesa GLSL compatibility for GLAD engines (hooks eglGetProcAddress): comments out `#pragma` before `#extension`, enables `GL_EXT_shader_implicit_conversions`, hides GL_OVR_multiview (`gl_hide_multiview`, default 1) and logs failed shaders. `-DGLSHIM_TRACE` adds per-FBO draw/error tracing. | | `glmv/` | `arm64-v8a/libfpglmv.so` | Single-view draws of OVR_multiview programs (patch `frame.gl_multiview_fbo`, opt-in, GitHub #77). Mesa enforces OVR_multiview's rule that a draw's program declares as many views as the draw framebuffer has; engines that compile every shader with `num_views=2` and also draw into ordinary 2D framebuffers (Doom3Quest's HUD/PDA pool) lose those draws. The engine library gets it as first DT_NEEDED (direct `gl*` imports) and its `dlopen("libGLESv3.so")` string is rewritten to `libfpglmv.so` (same length; its `qgl*` dlsym table); linked `--no-as-needed` to libGLESv3 so dlsym on its handle finds everything it doesn't wrap. It records stage sources at glLinkProgram, caches the draw framebuffer's color-attachment view count per bind, and when a multiview program draws into a 0-view framebuffer it binds a lazily built twin (`glmv_rewrite.h`: num_views layout blanked, `gl_ViewID_OVR` -> `(0u)`; attribute locations + uniform block bindings copied, default-block uniforms copied before every such draw with a shadow to skip unchanged ones), draws, rebinds the original. Logcat tag `GLMV`; `gl_mv_debug=1`. The rewriter is host-tested on Doom3Quest's 19 shaders (`tests/test_gl_multiview_fbo.py`); untested on the device. | | `tlskeys/` | `arm64-v8a/libfp_tkeys.so` | Thread-key pool for Unity (patch `frame.unity_tls_key_pool`, opt-in, GitHub #198; approach and the first helper by Klownicle). Bionic allows 128 pthread keys per process; Unity 6 plus large native libraries (Firebase, Gecko) and the Frame's own libraries use them up (Wander: `cannot create thread specific key for __cxa_get_globals()`). libunity.so's four key imports are renamed to `fp_tkey_create/_delete/_get/_set` (`elf.rename_imports`, same `.dynstr` bytes, versions set global) and this library is added to its DT_NEEDED: keys without a destructor become slots (512, generation-tagged, `0x40000000 + slot`) in a per-thread table under one real key; keys with a destructor and all other libraries keep real keys. Lock-free get/set. Logs `fp_tkeys: pooling Unity's thread keys` once (tag FrameBridge). Host-tested (rename); not yet run on the device. | | `zinkfix/` | `arm64-v8a/libVkLayer_fp_shaderfix.so` | Shader-fix Vulkan layer `VK_LAYER_FP_shader_fix` for OpenGL ES games (patch `frame.zink_shader_fix`): sits between Zink (Mesa's GL on Vulkan) and the driver and inserts the words of `zink_shader_fix` (same format as the Vulkan shim's `vk_shader_fix`: size + SHA-256 match) into SPIR-V modules; `zink_shader_dump=1` writes every distinct module to `files/fp_spirv/`. The engine library loads it first (DT_NEEDED); its constructor puts the layer in front of Android's GraphicsEnv debug layer list (private Android 11 `getDebugLayers`/`setDebugLayers`, found through libvulkan.so's dependencies) before EGL starts, so the loader picks it up from the app's lib dir. Exports only the enumeration functions and `VK_LAYER_FP_shader_fixGet*ProcAddr` (never plain `vk*` entry points). Vader Immortal's lightspeed shaders (Klownicle, GitHub #49). | | `xrlayer/` | `linux-arm64/libxr_frameport_timefix.so` + `XR_APILAYER_FRAMEPORT_timefix.json` | OpenXR API layer for Windows PC VR games under Proton on the Frame (glibc aarch64, built freestanding with the NDK's clang), on by default (patch `pcvr.xr_timefix`). Retries `xrCreateInstance` as an OpenXR 1.0 app when the runtime rejects 1.1 (the Frame's SteamVR runtime does, and Proton 11's VR helper asks for 1.1). Also emulates `xrConvertTimespecTimeToTimeKHR`/`xrConvertTimeToTimespecTimeKHR` if the runtime refuses them (the Frame's Android runtime does; its Linux runtime, used by Proton, supports them as of 2026-09-29 — so this part is only a fallback; Proton's wineopenxr needs them for `XR_KHR_win32_convert_performance_counter_time`, which Revive requires) with the adapter's xrWaitFrame-calibrated offset, and drops the extension from xrCreateInstance if the runtime rejects it. Enabled per game by the Proton launch.sh (`XR_API_LAYER_PATH`, `XR_ENABLE_API_LAYERS`). `tests/test_xrlayer.py` builds it for x86_64 and drives it via ctypes. | | `oculushmd/` | `win-x64/fp_oculushmd.exe` | PC VR counterpart of overport's `patch_oculus_unreal` (patch `pcvr.oculus_unreal`, default for Unreal Rift games on the Frame). Unreal's OculusHMD and LibOVR's `ovr_Detect` only start when the Windows named event `OculusHMDConnected` exists and is signalled (the Oculus service creates it). `fp_oculushmd.exe ` creates it (manual reset, signalled), runs the command (Revive's injector) in a job object and keeps the event until every process in the job — the game and its children — has exited (fallback without job support: 3 minutes); returns the command's exit code and logs `FramePort oculushmd: …` to stderr (launch.log). Freestanding Windows x64 console exe (no CRT, no Windows SDK): the NDK's clang + `lld-link /Brepro` with a kernel32 import library generated by `llvm-dlltool`, so it's byte-reproducible. Tested on Windows from WSL with a build whose event name is `-DEVENT_NAME=L"…"` (the Oculus service owns the real event on a PC with the Oculus app). | | `xrshim/` | `arm64-v8a/libframe_xrshim.so` | FrameBridge extension shim, only in builds with `controller_models=1`. overport's dispatcher (`libopenxr_loader.so`) answers `xrGetInstanceProcAddr` from a fixed table and refuses everything else (`overportOXR: Unknown proc addr: xrLoadRenderModelFB`), so the adapter never sees XR_FB_render_model lookups. Meta's OVRPlugin gets the function with `dlopen("libopenxr_loader.so")` + `dlsym`, so FramePort rewrites that `.rodata` string in `libOVRPlugin.so` (same length, in place) to `libframe_xrshim.so`. The shim returns the adapter's emulated functions (`framebridge_extension_proc`) and forwards every other name to overport. A DT_NEEDED interposer does not work here (dlsym on a handle searches that library first); tried and dropped. | | `venc/` | `linux-arm64-bin/fp_venc` | Hardware H.264 encoder for the live view, run on the Frame (spec: `venc/SPEC.md`). Reads SteamVR's "headset view" webcam (v4l2loopback `/dev/video99`, RGB24), box-scales and converts it to NV12 (BT.709 limited range; `convert.c`, NEON with a bit-identical scalar reference, also built for x86_64 by the host test), and feeds the SoC's V4L2 stateful encoder (`dev-encoder.rst`, Qualcomm iris `/dev/video-enc0`): CBR, no B-frames, SPS/PPS on every IDR, one frame per slot of a fixed grid (fps = a divisor of the panel refresh read through DRM KMS, default cap 45), Annex B on stdout. stdin `k` = keyframe, `q`/EOF = drain and stop; `--probe` prints the setup as JSON, `--selftest` compares NEON and scalar output. Fully static and freestanding (raw syscalls, own `_start`; only the NDK's kernel UAPI headers, staged in `.cache/uapi-arm64`), byte-reproducible. Lives in `linux-arm64-bin/` because `linux-arm64/` is uploaded whole as the OpenXR layer. | | `java-stubs/` | `dex/oculusos-stubs.dex` | No-op `com.oculus.os.AnalyticsEvent` / `UnifiedTelemetryLogger` for native code that looks them up. | The ovr_* stub library is *not* prebuilt: `src/frameport/analysis/stubgen.py` generates it per game from the exact missing symbols (no compiler needed). After changing anything here: `python native/build.py`, commit artifacts + SHA256SUMS, then run `frameport parity` (the report marks updated binaries as "expected") and a device launch test.