-
Notifications
You must be signed in to change notification settings - Fork 2
Device Patching
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.
/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.
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.
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` onceVerify: 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.
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 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.