Skip to content

Device Patching

xAstroBoy edited this page Jul 2, 2026 · 1 revision

Device Patching (patched VrShell via Magisk)

To load custom HSL environments and get quality-of-life patches (far-clip removal, hold-to-fly, the HSL bridge), the headset runs a patched VrShell.apk. It is delivered by a Magisk module that bind-mounts the patched apk over the system one — no re-flashing.

The vrshell_hotswap module

/data/adb/modules/vrshell_hotswap/
├── module.prop
├── VrShell.apk        # the patched apk (module record)
├── VrShell.apk.bak    # a stock backup
├── post-fs-data.sh    # early bind (kept; can be covered — see below)
└── service.sh         # late bind (the reliable one)
/data/adb/vrshell_patched.apk   # the PLAIN-ext4 copy that actually gets bound

The bind target is /system_ext/priv-app/VrShell/VrShell.apk.

Two hard-won gotchas

1. Reboot stale-inode

The module directory lives on magisk overlayfs. Reading VrShell.apk from there at boot can return a stale cached inode (an old build). Fix: the patched apk is written to a plain ext4 path /data/adb/vrshell_patched.apk, and the mount binds that directly (never a copy from the overlay). Always the freshest bytes.

2. magisk_overlayfs covers a post-fs-data bind ⭐ recently fixed

magisk_overlayfs mounts an overlay over /system_ext/priv-app in its own post-fs-data. Module post-fs-data run order on ext4 is hash-based (non-deterministic) — so the hotswap's post-fs-data file-bind sometimes runs before the overlay and gets covered (the overlay's lowerdir serves the stock apk). Result: reboot comes up on the stock shell.

Fix: do the bind from service.sh instead. service.sh runs after every module's post-fs-data (the overlay is guaranteed up) and before the VrShell launcher starts (~ boot_completed), so the bind wins and VrShell loads the patched apk fresh — no restart needed. The script:

STABLE=/data/adb/vrshell_patched.apk
TGT=/system_ext/priv-app/VrShell/VrShell.apk
# Phase 1: retry-bind for ~45s until live size == patched (lands before the launcher)
#   nsenter --mount=/proc/1/ns/mnt -- mount -o bind "$STABLE" "$TGT"
# Phase 2: after boot_completed, if still stock -> re-bind + `am force-stop com.oculus.vrshell` once

Verify: after a reboot, stat -c%s /system_ext/priv-app/VrShell/VrShell.apk should equal the patched size (distinguish patched vs stock by size), and VrShell should be running.

Deploying a new patched build (hotswap, no reboot)

adb push VrShell_patched.apk /data/local/tmp/vp.apk
adb shell 'cp /data/local/tmp/vp.apk /data/adb/vrshell_patched.apk;
           cp /data/local/tmp/vp.apk /data/adb/modules/vrshell_hotswap/VrShell.apk;
           chcon u:object_r:system_file:s0 /data/adb/vrshell_patched.apk;
           sh /data/adb/modules/vrshell_hotswap/service.sh;
           am force-stop com.oculus.vrshell'

The HSL bridge (patched shell, 127.0.0.1:27042)

The patched libquestctl_aio.so exposes a control server. Selected commands: stat (feature/hook report), world <v> / world setphase <0..1> (global anim-time freeze/scrub, mirrors the desktop timeline onto the headset), meshmat <0xproxy> (per-mesh material dump). Moonjump/fly are gated by persist.hsr.* props (default-on in code; a persistent 0 overrides).

⚠ Don't confirm the patched libs are loaded by grepping /proc/PID/maps for the .so name (libs mmap'd from inside an apk show the apk path). Check port 27042 listening + logcat instead.

Related: Architecture · Developer Guide · Restoring Haven 2025.

Clone this wiki locally