Kurt Himebauch 9294a60172 perf(proot): accelerate and cache glibc syscall relays (#1687)
Libc-internal file operations bypass the existing preload wrappers and
still stop in PRoot. Patch recognized ARM64 SVC sites in libc and the
runtime loader, reuse the current translator for 20 path syscall
numbers, and return declined calls to PRoot. Enabled by default after
the tracer-key check; `PROOT_FP_PATCH_SVC=0` remains the troubleshooting
opt-out.

The hardened implementation patches 47 sites on Thor's glibc 2.43.
Relays preserve general/SIMD registers, flags, FP state and errno, and
provide unwind metadata back to the original libc frame. Text is patched
through `/proc/self/mem` after relay pages become executable.
Initialization skips multiple live threads and SVE/SME. Alternate signal
stacks and insufficient remaining stack use PRoot; main-stack growth
respects the current limit. No proroot code or binaries are imported.

Robust-list set/get sites deliberately remain with PRoot/the kernel.
Device tests reproduced stale state when the earlier private-TLS
implementation mixed patched and raw callers. Removing that shortcut
fixes coherence and preserves the host's actual return policy; the
earlier thread-creation gain is retired. SDK forwarding still enables
faccessat2 ENOSYS fallback and clone3 ENOSYS only before Android 15.
Successful clone3 is never executed from the C relay. Existing fsync
handling is unchanged. No in-process RET_TRAP filter is added, so
SIGSYS-mask/handler machinery is not applicable.

Version-2 build-ID/API tables cache relative patch sites in `/tmp`.
Every hit revalidates executable bounds, alignment, instructions and
syscall numbers. Invalid/unsafe files rescan; publication uses a private
temporary file and atomic rename. The PRoot dependency fetch also gains
a mirror matching the existing pinned checksum, resolving upstream
availability failures without changing versions.

Five alternating pairs using the final bundled library on Thor/Android
13:

| operation | wrappers only | hardened relays |
|---|---:|---:|
| libc-internal stat | 49.6 us | 6.0 us |
| libc-internal open/close | 50.4 us | 7.8 us |
| libc faccessat | 49.7 us | 22.0 us |
| libc getcwd | 54.9 us | 5.0 us |
| thread create/join | 136 us | 144 us |
| complete tiny exec/wait | 4.44 ms | 5.60 ms |

An earlier five-pair hardened-library run measured stat 50.1 to 5.9 us
and open/close 51.9 to 7.1 us. The metadata gains survive the safety
checks. Neither run shows a thread-creation gain, and tiny execs retain
roughly 0.9-1.2 ms overhead.

Three full Steam startup pairs yielded Home-ready medians of 47.02 s off
/ 45.25 s on, with overlapping 43-50 s ranges. Steam/CEF-only PSS
medians were 1,223.1 / 1,233.9 MiB; the roughly 11 MiB difference is
smaller than observed variation, but does not establish zero memory
cost. Startup/memory used one unchanged signed bundled-library APK
without overrides; the final stack-gap guard only increases distance
reserved above other mappings. No reliable whole-client startup
improvement is claimed.

Earlier fresh Half-Life SD installs took 350/341 s off and 326/327 s on.
These are historical results from the preceding robust-list revision,
not a post-hardening download retest. Network/shader scheduling varied,
allocation did not reliably improve, and the repeat baseline committed
faster. This PR demonstrates metadata acceleration; it does not
demonstrate a reliable fix for Steam's SD allocation/commit bottleneck.

Validation on `2c3c135b` (production code unchanged since `4998b046`):

- Local release build, runtime/native-library checks, release signing
and four focused Android tests pass. [Final-head
CI](https://github.com/Droid-Deck/DroidDeck/actions/runs/40704837477)
passed: 1,431 Python/native tests (49 skips), 2,648 Android tests (zero
failures/skips), and 162 Rust tests. CodeRabbit is green on the same
head. The installed production APK forwards API 33 and patches 47 sites
with an unset enable flag and no override; Steam Home renders.
- Embedded and shared-PIC matrices pass on glibc 2.43; shared-PIC also
passes on separately loaded glibc 2.39. Coverage includes raw
results/errno/registers/FP state, path/flag/error and protected-pointer
cases, exact readlink boundaries, mixed raw/relay robust state,
concurrent threads, fork, thread churn, alternate-stack signals,
deferred/FIFO cancellation, signal siglongjmp recovery, forced unwind
with caller cleanup, and limited remaining stack.
- Four device test-process filters deny the memory patch file, relay
mapping, executable permission, or patch writes. Every case leaves
original syscall execution working. Host injection also verifies partial
installation after an individual site fails.
- Concurrent atomic symlink replacement passes 3,000 internal
stat/readlink iterations at default/zero TTL. Eight focused host tests
pass, covering instruction/branch limits, stack/limit fallback,
untrusted cache files and NDEBUG checks. API selection gives 45/47/46
sites for unknown/33/simulated-35; unset and explicit 1 agree, and 0
leaves sites untouched. This does not replace Android 15 hardware
testing.
- All bench tools compile with -DNDEBUG. Relative/absolute fixture
workloads agree, and thread/ABI/failure checks pass on Thor with those
builds.
- Earlier internal fts/realpath/NSS/dlopen and normalized read-path
equivalence pass. The write suite retains the existing wrapper SD
Unix-socket mismatch against PRoot.

CodeRabbit disposition: fixed relative fixture paths, prevented
benchmark checks disappearing under NDEBUG, corrected the
limited-fallback description, and narrowed memory-file denial so the
thread check still succeeds. The default-off recommendation was
addressed by fixing/testing the underlying guard and unwind issue while
retaining the requested default-on behavior. The readlink count
suggestion is intentionally not adopted: device boundary comparisons
reproduce current PRoot behavior, so changing that inherited policy
belongs in a separate correctness change.

This is not consequence-free instruction patching: constructor time,
private text copies and stack checks have costs. Android 15+ ARM64 and
16 KiB-page hardware, arbitrary asynchronous cancellation/non-local
exits, user-managed stacks, and attributable tracer-counter measurements
remain gaps. Loader calls before the constructor and arbitrary guest ELF
syscall sites remain outside scope.

Thor was restored to its original APK and bundled library, with app data
preserved and test settings, overrides, temporary files and version-2
test caches cleared.

Detailed evidence and revision boundaries: [proroot
analysis](https://github.com/Droid-Deck/DroidDeck/blob/2c3c135b52c3dd949ef5a99a67d8c12eba24406c/docs/development/proroot-analysis.md).
2026-10-28 10:53:26 -04:00
2026-10-13 21:27:42 -04:00
2026-10-13 21:27:42 -04:00
2026-10-13 21:27:42 -04:00
2026-10-13 21:27:42 -04:00
2026-10-13 21:27:42 -04:00

DroidDeck

DroidDeck brings the SteamOS experience to Android: Valve's Steam client in Big Picture on your Adreno handheld, with Windows games through Valve's ARM64 Proton.

Join the DroidDeck Discord

Star History Rank

Droid-Deck%2FDroidDeck | Trendshift Droid-Deck%2FDroidDeck | Trendshift

Droid-Deck%2FDroidDeck | Trendshift Droid-Deck%2FDroidDeck | Trendshift

Tapping DroidDeck on the Android home screen and landing in Steam Big Picture

Note: DroidDeck does not have a stand-alone website. Do not click on any download links from websites claiming to be the DroidDeck team.

Requirements and install

Use Android 9 or newer on a supported Adreno device (730 or newer, or 8xx). Adreno 6xx is experimental: DirectX 11 uses DXVK 2 and may run, and DirectX 12 games can still crash. Mali, Xclipse, PowerVR, and Adreno 710 are unsupported. No root is required. Allow about 3 GB for the runtime and 1.1 GB more for the desktop and emulators. Install the APK from Releases, install the Linux runtime, then press Play and sign in. Steam downloads on first launch. Install Desktop & apps to use the desktop and emulators. The Store installs Linux apps and games from Flathub (ARM64 builds) with Flatpak; with additional options to install Appimages and set up scripts. Stores (Setup › Stores › "Show Stores in the rail") signs in to GOG, Epic Games and Amazon Games and downloads games into the Games storage. Epic keeps its direct API install path. Opening Epic Stores prepares Epic’s official Linux launcher preview. When installed, Epic games launched from DroidDeck use that client, and games installed in the client are imported into DroidDeck’s library and shortcuts. The x86-64 client runs through FEX on ARM and DroidDeck retains its Proton hooks; this preview and ARM setup are outside Epic’s listed Linux support. The cog on a store’s chip row sets its opening tab and download speed tier.

On Android 14 and newer, before Steam launches, you must turn on Disable child process restrictions in Developer options. If this option is not available in developer settings (Android 12 and 13 devices), first launch of Steam will present a "Fix it for me" button, which will help automate the setup process.

Smart glasses

Use Setup → Smart glasses for app-wide virtual-screen settings across DroidDeck menus, launchers, desktop sessions, and games. Supported VITURE, RayNeo, XREAL, and Rokid connections provide 3DoF; Luma Ultra adds optional experimental 6DoF and stereo-assisted hand/Joy-Con tracking. Model support and SDK requirements differ: see the smart-glasses support matrix for supported devices, controls, setup, and limitations.

Community

Join the DroidDeck Discord for help, Preview builds, and device reports. Bug reports go in its #bug-reports forum; attach the zip from Share logs (Setup, or the session drawer) so the logs come with it.

Build

Run tools/build_local.sh with Docker, Java 17, the Android SDK/NDK, Rust through rustup, GitHub CLI, Python 3, and zstd installed. It prepares the same native libraries, pinned components and audio sinks as CI, then checks the APK's assets, JNI exports and dependencies. The APK is at app/build/outputs/apk/release/app-release.apk. Set DROIDDECK_PA13_SOURCE_DIR to an existing PulseAudio 13.0 source directory to skip downloading it. Set DROIDDECK_SIGNING_ENV to a signing environment file to install over a release build; relative keystore paths resolve beside that file. To install on an attached device, run tools/deploy_local.sh.

After preparation, ordinary ./gradlew :app:assembleRelease builds can reuse the verified inputs. If native sources, component pins or staged files change, packaging stops with instructions to rerun the helper. Generated audio stays under app/build/; the tracked base bundle is never modified. ./gradlew -PskipRust=true :app:testDebugUnitTest needs no native preparation. For an APK without store engines, use DROIDDECK_SKIP_RUST=1 tools/build_local.sh; it omits cached engine libraries too.

For the prepared Windows checkout, run ./tools/run_windows.ps1 (optionally with -Serial <adb-serial>), or select DroidDeck XR in Android Studio and run the app module on the connected phone. Machine-specific options live in the ignored local.properties: droiddeck.python selects a Python executable, droiddeck.skipRust=true omits the optional store download engines, and droiddeck.debugApplicationIdSuffix=.xr installs DroidDeck XR Dev alongside the signed release. The development app has its own runtime and Steam login. The local Java selection is in .gradle/config.properties.

python tools/prepare_windows_runtime.py restores this checkout's audited runtime inputs from the pinned CI APK, three locally rebuilt preload libraries, and the checksum-pinned Gamescope archive in build/windows-setup/. Start Docker Desktop's Linux engine first: preparation now rebuilds DroidDeck's ARM64 VR driver and Vulkan layer from current source and runs their native regression tests. The compiler image is built automatically on first use. Vulkan headers come from the NDK beside --llvm-strip, or from an explicit --vk-include <NDK-sysroot-include> directory.

To reuse that build, pass --rebuilt-vr build/windows-setup/vr. Preparation verifies its vr-build.json against the current Git revision, source files, Vulkan headers, output checksums, and ARM64 architecture; missing or stale builds fail instead of falling back to the donor's VR libraries. The script checks the actual donor CI merge tree, still rejects unrelated unaudited runtime changes, and records component provenance in app/build/windows-runtime-provenance.json. Preserve these ignored preparation files when rebuilding locally; clean removes the prepared assets and their verification manifest.

Run the helper and preload regression suite with bash tools/test_local.sh. On macOS it runs in Linux through Docker, matching the helpers' runtime and CI instead of compiling Linux preloads against macOS headers.

Limits

Compatibility and performance vary by device; hardware validation is limited. Desktop compositing uses software rendering. Firefox sandboxing is reduced under proot. Logs are kept app-private (files/logs/, the last 30 sessions); Share logs packs the newest session, with the stores and tool logs, into one scrubbed zip.

Credits and licence

GPL-3.0. Runtime, shim, input, and controller work build on WinNative and Bannerlator (maxjivi05). Epic Games and GOG support build on Phobos’s adaptation of Legendary and gogdl, respectively. Battle.net support is @kayakyakr's: it runs Blizzard's own Battle.net client, downloaded from Blizzard and not included, in Proton, and shows the key art from each game's Battle.net Shop page; without the Steam client its games see DroidDeck's pad as an Xbox 360 controller through PROTON_SPOOF_STEAMINPUT_VIDPID in Proton's dlls/winebus.sys. Its games install from Stores through Blizzard's own Agent and the local HTTP API it serves, driven the way @barncastle's Battle.Net-Installer drives it, with the requests described on wowdev.wiki; how other launchers find, install and start Blizzard games was read in @mubaraknumann's unifideck, @djmittens's neo-mittens bnetswitch, @BCD1210's soju and Lutris's product.db reader, without taking their code. The registry entries a GOG game's install script (goggame-<id>.script) sets are read the way Heroic Games Launcher's GOG setup (GPL-3.0) reads them. LSFG frame generation is from the work of Camille LaVey and the Eden emulator project, following lsfg-vk, ported to WinNative and DroidDeck by @maxjivi05; it needs your own copy of Lossless Scaling and ships none of its shaders. x86 AppImages, and any whose own runtime cannot unpack them, are unpacked with uruntime by VHSgunzo (MIT), shipped unmodified with its licence, which also starts the arm64 emulator AppImages whose own runtime cannot run its extractor on Android. x86 AppImages packed with sharun by VHSgunzo (MIT) start because DroidDeck's own FEX build (MIT) puts the platform name under AT_PLATFORM, which sharun's userland-execve loader (MIT) requires; FEX filed it under AT_BASE_PLATFORM. The Steam client's audio goes through @The412Banner's DirectAudio PulseAudio sink (LGPL-2.1-or-later), built from tools/directaudio/sink with its request size kept to its ring so clients are not asked to queue 100 ms ahead. mmap ring-buffer controller input was developed by ribbit384 (WinNative #345). Why the x86-64 Steam client refused the arm64 client's sign-in was checked by reading Steam's saved token with the ConnectCache format from deadlock-api-ingest. x86 programs that link OpenSSL 3 under FEX use the copy in the Steam Linux Runtime 4.0 platform. Hooks written with WriteProcessMemory into code FEX has translated go through /proc/<pid>/mem again when the kernel refuses process_vm_writev, as Wine's server/ptrace.c wrote them before Wine 11. x86 Linux games under FEX get the same /etc/asound.conf that pressure-vessel writes into the Steam Linux Runtime, so ALSA plays through PulseAudio. Games render with Mesa Turnip's own per-render-pass choice between tile memory and system memory, while the Steam client keeps TU_DEBUG=sysmem; FEX's code disk cache was checked in its source and is already on through Proton. The proot fast path picks the guest name of a host file bound under several names in the order PRoot's path/binding.c (GPL-2.0-or-later) sorts its bindings, so links and /proc/self/fd read back as they do through proot. The session's PipeWire (MIT) opens its sockets with the per-socket access settings documented in its module-access.c, and WirePlumber (MIT) keeps those clients at full permissions through its access.rules. Wine write watches use the userfaultfd support in Proton's dlls/ntdll/unix/virtual.c (LGPL-2.1-or-later): libblsession gives it the arm64 syscall number and answers its PAGEMAP_SCAN from /proc/self/pagemap where Android refuses the ioctl. Wine on arm64 sets a thread's full context by raising SIGUSR2 from syscall_dispatcher_return_slowpath in Wine's dlls/ntdll/unix/signal_arm64.c (LGPL-2.1-or-later); libblsession runs that handler in place on a frame laid out as the Linux kernel's arch/arm64/kernel/signal.c builds it and returns through rt_sigreturn, so proot does not stop the thread for it. Wine's process list reads each process's memory counters from /proc/<pid>/status in fill_vm_counters (Wine dlls/ntdll/unix/process.c, LGPL-2.1-or-later), so libblsession gives those reads the kernel's file instead of a copy with TracerPid cleared. droiddeck-ntsync is checked against the object, wait and error semantics documented for the Linux ntsync driver in Documentation/userspace-api/ntsync.rst (GPL-2.0). Xbox games load the Windows modules of @domsvoboda's xodus-nexus runtime (LGPL-2.1-or-later), including its wintypes.dll, whose ApiInformation answer Minecraft for Windows needs to start; how Minecraft installs GameInput under Wine was checked against @Wyze3306's BedrockOnLinux (MIT). ReSkate (GPL-3.0) by Dingo Shenanigans is set up with the Steam launch option from its README and starts at D3D12 feature level 12_1, as its game crashes at 12_2 here. See LICENSE. Steam and Proton belong to Valve Corporation; this project is not affiliated with Valve.

XR glasses support builds on WinNative's VITURE glasses work. VITURE protocol details were checked against XRLinuxDriver by @wheaney, including the VITURE protocol header it ships, ar-drivers-rs by @badicsalex and the go-macos/iokit VITURE package; the optional VITURE SDK belongs to VITURE and is not included. RayNeo support adapts @informalTechCode's RayNeo-Air-Series-3DoF-Scaffold (Apache-2.0) and references @verncat's RayNeo-Air-3S-Pro-OpenVR (MIT) and Monado (BSL-1.0); see docs/third-party/rayneo. XREAL 1S support references XRLinuxDriver and xrealOneDeviceKit by @wheaney, xreal_one_driver by @rohitsangwan01 (MIT) and xr-tools by @0xcaff. Rokid support follows the Rokid protocol notes in ar-drivers-rs and @badicsalex's Void Computing write-ups, and Monado's Rokid driver. The REDMAGIC glasses-display findings draw on @mekhontsev's MagicDesk investigation and @Coding-BR's decompiled Nubia kernel. VITURE, RayNeo, XREAL, Rokid and REDMAGIC are trademarks of their owners; this project is not affiliated with them.

Joy-Con VR controllers port the arm model of Google's Google VR SDK for Unity (Apache-2.0), adapt the hand skeleton of Valve's OpenVR handskeletonsimulation sample (BSD-3-Clause), which follows Moshi Turner's hand simulation in Monado, and use the Index controller offsets from Monado's vive_poses.c (BSL-1.0). Joy-Con behaviour was researched in Linux's hid-nintendo driver by Daniel J. Ogorchock, @dekuNukem's Nintendo_Switch_Reverse_Engineering, SDL and the Android Open Source Project, and checked against ALVR, OpenComposite, PSMoveSteamVRBridge, @F8LEFT's JoyConVr, @krnelpan1c's Daydream-Controller-SteamVR, @yoyobuae's joyconvr, Moonlight and Dolphin; see docs/third-party/joycon. Nintendo Switch and Joy-Con are trademarks of Nintendo; this project is not affiliated with Nintendo.

Camera hand tracking runs Google's MediaPipe Tasks Vision library with its Pose Landmarker (Lite) model, BlazePose GHUM 3D, and its Hand Landmarker model (all Apache-2.0), on frames from AndroidX CameraX (Apache-2.0); MediaPipe's usage reports are switched off. The design was checked against @F8LEFT's JoyConVr camera app (MIT), and the short Joy-Con movements follow the accelerometer mode of the Google VR SDK for Unity's arm model, Eric Foxlin's zero-velocity updates and Frosio, Pedersini and Borghese's accelerometer autocalibration, and the camera and Joy-Con are joined by a Kalman filter (R. E. Kalman, 1960). Bare camera hands take their gestures after ALVR's hand_gestures.rs (MIT) and Meta Quest's menu pinch, with distances checked on photos from the HaGRID gesture set (CC BY-SA 4.0, not included), fit the palm with Horn's quaternion method (1987) and smooth it with the 1€ filter (Casiez, Roussel and Vogel, 2012); the white glove model follows Valve's OpenVR render model reference and Index controller poses, is meshed with surface nets (Gibson, 1998; Mikola Lysenko) and blends and shades it with Inigo Quilez's smooth minimum and distance-field ambient occlusion, porting his polygon distance (MIT); see docs/third-party/handtracking.

The Store installs and runs apps with Flatpak (LGPL-2.1). Its sandbox goes through DroidDeck's own bubblewrap shim, which follows how Flatpak hands bwrap its environment. Chromium and Electron apps from Flathub start their browser processes through zypak by @refi64 (BSD-3-Clause); its source was read to see that it always spawns them through Flatpak's spawn portal, which is why DroidDeck starts them directly without Chromium's sandbox instead.

Remote Play from a PC to the arm64 Steam client goes through a stand-in for the V4L2 decoder and the libdrm dumb buffers that Steam's streaming_client expects: each frame is decoded by Android's MediaCodec through the app, or by the FFmpeg (LGPL-2.1-or-later) that ships in Steam's own runtime, into dma-heap buffers that its Vulkan renderer imports. The Qualcomm low-latency decoder settings follow Moonlight (GPL-3.0).

Steam's Browse buttons open a GTK 3 file chooser through PyGObject (LGPL-2.1-or-later). It runs under Steam's own reaper with Steam's app ID, which gamescope (BSD-2-Clause) reads from the process tree (GetAppIdFromReaper in src/Utils/Process.cpp) to show it over Steam.

Black Ops III and Dark Souls Remastered start without umu-protonfixes (BSD-2-Clause), whose fixes for them (311210.py sets ALSA audio, 570940.py installs VC++ 2017) fail under ARM64 Proton.

S
Description
No description provided
Readme GPL-3.0
855 MiB
0 Stars 1 Watchers 0 Forks
Languages
Kotlin 49.9%
Python 16.1%
C 15.9%
C++ 8.1%
Rust 3.5%
Other 6.3%