Files
daniel-lynch--ovrplugin-ope…/docs/research/RE-NOTES.md
T
Daniel LynchandClaude Opus 4.8 a72a79ad29 Initial public release: OVRPlugin→OpenXR interoperability shim
An independent reimplementation of Meta's libOVRPlugin ABI on top of OpenXR, so
VrApi-era Meta Quest VR titles can run on non-Meta OpenXR runtimes (Monado,
Steam Frame) instead of being locked to Meta hardware. Original code only — no
Meta/Epic/Capcom binaries, headers, or assets. Includes a desktop harness that
drives the shim against Monado headless.

Scope/legal: interoperability; entitlement handling is out of scope. See README
for the legal/scope section and docs/ for the research trail and design notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D6sFYGXZPsq3v7xtcDES6g
2026-06-29 00:48:48 -04:00

3.6 KiB

Reverse-engineering notes — libOVRPlugin.so

Pinned version: OVRPlugin 1.51 / pkg ovrplugin-android-universal:19.0.0.449.531. Cross-reference header: public OVRPlugin.cs @ v1.51 (Unity Oculus Integration, many GitHub mirrors) — gives ovrp_ signatures + [StructLayout] struct layouts.

RE pipeline (working)

Headless Ghidra 12.1.2, driven by ghidra_scripts/DumpOvrp.java:

export JAVA_HOME=~/dev/re4vr-port/tools/jdk-21.0.11+10   # portable Temurin JDK
tools/ghidra_12.1.2_PUBLIC/support/analyzeHeadless ghidra_proj re4vr \
  -import dump/apk_libs/lib/arm64-v8a/libOVRPlugin.so \
  -scriptPath ghidra_scripts -postScript DumpOvrp.java -deleteProject

Gotchas solved: Ghidra needs a JDK (JREs rejected) -> portable Temurin in tools/. Ghidra 12 dropped bundled Jython -> scripts must be Java, not .py. Output: analysis/ovrp_decomp.txt (537 ovrp_ sigs, 27 core bodies).

Key structural finding

Exported ovrp_* are THIN THUNKS: (*PTR_ovrp_X)() jumping to the real impl, which calls internal C++ OVR::Util::Compositor::* (partially symbolized). => The game (Java System.loadLibrary + dlsym) calls the EXPORTED symbols. So the SHIM just re-exports the same ovrp_ symbol names backed by OpenXR and replaces libOVRPlugin.so wholesale. We do NOT reverse the internal Compositor C++. Decompilation = reference for semantics + struct sizes only.

ABI confirmed (triangulation: Ghidra shape + v1.51 header types = MATCH)

ovrp_GetNodePoseState3(ovrpStep, int frameIndex, ovrpNode, ovrpPoseStatef* out):

  • null out -> returns 0xfffffc17 = -1001 (ovrpFailure_InvalidParameter)
  • not init -> returns 0xfffffc16 = -1002 (ovrpFailure_NotInitialized)
  • success -> memcpy(out, ..., 0x58) then return 0
  • ovrpPoseStatef = 0x58 = 88 bytes == public layout: ovrpPosef(28) + 4x ovrpVector3f(48) + double Time(8) -> pad 88. EXACT MATCH. => Public OVRPlugin.cs v1.51 struct layouts are TRUSTWORTHY for this binary. ovrpResult error-code convention confirmed (-1001 invalid, -1002 not-init).

Layer structs reversed (2026-06-23, analysis/struct_layouts.txt)

Ghidra recovered C++ type NAMES from demangled symbols but empty layouts (no DWARF); real offsets came from decompiling OVR::Util::Compositor methods.

  • Two compositor backends: CompositorVRAPI_OpenGL + CompositorVRAPI_Vulkan (RE4=Vulkan).
  • ovrpLayerDesc = 0x7c (124B) [VERIFIED]: ImportLayerDesc memset's 0x7c; switch on +0x00 = ovrpShape (cases 0,1,2,4,5 overlays; case 3 = EyeFov projection). Three historical copy sizes 0x68/0x6c/0x7c = base / +DepthFormat / +MotionVector. Full field layout written to header, offsets confirmed (Fov@0x20, VisibleRect@0x40, DepthFormat@0x68).
  • ovrpLayerSubmit = 0x130 (304B) [VERIFIED]: EndFrame4 allocs count*0x130. Header fields (LayerId, TextureStage, ViewportRect[2], Pose@0x28, Flags) = public layout; per-shape union tail still [TODO] (reserved bytes for now). Stored internally as ovrpLayerSubmitUnion in a std::map<int,pair<union,int>>. Header now has _Static_asserts sizeof(ovrpLayerDesc)==124 && ovrpLayerSubmit==304; both pass, shim rebuilds (239 symbols).

Next RE passes (when continuing)

  • Dedup the export-thunk vs impl entries in the dump (script polish).
  • For each CORE fn: pair Ghidra arg-shape with v1.51 C# sig -> finalized C header for the shim (ovrp types + struct layouts).
  • Reverse outliers NOT in the public header (e.g. SpaceWarp/ASW property paths the binary references, any custom Armature behavior).
  • Entitlement (libovrplatformloader's ovr_* surface) is out of scope for this repo: no replacement ships and nothing is circumvented (see README "Legal / scope").