Compare commits

...
Author SHA1 Message Date
spoopyghosty0 1e28aeaeda FrameBridge profile_remap: leave Meta's detached-controller paths out (#328)
Meta's Unity OpenXR features add Detached Touch/Touch Plus/Pro devices on
/user/detached_controller_meta/left|right. Unity merges same-named actions of all devices, so the
first such action (thumbstick) failed with XR_ERROR_PATH_UNSUPPORTED on the Frame (no
XR_META_detached_controllers), Unity attached no action set and neither Unity nor OVRPlugin got
controller input: hands on the floor (Spatial Zombies). Without the extension in the runtime, these
paths are now left out of xrCreateAction subaction paths and suggested bindings, and
xrGetCurrentInteractionProfile answers no profile for them. Headless: actions created, Touch
bindings accepted, action spaces created, no ACTIONSET_NOT_ATTACHED.
2026-10-15 23:55:29 -04:00
spoopyghosty0 43476bd180 FrameBridge profile_remap: report the game's own profile for remapped hands (#327, #328)
Games that suggest controller bindings only for Meta's Touch Plus/Pro profiles got them moved to
oculus/touch_controller, but xrGetCurrentInteractionProfile then answered oculus/touch_controller,
a profile they never suggested. Unity's OpenXR input creates its devices by the reported profile,
found none and left the controllers unconnected: hands lying on the floor, no buttons (Hotel
Infinity). Such hands are now reported with the game's own (last remapped) profile; games that
suggest Touch themselves are unchanged. Logic moved to profile_report.c with a host test.
2026-10-15 20:32:52 -04:00
spoopyghosty0 939845d379 Catalog: Clockwork is The Last Clockwinder (Meta's store name; reported in #110) 2026-10-15 19:51:45 -04:00
spoopyghosty0 35eb12df08 CLAUDE.md: every triage sweeps for unanswered comments (closed issues, PRs, discussion replies) 2026-10-15 19:41:32 -04:00
spoopyghosty0 570a98b91a Triage: meta-entitlement for Quest games; catalog: Deadly Delivery unsupported (#322)
Deadly Delivery 1.0.30 renders (72 fps), then its lobby's Meta entitlement check
fails four times and it quits. The oculus-entitlement signature is PC VR only,
so the Quest log had no finding. New Quest signature meta-entitlement (error:
offline games may log it and play on; an online game's exit fails the test).
FramePort doesn't get around entitlement checks. docs/GAMES.md regenerated
(it was also behind on other catalog entries).
2026-10-15 19:34:51 -04:00
18 changed files with 460 additions and 25 deletions

No files matched your search

+7
View File
@@ -1055,6 +1055,13 @@ GitHub identity + credentials are maintainer-local (`CLAUDE.local.md`): check `g
GitHub write. **The owner approves before posting, labelling or closing anything.** Reply style: "I"/neutral, never
we/our/us; never mention agent version numbers or internal tool versions; show the owner every draft in full with a
short recap of the thread so far; read **all** comments on a thread first (a later one often has the zip or the answer).
- **Every triage also sweeps for unanswered comments**, not just new threads: open AND recently closed issues, PRs
(incl. merged/closed ones) and discussions (top-level comments and their replies) whose latest comment isn't the
maintainer's, plus earlier user comments a later maintainer reply skipped. Users often ask follow-ups after a fix or
after closing (#294: two questions after the reporter closed it; #249: a Q&A discussion with no reply for days;
#161/#110: requests posted after the close). Quick check: `gh issue list --state all --search "updated:>=<date>"
--json number,comments --jq '.[] | select(.comments[-1].author.login != "spoopyghosty0")'`; discussions via GraphQL
(`discussions(orderBy:UPDATED_AT)` with `comments{replies}`).
- **Issue templates & labels** (`.github/ISSUE_TEMPLATE/`): `[Game report]` → `game-compatibility` (a game that
doesn't work right); `[Working recipe]` (`working-config.yml`) → `working-config` (a headset-verified config to add);
`[App]` → `bug` (FramePort itself). Mislabel/relabel to match.
+2 -2
View File
@@ -13,11 +13,11 @@ e8c0965c188665a4e565e4a3af983ef1866d1cf9225665cc90d8df286bc96ef3 ./arm64-v8a/li
5432674a59d02f411cd853a5dc57e3fa547d1ac518f25bf433fc54c43677177a ./arm64-v8a/libfpglmv.so
8a8f6b1da8952cb3933a5b53558424a5fc56043b2527a16bb6dd62a00d2de01a ./arm64-v8a/libframe_xrshim.so
19ba5ed3c4b780f5ae46251f6958f552acfc5f13c8bb39f995579593fa8990c6 ./arm64-v8a/libglshim.so
0f058921e9c1f5b9ef5847cc9e6b80a23e75028c76c1d3bb02d1610b535f81c9 ./arm64-v8a/libopenxr_loader_generic.so
1cc65695b223f7d56913275554c233f6b1d38a433068f26413fa97135f202a78 ./arm64-v8a/libopenxr_loader_generic.so
1feaeafad467c4cafdf2b018a4d84b0bee200c3f711697b4ce97e66a3ba256ca ./arm64-v8a/libovrplatformcompat.so
a4ed80ae45ca90758239f152ea5a2e8c127d491e1e697ea5680db6fd7f72db07 ./arm64-v8a/libvrapi.so
a4ed80ae45ca90758239f152ea5a2e8c127d491e1e697ea5680db6fd7f72db07 ./arm64-v8a/libvrapi_foveation.so
9e8a7ce599b62a7c607b0d3637e4ec1b1ea1974c3373a7af3f5920ad2adc5e00 ./armeabi-v7a/libopenxr_loader_generic.so
53a7d6340707766a7c7465f20084c520d259c76581f02a7e516a52cfeb624d51 ./armeabi-v7a/libopenxr_loader_generic.so
1871eae093432d277da4bc751bf5f3269dfcf11f9168260b0c3caadb0dedb19d ./dex/oculusos-stubs.dex
b634ab5640e258563c536e658cad87080553df6f34f62269a21d554844e58bfe ./hevc/COPYING.FFmpeg
141de01f0d40b97d4f9a720db5ab8ea442f6aa35db5272048899292884994a1c ./hevc/libstagefrighthw.so
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
package: com.FlatHeadStudio.DeadlyDelivery
title: Deadly Delivery
status: unsupported
notes: "Quits after Meta's entitlement check fails: it needs Meta's online services."
tested_version: 1.0.30
engine: Unity
xr: OpenXR
verified:
date: '2026-10-15'
issue: 322
+1 -1
View File
@@ -1,5 +1,5 @@
package: com.asg.clockworkdev
title: Clockwork
title: The Last Clockwinder
status: works
tested_version: '1.29'
engine: Unity
+10
View File
@@ -11,6 +11,16 @@ signatures:
severity: error
diagnosis: A DLC manager reports a Meta asset-file status error after VR starts (e.g. PixelRipped 1995). The asset-file check failed; the log does not identify the native call or asset ID. Try the experimental asset-file bypass (empty list, installed status with no file path); this does not supply DLC and has not been verified in the game.
suggest: [frame.asset_files_stub]
- id: meta-entitlement
# Quest games (the pcvr oculus-entitlement signature covers Rift games). Only explicit failure results: a bare
# ovr_PlatformInitialize error isn't matched. `error`, not fatal: offline games may log it and play on; an online
# game that then quits (e.g. Deadly Delivery, GitHub #322) fails the test through its exit.
pattern: '(?i)entitlement check failed|\bnot entitled to (use|play)|ovrPlatformInitialize_(NotEntitled|UnableToVerify|FileInvalid|SignatureInvalid)'
severity: error
diagnosis: The game's Meta licence check (entitlement) failed. It needs Meta's online login and licence check,
which the Frame doesn't have; games that depend on it (often online games) quit or stay offline. FramePort
doesn't get around such checks.
suggest: []
- id: lua-lightuserdata-pointer
pattern: 'bad light userdata pointer'
severity: fatal
+20 -3
View File
@@ -14,10 +14,12 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| Asgard's Wrath 2 | Quest | ✅ Works | |
| Assassin's Creed Nexus | Quest | ✅ Works | |
| Audica | Quest | ✅ Works | |
| AugmentedEmpire | Quest | ✅ Works | |
| BAM | Quest | ✅ Works | |
| BARTENDER VR SIMULATOR | Quest | ✅ Works | |
| Batman: Arkham Shadow | Quest | ✅ Works | |
| BattleGlide | Quest | ✅ Works | |
| Battlemarked | Quest | ✅ Works | |
| BattleSisters | Quest | ✅ Works | |
| Beat Saber | Quest | ✅ Works | |
| Beat Saber | Quest | ✅ Works | |
@@ -29,7 +31,6 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| BONELAB | Quest | ✅ Works | |
| Carve Snowboarding | Quest | ✅ Works | |
| Caves | Quest | ✅ Works | |
| Clockwork | Quest | ✅ Works | |
| COMPOUND | Quest | ✅ Works | |
| Cook-Out | Quest | ✅ Works | |
| Cosmodread | Quest | ✅ Works | |
@@ -37,6 +38,7 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| cubism | Quest | ✅ Works | |
| Dance Central | Quest | ✅ Works | |
| Deep Cuts | Quest | ✅ Works | |
| Demeo | Quest | ✅ Works | |
| Demeter | Quest | ✅ Works | |
| Dinosaur Island | Quest | ✅ Works | |
| Doom3Quest | Quest | ✅ Works | |
@@ -46,10 +48,12 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| Eleven Table Tennis | Quest | ✅ Works | |
| Eleven: Table Tennis VR | PC VR | ✅ Works | |
| Espire 2 | Quest | ✅ Works | |
| Eye of the Temple | Quest | ✅ Works | |
| FrenzyVR | Quest | ✅ Works | |
| Fruit Ninja | Quest | ✅ Works | |
| Fruit Salon | Quest | ✅ Works | |
| Genotype | Quest | ✅ Works | |
| Ghost Town | Quest | ✅ Works | |
| Ghostbusters: Rise of the Ghost Lord | Quest | ✅ Works | |
| GORN2 | Quest | ✅ Works | |
| Gravity Lab | Quest | ✅ Works | |
@@ -61,6 +65,7 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| In Death: Unchained | Quest | ✅ Works | |
| Into The Radius 2 | Quest | ✅ Works | |
| Job Simulator | Quest | ✅ Works | |
| Journey of the Gods | Quest | ✅ Works | |
| Jurassic World Aftermath Collection | Quest | ✅ Works | |
| Keep Talking and Nobody Explodes | Quest | ✅ Works | |
| Kill It With Fire VR | Quest | ✅ Works | |
@@ -68,18 +73,23 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| Lambda1VR | Quest | ✅ Works | |
| LEGO® Bricktales | Quest | ✅ Works | |
| Lucky's Tale | Quest | ✅ Works | |
| Maestro | Quest | ✅ Works | |
| Marvel's Deadpool VR | Quest | ✅ Works | |
| Marvel's Iron Man VR | Quest | ✅ Works | |
| Max Mustard | Quest | ✅ Works | |
| Medieval Dynasty New Settlement | Quest | ✅ Works | |
| Metro Awakening | Quest | ✅ Works | |
| Mini Motor Racing X | Quest | ✅ Works | |
| Mobile Suit Gundam: Silver Phantom | Quest | ✅ Works | |
| Moss | Quest | ✅ Works | |
| Moss: Book II | Quest | ✅ Works | |
| Munkr Funkr (The Legacy Days) | Quest | ✅ Works | |
| Nano | Quest | ✅ Works | |
| NEX Player | Quest | ✅ Works | |
| NFL PRO ERA II | Quest | ✅ Works | |
| NOPE CHALLENGE | Quest | ✅ Works | |
| Ocean Rift | Quest | ✅ Works | |
| Operation Wolf Returns: First Mission VR | Quest | ✅ Works | |
| palazzo_santacruz | Quest | ✅ Works | |
| Path of the Warrior | Quest | ✅ Works | |
| Pistol Whip | Quest | ✅ Works | |
@@ -97,9 +107,11 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| Riven | Quest | ✅ Works | |
| Robo Recall | Quest | ✅ Works | |
| RUINSMAGUS | Quest | ✅ Works | |
| Shadow Point | Quest | ✅ Works | |
| Shoot The Zombirds VR | Quest | ✅ Works | |
| Shores of Loci | Quest | ✅ Works | |
| SilentSlayer | Quest | ✅ Works | |
| Smash Drums | Quest | ✅ Works | |
| Sniper Elite VR | Quest | ✅ Works | |
| Sniper Elite VR: Winter Warrior | Quest | ✅ Works | |
| Space Pirate Trainer Quest | Quest | ✅ Works | |
@@ -112,9 +124,11 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| SUPERHOT VR | Quest | ✅ Works | |
| SynthRiders | Quest | ✅ Works | |
| Taskmaster | Quest | ✅ Works | |
| Tea For God | Quest | ✅ Works | |
| TetrisEffect | Quest | ✅ Works | |
| The Boys VR | Quest | ✅ Works | |
| The Climb 2 | Quest | ✅ Works | |
| The Last Clockwinder | Quest | ✅ Works | |
| The Room VR | Quest | ✅ Works | |
| The Tale of Onogoro | Quest | ✅ Works | |
| Time Crisis II VR (Test) | Quest | ✅ Works | |
@@ -128,6 +142,7 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| Vader Immortal: Episode II | Quest | ✅ Works | |
| Vader Immortal: Episode III | Quest | ✅ Works | |
| Vampire Survivors | Quest | ✅ Works | |
| Vendetta Forever | Quest | ✅ Works | |
| VR HOT Quest | Quest | ✅ Works | |
| VR4 | Quest | ✅ Works | |
| Walkabout Mini Golf | Quest | ✅ Works | |
@@ -144,12 +159,15 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| ExploreVR | Quest | ⚠️ Works with issues | The 3D world warps and shifts with head movement (wrong perspective). |
| Fisherman | Quest | ⚠️ Works with issues | Loading screens have corruption, but everything works otherwise. |
| Freedom | Quest | ⚠️ Works with issues | Volume can't be changed in the game. |
| Homeworld: Vast Reaches | Quest | ⚠️ Works with issues | The game starts but after selecting to start a new game it seems to crash. |
| MacacoVR | Quest | ⚠️ Works with issues | 2d loading screens are glitchy, otherwise main gameplay is fine |
| Majora's Mask VR | Quest | ⚠️ Works with issues | Audio on the file select screen sounds crunchy. |
| Myst | Quest | ⚠️ Works with issues | Minor graphical glitches on some objects. |
| Phantom: Covert Ops | Quest | ⚠️ Works with issues | DLC/store button crashes (no Meta store). |
| Pinball FX VR | Quest | ⚠️ Works with issues | Plays; mixed reality mode not working yet. |
| Plezy | Quest | ⚠️ Works with issues | Some videos play with strong artifacts and wrong colours; the cause is unknown, possibly hardware video decoding. |
| Quantum Void | Quest | ⚠️ Works with issues | no hands |
| Ragnarock | Quest | ⚠️ Works with issues | |
| Silhouette | Quest | ⚠️ Works with issues | Hand-tracking game; the Frame synthesizes hands from controllers, so it is janky. |
| Spunky Dungeon | Quest | ⚠️ Works with issues | Gear VR / Oculus Go game (32-bit only), runs through FramePort's 32-bit bridge (experimental). |
| Swarm 2 | Quest | ⚠️ Works with issues | MSAA creates visible seams around edge geometry and also the skybox edge |
@@ -158,9 +176,8 @@ Generated from [catalog/games](../catalog/games) by `scripts/compat_list.py`.
| Time Stall | Quest | ⚠️ Works with issues | Both eyes distort during movement (unresolved). |
| Vermillion | Quest | ⚠️ Works with issues | Runs. |
| WiiCompiled VR | Quest | ⚠️ Works with issues | To add a game: in FramePort's Files tab, upload your .wcgame file to this game's storage, folder Android/data/org.wiicompiled.quest/files/WiiCompiledOpenXRVR… |
| Deadly Delivery | Quest | ❌ Doesn't run | Quits after Meta's entitlement check fails: it needs Meta's online services. |
| Espire 1: VR Operative (Quest Edition) | Quest | ❌ Doesn't run | Mesa GL driver crash during texture upload. |
| GOLF+ | Quest | ❌ Doesn't run | Black screen. |
| HITMAN 3 VR: Reloaded | Quest | ❌ Doesn't run | Vulkan driver crash (freedreno), even without Valve layers. |
| Journey of the Gods | Quest | ❌ Doesn't run | 32-bit only; the Frame has no AArch32. |
| Roblox | Quest | ❌ Doesn't run | Crashes on its first VR frame on the Frame. |
| Shadow Point | Quest | ❌ Doesn't run | 32-bit only; the Frame has no AArch32. |
+3 -3
View File
@@ -904,7 +904,7 @@ Stops a gray or frozen screen in Unity games that turn on anti-aliasing while ru
- `frame.unity_gl_shim`
- **Changes:** Changes the APK (a rebuild and reinstall apply it).
- **Suggested (on):** Unity GLES game that may switch to MSAA at runtime: hide multisampled render-to-texture, which crashes the Frame's GL driver (for example The Room VR).
- **Catalog games (23):** Audica, BattleSisters, Beat Saber, Clockwork, COMPOUND, Cosmodread, Down the Rabbit Hole, Drunkn Bar Fight and 15 more.
- **Catalog games (23):** Audica, BattleSisters, Beat Saber, COMPOUND, Cosmodread, Down the Rabbit Hole, Drunkn Bar Fight, End Space and 15 more.
- **Suggested by launch-test signatures:**
- `unreal-msrtt-crash` (fatal): The game's graphics crashed a few seconds after start.
- **Source:** [patches/frame/vrapi_bridge.py](../src/frameport/patches/frame/vrapi_bridge.py)
@@ -973,7 +973,7 @@ Keeps a smoothing setting off that the game turns on itself and that can freeze
- `frame.unity_runtime_msaa_off` · revision 2
- **Changes:** Changes the APK (a rebuild and reinstall apply it).
- **Suggested (on):** GLES Unity game whose OVRManager turns 4x MSAA on at runtime: multisampled render-to-texture can hang the Frame's GPU.
- **Catalog games (28):** Audica, BattleSisters, Beat Saber, Clockwork, COMPOUND, Cosmodread, Down the Rabbit Hole, Drunkn Bar Fight and 20 more.
- **Catalog games (28):** Audica, BattleSisters, Beat Saber, COMPOUND, Cosmodread, Down the Rabbit Hole, Drunkn Bar Fight, End Space and 20 more.
- **Suggested by launch-test signatures:**
- `unity-render-crash` (fatal): The game's graphics crashed, so the picture freezes or stays gray.
- `unity-runtime-msaa` (info): The game turns MSAA on at runtime, which can hang the Frame's GPU.
@@ -1828,7 +1828,7 @@ For apps that only know Quest 3/Pro controllers: without it they get no buttons.
- Game settings → Controllers → "Treat newer Quest controllers as Touch" (advanced).
- **Source:** [patches/settings.py](../src/frameport/patches/settings.py)
> Apps that suggest controller bindings only for Meta's newer profiles (Touch Plus, Touch Pro), which the Frame's runtime rejects (XR_ERROR_PATH_UNSUPPORTED), get the same bindings as oculus/touch_controller instead (components Touch lacks are dropped). Without it such apps get no controller input.
> Apps that suggest controller bindings only for Meta's newer profiles (Touch Plus, Touch Pro), which the Frame's runtime rejects (XR_ERROR_PATH_UNSUPPORTED), get the same bindings as oculus/touch_controller instead (components Touch lacks are dropped), and the runtime's answer for each hand is reported to them as their own profile (Unity's OpenXR input only connects controllers of a profile it knows). Meta's detached-controller paths (XR_META_detached_controllers, which the Frame lacks) are left out of the app's actions and bindings: one such path made Unity attach no controller input at all. Without it such apps get no controller input.
### <a id="adapter-proximity_emul"></a>Animate fingers from touch
+3
View File
@@ -24,6 +24,7 @@ version is `catalog/triage.yaml` (used by `frameport test` / the Job screen); ke
| A Unity OpenXR game closes right after the OpenXR instance is created: `F VrApiLoader: vrapi_SetPropertyInt was called before vrapi_Initialize()!`, SIGABRT on UnityMain (GitHub #62, Jurassic World Aftermath) | the APK still ships Meta's VrApi loader; OVRPlugin (1.89, on OpenXR) calls it without starting VrApi and Meta's loader aborts. The VrApi bridge can't replace it (39 of the loader's 114 functions: OVRPlugin wouldn't link) | `frame.vrapi_stub` (same exports, each returns 0); triage `vrapi-before-init` |
| A VrApi game exits at once: `UnsatisfiedLinkError … dlopen failed: cannot locate symbol "vrapi_PollEvent"` (GitHub #57, BlazeRush) | the engine imports a VrApi function the VrApi bridge didn't implement | the bridge has `vrapi_PollEvent`, `RecenterPose`, `SetDisplayRefreshRate`, `GetSystemPropertyFloatArray` (rebuild the game); the static check "VrApi functions resolvable" names any other missing one; triage `vrapi-symbol-missing` |
| A Team Beef port (Lambda1VR, RTCWQuest, …) exits at once: `UnsatisfiedLinkError: dlopen failed: library "libopenxr_loader_valve.so" not found` (GitHub #59) | TBXR loads `openxr_loader_<Build.MANUFACTURER>` (Lepton: valve) and takes the Meta path only when `strstr(OPENXR_HMD, "meta")` matches, else Pico's | `frame.tbxr_vendor` (empty `libopenxr_loader_valve.so` + the "meta" literal → "alve"). Game data (`xash/`, …) goes to /sdcard: upload it with the Files tab to the game's storage. Black eyes with `TBXR: Incomplete frame buffer object: GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE`: TBXR always uses multisampled render-to-texture (even `--msaa 1`); `frame.tbxr_vendor` links the GL shim, which hands it single-sampled versions (log `GL shim: multisampled render-to-texture … drawn single-sampled`) |
| A Quest game renders, then logs `Entitlement check failed` / `ovrPlatformInitialize_NotEntitled` (often with retries) and quits, e.g. `SIG: 9` after `[Photon] Disconnected` (GitHub #322, Deadly Delivery) | The game's Meta licence check (entitlement) needs Meta's online login, which the Frame doesn't have; online-only games quit (triage `meta-entitlement`) | None: FramePort doesn't get around entitlement checks. Offline games that log it and play on are fine |
| Black screen after VR starts; `Asset status error (code 100)` in `DLC_Manager:AssetStatusCallback` (GitHub #203, e.g. PixelRipped 1995) | Meta asset-file status fails after entitlement succeeds; the log does not identify the native call or asset ID | Try `frame.asset_files_stub` (opt-in, triage-only): native/langpack answers GetList with an empty successful list and StatusById with installed status, an empty path and the requested ID. No DLC files are supplied; download requests retain their existing behavior. Disable with `FRAMEPORT_ASSET_FILES_STUB=0`. Host-tested only; whether this fixes startup needs a game/device test |
| Missing sound or extra content in a game whose OBB folder has separate `*.pak` files (GitHub #61, Star Wars: Tales from the Galaxy's Edge: seasons + Wwise banks) | the game asks Meta's platform for these asset files (`ovr_AssetFile_GetList` / `DownloadById`); OVRPort's loader doesn't list them | copy every content/sound-bank PAK and use `frame.asset_files` (native/langpack lists the data's `*.pak` as installed `default` assets, IAP `free`, and answers DownloadById at once with the path; off: env FRAMEPORT_ASSET_FILES=0). Tales also needs the splash and depth-size fixes below |
| A SideQuest backup has `apk/` and a sibling `obb/` with content/sound-bank PAKs, but the Frame only has the main OBB (GitHub #61, Tales from the Galaxy's Edge) | older scans treated that layout as loose expansion files and selected only the named OBBs; the asset-file patch then found no PAKs | the scanner chooses the OBB folder by version first, then keeps the whole sibling data folder when all its expansion files are selected for this APK; app updates repair affected library entries and refresh asset/language-pack detection. Reinstall to copy the missing files, or scan the backup again first. Folders with other packages or unselected OBB versions still send only the selected OBBs |
@@ -138,6 +139,8 @@ version is `catalog/triage.yaml` (used by `frameport test` / the Job screen); ke
| Unity game freezes on its loading screen ~10-15 s in (Yupitergrad, GitHub #235); launch.log has `GrantPermissionsActivity: Permission grant result … permission=android.permission.BLUETOOTH_CONNECT`, then OVRPlugin `SetupDisplayObjects(): Unable to find EGLConfig for configId 0` and no more frames; triage `bhaptics-permission-prompt` | bHaptics' vest SDK (`com.bhaptics.commons.PermissionUtils.requestBluetoothPermission`) asks for Android 12's BLUETOOTH_CONNECT; on Lepton's Android 11 it can't be granted at install, so Android's permission screen takes the foreground for a moment, the game pauses/resumes and OVRPlugin's GLES compositor stops drawing | `frame.bhaptics_permission` (suggested by analysis `bhaptics_permission`): the request returns at once (`Dex.return_early`) |
| Unity game aborts at start: `Abort message: 'cannot create thread specific key for __cxa_get_globals()'` (SIGABRT in a library such as `libFirebaseCppApp-*.so`, for example Wander 0.3570, GitHub #198); triage `pthread-keys-exhausted` | Bionic allows 128 pthread keys per process; Unity 6 takes ~40 (most without a destructor), large native libraries (Gecko `libxul.so`, Firebase, avatar SDKs) and the Frame's own libraries (Mesa/Zink, FrameBridge, OVRPort, Valve's Vulkan layers) take the rest | `frame.unity_tls_key_pool` (opt-in; suggested for Unity 6 with Firebase C++ or Gecko): libunity.so's `pthread_key_*`/`pthread_get/setspecific` imports are renamed to `fp_tkey_*` (`elf.rename_imports`, versions set global) and `libfp_tkeys.so` (`native/tlskeys`) is added to its DT_NEEDED; destructor-less keys become slots behind one real key (logs `fp_tkeys: pooling Unity's thread keys`). Approach by Klownicle (#198) |
| Controllers do nothing (for example stuck on a setup screen); log: `xrSuggestInteractionProfileBindings … XR_ERROR_PATH_UNSUPPORTED` | the game only suggests bindings for Meta's newer profiles (Touch Plus / Touch Pro); the Frame's Android runtime knows `oculus/touch_controller`, not those | adapter `profile_remap` (default on): the bindings are suggested again for Touch (log: `controller profile … -> oculus/touch_controller`) |
| Controllers "don't sync": virtual hands/controllers lie on the floor below you, no buttons (Unity OpenXR games that only suggest Touch Plus/Pro, e.g. Hotel Infinity #327); log with `input_diag=1`: only `meta/touch_controller_plus` (or Pro) suggested, `controller profile … -> oculus/touch_controller` | the runtime then reports `oculus/touch_controller` for each hand, a profile the game never suggested: Unity's OpenXR input creates no device for it | adapter `profile_remap` (default on) reports the game's own profile for such hands (log: `profile_remap: /user/hand/left reported as meta/touch_controller_plus`) |
| Controllers "don't sync": hands on the floor below you, no buttons, in a Unity OpenXR game with Meta's OpenXR features (e.g. Spatial Zombies #328); log: `xrCreateAction: XR_ERROR_PATH_UNSUPPORTED` (input_diag: `xrCreateAction -> XR_ERROR_PATH_UNSUPPORTED (thumbstick)`), then `xrGetCurrentInteractionProfile: XR_ERROR_ACTIONSET_NOT_ATTACHED` | Meta's "Detached … Controller" devices add `/user/detached_controller_meta/left|right` to Unity's merged actions; the Frame has no XR_META_detached_controllers, the action fails and Unity attaches no action set (OVRPlugin's input goes with it) | adapter `profile_remap` (default on) leaves those paths out of actions and bindings (log: `profile_remap: action thumbstick without 2 detached-controller path(s)`) |
| Some buttons do nothing or the wrong thing (for example a Quest game's X/Y on the Frame's left controller) | the game binds only Quest Touch and SteamVR maps Touch onto the Frame controllers with its own remap (Touch left X/Y land on the d-pad, right X and Y repeat B), or the runtime rejected a profile or an input call | turn on adapter `input_diag` (diagnostics, off by default; `frameport settings <pkg> input_diag=1`) and look for `input_diag:` lines: `bindings: <profile> accepted` or `unsupported: interaction profile <profile> -> <result>` with the rejected paths, `bindings: /user/hand/<hand> uses <profile>`, `unsupported: function <name>`, `unsupported: xrCreateAction -> <result> (<action>)` |
| Unity game hangs or the whole Frame restarts (for example in a menu); log: `The current MSAA level is 0, but the recommended MSAA level is 4. Switching to the recommended level.` (for example Lucky's Tale) | Meta's OVRManager (`useRecommendedMSAALevel`) turns 4x MSAA on at runtime, past QualitySettings; multisampled render-to-texture on GLES/Zink hangs the GPU | `frame.unity_runtime_msaa_off` (Cpp2IL: `OVRDisplay.get_recommendedMSAALevel` → 0) |
| Unity game: one eye shows only effects or gray, the other is fine (for example I Am Cat, Oculus XR Plugin on GLES) | the game's single-pass multiview rendering goes wrong for array slice 1 (the adapter submits both slices correctly) | `frame.unity_multipass` (Cpp2IL: `OculusSettings.GetStereoRenderingMode` → MultiPass); `swap_eyes=1` tells whether the game or the adapter is at fault |
+78
View File
@@ -0,0 +1,78 @@
// SPDX-License-Identifier: GPL-3.0-only
// Included by session_fixes.c: Meta's detached controllers (XR_META_detached_controllers: a controller put down while
// hand tracking runs) on a runtime without that extension. Uses profile_remap, runtime_has_detached_controllers,
// active_instance, lookup and LOG.
//
// Meta's Unity OpenXR features add "Detached ... Controller" devices for /user/detached_controller_meta/left|right.
// Unity merges same-named actions of every enabled device into one action, so the first action with such a subaction
// path (e.g. "thumbstick") fails with XR_ERROR_PATH_UNSUPPORTED on the Frame's runtime, Unity attaches no action set
// at all and neither Unity nor OVRPlugin get controller input: hands lying at the play-space origin on the floor, no
// buttons (Spatial Zombies, GitHub #328). With profile_remap on and the runtime lacking the extension, these paths are
// left out of the game's actions and suggested bindings, and xrGetCurrentInteractionProfile answers "no profile"
// for them (a detached controller that never shows up) instead of XR_ERROR_PATH_UNSUPPORTED.
#define DETACHED_PREFIX "/user/detached_controller_meta/"
static int detached_filter_on(void) { return profile_remap && !runtime_has_detached_controllers && active_instance; }
// 1 if `path` is a detached controller's top-level path or one of its input/output paths
static int detached_path(XrPath path) {
PFN_xrPathToString to_string = (PFN_xrPathToString)lookup(active_instance, "xrPathToString");
char name[XR_MAX_PATH_LENGTH];
uint32_t size = 0;
return path && to_string && XR_SUCCEEDED(to_string(active_instance, path, sizeof(name), &size, name)) &&
!strncmp(name, DETACHED_PREFIX, strlen(DETACHED_PREFIX));
}
// xrCreateAction: `info` without the detached subaction paths, in *copy (paths in `kept`, room for all of them).
// Returns `info` itself when there is nothing to leave out.
static const XrActionCreateInfo *detached_strip_action(const XrActionCreateInfo *info, XrActionCreateInfo *copy,
XrPath *kept) {
if (!info || !info->countSubactionPaths || !info->subactionPaths || !kept || !detached_filter_on()) return info;
uint32_t n = 0;
for (uint32_t i = 0; i < info->countSubactionPaths; ++i)
if (!detached_path(info->subactionPaths[i])) kept[n++] = info->subactionPaths[i];
if (n == info->countSubactionPaths) return info;
*copy = *info;
copy->countSubactionPaths = n;
copy->subactionPaths = kept;
static int logged;
if (logged++ < 3)
LOG("profile_remap: action %s without %u detached-controller path(s) (the runtime has no "
"XR_META_detached_controllers)", info->actionName, info->countSubactionPaths - n);
return copy;
}
// xrSuggestInteractionProfileBindings: `suggested` without bindings of detached controllers, in *copy (bindings in
// `kept`, room for all of them). Returns `suggested` itself when there is nothing to leave out.
static const XrInteractionProfileSuggestedBinding *detached_strip_bindings(
const XrInteractionProfileSuggestedBinding *suggested, XrInteractionProfileSuggestedBinding *copy,
XrActionSuggestedBinding *kept) {
if (!suggested || !suggested->countSuggestedBindings || !suggested->suggestedBindings || !kept ||
!detached_filter_on())
return suggested;
uint32_t n = 0;
for (uint32_t i = 0; i < suggested->countSuggestedBindings; ++i)
if (!detached_path(suggested->suggestedBindings[i].binding)) kept[n++] = suggested->suggestedBindings[i];
if (n == suggested->countSuggestedBindings) return suggested;
*copy = *suggested;
copy->countSuggestedBindings = n;
copy->suggestedBindings = kept;
static int logged;
if (logged++ < 8) {
PFN_xrPathToString to_string = (PFN_xrPathToString)lookup(active_instance, "xrPathToString");
char profile[XR_MAX_PATH_LENGTH] = "?";
uint32_t size = 0;
if (to_string) to_string(active_instance, suggested->interactionProfile, sizeof(profile), &size, profile);
LOG("profile_remap: %s bindings without %u detached-controller path(s)", profile,
suggested->countSuggestedBindings - n);
}
return copy;
}
// xrGetCurrentInteractionProfile: a detached controller's path the runtime doesn't know has no profile.
static XrResult detached_current_profile(XrPath user, XrInteractionProfileState *state, XrResult result) {
if (result != XR_ERROR_PATH_UNSUPPORTED || !state || !detached_filter_on() || !detached_path(user)) return result;
state->interactionProfile = XR_NULL_PATH;
return XR_SUCCESS;
}
+6 -12
View File
@@ -147,6 +147,7 @@ static int sync_guard; // xrSyncActions one at a time with xrPollEvent, pa
static int profile_remap = 1; // Meta's newer controller profiles (rejected by the Frame) -> oculus/touch_controller
static int proximity_emul; // finger proximity from capacitive touch: 1 thumb, 2 thumb + index (session_fixes.c)
static int runtime_has_proximity; // the runtime has XR_FB_touch_controller_proximity itself
static int runtime_has_detached_controllers; // the runtime has XR_META_detached_controllers (detached_controllers.c)
static int layer_debug; // diagnostics: layers, swapchains, session states, spaces, aim/grip, refresh rates
static int input_diag; // diagnostics: controller profiles, bindings, missing functions, failing input calls
static int stable_local; // keep every LOCAL space the app creates on the session-start origin
@@ -422,6 +423,8 @@ XRAPI_ATTR XrResult XRAPI_CALL xrCreateInstance(const XrInstanceCreateInfo *info
for (uint32_t j = 0; available && j < available_count; ++j)
if (!strcmp(available[j].extensionName, "XR_FB_composition_layer_image_layout")) runtime_has_image_layout = 1;
else if (!strcmp(available[j].extensionName, "XR_FB_touch_controller_proximity")) runtime_has_proximity = 1;
else if (!strcmp(available[j].extensionName, "XR_META_detached_controllers"))
runtime_has_detached_controllers = 1;
// Layer types are only valid if their extension ends up enabled; some apps submit them regardless.
int has_equirect = 0, has_equirect2 = 0, has_cylinder = 0, has_cube = 0;
for (uint32_t i = 0; i < kept; ++i) {
@@ -2004,7 +2007,7 @@ XRAPI_ATTR XrResult XRAPI_CALL xrGetCurrentInteractionProfile(XrSession session,
PFN_xrGetCurrentInteractionProfile fn =
(PFN_xrGetCurrentInteractionProfile)lookup(active_instance, "xrGetCurrentInteractionProfile");
if (!fn) return XR_ERROR_FUNCTION_UNSUPPORTED;
XrResult result = fn(session, user, state);
XrResult result = detached_current_profile(user, state, fn(session, user, state));
if (input_diag && XR_SUCCEEDED(result) && state) input_diag_current_profile(user, state->interactionProfile);
if (layer_debug && active_instance) { // which device each hand has, as the runtime reports it
PFN_xrPathToString str = (PFN_xrPathToString)lookup(active_instance, "xrPathToString");
@@ -2018,16 +2021,7 @@ XRAPI_ATTR XrResult XRAPI_CALL xrGetCurrentInteractionProfile(XrSession session,
static int logged;
if (logged++ < 40) LOG("layer_debug: interaction profile of %s: %s (result %d)", who, what, result);
}
if (!controller_fix || XR_FAILED(result) || !state || !state->interactionProfile || !active_instance)
return result;
PFN_xrPathToString to_string = (PFN_xrPathToString)lookup(active_instance, "xrPathToString");
PFN_xrStringToPath to_path = (PFN_xrStringToPath)lookup(active_instance, "xrStringToPath");
char name[XR_MAX_PATH_LENGTH];
uint32_t size = 0;
if (to_string && to_path &&
XR_SUCCEEDED(to_string(active_instance, state->interactionProfile, sizeof(name), &size, name)) &&
(strstr(name, "/valve/") || strstr(name, "/khr/generic_controller")))
to_path(active_instance, "/interaction_profiles/oculus/touch_controller", &state->interactionProfile);
if (XR_SUCCEEDED(result) && state) current_profile_fix(user, state);
return result;
}
@@ -2199,7 +2193,7 @@ XRAPI_ATTR XrResult XRAPI_CALL xrGetInstanceProcAddr(XrInstance instance, const
if (layer_debug) HOOK_AS(xrGetActionStateBoolean, hook_xrGetActionStateBoolean)
if (layer_debug || aim_correction_on() || profile_remap || input_diag || proximity_emul)
HOOK_AS(xrSuggestInteractionProfileBindings, hook_xrSuggestInteractionProfileBindings)
if (proximity_emul) HOOK_AS(xrCreateAction, hook_xrCreateAction)
if (proximity_emul || profile_remap) HOOK_AS(xrCreateAction, hook_xrCreateAction)
if (layer_debug || aim_correction_on()) {
HOOK_AS(xrCreateActionSpace, hook_xrCreateActionSpace)
}
+38
View File
@@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-3.0-only
// Included by session_fixes.c (after TOUCH_PROFILE and app_suggested_touch): what xrGetCurrentInteractionProfile
// tells the game about each hand. Uses controller_fix, profile_remap, active_instance, lookup and LOG.
// * controller_fix: SteamVR's own profiles (valve/..., khr/generic_controller) are reported as oculus/touch_controller,
// the profile Quest games have bindings for.
// * profile_remap: when the game suggested bindings only for one of Meta's newer profiles (Touch Plus/Pro) and
// hook_xrSuggestInteractionProfileBindings suggested them again for Touch, the runtime reports
// oculus/touch_controller, a profile the game never suggested. Unity's OpenXR plugin creates its input devices by
// the reported profile, finds none for Touch and leaves the controllers unconnected: hands lying at the play-space
// origin on the floor, no buttons (e.g. Hotel Infinity, GitHub #327). Such a hand is reported with the game's own
// profile instead (the last one remapped: the Touch bindings the runtime uses are that profile's).
static char remapped_profile[XR_MAX_PATH_LENGTH]; // set by hook_xrSuggestInteractionProfileBindings
static void current_profile_fix(XrPath user, XrInteractionProfileState *state) {
if ((!controller_fix && !profile_remap) || !state->interactionProfile || !active_instance) return;
PFN_xrPathToString to_string = (PFN_xrPathToString)lookup(active_instance, "xrPathToString");
PFN_xrStringToPath to_path = (PFN_xrStringToPath)lookup(active_instance, "xrStringToPath");
char name[XR_MAX_PATH_LENGTH];
uint32_t size = 0;
if (!to_string || !to_path ||
XR_FAILED(to_string(active_instance, state->interactionProfile, sizeof(name), &size, name)))
return;
int touch = !strcmp(name, TOUCH_PROFILE);
if (controller_fix && (strstr(name, "/valve/") || strstr(name, "/khr/generic_controller")) &&
XR_SUCCEEDED(to_path(active_instance, TOUCH_PROFILE, &state->interactionProfile)))
touch = 1;
if (!touch || !profile_remap || !*remapped_profile || app_suggested_touch ||
XR_FAILED(to_path(active_instance, remapped_profile, &state->interactionProfile)))
return;
static int logged;
if (logged++ < 4) {
char who[XR_MAX_PATH_LENGTH] = "?";
to_string(active_instance, user, sizeof(who), &size, who);
LOG("profile_remap: %s reported as %s (the app's profile) instead of oculus/touch_controller", who,
remapped_profile + strlen("/interaction_profiles/"));
}
}
+25 -2
View File
@@ -296,6 +296,8 @@ static const char *const TOUCH_LEFT[] = {"input/x/click", "input/x/touch", "inpu
static const char *const TOUCH_RIGHT[] = {"input/a/click", "input/a/touch", "input/b/click", "input/b/touch",
"input/system/click"};
static int app_suggested_touch;
#include "profile_report.c" // remapped_profile, current_profile_fix (xrGetCurrentInteractionProfile)
#include "detached_controllers.c" // detached controllers' paths on a runtime without them
static int in_list(const char *s, const char *const *list, size_t n) {
for (size_t i = 0; i < n; ++i)
@@ -332,8 +334,13 @@ static XRAPI_ATTR XrResult XRAPI_CALL hook_xrCreateAction(XrActionSet set, const
XrAction *action) {
PFN_xrCreateAction fn = (PFN_xrCreateAction)lookup(active_instance, "xrCreateAction");
if (!fn) return XR_ERROR_FUNCTION_UNSUPPORTED;
XrResult r = fn(set, info, action);
if (XR_SUCCEEDED(r) && info && action) { // OVRPlugin's action names
XrActionCreateInfo copy;
XrPath *kept = info && info->countSubactionPaths ? calloc(info->countSubactionPaths, sizeof(*kept)) : NULL;
XrResult r = fn(set, detached_strip_action(info, &copy, kept), action);
free(kept);
if (XR_FAILED(r) && input_diag) // this hook replaces input_diag's wrapper of xrCreateAction
diag_call_failed("xrCreateAction", r, diag_name_detail(info ? info->actionName : NULL));
if (proximity_emul && XR_SUCCEEDED(r) && info && action) { // OVRPlugin's action names
if (!strcmp(info->actionName, "hand_thumb_proximity")) thumb_proximity_action = *action;
else if (!strcmp(info->actionName, "hand_trigger_proximity")) trigger_proximity_action = *action;
else return r;
@@ -429,11 +436,26 @@ static XrResult remap_to_touch(XrInstance instance, PFN_xrSuggestInteractionProf
return r;
}
static XrResult suggest_bindings(XrInstance instance, PFN_xrSuggestInteractionProfileBindings fn,
const XrInteractionProfileSuggestedBinding *suggested);
static XRAPI_ATTR XrResult XRAPI_CALL hook_xrSuggestInteractionProfileBindings(XrInstance instance,
const XrInteractionProfileSuggestedBinding *suggested) {
PFN_xrSuggestInteractionProfileBindings fn =
(PFN_xrSuggestInteractionProfileBindings)lookup(instance, "xrSuggestInteractionProfileBindings");
if (!fn) return XR_ERROR_FUNCTION_UNSUPPORTED;
XrInteractionProfileSuggestedBinding copy;
XrActionSuggestedBinding *kept = suggested && suggested->countSuggestedBindings
? calloc(suggested->countSuggestedBindings, sizeof(*kept)) : NULL;
const XrInteractionProfileSuggestedBinding *use = detached_strip_bindings(suggested, &copy, kept);
// a profile with only detached-controller bindings: nothing the runtime could bind
XrResult r = use != suggested && !use->countSuggestedBindings ? XR_SUCCESS : suggest_bindings(instance, fn, use);
free(kept);
return r;
}
static XrResult suggest_bindings(XrInstance instance, PFN_xrSuggestInteractionProfileBindings fn,
const XrInteractionProfileSuggestedBinding *suggested) {
PFN_xrPathToString to_string = (PFN_xrPathToString)lookup(instance, "xrPathToString");
if (suggested && to_string && (layer_debug || aim_correction_on()))
for (uint32_t i = 0; i < suggested->countSuggestedBindings; ++i) {
@@ -456,6 +478,7 @@ static XRAPI_ATTR XrResult XRAPI_CALL hook_xrSuggestInteractionProfileBindings(X
}
if (r != XR_ERROR_PATH_UNSUPPORTED || app_suggested_touch || !IN(profile, NEWER_TOUCH)) return r;
XrResult remapped = remap_to_touch(instance, fn, to_string, suggested, profile);
if (XR_SUCCEEDED(remapped)) snprintf(remapped_profile, sizeof(remapped_profile), "%s", profile);
return XR_SUCCEEDED(remapped) ? XR_SUCCESS : r;
}
+1 -1
View File
@@ -173,7 +173,7 @@
"Appearance": "",
"AppImage": "",
"Apply with Update on Frame.": "",
"Apps that suggest controller bindings only for Meta's newer profiles (Touch Plus, Touch Pro), which the Frame's runtime rejects (XR_ERROR_PATH_UNSUPPORTED), get the same bindings as oculus/touch_controller instead (components Touch lacks are dropped). Without it such apps get no controller input.": "",
"Apps that suggest controller bindings only for Meta's newer profiles (Touch Plus, Touch Pro), which the Frame's runtime rejects (XR_ERROR_PATH_UNSUPPORTED), get the same bindings as oculus/touch_controller instead (components Touch lacks are dropped), and the runtime's answer for each hand is reported to them as their own profile (Unity's OpenXR input only connects controllers of a profile it knows). Meta's detached-controller paths (XR_META_detached_controllers, which the Frame lacks) are left out of the app's actions and bindings: one such path made Unity attach no controller input at all. Without it such apps get no controller input.": "",
"Artwork for {get}": "",
"Artwork updated ({src}). The Frame's Steam library still shows the old art.": "",
"Artwork updated for {get}": "",
+4 -1
View File
@@ -106,7 +106,10 @@ SETTINGS = [
("profile_remap", "int", 1, "Newer Touch profiles as Touch",
"Apps that suggest controller bindings only for Meta's newer profiles (Touch Plus, Touch Pro), which the Frame's "
"runtime rejects (XR_ERROR_PATH_UNSUPPORTED), get the same bindings as oculus/touch_controller instead "
"(components Touch lacks are dropped). Without it such apps get no controller input."),
"(components Touch lacks are dropped), and the runtime's answer for each hand is reported to them as their own "
"profile (Unity's OpenXR input only connects controllers of a profile it knows). Meta's detached-controller paths "
"(XR_META_detached_controllers, which the Frame lacks) are left out of the app's actions and bindings: one such "
"path made Unity attach no controller input at all. Without it such apps get no controller input."),
("haptic_scale", "float", 1.0, "Vibration strength",
"Scales every controller vibration the game asks for (0-1): plain vibrations, Meta's amplitude envelopes and "
"PCM buffers. For games whose vibrations feel much stronger than on a Quest."),
+223
View File
@@ -0,0 +1,223 @@
"""What FrameBridge's xrGetCurrentInteractionProfile reports (native/adapter/profile_report.c): SteamVR's profiles as
Touch (controller_fix) and, after profile_remap moved a game's Touch Plus/Pro bindings to Touch, the game's own profile
(GitHub #327: Unity's OpenXR input found no device for oculus/touch_controller, the hands lay on the floor). Compiled
for this host with a small harness standing in for the adapter and the runtime.
Opt-in (FRAMEPORT_NATIVE_TESTS=1, -m native): needs the OpenXR headers in native/.cache (from native/build.py)."""
import os
import shutil
import subprocess
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
INC = next(iter((ROOT / "native/.cache").glob("openxr-*")), None)
CC = shutil.which("cc") or shutil.which("clang")
pytestmark = [pytest.mark.native, pytest.mark.skipif(
not os.environ.get("FRAMEPORT_NATIVE_TESTS") or not INC or not CC,
reason="set FRAMEPORT_NATIVE_TESTS=1 (needs the OpenXR headers from native/build.py and a host C compiler)")]
HARNESS = r"""
#include <openxr/openxr.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LOG(...) (fprintf(stderr, __VA_ARGS__), fputc('\n', stderr))
static XrInstance active_instance = (XrInstance)(uintptr_t)1;
static int controller_fix = 1, profile_remap = 1, app_suggested_touch;
static const char *const TOUCH_PROFILE = "/interaction_profiles/oculus/touch_controller";
static char paths[16][XR_MAX_PATH_LENGTH];
static int path_count;
static XRAPI_ATTR XrResult XRAPI_CALL fake_string_to_path(XrInstance i, const char *s, XrPath *out) {
(void)i;
for (int k = 0; k < path_count; ++k) if (!strcmp(paths[k], s)) { *out = (XrPath)(k + 1); return XR_SUCCESS; }
snprintf(paths[path_count], XR_MAX_PATH_LENGTH, "%s", s);
*out = (XrPath)++path_count;
return XR_SUCCESS;
}
static XRAPI_ATTR XrResult XRAPI_CALL fake_path_to_string(XrInstance i, XrPath p, uint32_t cap, uint32_t *n,
char *out) {
(void)i;
if (!p || p > (XrPath)path_count) return XR_ERROR_PATH_INVALID;
*n = (uint32_t)snprintf(out, cap, "%s", paths[p - 1]) + 1;
return XR_SUCCESS;
}
static PFN_xrVoidFunction lookup(XrInstance i, const char *name) {
(void)i;
if (!strcmp(name, "xrPathToString")) return (PFN_xrVoidFunction)fake_path_to_string;
if (!strcmp(name, "xrStringToPath")) return (PFN_xrVoidFunction)fake_string_to_path;
return NULL;
}
#include "profile_report.c"
static XrPath P(const char *s) { XrPath p; fake_string_to_path(0, s, &p); return p; }
static const char *answer(const char *hand, const char *runtime) {
XrInteractionProfileState state = {XR_TYPE_INTERACTION_PROFILE_STATE, NULL, runtime ? P(runtime) : XR_NULL_PATH};
current_profile_fix(P(hand), &state);
return state.interactionProfile ? paths[state.interactionProfile - 1] : "(none)";
}
int main(void) {
const char *touch = "/interaction_profiles/oculus/touch_controller";
const char *frame = "/interaction_profiles/valve/frame_controller";
const char *plus = "/interaction_profiles/meta/touch_controller_plus";
// no remap: SteamVR's profile becomes Touch, Touch stays Touch, none stays none
printf("%s\n", answer("/user/hand/left", frame));
printf("%s\n", answer("/user/hand/left", touch));
printf("%s\n", answer("/user/hand/left", NULL));
// the game's Touch Plus bindings were moved to Touch: Touch (and SteamVR's profile) are reported as Touch Plus
snprintf(remapped_profile, sizeof(remapped_profile), "%s", plus);
printf("%s\n", answer("/user/hand/left", touch));
printf("%s\n", answer("/user/hand/right", frame));
printf("%s\n", answer("/user/hand/right", NULL));
printf("%s\n", answer("/user/hand/right", "/interaction_profiles/htc/vive_controller"));
// controller_fix off: SteamVR's own profile is passed on unchanged, Touch is still reported as the game's
controller_fix = 0;
printf("%s\n", answer("/user/hand/left", frame));
printf("%s\n", answer("/user/hand/left", touch));
// the game suggested Touch bindings itself: Touch is reported as Touch
controller_fix = 1;
app_suggested_touch = 1;
printf("%s\n", answer("/user/hand/left", touch));
// profile_remap off: Touch as Touch
app_suggested_touch = 0;
profile_remap = 0;
printf("%s\n", answer("/user/hand/left", touch));
return 0;
}
"""
STDOUT = [
"/interaction_profiles/oculus/touch_controller",
"/interaction_profiles/oculus/touch_controller",
"(none)",
"/interaction_profiles/meta/touch_controller_plus",
"/interaction_profiles/meta/touch_controller_plus",
"(none)",
"/interaction_profiles/htc/vive_controller",
"/interaction_profiles/valve/frame_controller",
"/interaction_profiles/meta/touch_controller_plus",
"/interaction_profiles/oculus/touch_controller",
"/interaction_profiles/oculus/touch_controller",
]
@pytest.fixture(scope="module")
def harness(tmp_path_factory):
d = tmp_path_factory.mktemp("profile_report")
(d / "harness.c").write_text(HARNESS)
subprocess.run([CC, "-std=c11", "-D_GNU_SOURCE", "-Wall", "-Wextra", "-Werror", "-Wno-unused-function",
"-Wno-missing-field-initializers", "-I", str(INC), "-I", str(ROOT / "native/adapter"),
str(d / "harness.c"), "-o", str(d / "harness")], check=True)
return d / "harness"
def test_reports_the_games_profile_after_a_remap(harness):
p = subprocess.run([str(harness)], capture_output=True, text=True)
assert p.returncode == 0, p.stderr
assert p.stdout.splitlines() == STDOUT
logged = [line for line in p.stderr.splitlines() if line.startswith("profile_remap:")]
assert logged[:2] == [
"profile_remap: /user/hand/left reported as meta/touch_controller_plus (the app's profile) instead of "
"oculus/touch_controller",
"profile_remap: /user/hand/right reported as meta/touch_controller_plus (the app's profile) instead of "
"oculus/touch_controller",
]
assert len(logged) == 3 # once per report (at most 4)
DETACHED = r"""
#include <openxr/openxr.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define LOG(...) (fprintf(stderr, __VA_ARGS__), fputc('\n', stderr))
static XrInstance active_instance = (XrInstance)(uintptr_t)1;
static int profile_remap = 1, runtime_has_detached_controllers;
static char paths[32][XR_MAX_PATH_LENGTH];
static int path_count;
static XRAPI_ATTR XrResult XRAPI_CALL fake_string_to_path(XrInstance i, const char *s, XrPath *out) {
(void)i;
for (int k = 0; k < path_count; ++k) if (!strcmp(paths[k], s)) { *out = (XrPath)(k + 1); return XR_SUCCESS; }
snprintf(paths[path_count], XR_MAX_PATH_LENGTH, "%s", s);
*out = (XrPath)++path_count;
return XR_SUCCESS;
}
static XRAPI_ATTR XrResult XRAPI_CALL fake_path_to_string(XrInstance i, XrPath p, uint32_t cap, uint32_t *n,
char *out) {
(void)i;
if (!p || p > (XrPath)path_count) return XR_ERROR_PATH_INVALID;
*n = (uint32_t)snprintf(out, cap, "%s", paths[p - 1]) + 1;
return XR_SUCCESS;
}
static PFN_xrVoidFunction lookup(XrInstance i, const char *name) {
(void)i;
if (!strcmp(name, "xrPathToString")) return (PFN_xrVoidFunction)fake_path_to_string;
return NULL;
}
#include "detached_controllers.c"
static XrPath P(const char *s) { XrPath p; fake_string_to_path(0, s, &p); return p; }
static void show_action(const XrActionCreateInfo *info) {
printf("%s:", info->actionName);
for (uint32_t i = 0; i < info->countSubactionPaths; ++i) printf(" %s", paths[info->subactionPaths[i] - 1]);
printf("\n");
}
int main(void) {
XrPath sub[4] = {P("/user/hand/left"), P("/user/hand/right"), P("/user/detached_controller_meta/left"),
P("/user/detached_controller_meta/right")}, kept[4];
XrActionCreateInfo info = {XR_TYPE_ACTION_CREATE_INFO, NULL, "thumbstick", XR_ACTION_TYPE_VECTOR2F_INPUT, 4, sub,
"Thumbstick"}, copy;
show_action(detached_strip_action(&info, &copy, kept));
info.countSubactionPaths = 2; // hands only: passed on as it is
printf("%d\n", detached_strip_action(&info, &copy, kept) == &info);
XrActionSuggestedBinding b[3] = {{(XrAction)(uintptr_t)7, P("/user/hand/left/input/thumbstick")},
{(XrAction)(uintptr_t)7, P("/user/detached_controller_meta/left/input/thumbstick")},
{(XrAction)(uintptr_t)8, P("/user/hand/right/input/a/click")}}, kb[3];
XrInteractionProfileSuggestedBinding s = {XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING, NULL,
P("/interaction_profiles/oculus/touch_controller"), 3, b}, scopy;
const XrInteractionProfileSuggestedBinding *u = detached_strip_bindings(&s, &scopy, kb);
for (uint32_t i = 0; i < u->countSuggestedBindings; ++i) printf("%s\n", paths[u->suggestedBindings[i].binding - 1]);
XrInteractionProfileState st = {XR_TYPE_INTERACTION_PROFILE_STATE, NULL, 5};
XrResult r = detached_current_profile(sub[2], &st, XR_ERROR_PATH_UNSUPPORTED);
printf("%d %llu\n", r, (unsigned long long)st.interactionProfile);
printf("%d\n", detached_current_profile(sub[0], &st, XR_ERROR_PATH_UNSUPPORTED)); // hands: unchanged
printf("%d\n", detached_current_profile(sub[2], &st, XR_ERROR_ACTIONSET_NOT_ATTACHED)); // other errors stay
runtime_has_detached_controllers = 1; // the runtime knows them: nothing changes
info.countSubactionPaths = 4;
printf("%d %d %d\n", detached_strip_action(&info, &copy, kept) == &info,
detached_strip_bindings(&s, &scopy, kb) == &s,
detached_current_profile(sub[2], &st, XR_ERROR_PATH_UNSUPPORTED));
runtime_has_detached_controllers = 0;
profile_remap = 0; // off: nothing changes
printf("%d %d\n", detached_strip_action(&info, &copy, kept) == &info, detached_strip_bindings(&s, &scopy, kb) == &s);
return 0;
}
"""
def test_detached_controller_paths_left_out(tmp_path):
(tmp_path / "detached.c").write_text(DETACHED)
subprocess.run([CC, "-std=c11", "-D_GNU_SOURCE", "-Wall", "-Wextra", "-Werror", "-Wno-unused-function",
"-Wno-missing-field-initializers", "-I", str(INC), "-I", str(ROOT / "native/adapter"),
str(tmp_path / "detached.c"), "-o", str(tmp_path / "detached")], check=True)
p = subprocess.run([str(tmp_path / "detached")], capture_output=True, text=True)
assert p.returncode == 0, p.stderr
assert p.stdout.splitlines() == [
"thumbstick: /user/hand/left /user/hand/right",
"1",
"/user/hand/left/input/thumbstick",
"/user/hand/right/input/a/click",
"0 0",
str(-22), # XR_ERROR_PATH_UNSUPPORTED
str(-46), # XR_ERROR_ACTIONSET_NOT_ATTACHED
"1 1 -22",
"1 1",
]
assert p.stderr.splitlines() == [
"profile_remap: action thumbstick without 2 detached-controller path(s) (the runtime has no "
"XR_META_detached_controllers)",
"profile_remap: /interaction_profiles/oculus/touch_controller bindings without 1 detached-controller path(s)",
]
+29
View File
@@ -510,3 +510,32 @@ def test_obb_and_cube_advice_describes_limits():
advice = next(f for f in cube.findings if f.id == "cube-swapchain-refused")
assert "GLES" in advice.diagnosis
assert "For a Vulkan game, the current build cannot stand in" in advice.diagnosis
# Deadly Delivery 1.0.30 (GitHub #322): renders, then its lobby's Meta entitlement check fails four times and it quits
LOG_ENTITLEMENT = """
10-15 12:45:23.964 212 240 I ActivityManager: Start proc 1214:com.FlatHeadStudio.DeadlyDelivery/u0a14 for \
pre-top-activity {com.FlatHeadStudio.DeadlyDelivery/com.unity3d.player.UnityPlayerActivity}
10-15 12:45:38.349 1214 1238 E Unity : [LobbyManager]: Entitlement check failed: .
10-15 12:45:38.349 1214 1238 E Unity : FlatHeads.<>c__DisplayClass105_0:<CheckEntitlementAndConfigureAsync>b__0(Message)
10-15 12:45:38.352 1214 1238 I Unity : [LobbyManager]: Retrying oculus authentication in 2 seconds.
10-15 12:45:43.372 1214 1286 I FrameBridge: pacing: 71.6 fps, displayTime vs predicted: avg 13.97 ms, max 83.33 ms
10-15 12:45:46.349 1214 1238 I Unity : stopping xr plugin
10-15 12:45:47.424 1214 1238 I Unity : [Photon] Disconnected from Photon (None)
10-15 12:45:48.857 1214 1214 I Process : Sending signal. PID: 1214 SIG: 9
"""
def test_meta_entitlement_quest():
r = triage(LOG_ENTITLEMENT, "EXITED", "com.FlatHeadStudio.DeadlyDelivery")
f = next(f for f in r.findings if f.id == "meta-entitlement")
assert f.severity == "error" and f.suggest == []
assert "Entitlement check failed" in f.evidence
assert "oculus-entitlement" not in {x.id for x in r.findings} # the Rift signature stays PC VR only
assert r.verdict == "fail"
assert "meta-entitlement" in {x.id for x in triage("E Unity: ovrPlatformInitialize_NotEntitled", "EXITED").findings}
# routine Platform SDK lines of games that work don't match; a game that logs it and keeps running still passes
for ok in ("I Unity : CheckEntitlementAndConfigureAsync", "I Unity : Entitlement check passed",
"I Unity : ovr_Entitlement_GetIsViewerEntitled", "I Unity : User is entitled"):
assert "meta-entitlement" not in {x.id for x in triage(ok + "\n" + LOG_OK, "RUNNING").findings}
assert triage("E Unity : Entitlement check failed\n" + LOG_OK, "RUNNING").verdict == "pass"