diff --git a/CLAUDE.md b/CLAUDE.md index 6aef988..01cc1f3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -547,6 +547,13 @@ wait now also applies to Unity 2019 without the Oculus XR Plugin (`UnityOculusCh config `hijack_responses`), but OVRPort's `patch_clean_up_frida` removes its loadLibrary call, so it never runs on the Frame; `frame.ovr_trace` (opt-in, `native/ovrtrace`: 1138 exported ovr_* stubs → real loader functions, logcat tag `fp_ovrtrace`: calls, PopMessage answers, unanswered requests every 10 s) is the next diagnostic. +**Unity built-in VR input (2026-10-05):** probe builds (`FRAMEPORT_INPUT_PROBE=1 frameport build …`) showed +OVRPlugin returns the full Touch state for every controller mask (also Go masks) and the presses (A/B/stick clicks) +reach both BattleSisters and Accounting+ code. Unity 2019's libunity creates Touch devices only when +SystemInfo.deviceModel == "Oculus Quest"; Lepton's Android is "Valve" "Lepton" (liblepton/properties.sh) → Go-style +devices only (libunity polls masks 0x8000000/0x1000000/0x2000000) → `unity_oculus_check` revision 3 repoints that +string to "Valve Lepton" (same length). Unity 2017 (Accounting+) has no such string ("Oculus Pacific" only): still +open. The probe log caps at 120 changes. **Lepton storage (2026-09-30):** each app's /sdcard (= /storage/emulated/0 → `/lepton-data/external`) has `Movies`/`Download`/`Documents` symlinked to the Frame's `~/Videos`/`~/Downloads`/`~/Documents` (liblepton/mounting.sh, only if they exist at start); agent v24 `storage_targets` reads that mapping. Android's MediaProvider canonicalises paths to /home/steamos/... and rejects every file ("doesn't appear under [/system/media...]"), `sm list-volumes` is empty: the media index never works, apps must browse folders. Lepton installs with `adb install -g` (runtime permissions granted, MANAGE_EXTERNAL_STORAGE too). Files: `install/files.py`, `frameport frame send|storage`, GUI Files tab (formerly Frame → Send files). **SteamVR per-app settings (2026-09-30):** editing steamvr.vrsettings while SteamVR runs is lost; the web API (127.0.0.1:27062 /app/setsettings) needs `x-steamvr-secret`. `native/vrsettings` = `fp_vrsettings.exe` (freestanding, OpenVR `FnTable:IVRSettings_003` as a Utility app, loads SteamVR's bin/win64/openvr_api.dll) sets them live and SteamVR persists them: section `steam.app.`, keys `preferredRefreshRate` (float) and `motionSmoothingOverride` (0 global, 1 on, 2 off, 3 always). Steam Link (vrlink) lists the Frame's rates 72/80/90/96/108/120/144 in vrserver.txt and follows the per-app preference ("host preferred N Hz"; whether the key is honoured is unverified in-headset yet). Judder metric: vrcompositor.txt session summary dropped + "Timed out. N total" (Stormland: 0 dropped but 313 timeouts in 2 min); fpsVR (`%LOCALAPPDATA%\fpsVR\*.json`, 0.1 ms histograms) gives p99 CPU/GPU ms. `pcvr.steamvr_tuning` (default on, PC only) applies on Play: highest rate whose budget ≥ p99×1.05, at least one step down, smoothing on. diff --git a/src/frameport/locales/template.json b/src/frameport/locales/template.json index 8af42b7..8358b37 100644 --- a/src/frameport/locales/template.json +++ b/src/frameport/locales/template.json @@ -1093,7 +1093,7 @@ "Uninstall. ": "", "Uninstalled {title}": "", "Uninstalled {title} and deleted its files on this PC ({size})": "", - "Unity's built-in Oculus support (Unity 2017–2019) only starts VR when Android has Meta's com.oculus.systemactivities package; without it the game runs as a 2D app (the Android home screen or a black window, e.g. Accounting+, BattleSisters). Points that package name in libunity.so at \"android\", which always exists. Games on Unity's built-in VR (2017–2018, and 2019 without the Oculus XR Plugin) also get the frame wait their legacy frame loop never makes (libfp_ovrp.so calls ovrp_WaitToBeginFrame before ovrp_Update2; without it no frame starts, the dashboard freezes or the GPU hangs).": "", + "Unity's built-in Oculus support (Unity 2017–2019) only starts VR when Android has Meta's com.oculus.systemactivities package; without it the game runs as a 2D app (the Android home screen or a black window, e.g. Accounting+, BattleSisters). Points that package name in libunity.so at \"android\", which always exists. Games on Unity's built-in VR (2017–2018, and 2019 without the Oculus XR Plugin) also get the frame wait their legacy frame loop never makes (libfp_ovrp.so calls ovrp_WaitToBeginFrame before ovrp_Update2; without it no frame starts, the dashboard freezes or the GPU hangs), and Unity's Touch controllers, which it only creates on a device model \"Oculus Quest\" (Lepton's Android is \"Valve Lepton\"; else buttons do nothing).": "", "Unity's TMP_InputField / InputField wait for Android's on-screen keyboard and close themselves a frame later when there is none (Lepton has no on-screen keyboard; Meta's system keyboard does this on a Quest), so a selected text field only flashes a caret. Rewrites TouchScreenKeyboardShouldBeUsed -> false and isKeyboardUsingEvents (TMP) / InPlaceEditing (uGUI) -> true in libil2cpp.so, found per game with Cpp2IL (downloaded on first use), so fields stay selected and take key presses: Steam's keyboard (with \"Show the app window\") or Type on Frame from the PC.": "", "Unity: disable MSAA": "", "Unity: keep MSAA off at runtime (OVRManager)": "", diff --git a/src/frameport/patches/frame/unity_oculus_check.py b/src/frameport/patches/frame/unity_oculus_check.py index 043c5c2..8534bcb 100644 --- a/src/frameport/patches/frame/unity_oculus_check.py +++ b/src/frameport/patches/frame/unity_oculus_check.py @@ -16,6 +16,11 @@ ALWAYS_THERE = "android" # Unity's legacy frame loop never calls ovrp_WaitToBeginFrame: its ovrp_Update2 lookup goes to native/ovrpshim SHIM = "libfp_ovrp.so" UPDATE, SHIM_UPDATE = "ovrp_Update2", "fpov_Update2" +# Unity's built-in Oculus input creates Touch controllers only when SystemInfo.deviceModel (Build.MANUFACTURER + " " + +# Build.MODEL) is "Oculus Quest"; anything else gets Go-style devices (touchpad, tracked remotes), so the game's Touch +# bindings never fire (BattleSisters, Accounting+: hands tracked, buttons dead, although OVRPlugin reports every press). +# Lepton's Android is "Valve" "Lepton": the same length, so the compared string is repointed in place. +QUEST_MODEL, LEPTON_MODEL = "Oculus Quest", "Valve Lepton" # input diagnostics (off): the C# P/Invoke names in libil2cpp.so pointed at the shim's wrappers, which log what they # return, report input focus as true and release buttons held > 2 s. Accounting+ still didn't pass "press any button" # with them (input reached the game cleanly), so they stay off; switch on to investigate another game. @@ -34,9 +39,10 @@ class UnityOculusCheck(Patch): "libunity.so at \"android\", which always exists. Games on Unity's built-in VR (2017–2018, and 2019 " "without the Oculus XR Plugin) also get the frame wait their legacy frame loop never makes " "(libfp_ovrp.so calls ovrp_WaitToBeginFrame before ovrp_Update2; without it no frame starts, the " - "dashboard freezes or the GPU hangs).") + "dashboard freezes or the GPU hangs), and Unity's Touch controllers, which it only creates on a " + "device model \"Oculus Quest\" (Lepton's Android is \"Valve Lepton\"; else buttons do nothing).") order = 45 - revision = 2 # 2: frame wait also for Unity 2019 without the Oculus XR Plugin + revision = 3 # 2: frame wait also for Unity 2019 without the Oculus XR Plugin; 3: Touch input (device model) @staticmethod def _major(a) -> int: @@ -71,6 +77,10 @@ class UnityOculusCheck(Patch): return False data, loops = elf.replace_rodata_string(data, UPDATE, SHIM_UPDATE) if self.legacy_loop(ctx.analysis) \ else (data, 0) + if loops: + data, models = elf.replace_rodata_string(data, QUEST_MODEL, LEPTON_MODEL) + if models: + ctx.notes.append(f"libunity.so: device model \"{QUEST_MODEL}\" -> \"{LEPTON_MODEL}\" (Touch input)") plugin = ws.lib("libOVRPlugin.so") if loops and ws.abi == "arm64-v8a" and ws.has(plugin): ovrp = ws.read(plugin) diff --git a/tests/test_unity_runtime.py b/tests/test_unity_runtime.py index 3d590da..db0104d 100644 --- a/tests/test_unity_runtime.py +++ b/tests/test_unity_runtime.py @@ -272,3 +272,11 @@ def test_user_recipes_get_a_catalog_update_offer(tmp_path, monkeypatch): g = library.game("com.x.vr4") assert "catalog_update" not in g and g["recipe"]["patches"]["adapter.scale"] == {"value": 0.9} assert g["recipe"].get("source") != "user" and "adapter.vk_shader_fix" in g["recipe"]["patches"] + + +def test_unity_device_model_is_repointed_in_place(): + from frameport.patches.frame import unity_oculus_check as C + + # Unity compares SystemInfo.deviceModel with "Oculus Quest" before creating Touch controllers; Lepton's Android is + # Build.MANUFACTURER "Valve" + Build.MODEL "Lepton" (liblepton/properties.sh): an in-place edit needs equal length + assert len(C.LEPTON_MODEL) <= len(C.QUEST_MODEL) and C.LEPTON_MODEL == "Valve Lepton"