mirror of
https://github.com/daniel-lynch/ovrplugin-openxr-shim.git
synced 2026-10-06 02:00:05 +02:00
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
This commit is contained in:
commit
a72a79ad29
57 files changed
+9304
No files matched your search
@@ -0,0 +1,66 @@
|
||||
# Packaging — repack the RE4 VR APK with the shim
|
||||
|
||||
Produces an installable, re-signed APK with our OpenXR `libOVRPlugin.so` swapped in. The
|
||||
original `libovrplatformloader.so` is kept untouched, so the platform's real entitlement
|
||||
check runs unchanged — on Quest you own the title. Dump-your-own only; nothing here is
|
||||
redistributed. Entitlement handling on hardware with no Meta backend is **out of scope** for
|
||||
this project and is the user's responsibility. See ../TESTING.md for the on-device plan.
|
||||
|
||||
## One-time setup
|
||||
```
|
||||
./build_openxr_loader.sh # builds libopenxr_loader.so (arm64) -> libs/arm64/
|
||||
./make_debug_keystore.sh # debug.keystore for re-signing (repack.sh auto-runs it)
|
||||
```
|
||||
Also build the shim libs first: `../shim/build_android.sh`.
|
||||
|
||||
## Repack
|
||||
```
|
||||
./repack.sh [input.apk]
|
||||
# example:
|
||||
./repack.sh # ../dump/base.apk
|
||||
```
|
||||
Swaps only `libOVRPlugin.so` + bundles `libopenxr_loader.so`, and keeps the original
|
||||
`libovrplatformloader.so` so the platform's real entitlement check runs unchanged.
|
||||
|
||||
Output: `out/re4vr-shim.apk` (zipaligned, v1+v2+v3 signed with the debug key).
|
||||
|
||||
## Off-Quest port (Pico / Steam Frame / Monado-on-Android)
|
||||
```
|
||||
./steamframe_patches.sh [in.apk] [out.apk] # default: out/re4vr-shim.apk -> out/re4vr-steamframe.apk
|
||||
```
|
||||
Run AFTER `repack.sh` (which swaps in our shim). Needs `apktool`. Applies the Java/manifest
|
||||
fixes a non-Quest target needs but a real Quest doesn't: spoofs `Build.MANUFACTURER`/`MODEL`
|
||||
so UE takes the Oculus HMD path (else our shim is never called), neuters
|
||||
`AndroidThunkJava_ForceQuit`, and strips Meta-only `<uses-(native-)library>` manifest entries
|
||||
(keeps `libopenxr.google.so`). Rationale + the patches we deliberately skip:
|
||||
`../docs/research/related-work.md`. Prepared for the bring-up; not yet hardware-validated.
|
||||
|
||||
⚠️ **Do NOT install the output on a Quest** — the manifest strip removes Meta libs the
|
||||
Quest needs. The script warns + prompts for confirmation; pass `NOT_QUEST=1` to bypass the
|
||||
prompt in automation. On a Quest, install the plain `repack.sh` output (`out/re4vr-shim.apk`).
|
||||
|
||||
## Install + run (Quest, dev mode)
|
||||
```
|
||||
adb install -r out/re4vr-shim.apk
|
||||
# push the OBB you dumped (same versionCode 203):
|
||||
adb push ../dump/obb/main.203.com.Armature.VR4.obb /sdcard/Android/obb/com.Armature.VR4/
|
||||
adb push ../dump/obb/patch.203.com.Armature.VR4.obb /sdcard/Android/obb/com.Armature.VR4/
|
||||
adb logcat | grep -iE 'xrr|openxr|OVRPlugin|Armature' # watch [xrr] logs
|
||||
```
|
||||
|
||||
## Manifest
|
||||
```
|
||||
./inspect_manifest.sh [input.apk] # confirms VR/OpenXR declarations are present
|
||||
```
|
||||
RE4 VR is already a shipping Quest VR app, so its manifest almost certainly already
|
||||
has the headtracking feature + VR intent category; switching vrapi->OpenXR usually
|
||||
needs no manifest change. inspect_manifest.sh reports any gaps; edit the decoded
|
||||
manifest + rebuild with apktool only if something's missing.
|
||||
|
||||
## Notes
|
||||
- Re-signing with our own key is unavoidable (we modify a lib). That's what risks
|
||||
the legit entitlement on Quest — see ../TESTING.md.
|
||||
- Libs are added stored (-0) and the APK is `zipalign -p 4`'d so native libs stay
|
||||
page-aligned (extractNativeLibs=false convention).
|
||||
- Tools used: tools/android-14 (build-tools 34: zipalign/apksigner), tools/apktool.jar,
|
||||
tools/jdk-21 (keytool), tools/android-ndk-r27c (loader build).
|
||||
@@ -0,0 +1,20 @@
|
||||
# Shared, portable path/tool detection for the packaging scripts. `source` this.
|
||||
# Derives the repo root from this file's location and auto-detects tool versions
|
||||
# under tools/ (or env overrides), so the repo builds/repacks anywhere.
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
PKG="$ROOT/packaging"
|
||||
SHIM_OUT="$ROOT/shim/build/arm64"
|
||||
|
||||
# JDK: prefer a bundled tools/jdk-*, else an existing JAVA_HOME, else system java.
|
||||
_bundled_jdk="$(ls -d "$ROOT"/tools/jdk-* 2>/dev/null | head -1 || true)"
|
||||
if [ -n "${_bundled_jdk:-}" ]; then export JAVA_HOME="$_bundled_jdk"; fi
|
||||
KEYTOOL="${JAVA_HOME:+$JAVA_HOME/bin/}keytool"
|
||||
|
||||
# Android build-tools dir (zipalign / apksigner / aapt2), e.g. tools/android-14.
|
||||
BT="$(ls -d "$ROOT"/tools/android-[0-9]* 2>/dev/null | head -1 || true)"
|
||||
|
||||
# apktool jar (optional, for manifest work).
|
||||
APKTOOL_JAR="$ROOT/tools/apktool.jar"
|
||||
|
||||
# Android NDK (for building the OpenXR loader).
|
||||
NDK="${ANDROID_NDK:-$(ls -d "$ROOT"/tools/android-ndk-* 2>/dev/null | head -1 || true)}"
|
||||
Executable
+29
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the Khronos OpenXR loader (libopenxr_loader.so) for Android arm64 so the
|
||||
# shim's NEEDED dependency resolves at runtime. Output -> packaging/libs/arm64/.
|
||||
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; }
|
||||
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"
|
||||
tar xzf "$ROOT/tools/oxrsdk.tgz" -C "$ROOT/tools"
|
||||
mv "$ROOT/tools/OpenXR-SDK-main" "$SRC"
|
||||
fi
|
||||
|
||||
GEN="Unix Makefiles"; command -v ninja >/dev/null && GEN="Ninja"
|
||||
cmake -S "$SRC" -B "$SRC/build-android" \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$NDK/build/cmake/android.toolchain.cmake" \
|
||||
-DANDROID_ABI=arm64-v8a -DANDROID_PLATFORM=android-29 \
|
||||
-DDYNAMIC_LOADER=ON -DBUILD_TESTS=OFF -DBUILD_API_LAYERS=OFF \
|
||||
-DBUILD_CONFORMANCE_TESTS=OFF -DBUILD_WITH_SYSTEM_JSONCPP=OFF \
|
||||
-G "$GEN" >/dev/null
|
||||
cmake --build "$SRC/build-android" --target openxr_loader -j4
|
||||
|
||||
mkdir -p "$PKG/libs/arm64"
|
||||
find "$SRC/build-android" -name 'libopenxr_loader.so' -exec cp {} "$PKG/libs/arm64/" \;
|
||||
echo "loader -> $PKG/libs/arm64/libopenxr_loader.so"
|
||||
file "$PKG/libs/arm64/libopenxr_loader.so" 2>/dev/null || true
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# Decode the APK manifest and report whether the VR/OpenXR declarations Meta's
|
||||
# runtime expects are present. For RE4 VR (already a shipping Quest VR app) these
|
||||
# are almost certainly already there, so this is usually a no-op confirmation.
|
||||
# If something IS missing, edit the decoded manifest and rebuild with apktool.
|
||||
set -euo pipefail
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_env.sh"
|
||||
APKTOOL=("${JAVA_HOME:+$JAVA_HOME/bin/}java" -jar "$APKTOOL_JAR")
|
||||
ORIG="${1:-$ROOT/dump/base.apk}"
|
||||
WORK="$ROOT/packaging/work/decoded"
|
||||
|
||||
rm -rf "$WORK"
|
||||
"${APKTOOL[@]}" d -f -s -o "$WORK" "$ORIG" >/dev/null
|
||||
M="$WORK/AndroidManifest.xml"
|
||||
echo "decoded manifest: $M"; echo
|
||||
|
||||
check() { grep -q "$2" "$M" && echo " [present] $1" || echo " [MISSING] $1 -> $2"; }
|
||||
echo "VR / OpenXR declarations:"
|
||||
check "headtracking feature" 'android.hardware.vr.headtracking'
|
||||
check "VR intent category" 'com.oculus.intent.category.VR'
|
||||
check "Samsung vr_only mode" 'com.samsung.android.vr.application.mode'
|
||||
check "supportedDevices meta" 'com.oculus.supportedDevices'
|
||||
check "handtracking permission" 'com.oculus.permission.HAND_TRACKING'
|
||||
echo
|
||||
echo "If any are MISSING, edit $M then rebuild:"
|
||||
echo " ${APKTOOL[*]} b -o repacked.apk $WORK # then zipalign + apksigner (see repack.sh)"
|
||||
echo "Note: switching vrapi->OpenXR usually needs NO manifest change; the loader"
|
||||
echo " resolves the runtime. This is a confirmation step."
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env bash
|
||||
# Create a debug keystore for re-signing the repacked APK (one-time).
|
||||
set -euo pipefail
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_env.sh"
|
||||
KS="$PKG/debug.keystore"
|
||||
[ -f "$KS" ] && { echo "keystore exists: $KS"; exit 0; }
|
||||
"$KEYTOOL" -genkeypair -v -keystore "$KS" -alias re4vrshim \
|
||||
-keyalg RSA -keysize 2048 -validity 10000 \
|
||||
-storepass android -keypass android \
|
||||
-dname "CN=RE4VR Shim, OU=Dev, O=Homebrew, C=US"
|
||||
echo "created $KS (storepass/keypass: android)"
|
||||
Executable
+58
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env bash
|
||||
# Repack a dumped-your-own RE4 VR APK with the OpenXR shim + the OpenXR loader, then
|
||||
# zipalign + re-sign. The original libovrplatformloader.so is kept untouched, so the
|
||||
# platform's real entitlement check runs unchanged — on Quest you own the title.
|
||||
# Entitlement handling on hardware with no Meta backend is out of scope for this repo.
|
||||
#
|
||||
# ./repack.sh [input.apk]
|
||||
# input.apk : your dumped base.apk (default: ../dump/base.apk)
|
||||
set -euo pipefail
|
||||
source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/_env.sh"
|
||||
SHIM="$SHIM_OUT"
|
||||
[ -n "${BT:-}" ] && [ -d "$BT" ] || { echo "Android build-tools not found. Run scripts/fetch_deps.sh"; exit 1; }
|
||||
|
||||
ORIG="${1:-$ROOT/dump/base.apk}"
|
||||
OUT="$PKG/out/re4vr-shim.apk"
|
||||
|
||||
[ -f "$ORIG" ] || { echo "input APK not found: $ORIG"; exit 1; }
|
||||
[ -f "$SHIM/libOVRPlugin.so" ] || { echo "build the arm64 shim first: shim/build_android.sh"; exit 1; }
|
||||
|
||||
mkdir -p "$PKG/out" "$PKG/work"
|
||||
WORK="$PKG/work/re4vr.apk"
|
||||
cp "$ORIG" "$WORK"
|
||||
|
||||
# 1) strip old signatures (we re-sign below)
|
||||
zip -q -d "$WORK" 'META-INF/*.RSA' 'META-INF/*.SF' 'META-INF/*.MF' 'META-INF/*.EC' 2>/dev/null || true
|
||||
|
||||
# 2) stage the replacement libs under lib/arm64-v8a/
|
||||
STAGE="$PKG/work/stage"; rm -rf "$STAGE"; mkdir -p "$STAGE/lib/arm64-v8a"
|
||||
cp "$SHIM/libOVRPlugin.so" "$STAGE/lib/arm64-v8a/"
|
||||
if [ -f "$PKG/libs/arm64/libopenxr_loader.so" ]; then
|
||||
cp "$PKG/libs/arm64/libopenxr_loader.so" "$STAGE/lib/arm64-v8a/"
|
||||
echo "bundling libopenxr_loader.so"
|
||||
else
|
||||
echo "WARN: packaging/libs/arm64/libopenxr_loader.so missing — shim NEEDs it at"
|
||||
echo " runtime. Build it: packaging/build_openxr_loader.sh"
|
||||
fi
|
||||
# original libovrplatformloader.so is left untouched -> the real entitlement check runs.
|
||||
echo "keeping original libovrplatformloader.so (real entitlement; you own the title)"
|
||||
# P4 passthru RE: bundle the SONAME-patched REAL OVRPlugin so the shim can dlopen + forward
|
||||
# to it (debug.re4vr.passthru*). Opt-in: only when staged in packaging/libs/arm64/.
|
||||
if [ -f "$PKG/libs/arm64/libOVRPlugin_real.so" ]; then
|
||||
cp "$PKG/libs/arm64/libOVRPlugin_real.so" "$STAGE/lib/arm64-v8a/"
|
||||
echo "bundling libOVRPlugin_real.so (P4 passthru)"
|
||||
fi
|
||||
|
||||
# 3) replace/add the libs, stored (-0) so zipalign -p can page-align them
|
||||
( cd "$STAGE" && zip -q -0 -X "$WORK" lib/arm64-v8a/*.so )
|
||||
|
||||
# 4) align, then sign (v1+v2+v3)
|
||||
"$BT/zipalign" -f -p 4 "$WORK" "$PKG/work/aligned.apk"
|
||||
[ -f "$PKG/debug.keystore" ] || "$PKG/make_debug_keystore.sh"
|
||||
"$BT/apksigner" sign --ks "$PKG/debug.keystore" --ks-pass pass:android \
|
||||
--out "$OUT" "$PKG/work/aligned.apk"
|
||||
"$BT/apksigner" verify "$OUT" && echo "signature OK"
|
||||
|
||||
echo
|
||||
echo "built: $OUT"
|
||||
echo "install: adb install -r \"$OUT\" (push the OBB too: dump/obb/* -> /sdcard/Android/obb/com.Armature.VR4/)"
|
||||
Executable
+106
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/env bash
|
||||
# steamframe_patches.sh — off-Quest APK patches for porting RE4 VR (UE4) to a NON-Meta
|
||||
# Android VR device (Pico / Steam Frame / Monado-on-Android). These are NOT needed on a
|
||||
# real Quest 2 (the shim alone suffices there); they only matter once Build.MANUFACTURER
|
||||
# isn't "Oculus" and Meta-only manifest libs aren't present on the target.
|
||||
#
|
||||
# Our own implementation of the transforms; the set is informed by Overport's open-source
|
||||
# patcher (docs/research/related-work.md). It does NOT use any of their binaries — we ship
|
||||
# our own libOVRPlugin shim via repack.sh; this only fixes the APK's Java/manifest so UE
|
||||
# takes the Oculus HMD path and the package installs off-Quest.
|
||||
#
|
||||
# Pipeline: apktool decode -> manifest + smali patches -> apktool build -> zipalign -> sign.
|
||||
# Run repack.sh FIRST (it swaps in our shim libs); feed its output here.
|
||||
#
|
||||
# ./steamframe_patches.sh [in.apk] [out.apk]
|
||||
# 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.
|
||||
# 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; }
|
||||
|
||||
IN="${1:-$PKG/out/re4vr-shim.apk}"
|
||||
OUT="${2:-$PKG/out/re4vr-steamframe.apk}"
|
||||
[ -f "$IN" ] || { echo "input APK not found: $IN (run repack.sh first)"; exit 1; }
|
||||
|
||||
# --- DO NOT INSTALL ON QUEST guard ----------------------------------------------------
|
||||
# This output is for NON-Quest devices. On a Quest the uses-(native-)library strip removes
|
||||
# Meta libs the runtime relies on -> can break launch/features. The Quest build is the plain
|
||||
# repack.sh output (out/re4vr-shim.apk). Require explicit acknowledgement so this can't be
|
||||
# produced/installed against a Quest by accident. Bypass in automation with NOT_QUEST=1.
|
||||
cat <<'WARN'
|
||||
========================================================================
|
||||
WARNING: NON-QUEST build. DO NOT install the output on a Meta Quest.
|
||||
It strips Meta-only manifest libs the Quest needs (can break launch).
|
||||
For Quest, install the plain repack.sh output: out/re4vr-shim.apk
|
||||
========================================================================
|
||||
WARN
|
||||
if [ "${NOT_QUEST:-0}" != 1 ]; then
|
||||
if [ -t 0 ]; then
|
||||
read -r -p "Target is NOT a Quest and I understand this build will break on Quest [y/N] " ack
|
||||
case "$ack" in y|Y|yes|YES) ;; *) echo "aborted (not confirmed)"; exit 1 ;; esac
|
||||
else
|
||||
echo "Refusing to run non-interactively. Re-run with NOT_QUEST=1 to confirm the target is not a Quest."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
WORK="$PKG/work/steamframe"
|
||||
rm -rf "$WORK"; mkdir -p "$WORK"
|
||||
DEC="$WORK/dec"
|
||||
|
||||
echo "== apktool decode =="
|
||||
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' \
|
||||
-o -path '*/com/epicgames/unreal/GameActivity.smali' 2>/dev/null)
|
||||
[ "${#GA[@]}" -gt 0 ] || echo "WARN: no com/epicgames/{ue4,unreal}/GameActivity.smali found (UE patches skipped)"
|
||||
|
||||
# --- 1) Oculus device spoof: UE gates the Oculus HMD path on Build.MANUFACTURER/MODEL.
|
||||
# Off-Quest these are wrong, so OculusHMD never inits and our shim is never called.
|
||||
# Replace the field reads with constant "Oculus" / "Quest 2" in the same register. ---
|
||||
spoofed=0
|
||||
for f in "${GA[@]}"; do
|
||||
perl -0777 -pe 's/sget-object (v\d+|p\d+), Landroid\/os\/Build;->MANUFACTURER:Ljava\/lang\/String;/const-string $1, "Oculus"/g' -i "$f"
|
||||
perl -0777 -pe 's/sget-object (v\d+|p\d+), Landroid\/os\/Build;->MODEL:Ljava\/lang\/String;/const-string $1, "Quest 2"/g' -i "$f"
|
||||
spoofed=1
|
||||
done
|
||||
[ "$spoofed" = 1 ] && echo "patched: Build.MANUFACTURER->\"Oculus\", MODEL->\"Quest 2\" (device spoof)"
|
||||
|
||||
# --- 2) Neuter AndroidThunkJava_ForceQuit: drop the System.exit(I) call so a failed
|
||||
# off-Quest check can't hard-kill the app before we recover. ---
|
||||
for f in "${GA[@]}"; do
|
||||
perl -0777 -pe 's/(\.method public AndroidThunkJava_ForceQuit\(\)V.*?)(invoke-static \{[vp]\d+\}, Ljava\/lang\/System;->exit\(I\)V\n)(.*?\.end method)/$1$3/s' -i "$f" \
|
||||
&& grep -q 'AndroidThunkJava_ForceQuit' "$f" && echo "patched: removed System.exit in AndroidThunkJava_ForceQuit ($(basename "$(dirname "$f")"))"
|
||||
done
|
||||
|
||||
# --- 3) Manifest: strip <uses-library>/<uses-native-library> that name Meta-only libs
|
||||
# (keep libopenxr.google.so) — they'd block install/launch off-Quest. ---
|
||||
MAN="$DEC/AndroidManifest.xml"
|
||||
if [ -f "$MAN" ]; then
|
||||
before=$(grep -cE 'uses-(native-)?library' "$MAN" || true)
|
||||
perl -0777 -pe 's/[ \t]*<uses-(native-)?library[^>]*android:name="(?!libopenxr\.google\.so)[^"]*"[^>]*\/>\n//g' -i "$MAN"
|
||||
after=$(grep -cE 'uses-(native-)?library' "$MAN" || true)
|
||||
echo "patched: manifest uses-(native-)library entries $before -> $after (kept libopenxr.google.so)"
|
||||
fi
|
||||
|
||||
echo "== apktool build =="
|
||||
apktool b -o "$WORK/unsigned.apk" "$DEC" >/dev/null
|
||||
|
||||
echo "== align + sign =="
|
||||
"$BT/zipalign" -f -p 4 "$WORK/unsigned.apk" "$WORK/aligned.apk"
|
||||
[ -f "$PKG/debug.keystore" ] || "$PKG/make_debug_keystore.sh"
|
||||
"$BT/apksigner" sign --ks "$PKG/debug.keystore" --ks-pass pass:android --out "$OUT" "$WORK/aligned.apk"
|
||||
"$BT/apksigner" verify "$OUT" && echo "signature OK"
|
||||
|
||||
echo
|
||||
echo "built: $OUT"
|
||||
echo "NOTE: NON-QUEST build — do NOT install on a Quest (use repack.sh's re4vr-shim.apk there)."
|
||||
echo " prepared for the non-Quest bring-up; validate on the target headset."
|
||||
echo "Still a shim TODO (runtime, not packaging): controller-pose offset for non-Touch"
|
||||
echo "controllers (Overport's DisableControllerOffset) — handle in shim/src/xr_input.c."
|
||||
Reference in new issue
Block a user