Commit Graph
59 Commits
Author SHA1 Message Date
MaxsTechReview 8cf01ebb77 fix: Linux games on an NTFS USB drive start the first time instead of failing to load a library (#1850)
* fix: a read-only file on a USB drive no longer fails to close the first time it is read

On the RedMagic Astra 2, Android's NTFS USB drive returns EIO from close() for a file opened read-only when that first read updates its access time; the update itself succeeds and the descriptor is released. The x86 loader under FEX treats a failed close as fatal, so every Linux game newly installed or updated on such a drive failed to start once (libfreetype.so.6: cannot close file descriptor: Input/output error). The session library's close() now reports success for EIO on read-only descriptors; errors on descriptors opened for writing are still returned.

* Check the read-write close still reports EIO
2026-10-30 18:36:08 -04:00
MaxsTechReview 83980035c6 fix(desktop): video wallpapers no longer race ahead after DroidDeck returns from the background (#1833)
* fix(desktop): video wallpapers no longer race ahead after DroidDeck returns from the background

* test: give the vrclient loader harness the clock syscall hook
2026-10-30 13:58:49 -04:00
MaxsTechReview 31c943c82a fix: Windows installs to Android storage no longer ask for an administrator (#1791) 2026-10-29 19:12:22 -04:00
MaxsTechReview 22d39998f0 fix(desktop): let desktop apps save read-only game files, and make game libraries writable (#1781) 2026-10-29 15:16:17 -04:00
MaxsTechReview 29c83976d0 Move the Linux runtime to Ubuntu 26.04 LTS with a separate Kubuntu desktop (#1733) 2026-10-29 09:05:20 -04:00
MaxsTechReview e9e0186e66 perf(fex): answer raw stat, readlink and statx syscalls through the proot fast path (#1757)
* perf(fex): answer raw stat, readlink and statx syscalls through the proot fast path

FEX issues newfstatat, readlinkat and statx as raw syscall() calls, which skipped the libc wrappers the fast path hooks and fell through to ptrace. Route them through libblfastpath the same way raw openat already is, and keep the fast path's own /proc/self/exe lookup off the hooked syscall().

* test(fex): keep the raw lookup test passing on kernels without statx
2026-10-29 07:25:39 -04:00
maxjivi05 bb9a64fa5f Reconfigure the app decoder when the capture layout changes
A capture format or buffer change now closes the app's decoder so the
next packet reopens it with the new stride and height, and a capture
slot whose remap fails no longer keeps its old mapping.
2026-10-26 20:36:24 -04:00
maxjivi05 916a39585f Decode Remote Play streams on the arm64 Steam client
Steam's arm64 streaming_client only decodes through a V4L2 stateful
decoder and libdrm dumb buffers, neither of which exist on Android, so
streams from a PC never showed a picture. The preload now offers it a
/dev/video-dec0 and a libdrm stand-in backed by dma-heap buffers, and
each frame is decoded by the app's MediaCodec (Qualcomm low-latency
mode) over the encoder socket, falling back to the FFmpeg in Steam's
runtime.
2026-10-26 20:36:24 -04:00
maxjivi05 a66d7d74a4 fix: Steam Input mouse clicks, scrolling and pointer moves reach games again 2026-10-24 09:07:04 -04:00
maxjivi05 9438aac0db fix: droiddeck-ntsync leaves one-shot handlers alone and replays deferred signals exactly once
- Handlers installed with SA_RESETHAND are no longer wrapped, so a deferred one-shot signal can't fall through to the default action.
- Deferred signals are replayed with every signal blocked, so a signal landing mid-replay can't send an entry twice.
- Taking a deferral slot and holding a signal are safe when one signal interrupts another, and a held signal is released only at the nesting level that held it, so a handler's return can't block it again.
2026-10-23 14:55:33 -04:00
maxjivi05 77dd04ec8c fix: droiddeck-ntsync keeps protection when more than four signals arrive and keeps waits intact when a handler waits again
- A fifth distinct signal inside a critical section is now held pending with the thread's signal mask and released once the section ends, instead of running its handler while an object lock is held.
- A wait from a signal handler during another wait's sleep uses its own queue and, for a second device, a reference for that call only, so the outer wait keeps its queue and can't return the inner wait's result.
2026-10-23 12:15:43 -04:00
maxjivi05 4bcd4e3cca perf: droiddeck-ntsync without signal-mask syscalls, waits that skip the queue, and per-thread device references 2026-10-23 10:48:06 -04:00
MaxsTechReview ac0909eaa3 perf: let Wine read process memory counters from /proc directly (#1374) 2026-10-23 07:18:56 -04:00
MaxsTechReview ffa40a15d5 perf: run Wine's arm64 SIGUSR2 context switch without a kernel signal (#1371) 2026-10-23 02:12:49 -04:00
MaxsTechReview f27d0b006f perf: Wine's write watches stop costing a signal per page on arm64 (#1333)
Wine asks for userfaultfd with the i386 syscall number on arm64, so it
never gets one and falls back to mprotect and a SIGSEGV per first write,
which under proot is a ptrace stop each time. libblsession hands it the
real userfaultfd and, where Android refuses the PAGEMAP_SCAN ioctl, answers
it from /proc/self/pagemap and UFFDIO_WRITEPROTECT.
2026-10-22 19:53:12 +00:00
xXJsonDeruloXx 1b14939731 fix: suspend page-out waits for each handler's ack and can be cancelled 2026-10-22 10:01:49 -04:00
xXJsonDeruloXx 240b7fcce6 feat: page the session out to zram before suspending it, and zram is always on where supported 2026-10-22 09:02:37 -04:00
MaxsTechReview 3305724f06 perf: FEX's file lookups and opens go through the fast path instead of proot (#1283)
* perf: FEX's in-root path lookups go through the fast path instead of proot

Android has no openat2, so libblsession walks RESOLVE_IN_ROOT lookups for
FEX one component at a time. Every fstatat, readlinkat and openat in that
walk was a raw syscall that stopped in proot. When libblfastpath is loaded,
the walk now calls its fstatat, readlinkat and openat, which answer without
proot and fall back to the raw syscall themselves. Without it the walk uses
the raw syscalls as before.

One lookup drops from 250-940 us to 26-86 us. A Graveyard Keeper launch
makes 37.5k proot stops instead of 54.0k and reaches its first scene
0.31 s sooner.

* test: count fstatat64 where the preload asks for it

* perf: FEX's own file opens go through the fast path instead of proot

FEX opens host files for the game with syscall(SYS_openat), so every one
stopped in proot even when the fast path could answer it. libblsession's
syscall() now sends SYS_openat to the fast path's openat as well, the same
way the in-root walk does, and makes the raw syscall when the fast path is
not loaded.

A Graveyard Keeper launch makes 28.0k proot stops instead of 37.5k; FEX's
openat stops go from 6,739 to none.

* fix: the fast path's start-up reads don't come back through syscall()

libblsession's syscall() now sends SYS_openat to the fast path. A process
named FEX starts the fast path by reading /proc/self/cmdline with
syscall(SYS_openat), which came straight back into the fast path and
waited on its own pthread_once. Its start-up reads now call libc's
syscall directly.
2026-10-21 22:33:39 -04:00
MaxsTechReview ed29f77fc2 fix: Chromium browsers start on the desktop (#1280)
A process's status read for another process is served read-only, so
Chromium's ReadOnlyFile check no longer kills the browser. Chromium-based
AppImages without chrome-sandbox get --no-sandbox, and Flathub Chromium and
Electron apps skip zypak's spawn portal, which cannot work here.
2026-10-21 18:17:07 -04:00
MaxsTechReview 7fa8008944 perf: network tables Android refuses are served from the synthesized copy without retrying the open (#1275) 2026-10-21 17:18:11 -04:00
MaxsTechReview e04727dbaf perf: SysV message queues stay mapped instead of reopening per call (#1272) 2026-10-21 17:14:03 -04:00
Kurt Himebauch e9891d5a29 feat: Remote Play host: PipeWire capture, hardware encoding, touch and game audio (#1139)
* feat: run SteamOS PipeWire and WirePlumber for gamescope screen capture

* feat: hardware Remote Play encoding through MediaCodec behind Steam's V4L2 stage

* feat: deliver Remote Play touches to gamescope as touches

* feat: route game audio through PulseAudio while a Remote Play client is connected

* fix: keep the Remote Play marker when a forked child closes the encoder

* fix: allow standalone preload tests without V4L2 encoder hook
2026-10-21 17:57:24 +00:00
MaxsTechReview 6ba159a2f2 fix: let Wine write hooks into code FEX has translated (Ubisoft "Error at hooking API") (#1163)
fix: let Wine write hooks into code FEX has translated

Wine 11's wineserver writes another process's memory with process_vm_writev,
which the kernel refuses on a page that is not writable. FEX's WoW64 layer
keeps the pages it has translated read-only to catch self-modifying code and
re-protects them right after a program unprotects them, so WriteProcessMemory
and NtWriteVirtualMemory into ntdll failed with a partial copy and Ubisoft
games stopped at "Error at hooking API NtProtectVirtualMemory". Wine only
falls back to /proc/<pid>/mem on ENOSYS or EPERM.

libblsession now completes a single-buffer write that wineserver was refused
with EFAULT through /proc/<pid>/mem, the way Wine wrote before Wine 11. Other
programs and every other error keep the kernel's answer. The iovecs are read
with process_vm_readv, so a bad pointer still returns EFAULT, and offsets are
64-bit in the i386 build.
2026-10-20 00:31:03 -04:00
MaxsTechReview 48a70c8229 Boot Linux games under the FEX compatibility tool again (#1100)
Valve's FEX opens rootfs paths with openat2(RESOLVE_IN_ROOT), which the
preload emulates under proot. droiddeck-fex's rootfs links to the Steam
runtimes with absolute paths outside the rootfs, so every lookup missed
and FEX fell back to the arm64 host files: x86 bash, and with it Stardew
Valley's launcher, failed on libdl.so.2. An absolute link whose target
is missing in the root but present on the host now finishes there.
2026-10-18 20:38:08 -04:00
The412Banner db8d1c9910 feat: optional x86-64 Steam client (Valve's Linux client under FEX) beside the arm64 one (#152)
An opt-in second Steam client (Steam settings, Steam client: x86-64): Valve's
x86-64 Linux client, unchanged, runs under FEX in its own home with its own
game library and the same sign-in. Linux builds run directly, so titles such
as Team Fortress 2 and Half-Life use their Linux versions; Windows games use
DroidDeck-Proton as on arm64. The arm64 client stays the default.

- droiddeck-steam-x64: FEX and the x86-64 rootfs from DroidDeck-Components
  (sha256-pinned), client install and sync from Valve's CDN, sign-in shared
  with the arm64 client, its web helper on the GPU (ANGLE GL over FEX's GL
  thunk, zink, Turnip), Proton wrappers for the arm64 client's tools,
  DroidDeck direct for Linux builds, SD-card library (x86-64/ beside the
  arm64 one), repair.
- FEX 2610 built by build-fex.yml with thunks and three patches: an openat2
  fallback without openat2 (proot), newer-clang thunkgen, MAP_32BIT
  exhaustion returning ENOMEM. libblsession resolves RESOLVE_IN_ROOT paths
  inside the root.
- Session: x86-64 gating, shortcuts and compatibility tools per client,
  logs from the client that ran.
- App: client setting with the FEX version, an x86-64 chip on the Games
  header, the active client's library, art, storage, downloads, repair,
  Offline Mode, Decky, desktop mode, optional VAC diagnostics; per-client
  state for art, the content log, repair and leftovers.
- Strings in every locale.
- Review fixes: the library cache is saved only by the newest scan; an
  abandoned session's Steam logs come from the client it recorded; distinct
  VAC console names; FEXInterpreter in CPU pinning; clearer VAC hint in
  pl, ru, uk, cs and bg.
2026-10-17 23:57:52 -04:00
TideGear 3f3ed3923b fix: hold XInput rumble until the game changes it, instead of stopping after a second (#938) 2026-10-16 17:30:21 +00:00
Kurt Himebauch 9ce5a24459 fix: keep SteamVR syscall hooks and report Steam crashes (#905)
* fix: preserve SteamVR syscall hooks

* fix: report Steam crashes through gamescope
2026-10-15 15:50:26 +00:00
William Allen 422429d63f fix: read the replaced listing's watch descriptor before detach can free it (#685)
install() replaces an existing cache entry by calling detach(old), which
destroys the listing when no reader holds it, and then reads old->wd to
decide whether to release the watch. When the old listing has no readers,
that read happens after the free.

Two threads that enumerate the same uncached directory at the same time
reach this path: both build a listing, the first installs an entry with
no readers, and the second replaces it.

Save the watch descriptor before detaching, as drop() already does.
2026-10-14 13:51:44 -04:00
Kurt Himebauch a0d3e03ca0 perf: Steam's web helper runs its GPU, audio and zygote work inside one process (#566) 2026-10-14 01:01:49 -04:00
Kurt Himebauch aecd2ebe1d cleanup (#764) 2026-10-13 21:27:42 -04:00
MaxsTechReviewandxXJsonDeruloXx 9a6d48c04b Add opt-in zRAM compression for the Steam client while a game runs (#442)
* Add opt-in zRAM compression for the Steam client while a game runs

A "zRAM compression" toggle under Steam > Settings > Session, below
"Keep downloads running". Off by default, kept once set, and disabled
on devices without swap or with a kernel older than 5.4.

When on, the session watches for Steam's reaper. 20 s into a game the
client and its web helpers page their private anonymous memory out to
the device's zram with MADV_PAGEOUT from inside their own process
(libblsession zram.c, SIGURG), again every 5 minutes, and read every
swapped page back once the game exits. No root is needed: only a
process itself may do this without CAP_SYS_NICE. With the toggle off
nothing is installed or started.

RedMagic NP06J, Palworld: Steam's resident memory fell from ~1.7 GB to
~1.1 GB with ~560 MB in zram, available memory during play rose by
~0.5-0.8 GB, and ~2 MB was left in zram after the read-back.

* Turn zRAM compression on by default below 12 GB of RAM

Until the toggle is changed, it follows the device: on when the kernel
supports it and the device has less than 12 GB of RAM, read from the
advertised memory size on Android 14+ and from total memory before that.
Turning it off is kept.

---------

Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
2026-10-13 13:33:19 -04:00
Kurt Himebauch f4b0ab6867 feat: Cardboard VR mode with ARM64 SteamVR (#704)
* feat: add Cardboard mode with side-by-side lens-distorted eyes and 3DoF head tracking

* fix: derive the Cardboard field of view from the panel and recentre fully when far from upright

* feat: share the Cardboard head pose with the guest and add a SteamVR driver for it

* feat: run SteamVR's compositor on Turnip/KGSL with a display-extension layer and a driver-direct HMD

* feat: show SteamVR frames in Cardboard mode through a driver-side presenter and a fake direct-mode display

* feat: install and start SteamVR for Cardboard mode from the session drawer

* docs: describe Cardboard mode and the SteamVR path

* feat: drive SteamVR hands from the game pad and keep the Cardboard headset out of standby

* docs: record where the SteamVR dashboard stops

* fix: map Cardboard head tracking to the screen's axes and keep turns about the real vertical as yaw

* fix: give Cardboard its previous picture back when SteamVR stops

* fix: report sem_otime from emulated SysV semaphores so SteamVR's web helper can start

* fix: bridge SteamVR's shared semaphores across processes and keep Steam alive while SteamVR runs

* feat: present SteamVR straight to DroidDeck's compositor at panel resolution

* docs: update Cardboard VR notes for the dashboard, semaphore bridge and direct presenter

* fix: keep Wine and FEX signal handlers in the VR layer; add semaphore tracing

* fix: clear a stale SteamVR active marker on status

* fix: clear only an old SteamVR active marker

* fix: unblock SteamVR clients on Turnip and keep Steam's web helper alive across SteamVR runs

* fix: judge shared waits before a submit's own signals and on Vulkan 1.0 devices

* docs: record VR games, SteamVR Home and OpenXR results on the Thor

* docs: add Cardboard VR learnings from the Thor bring-up

* docs: add Cardboard VR screenshots from the Thor

* fix: keep the VR layer's display stand-in to SteamVR runs and clear a leftover marker at session start

* fix: report SteamVR start and stop failures from the helper's exit code
2026-10-13 14:36:56 +00:00
Jagger15 6c35297dba Add Quest 3/3S compatibility to the standard APK (#642) 2026-10-12 17:13:07 -04:00
MaxsTechReview 2a06204eb4 Desktop: run programs, scripts and AppImages; Switch to Desktop without Deck mode (#549)
* Run programs, scripts and AppImages from the desktop

Windows programs open with the Proton Steam has chosen, and a Steam game's own
launch program is handed to Steam. x86-64 Linux programs and game scripts run
under FEX, and an exec the kernel refuses goes to droiddeck-open through
libblsession's binfmt.c. Programs on noexec shared storage pass KIO's checks; a
folder of Linux programs there, and sharun AppImages, are added by the app once
and run from the copy. Executable scripts run without KIO's prompt, mpv plays
on the software desktop, FEXServer's socket moves under XDG_RUNTIME_DIR, and
Proton runtimes no longer show up as games.

* Switch to Desktop from Big Picture without Deck mode

Big Picture's Switch to Desktop asks the client's SteamOS manager, which the
client only has when started as SteamOS (Deck mode). Without it the request
failed and the screen stayed on "Switching to Desktop..." for good. The live
compat helper now sees the request on its way to the client and hands it to
steamos-session-select, which opens the KDE desktop as Deck mode already did.

* Run x86 programs from shared storage where they lie

FEX asks for the guest's PROT_EXEC when it maps a program or library, which
the kernel refuses on noexec shared storage; FEX never runs those pages
itself, so libblsession maps them without it in FEX's processes. An x86
game, its libraries and its start script now run in place instead of being
copied in; only native code on noexec storage is still copied.

A Steam library's games are never copied, and a Linux game's launch program
opened from the desktop goes to Steam like a Windows one.
2026-10-10 19:12:20 -04:00
MaxsTechReview 15b2d19f68 Performance: idle front end, proot stops, launch-time passes and log collection (#529)
* Front end: draw the capsule wall without clipping it every frame

The tilted wall clipped each capsule to a rounded rect and recomposed its columns on every drift
step, and a rotated rounded clip is rasterised in software on the render threads: the idle main
page held about 1.7 cores. The art is now loaded at the capsule's size with its corners already
rounded, each column draws its capsules itself in one layer moved by the drift, and the drift
advances at most every 15 ms. The idle page takes about a third of a core.

* libfakeinput: let calls on descriptors it does not serve skip its lock

Every read, write, ioctl, close and poll of every process in a Proton session went through the
controller lock and the three map lookups, Wine's wineserver pipes and the game's files alike.
A bitmap of the descriptors the maps hold, set before one is handed out and cleared when it is
closed, lets everything else go straight to the kernel.

* Pad motion: ask the display for its rotation four times a second

The rotation was looked up for every sensor reading, every 2 ms, and cost more than the rest of
the reading; it is now refreshed every 250 ms, and the readings no longer allocate pairs.

* Session: keep the library passes out of a game's launch

The redist seeder stamped each prefix with the checksum of the whole set of names, which differs
between the session's pass and the game launch's (the game's own names are added), so every
launch re-appended the markers to every prefix in the library while the game loaded. Each
prefix's marker now lists the names it was given and only the missing ones are added.

The beat's seed and compatibility passes, and the Lossless Scaling ownership probe (which loads
steamclient.so), also wait while a wineserver runs: a launch rewrites the manifest and made them
due at exactly that moment. The game's own launch seeds its prefix regardless.

* Agent: keep the focus watcher's X connection open while nothing changes

The connect timeout stayed on the socket, so with no property change the read timed out, and the
watcher dropped the connection, reconnected and took a fresh snapshot every few seconds.

* Game environment: start Cyberpunk 2077 without REDlauncher

The prelauncher opens REDlauncher, a Qt WebEngine window that hands over to the game half a
minute later and then stays up beside it, well over a core busy for as long as the game runs; its
own --launcher-skip started the game twice. The game's executable is started directly instead.

* netif: answer Wine's network driver from a reading of the link

Mortal Kombat X has Wine's network driver enumerate every interface, address and route table
hundreds of times a second. Each enumeration read /etc/droiddeck-net several times, asked the
kernel for its interface dump again (a netlink socket bound under proot, refused each time) and
looked the real functions up with dlsym. The link file is now read at most once a second, a
refused dump is not asked for again within that second, and the real functions are looked up
once.

* Fast path: create memfds without a stop in proot

proot looks at a memfd's name only to refuse the few that are then executed or locked; every other
memfd_create is now issued from the trampoline. The session's /proc/net and status copies made
one per read, a stop each. Without memfd in the kernel the call still goes to proot's ashmem.

* Logs: run each redaction rule only on a line that could match it

A session's end copies some 70 MB of the Steam client's logs and puts every line through all
twenty-odd rules, most of a minute of a core on the phone after every session. Each rule now runs
only on a line holding what it cannot match without: its keyword, the five separators of a MAC,
the four dashes of a GUID, a long enough hex or token run, the learned account or address text.
Lines with non-ASCII text still meet every case-insensitive rule. Over 895,000 lines of real
client logs, with the device's accounts and addresses learned, the output is identical and the
pass takes a twentieth of the time.
2026-10-10 19:00:33 -04:00
Kurt Himebauchandmaxjivi05 0da45e53cb Steam's touch controller as on-screen controls (beta) (#431)
* feat: Steam's touch controller as on-screen controls (beta)

* fix: Steam touch controls appear once the session makes the device; tidier default layout

* fix: Steam touch layout decoding, saved layouts and new sessions

* docs: Steam's touch controls

* feat: Steam's touch icons, labels and colours on the touch controls; pick and save them like Steam

* feat: Steam touch icon picker as in-session panels; layout colour; docs

* Give each physical controller its own player slot

Every controller fed the one exported pad, so two pads played as one.
The first controller keeps slot 0 (the Deck in a Steam session); further
ones get slots 1-3 as separate Xbox 360 pads, created on first input and
removed on disconnect. In Deck mode only the Steam client sees those
pads and hands them to games through Steam Input. Rumble follows the
slot it names.

* feat: Steam Link's touch menu, per-game input modes, action set editing, add/copy/paste, oval controls, paste control

* feat: Steam touch controls drawn as Steam Link draws them; Steam Link's touch menu and layout tray

* feat: Steam touch controls in DroidDeck's style, D-mark guide button, shake to hide

* refactor: Steam touch controls become an On-screen controls choice instead of a second dropdown

---------

Co-authored-by: maxjivi05 <maxjividen@gmail.com>
2026-10-09 21:12:46 -04:00
MaxsTechReview a6d15bcd0b Expanded Fastpaths for proot and increased performance up to 57% (#494)
* perf: fast path resolves links, /proc and glue as proot does, and answers uname, isatty and passwd

The fast path handed back to proot whatever crossed a symlink, /proc or a directory proot
only shows as glue, which is most of what Steam and Wine ask. It now walks a path the way
proot's canonicalize() does, so those calls stay in-process:

- links are followed in guest terms and their text detranslated as detranslate_path() does,
  including readlink into a buffer the kernel cut short and /proc's exe, cwd and fd links;
- a binding proot dropped at start is dropped here too, glue above a binding is ENOENT, and a
  last component proot cannot stat is left to the kernel;
- pipe and socket dirfds are ENOTDIR, rmdir("dir/") removes dir, and "." and ".." go to proot;
- a blocking accept() with a connection already waiting no longer goes to proot.

Also answered: uname() and gethostname() (proot's utsname never changes), isatty() (glibc
2.43 asks with TCGETS2, which proot rewrites) and getpwnam/getpwuid when nsswitch.conf
sends passwd to files first and the entry is there (Xwayland asks for every client).

equiv.py, equiv2.py and equiv3.py match line for line with and without the fast path
(222, 507 and 924 lines). Per call on the RedMagic: realpath 240 us -> 1.0 us,
realpath /proc/self/fd/N 408 -> 9.9, getpwuid 41.5 -> 4.6, uname 28 -> 0.006.

* perf: LD_AUDIT module skips library lookups proot would fail

ld.so looks each library up along the game's search path, and Proton puts the game's
directory, Steam's runtime and video directories and its own library directories ahead of
/usr/lib: 72% of ld.so's opens in an Among Us launch failed, each a round trip to proot.

libblaudit.so answers la_objsearch() for a candidate proot would certainly fail with ENOENT
(its directory walked as canonicalize() walks it, the name or a directory on the way missing).
It has no libc and maps the fast path's trampoline itself. Each lookup's first candidate is
still opened, since a skipped one fails with ld.so's errno as it stands, and the default
directories are never skipped, so dlerror() reads the same. droiddeck-proton and the extra
wrapper add it to LD_AUDIT when the fast path is on.

* perf: droiddeck-proton checks a tool's components with one sha256sum

Each program the launcher starts pays a dozen traced library lookups under the game's search
path before it does anything. One sha256sum over every listed file replaces one per file;
the check itself is unchanged.

* perf: build a locale archive for C.UTF-8

Every program loads the locale LANG names before main(). glibc ships C.UTF-8 as a directory
it looks for once per category under two spellings, before reading gconv's module lists:
about 30 opens, each a stop in proot, in every process of the session. droiddeck-session
builds /usr/lib/locale/locale-archive (and gconv's cache) once per glibc package, so that is
one open.

localedef names a finished archive by hard link, which Android refuses; with
DROIDDECK_LINK_RENAME=1 libblsession turns a refused link into a rename that will not replace
an existing name.

* perf: library beat starts its helpers only when what they read changed

Every 15 s the session ran droiddeck-seed-redists (some forty programs under proot) and, when
a library directory had changed, steam-compatibility: a burst of thousands of proot stops each
quarter minute, game loads included. The seeder now runs when a library changed or a prefix
(or Proton's template for them) is unmarked, the compat pass when a manifest, depot, tool or
config.vdf changed, both checked from bash without starting anything, and each every five
minutes regardless. The seeder reads its stamp and the time without a subshell each.

* perf: fakeinput opens pad.log on its first line

Every process the library is loaded in opened the session's pad.log at start, a stop in proot
on shared storage, and most never write to it. The file is now opened when there is a line to
write.

* docs: the fast path as it ships, the audit module and measured launch times

* fix: a game the app launches after an idle spell comes to the front

The Steam client brings a game it launched forward only while it counts someone as present,
which it reads from gamescope's input counter. A game the app launched after four or more
minutes without a touch started, mapped its window, and stayed behind the library page until
the next input (device: Palworld, Stardew Valley, Fallout: New Vegas, Mortal Kombat X; the same
on main). The session now watches a requested launch, and when the game's window is focusable
but the interface keeps focus for two polls, counts one input on gamescope's counter, as a key
would. Nothing changes for a launch the client already brings forward.
2026-10-09 20:55:18 -04:00
Kurt Himebauch fe71084f23 feat: replace the LXQt desktop with SteamOS-style KDE Plasma (#473)
* feat: replace the LXQt desktop with SteamOS-style KDE Plasma

* feat: leaving the KDE desktop logs out through Plasma and returns to gaming mode

* feat: DroidDeck logo wallpaper as the KDE desktop default

* feat: build the KDE wallpaper from the DroidDeck mark artwork

* ci: keep the built desktop package as a short-lived artifact for device testing

* fix: the desktop loading screen no longer promises Steam on the desktop

* docs: the output resize comments name the desktop's KWin

* fix: the desktop package verifies every package's Arch Linux ARM signature and never replaces a published tag

* fix: programs sent to the GPU from the desktop drop its software rendering overrides

* fix: an interrupted desktop install no longer counts as installed

* feat: pin the desktop package to its DroidDeck-Components release instead of the catalog
2026-10-09 10:39:50 -04:00
SG3xHERO e93397011d Add a Follow screen resolution for foldables (#403) 2026-10-09 07:13:06 -04:00
Kurt Himebauch 7c758c817f perf: fast path answers Steam's pre-2.33 stat calls and stays off without the patched proot (#466) 2026-10-09 03:14:29 +00:00
MaxsTechReview 0b7529da63 fix: keep Steam's directory listings on FUSE libraries (#410)
* fix: keep Steam's directory listings on FUSE libraries

Before it creates, moves or deletes a content file the Steam client lists
the whole destination directory about ten times to match names
case-insensitively. On Android's FUSE storage every listing goes through
the media provider, so reserving space grew quadratically with the number
of files: 33 minutes for Haunted Paws Demo (3,938 files) in a Games folder.

The client's listings under steamapps/ on FUSE are now kept while the
directory's stat is unchanged, for at most five seconds, and patched by
the client's own creates, renames and removals. Other changes fail the
stat check and the directory is read again. BL_NO_DIR_CACHE=1 turns it off.

Haunted Paws Demo into the internal shared Games folder: reserving space
33m08s -> 25-52s, committing 6m15s -> 19-30s, install 39m50s -> 1m12s-1m51s.
Internal storage is unchanged (5s reserve).

* fix: drop stale directory listings on coarse-timestamp volumes

NTFS reports whole-second directory times and a fixed size, so a change
made outside the client's own calls could leave a cached listing stale.
Listings now carry an inotify watch and are dropped on any event the
client's own patched changes don't account for. A forked child stops
caching, since it shares the parent's queue.

Covers rewinddir, telldir and seekdir on cached listings, and directories
opened by the fast-path preload's opendir, which goes through fdopendir.
2026-10-08 08:50:00 -04:00
Kurt Himebauch 415b93f943 feat: agent bridge wayland and gamescope coverage (#406)
* feat: run a guest agent in every session for commands, focus and the Steam client's DevTools

* feat: give the agent bridge game, input, UI and experiment commands behind a Setup toggle

* feat: droiddeckctl commands for the new bridge, a scenario runner and contract tests

* feat: run cleanup steps after every scenario run so a failure cannot leak into the next

* feat: restart a session that died between scenario runs with start --reuse

* fix: give droiddeckctl stop its own, longer wait for the session's logs

* fix: harden debugging tools access and output
2026-10-08 03:50:58 +00:00
MaxsTechReview 6ee8866599 Make Games folders Steam libraries and skip the zero-filled reservation (#388)
With Second library on Automatic, every writable Games folder is now a
Steam library: it gets the same private prefix and tool binds as the card,
the app hands the session the list (BL_STEAM_LIBRARIES), and
droiddeck-steam-library registers, relabels and forgets entries from it.
Steam offers them in the install dialog as "<folder> (<volume>)", and
Proton mapping, esync, the redist seeder, desktop entries and the compat
beat read every registered library instead of only the card. Games folder
libraries stop at the fast path's bind budget.

Steam's preallocation on Android's FUSE storage either failed outright
(NTFS answers EPERM, so installs were cancelled as "not enough disk
space") or zero-filled the whole download first (exFAT, minutes for a
small game). After fallocate is refused, the Steam client's ftruncate
extension of its own staging files is reported as done; the writes that
follow fill the file. A 76 MB install to a USB SSD now preallocates
instantly instead of taking about 3.5 minutes.

A library that is not there is left out of the list and forgotten until
it returns, imports whose folder is missing are set aside rather than
offered as broken installs, and the front end refreshes when storage is
mounted or removed.
2026-10-07 09:31:33 -04:00
Kurt Himebauch e95009fac2 fix: catch KGSL GEM_CLOSE at ioctl instead of exporting drmIoctl (#305)
* fix: avoid reusing retired KGSL GEM handles

* fix: defer closing GEM_CLOSE'd KGSL dma-buf descriptors

* Revert "fix: defer closing GEM_CLOSE'd KGSL dma-buf descriptors"

This reverts commit 191bf4b6f2.

* test: interpose drmIoctl without releasing GEM_CLOSE handles

* fix: catch KGSL GEM_CLOSE at ioctl instead of exporting drmIoctl
2026-10-06 11:52:16 -04:00
The412BannerandThe412Banner 2ccc8204ad Fix Xwayland dma-buf leak: close Zink's emulated GEM handles on KGSL (#275)
Close Zink's emulated GEM handles on KGSL (Xwayland dma-buf leak)

The session preload hands out GEM handles on the KGSL render node by
duplicating the dma-buf descriptor, and released them only through
drmCloseBufferHandle. Zink gives handles back with
drmIoctl(DRM_IOCTL_GEM_CLOSE) instead, which KGSL refuses, so every
duplicated descriptor stayed open and kept its buffer alive.

Xwayland asks for a handle for each buffer it imports through gbm. With
"Force game windows fullscreen" fighting a game's own resolution
(DiRT 3 at 1280x720), the game recreates its swapchain many times a
second and Xwayland piled up ~2,000 old swapchain images (6+ GB) in
seconds until Android killed the session. A preload trace on the device
put 2,090 of 2,118 open dma-buf descriptors in Xwayland on
drmPrimeFDToHandle called from Zink.

Handle GEM_CLOSE on the KGSL node in drmIoctl, and check the three DRM
symbols are exported in CI.

Co-authored-by: The412Banner <the412banner@users.noreply.github.com>
2026-10-05 12:44:16 -04:00
MaxsTechReview 27782068a3 ESync, FSync, NTSync + OpenNow + FEX for Desktop Apps (#197)
* Make the Compatible Proton ARM64 the default and label compatible builds

The CompatToolMapping "0" default always points at DroidDeck's Proton
ARM64 tool, and per-game picks of adopted GE/CachyOS builds are kept,
including when the shortcuts writer remaps.

The tool is named after the Proton build it runs, Proton Experimental
ARM64 (Compatible), or Proton 11.0 ARM64 (Compatible) once that newer build
is installed. Adopted builds carry the same Compatible mark, read from a
string resource the app stages into the runtime, and the Proton page
explains which builds have the prefix fixes.

The registrar is renamed steam-compatibility and the tool
droiddeck-proton-arm64. Existing installs migrate on the next session: the
old tool directory is removed, every mapping that named it moves to the new
tool, and the retired bannerlator-steam-compat script is removed from the
runtime. CI and local packaging include the renamed script.

* Drop the duplicate R import left by merging main

* Add userspace ntsync for Proton on Android

Android kernels have no /dev/ntsync and its app policy refuses futex_waitv,
so every Proton 11 build here fell back to wineserver for every wait.
libblsession now serves /dev/ntsync itself: wineserver opens a memfd region,
each sync object is a timerfd that carries its slot, and the kernel ntsync
ioctls are answered in shared memory with futexes, following the kernel
driver's wake order. Valve's Proton Experimental ARM64, GE-Proton and
proton-cachyos use it unchanged.

Locks record their owner so a killed process cannot wedge the others,
signals are held off inside each critical section, dead processes and
exiting threads are swept, and object lifetimes follow close, dup2, dup3,
close_range and received descriptors.

Fast sync is on by default under Performance > Session fixes, and
PROTON_NO_NTSYNC=1 turns it off per game.

* Add droiddeck-esync as the default sync for Proton on Android

Without a kernel ntsync driver, Proton 11 builds here fell back to
wineserver for every wait. droiddeck-esync brings eventfd and shared-memory
sync to the exact Proton build a game runs on: a pack holds that build's
ntdll.so and wineserver with the patch series in tools/droiddeck-esync
applied, and the guest runs Proton from a shadow tree that links the stock
tool and swaps in those two files. A wineserver from a pack still prefers
/dev/ntsync when a kernel driver or droiddeck-ntsync is there.

Packs are built in Valve's SDK image from each release's own wine source
(Proton Experimental ARM64 tags, GE-Proton and proton-cachyos releases); the
control build must reproduce the release binaries byte for byte, and a
signed index lists the packs. The APK bundles the packs pinned in
release.env, the app fetches newer ones, and Steam's compatibility tool
picks the pack for each Proton build, so new Proton releases get packs
without an app update.

The ntdll side reads, writes and polls its eventfds with raw system calls,
so the guest's LD_PRELOAD wrappers stay out of the sync path. The server
abandons a mutex once a thread killed by TerminateThread has really exited
and when its process dies, and a mutex's owner and recursion count change
in one atomic step, so a killed owner cannot touch the next owner's count.

droiddeck-esync is on by default and PROTON_NO_ESYNC=1 turns it off per
game; droiddeck-ntsync, the userspace ntsync, is now an opt-in toggle.

* Watch Valve's Proton releases and build droiddeck-esync packs for them

A scheduled workflow reads the Proton ARM64 builds Steam ships (anonymous
steamcmd app info) and the upstream source tags, compares them with the
previous watch state and starts the pack build for any build that has no
pack yet. The pack build applies the patch series with --fuzz=0 and runs a
smoke test of each new pack against the esstress suite before it is
published.

* Add droiddeck-fsync, keep Steam and the app on one Proton, fix DirectAudio

droiddeck-fsync: libblsession answers futex_waitv(2), which the app
sandbox refuses, in userspace, so Proton's own fsync runs on the stock
Proton files with no pack. Waiters register the words they wait on in a
table beside the fsync shared memory and sleep on a futex of their own;
FUTEX_WAKE on the fsync memory wakes the registered waiters. Slots carry
one owner word that the sweeper and the owner claim with a CAS, and the
mapping table is rebuilt under a lock held across the real munmap, so a
reused address or a recycled slot cannot lose a waiter. It stands in for
a Proton build without a pack (droiddeck-ntsync where fsync is off), takes
over for games with esync off, and the "droiddeck-fsync first" toggle
(BL_FSYNC_FIRST, also per game) puts it ahead of the packs. The games
Proton keeps off fsync and esync never get it. A launch into a prefix
whose wineserver is still running keeps that server's sync choice.

One Proton for Steam and the app: the Components page's Proton is the
default Steam uses, applied live through the client while Steam runs
(droiddeck-steam-compat) and at the next start otherwise; a choice made in
Steam's menu comes back to the app. Requests are ordered by a sequence that
never goes backwards, and the page no longer jumps back to the default
after a swap or restore.

DirectAudio: the wrapper wrote the driver key with its backslashes
stripped, so no game ever used DirectAudio. Wine 11 also loads a builtin
only from the prefix, so the driver is now linked into system32 and
syswow64 before "directaudio,pulse" is selected; mmdevapi falls back to
winepulse whenever DirectAudio cannot start.

* Pick esync, ntsync or fsync from the Steam settings' Games group

A three-way tab row above the FEX preset sets the sync Proton games use. It
writes the existing switches together (droiddeck-ntsync, droiddeck-fsync
first, droiddeck-esync), so the Performance toggles and the tabs always agree,
starts on esync and keeps the choice until it is changed again.

* Make droiddeck-fsync the default sync for Proton games

A fresh install, or one that never picked a sync, starts on fsync; a choice made
in the Steam settings' Games group still sticks. Games Proton keeps off fsync
still fall back to droiddeck-esync.

* Add wineserver to the Wine sync tabs and order them ntsync, fsync, esync, wineserver

wineserver turns every in-process sync off so games wait through wineserver;
picking any other tab turns sync back on. The tabs shrink to fit a narrow pane.

* Shorten the Wine sync description

* Answer openat2 and execveat for FEX in the session preload

FEX opens every file of its x86 rootfs with openat2(RESOLVE_IN_ROOT) and
only falls back to the host path on EXDEV. Under proot on Android openat2
answers ENOSYS, so each lookup of an x86 library silently went to the host
and missed. The exported syscall() now turns openat2 into openat once the
real call reports ENOSYS: RESOLVE_IN_ROOT paths are taken relative to the
root fd (FEX has already resolved the links inside the rootfs), and an
absolute path under RESOLVE_BENEATH fails with EXDEV as the kernel would.

execveat with an absolute path, or relative to AT_FDCWD, and without
AT_EMPTY_PATH or AT_SYMLINK_NOFOLLOW, is plain execve; it is sent as such
because proot refuses execveat with a directory fd, which FEX uses when it
re-executes itself for a child x86 program.

* Run x86 Linux games and programs through FEX

Steam: Linux-only titles that have no ARM64 build (from the client's
appinfo cache: type game, demo, application or beta) are mapped to a new
compatibility tool, droiddeck-fex ("Linux x86 (FEX)", linux to linux,
no required runtime). Valve's own "fex" tool needs pressure-vessel, which
cannot run under proot, so a mapping to it or to a Steam Linux Runtime is
replaced; any other pick the user made stays. Titles that also ship for
Windows stay on Proton. When such a title is installed and FEX or the
sniper runtime is missing, the client is asked to install them.

droiddeck-fex runs the program under the FEX that Steam installs (app
3127680), with Steam Linux Runtime 3.0 (sniper, app 1628350) as the x86
rootfs:
- the sniper platform is linked in place from its usr-mtree (relative
  links only, files/usr -> .), once per runtime version;
- the x86_64 and i386 builds of libblsession and libfakeinput go into the
  runtime's lib/<triplet>/droiddeck and its etc/ld.so.preload, mirroring
  the session's own list, so x86 programs get the pads and the session
  fixes (FEX passes their system calls straight to the kernel, so the
  ARM64 preloads cannot reach them);
- Config.json sets RootFS and the GL and Vulkan thunks, so x86 games draw
  with the device's Turnip and Mesa;
- the FEX preset and the game's own environment from the Steam settings
  apply, and ARM64 entries are dropped from LD_PRELOAD/LD_LIBRARY_PATH.
It also links ~/.steam/sdk64 and sdk32 to the client's x86 steamclient
when the client has one and nothing is there yet.

Desktop: x86_64 and i386 AppImages can be imported now; they unpack
through FEX (or unsquashfs) and record their architecture. Scripts,
AppImages and programs started in a run session use FEX automatically
when they are x86, and each added app has a "Use FEX" Auto/On/Off choice
in its edit dialog. The launchers' "still running" checks also look at
FEX processes, whose exe is FEX rather than the program. A GitHub release
without an ARM64 AppImage falls back to its x86_64 one. Decky's x86
loader starts through droiddeck-fex instead of the fex-mesa rootfs path,
which no runtime ships.

The x86 preloads are built against glibc 2.31 (Debian 11) so they load
in sniper: tools/linuxfs/build-x86-preloads.sh, run in CI and by
build_local.sh. fakeinput's stat hooks fall back to __xstat on a glibc
without a stat export, and mmap64 takes off64_t for the i386 build.

* Keep a FEX unixlib from outliving its DLL

A FEX package or original bundle without the aarch64-unix helpers left
the previous version's libarm64ecfex.so / libwow64fex.so in the Proton,
so the new DLLs loaded a unixlib built for another FEX. A swap now removes
the helpers the package does not carry; FEX then runs DLL-only, which it
already falls back to when Wine cannot load the unixlib.

* Fix the Wine sync choices an audit found

- wineserver tab: it passed Proton's environment through, so a build with
  esync built in (GE, cachyos) still ran esync and a kernel ntsync would
  still be used. It now sets PROTON_NO_ESYNC, PROTON_NO_FSYNC and
  PROTON_NO_NTSYNC (unless the game's own environment sets them).
- ntsync tab: a Proton whose wineserver has no /dev/ntsync client got
  wineserver-only sync although fsync would work. The hash cache records
  whether the wineserver has one; without it the launch goes to
  droiddeck-fsync, and no stand-in or pack turns droiddeck-ntsync on.
- A build with esync built in returned before the per-game fallbacks, so
  a game that turns esync off ran with no in-process sync. It now gets
  droiddeck-fsync, or droiddeck-ntsync where fsync is off too.
- When a pack's proton cannot start and the stock one is used, or the
  selection fails, the prefix's record now says stock, so a second
  launch into the same running prefix no longer picks the pack's ntdll.
- droiddeck-fsync: its mutex is held across fork and re-made in the
  child, a closed or replaced shm fd is forgotten (close, dup2, dup3,
  close_range, closefrom), and a waiter woken while it was leaving on a
  timeout or signal reports the wake instead of losing it.
- BL_SYNC_MIN_NOFILE applies per game to every verb, debug logs carry
  prefixes.json, the device report names the chosen sync instead of the
  three raw switches, and PATCHES.md and the proot notes describe the
  selector, the fsync default and droiddeck-fsync's futex_waitv.

* Name the runtime's scripts and files droiddeck, migrating old installs

The guest scripts are droiddeck-* now (droiddeck-session and the other 19),
as are the files the app and scripts generate (etc/droiddeck, the network
state files, the components dir, the Steam install stamp, the redist marker,
the extra-Proton wrapper and backups) and the compositor's own names
(libdroiddeckwayland, droiddeck_* sources, DROIDDECK_* switches, the
wl_output and seat names).

Installed runtimes carry on: LegacyNames moves etc/bannerlator and
share/bannerlator over and leaves relative links at the old paths before
every proot command, turns any old script into a link to its new name,
moves the generated files and points menu entries at the new scripts.
The session scripts and steam-compatibility move their markers on first
use, the components dir's desired.tsv is rewritten with it, and the
runtime counts as installed under either session script.

Kept: the banner_ahb_v1/banner_desktop_v1 wire names and BANNER_AUDIO_DIRECT
(read by prebuilt Turnip and Wine builds), Banners-Turnip and other
third-party names, BL_* switches, libblsession, and credits to Bannerlator.

* Unpack x86 AppImages without FEX, then ask Steam for FEX

Importing an x86 AppImage failed when FEX was not installed yet ("needs FEX
to unpack and no unsquashfs is installed"). AppImages now unpack with
uruntime (VHSgunzo, MIT, v0.8.1 aarch64, pinned by sha256 and shipped
unmodified with its licence), which reads SquashFS and DwarFS images of any
architecture natively. FEX's own --appimage-extract and unsquashfs stay as
fallbacks, and an ARM64 image whose own runtime cannot unpack it goes the
same way.

An x86 import with FEX missing records that FEX is wanted, so the next
Steam session requests FEX and the Steam Linux Runtime, and the program's
page says FEX is not set up yet and how to get it.

* Run Vulkan Qt Quick AppImages on Wayland

OpenNOW (Qt 6.8, Qt Quick on Vulkan) closed within two seconds in a run
mode. The session sends toolkits to gamescope's Xwayland, and Qt's xcb
backend only presents with Vulkan when Mesa's X11 WSI does, which needs
DRI3; Xwayland on KGSL has none, so Qt found no queue that can present and
aborted ("Failed to create RHI").

An AppImage that bundles Qt Quick, the Vulkan loader and Qt's Wayland
plugin now runs on gamescope's Wayland socket (the desktop's, outside
gamescope), where Turnip presents as it does for games, with Qt's xcb
backend as the fallback. SDL_VIDEODRIVER and XDG_SESSION_TYPE follow, so
helpers that pick their window system from them (OpenNOW's streamer) agree
with Qt. Everything else stays on X11; a qt file in the app's directory
(wayland or xcb) overrides the choice.

* Keep gamescope's WSI layer away from Qt apps on its Wayland socket

OpenNOW still closed at once: its FFmpeg (the Raspberry Pi fork, libavutil
61) creates its Vulkan queues internally synchronized whenever the driver
offers VK_KHR_internally_synchronized_queues, which DD-Turnip does. The
gamescope WSI layer registers every queue of a new device through
vkGetDeviceQueue, which only returns queues created without flags, so it
got VK_NULL_HANDLE and vkroots aborted on assert(obj) before Qt opened a
window.

Programs that draw on gamescope's Wayland socket do not need the layer,
which only carries X11 windows past Xwayland, so it is turned off for
them. OpenNOW bundles no Vulkan loader (it reaches Vulkan through Qt and
dlopen), so a binary that uses QVulkanInstance counts as a Vulkan app too,
and a Qt Quick app left on X11 says why in the session log.

* Keep the end of the session log, and say when FEX is missing

The session sends its output through head -c into session.log. head
writes through stdio, so it held up to 4 KB that proot's --kill-on-exit
threw away when the guest ended: every log stopped mid-line at a 4096-byte
boundary, losing exactly the lines that say why a program quit (an x86
AppImage's "x86 Linux programs need FEX" among them). dd writes each read
as it comes and keeps the same cap.

A run mode always ends with status 0 (gamescope does not pass its child's
on), so a program that could not start just closed. When the log says FEX
is missing, the ended screen now says so and how to get it.

The app reads FEX's readiness from status.json, which only a launch or an
import wrote, so it still said "not set up" after Steam installed FEX.
droiddeck-fex status rewrites it, and a Steam session ends with it while
FEX is wanted. FEX is also looked for at the tool's root and under files/,
and a tool folder without it lists what it holds.

* Let FEX and its runtime install runnable on an SD card library

A Steam library on shared storage loses the executable bits, so native
tools are staged in private directories bound over it; only Proton and
SteamLinuxRuntime folders were. FEX (3127680) installed there could never
run, droiddeck-fex saw no FEX and kept asking Steam for it. FEX and the
sniper runtime (1628350) now get private directories from their first
download, whatever their install folder is called.

* Keep fsync off in the pack smoke test

A runner whose kernel allows futex_waitv started stock Proton with fsync,
so the server-side baseline never said what the test waits for and every
pack was waved through as untestable; the esync run would have measured
fsync too.

* Judge a pack against stock's own esstress failures

Stock Proton fails esstress's dup wait all under server-side sync as well
(a Wine bug, seen on the phone too), and any stock failure made the test
call the runner unusable. A pack now fails only when it crashes, hangs or
fails a check stock passes.

* Ask Steam for FEX and its runtime one at a time

Both steam://install requests were started together, and the running client
took only the first: FEX installed, the sniper runtime FEX needs never did.
Each request now waits for the client to take it before the next.

* Bundle the first published droiddeck-esync packs in the apk

* Log how an x86 program under FEX ends

FEX keeps its own errors quiet unless asked, so a program it could not run
left nothing in the session log. Its log now goes to stderr, and the AppImage
launcher records the program's exit status or the signal that ended it.

* Show the log a failed AppImage program wrote

Programs like Eden stop themselves on a failed check and say why only in
their own log under the home folder. When the program ends badly, the end of
any log it wrote during the run is copied into the session log.

* Report where an x86 program under FEX hit an illegal instruction

A tiny x86_64 preload, built without libc, catches SIGILL and writes the
address, the instruction bytes and the mapping they belong to before the
program dies as it would have. droiddeck-fex preloads it into x86_64 programs.

* Load the fault reporter from the x86 runtime

Programs under FEX look libraries up in the x86 runtime, so the reporter is
copied there with the other preloads and named by its path inside it.

* Hand FEX its x86 runtime and thunks through the environment

Valve's FEX runs portable and reads its config beside itself, so the
Config.json droiddeck-fex writes was not seen: programs ran without the
runtime. The runtime, thunk libraries and thunk list now also go in FEX's
own environment variables, and each launch says what it was given.

* Use the sync pack built for the installed Proton release

Some Proton releases ship byte-identical ntdll.so and wineserver (Valve
11.0-2b/2c, CachyOS 0702/0703), so a pack built for a newer release
matched an older install exactly and the older install never fetched its
own pack. A pack from a newer release no longer counts as an exact match;
among matches the one built for the installed version wins, then rev.

The app ranks the pack a tool already uses the same way and only fetches
a pack that ranks higher, reads the release epoch from tools.tsv and
refetches when tools.tsv changes. A prefix whose server is still running
re-checks its recorded pack on waitforexitandrun and starts fresh when a
better one now applies.

* Keep published sync packs and revocations consistent

The publish step never replaces a pack that has an entry beside it; a
pack left without its entry by an interrupted run is replaced so the
build stops repeating. Discovery counts revocations the signed index does
not show yet, including superseded packs only the flavor releases list,
so the index is signed again. The release watch always builds every
flavor.

* Fetch the droiddeck-esync packs from DroidDeck-Components

The packs, the signed index and the apk bundle now live in the releases
of Droid-Deck/DroidDeck-Components, together with the pipeline that
builds them, so this repository keeps only the app and the guest code
that consumes them. The app's index and asset URLs point there,
release.env names the repository its bundle comes from, and the apk
builds drop bundled packs that the published index revokes.

* Stop the apk build when the published sync index cannot be read

* Ask Steam for one FEX dependency at a time

The client shows a confirmation dialog for each steam://install, and a
second request replaces the first dialog, so FEX was never installed
when the runtime was requested right after it. The next dependency is
now requested once the previous one has an app manifest.

* Use generic names in the AppImage test fixtures

* Give x86-64 apps under FEX the Vulkan thunk

Apps that bring their own glibc and Vulkan loader skipped FEX's thunk
overlay and loaded their bundled desktop Mesa drivers, which crash on the
phone's GPU during Vulkan setup. droiddeck-fex now adds an x86-64 audit
module when a program's bundled glibc and libstdc++ meet the thunk's
version needs. The module sends libvulkan.so.1 lookups to a nodelete stub
that needs FEX's thunk path. That keeps the thunk mapped when an app
unloads Vulkan and loads it again, since FEX keeps its first trampoline
links and the old addresses get reused.

The fault reporter now restores the default action before re-raising,
because FEX ignores SA_RESETHAND and the handler looped.
2026-10-04 12:43:57 -04:00
Kurt Himebauch b2bde1c14d Add opt-in Steam storage diagnostics (#158) 2026-10-04 08:19:48 -04:00
xXJsonDeruloXx 69b5a17e20 Merge main into PR #93 2026-09-30 19:41:59 -04:00
MaxsTechReview 5251c6a5f6 Additional performance fixes (#87)
* perf: answer Turnip's zero-timeout KGSL timestamp polls without blocking

Turnip polls a timeline point with IOCTL_KGSL_DEVICE_WAITTIMESTAMP_CTXTID
and a timeout of 0, which KGSL reads as wait forever; Mesa does it under
a lock on every signalling submit, so vkd3d-proton, DXVK, gamescope and
Zink ran in lock-step with the GPU. The session preload now answers
those polls from the context's retired timestamp and passes everything
else to the kernel. Bannerlator measured a D3D12 demo at 614 -> 4335 fps
with the same answer in its Vulkan adapter. BL_NO_KGSL_POLL_FIX=1 turns
it off.

* perf: frame limiter, live panel refresh, opt-in GPU clock pin, cheaper cursor sync

- A per-mode frame limit (Steam and desktop settings, off by default):
  gamescope's -r, the compositor's buffer-release pacer, the display
  layer's frame-rate vote and a panel mode the cap divides evenly, as
  WinNative's per-shortcut limit does. BL_FPS was always 0 and the
  pacer's limit was never set.
- The compositor follows the panel's refresh rate when it changes
  mid-session instead of reporting the rate read at start. (WinNative.)
- Performance > GPU: hold the Adreno clock at its top through KGSL's
  power-control property, no root (Bannerlator's PerfGpuTurbo). Off by
  default; cleared when the session stops and at the next app start if
  a killed process left it set.
- The cursor image is copied across JNI only when its serial changes,
  not on every pointer move.

* perf: stop re-seeding every prefix every 5 s, and run the library helpers only on change

- bannerlator-seed-redists stamped its change check from a block that
  carried the current time, so it never matched: every prefix had ~32
  keys appended to system.reg on every beat. The stamp now comes from
  the names; Proton's prefix template is seeded too (date kept), with
  WinNative's built-in list until the client has the install scripts.
- The library beat runs the seeder every 15 s and steam-compat only
  when a directory it reads has changed, or once a minute.
- The client-core pin reads /proc itself and runs taskset -a only for
  new processes or ones that grew threads.
- The games watcher backs off after a failed ownership probe and does
  not probe while a game runs.
- The Proton launcher runs the newest ARM64 depot by build number, and
  PROTON_USE_PIPEWIRE defaults to 0. (WinNative.)

* perf: Mesa's database shader cache, and proot's tracer above the guest

- MESA_DISK_CACHE_DATABASE=1 for the session: the file cache opens a
  file per lookup and opens and renames one per store, each a proot
  stop; the database is opened once and used with pread/pwrite.
- proot's single tracer thread is raised to nice -6 (under the
  compositor's -8) two seconds after it starts, so the first guest
  process it forks keeps nice 0. setpriority() directly, not
  Process.setThreadPriority, which would also move its cgroup.
2026-09-30 16:43:52 -04:00
xXJsonDeruloXx be86fd953a fix: report Adreno 840v2 as the A840 the runtime's Turnip knows 2026-09-30 07:53:12 -04:00