* feat: Show the drawer's game settings when a game starts without Steam
A session started without the client has no Steam overview to read the
current title from, so the drawer's game profile group stayed empty and the
per-game settings, FEX preset, environment and unfolded controls were hidden.
The launch already knows which game it starts, so carry its id into the
session and fall back to it when Steam has nothing to report.
* fix: Pass the game id into the service start, and keep the unfolded toggle to Steam
SessionActivity.surfaceCreated() hands the mode, program and args to
SessionService.start(), which builds the service intent. EXTRA_GAME_ID was
only put on the activity intent, so a new no-Steam session never received it
and the drawer fallback stayed empty. Pass it through.
The unfolded-controls toggle also went out for direct sessions, but
applyUnfoldedControls() still requires Steam mode and a Deck pad, neither of
which a direct session sets up. Keep the toggle to Steam.
* fix: hide Steam controls in direct session drawer
---------
Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
Every grid-<slot> record held only a file signature, not which grid
folder it came from. After switching accounts or reinstalling the
runtime, the new, empty grid folder looked like the user removed every
image in Steam, and all chosen art was deleted. The grid folder now
carries a random id and records store it; a record from another folder
counts as a first look, so the choice is placed again.
fix: never accept a record made with the fallback grid id
CodeRabbit: when the grid id could not be read or written, "?" was
stored in records and a later "?" matched it, so an empty grid could
still delete local art. Records now never match the fallback id, and a
blank .droiddeck-grid file is replaced instead of blocking the rename.
Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
* fix: the update status receiver only acts on this app's own install session
Before Android 13 the receiver was exported, so any app could send a
fake PENDING_USER_ACTION broadcast and have DroidDeck launch an arbitrary
intent. The receiver now ignores broadcasts whose session id does not
match the running install, launches only a system installer activity,
and strips URI grant flags.
* fix: record the install session id before committing it
CodeRabbit: the status broadcast could arrive between commit() and
install() returning, while session was still -1, and be dropped. The id
is now reported through a callback before commit, and reset to -1 at
the start of each attempt so a late broadcast from an earlier session
is ignored.
* fix: declare package installer confirmation query
* Query current PackageInstaller confirmation action
---------
Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com>
Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
* feat: choose default storage for new store installs
Store installs and the Epic Games Launcher can use internal storage, external storage, a folder picked through Choose folder..., or ask every time. The choice is stored in SessionPrefs and used by StoreInstallRoot; Storage > Default storage exposes it.
* fix: allow choosing any folder for default storage
The default storage choice can now hold an arbitrary folder path, not just internal or app-specific external storage. StoreInstallRoot resolves and validates picked folders, keeps a disconnected choice instead of silently falling back, and the folder picker stays available from Storage.
* fix: keep the Epic install folder when external storage is away
* test: use a unique missing folder for the Epic storage check
* fix: detect USB SSDs that the storage manager hides
List storage volumes that have no UUID instead of dropping them, keep volumes that are present but not yet mounted, and fall back to parsing /proc/mounts so USB OTG drives appear even when Android does not expose them as storage volumes or app directories. Prefer the system's volume description for removable drives. (Raw removable drives as move targets, the folder-picker fallback, and the unreadable-drive guidance follow with the move stack.)
* fix: compare mount paths as strings in StorageRootsTest
* fix: never drop a present storage volume from the drive list
Key volumes by description or index when they report no UUID and no directory, keep nofs/unmountable states so a plugged-in drive stays visible with guidance, only attach app directories on strict UUID matches, and accept any fstype under vold-managed locations plus usb-ish mounts directly under /mnt.
* fix: probe folder writes with a fresh temp file and keep Epic's folder unset while external storage is away
---------
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
Adds a Performance mode switch under Mode settings > Frame rate, off by default and stored per mode like the frame limit. Off keeps today's behaviour. On:
- with no frame limit, the compositor hands buffers back to the game as soon as they are replaced instead of once per refresh, and gamescope's WSI layer is asked for at least 8 swapchain images; a chosen frame limit still paces
- DROIDDECK_WSI_BYPASS_CHECK_MS=100 lets gamescope's WSI layer reuse its present-time Xwayland bypass check (patch 0117, gamescope-3.16.29-p13); WINE_OFFSCREEN_CHECK_MS=100 does the same for winex11 once a DroidDeck-Proton release carries that fix. Off sets both to 0.
The gamescope pack now ships the patched WSI layer and SessionFiles stages it over the runtime's stock copy. Two per-game options, off by default: PROTON_DXVK_D3D8 and WINE_D3D_CONFIG renderer=vulkan|gl.
AIO Graphics Test on an AYANEO Pocket FIT (Adreno 750), off -> on: Vulkan 204 -> 1787, OpenGL 296 -> 2169, D3D12 308 -> 3563, D3D11 1133 -> 2874, D3D9 1253 -> 3098 fps; DiRT Showdown 92.2 -> 93.4 (GPU-bound).
Improve Nova’s compact layouts with scrolling store tabs and build details, remove the redundant Updates heading, preserve full portrait artwork, and add drawer PiP and Select/Start shortcuts for Guide/QAM.
* fix: find GOG Galaxy SDK cloud saves when remote-config lists no locations
cloudStorage.enabled with an empty locations list is Galaxy SDK IStorage
under __default. DroidDeck treated that as no cloud.
Use the SDK folder, strip/prefix the __default namespace, and inflate gzip
bodies GOG stores on download. An empty cached template is fetched again
so devices that already recorded "no cloud" recover.
* fix: keep an empty GOG namespace, expire cached none, close gzip
An explicit empty cloud_namespace is Auto Cloud and must not become
__default just because the path template looks like Galaxy storage.
A cached none is retried after a day so a later enablement is picked up.
GZIPInputStream is closed after a compressed download.
* fix: peel GOG cloud gzip only when the service or PUT compressor says so
GET asks for identity encoding so the body is what GOG stored. A
Content-Encoding of gzip is inflated once. Otherwise a gzip body is
inflated only when it round-trips as GOG's PUT gzip (deflate 6, mtime 0),
so a gzip-format save is not rewritten.
* Reject corrupt GOG cloud downloads
---------
Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
GOG lists every cloud object as <location name>/<path>. The location name
is not part of the local folder, so Silksong's saves/user1.dat belongs at
default/user1.dat, not default/saves/user1.dat. Strip the segment for local
paths and add it back for HEAD/GET/PUT, cache the location name, and migrate
files written by the old layout without touching files the game wrote.
Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
* fix: a Linux runtime update keeps imported apps and catalog AppImages
The update carried only /root into the new tree and then discarded the
old one, so every imported AppImage, script app, Flatpak override and
catalog emulator AppImage was deleted. User-owned paths are now moved
across as a list, moved back if the carry or swap fails partway, and
recovered after an interrupted update.
* fix(runtime): retain user data when carry and recovery fail
* fix(runtime): mark staging carry start so interrupted extraction is discardable
* test: mark interrupted runtime data for recovery
* fix: make runtime data carry recoverable
* fix: the runtime update with carried user data builds again
carryUserData passed the de-duplicated Set of user paths where a List is expected after the two carry changes met; copy it into a List.
* fix: a runtime update stops instead of dropping app markers it cannot inspect, and keeps app names with line breaks
userPaths now uses the throwing exists() helper for the AppImage, menu entry and marker checks. The carry marker stores each path URL-encoded on its own line, so a name containing a newline cannot split into two paths. The symlink test now links a parent directory, which is what safePath rejects.
* fix: retain conflicting runtime recovery data on every retry
Stop on unreadable carry markers and keep rollback tests independent of add-on downloads.
---------
Co-authored-by: ovurrsl <ovurrsl@users.noreply.github.com>
Co-authored-by: xXJsonDeruloXx <danielhimebauch@gmail.com>
Co-authored-by: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com>
* itch.io: temporary Test itch.io button that logs what the API answers (not for merge)
* itch.io: show Test itch.io only while Debugging tools is on
* itch.io: version picker and installing free games
* itch.io: build the platform names outside joinToString so the composable call compiles
* itch.io: ask for the version before resuming an unfinished install
* itch.io: keep the chosen upload, save free games in the queue, don't auto-install a closed picker
* itch.io: install straight away when exactly one build is installable
* itch.io: show the picker again unless the game has exactly one build
* itch.io: ignore a second build tap while the picker is closing
* itch.io: empty a partial install before extracting so builds never mix
* stores: deleteTree removes a symlink without following it
* stores: skip symlinked folders in the uninstall walk, and only empty a partial install that is this game's own
* Add an optional HTTP proxy for downloads
Downloads only reached GitHub directly or through URL-rewrite
mirrors, which does not help where a local proxy is the workable
route. Setup now takes a host:port HTTP proxy that every Downloader
connection uses, independently of the download source mode.
* Reference the Kotlin object instance from Java
Java cannot call a Kotlin object member on the class name without
@JvmStatic; the surrounding code already uses INSTANCE.
* Assert the proxy host literal without a reverse lookup
InetSocketAddress.getHostName reverse-resolves on some JVMs
(127.0.0.1 became localhost on CI); hostString is the literal.
* Pass the app proxy into the guest environment
The Steam client installer and the guest package tools fetch with
plain curl against their own CDNs, outside Downloader. Export the
usual proxy variables for them when the app proxy is set; proot
shares the host network, so a localhost proxy is reachable as-is.
---------
Co-authored-by: VIIII4 <VIIII4@users.noreply.github.com>
* feat: adjustable RAM and VRAM caps for the container
Performance > Memory gets two sliders:
- RAM cap (soft, Steam sessions): when the session is resident above the
cap, the session watcher compresses the Steam client into zRAM, waits for
its page-out acknowledgment, then compresses the rest of the session
(processes that loaded libblsession) if still over. Nothing is closed.
Measured at most every 30 s; the client is read back once usage drops
below the cap. Invalid values leave the cap off. Needs zRAM swap.
- VRAM cap: sets dxgi.maxDeviceMemory ahead of the game's own DXVK_CONFIG,
so Direct3D games budget for less video memory. A game profile still
wins. Applies from the next session.
* fix: back off the RAM cap when paging out cannot get under it
A cap below the game's working set was never met, so the watcher paged out
the client and the whole session every 30 s for the rest of the session.
Usage is still measured every 30 s, but after a page-out that leaves the
session over the cap the next one waits twice as long (60 s up to 10 min),
resetting once usage is under the cap. The client step is skipped while the
client is already under 512 MB resident.
* fix: RAM cap covers desktop Steam, signals each group once, no idle churn
- Steam opened from the desktop runs the same session watcher, so the cap
is exported whenever the Steam client runs, not only in Steam mode.
- The second page-out goes to the rest of the session only; Steam was
signalled again even when its own step had been skipped as already small.
- A cap page-out while no game runs stays paged out. Reading the client back
right away pushed it over the cap again and repeated every minute; only a
game closing while over the cap still defers a read-back.
* fix: RAM cap marks the client packed only when it compressed it
When the cap skipped the client as already small during a game, it still
recorded the client as packed, so the regular in-game client page-out
waited up to 300 s for a page-out that never happened.
---------
Co-authored-by: jazzyjames <182448902+jazzyjamesgames@users.noreply.github.com>
The GOG sign-in WebView sent GOG Galaxy's Windows user agent while the page's scripts and client hints report an Android WebView, so Cloudflare's check on gog.com never passed (#1486). The sign-in flow now uses the WebView's own Chrome user agent, like the Epic, Google, Facebook and Apple sign-ins. API calls outside the WebView keep the Galaxy user agent.
Fixes#1486
Xbox Cloud Gaming: stream from the store and Games tab, native player with DroidDeck frame generation
Stream Xbox games with Xbox Cloud Gaming (Game Pass) from DroidDeck, outside
the Linux session.
- Backend: `xodus-cli cloud-token` (tools/xbox/droiddeck-cloud-token.patch)
gets the streaming tokens; they stay in memory.
- Web player (default): the Greenlight player page (tools/xcloud-player, MIT)
in a WebView, plus Microsoft's own xbox.com/play player. In-stream menu with
screen effects (sharpen, colour, deband, FXAA, CRT, NTSC, Toon, fps cap),
stream quality, performance overlay (level, layout, top right / top left)
and rumble.
- Native player (opt-in, Android 10+, Player engine: Native): Google's WebRTC
(io.github.webrtc-sdk 125.6422.07, BSD) with the Greenlight protocol ported
to Kotlin, in its own process (:xcloudnative) with its own instance of
DroidDeck's compositor. Decoded frames reach the compositor as
AHardwareBuffers (new stream mode, VK_ANDROID_external_memory_android_
hardware_buffer import), so DDFG 2x and LSFG 2x/3x/4x run on them. Live
frame generation, frame-rate target (presets and a 10-165 fps slider, even
pacing), zero-copy picture, small network buffer, fast decoder, instant
controller input, overlay with engine and on-screen fps, background end,
restart and web fallback. It takes the main process's tokens through
XboxCloudTokenService; they are fetched ahead while a cloud game is shown.
- The compositor requests the dma-buf import extensions only where the driver
has them (the system driver has none).
- Catalog: cloud availability, Game Pass plan CFQ7TTC0K6L8 counted, library
filter All / Owned / Game Pass / Cloud, Stream and Add to Games on the store
page, stream-only Games-tab entries (Stream + Download / Play + Stream).
- Xbox games get VC++ 2015-2022 and the ARM64 runtime registry entries
(vcredist2022_arm64, built in until the catalog has it) before each launch.
- Controller focus reaches pickers opened from a game page (install location,
uninstall); Add / Remove from Games keep the focus.
Credits: Greenlight by UnknownSKL (Jim Kroon), MIT, player and protocol;
Better xCloud for the resolution / OS mapping; MGamer404's Bannerlator #627
for the ARM64 runtime approach. Ported from the412banner's Bannerlator.
On some phones (tester: Honor BKQ-N49, Android 17) Xbox sign-in stopped with
"the Xbox helper stopped with exit code 1" before Microsoft's page opened.
The backend asks the app to read and save credentials over a socket and waits
10 s for each reply. The app sealed credentials with a StrongBox-backed
Keystore key, and each StrongBox operation took 7-15 s on that phone, so the
backend gave up. The app still finished those saves afterwards, leaving a
record without a username that every later attempt had to read back slowly.
- Seal new store credentials with a TEE Keystore key (v2 envelopes); v1
envelopes still open with the old key and move to v2 on their next write.
- A sign-in discards an Xbox record without a username (an unfinished
sign-in) before starting the backend.
- The backend waits 30 s for the app's credential reply.
- The backend names the failing step instead of only credential-io (socket
connect/exchange, session lock, missing socket name/port/key), and falls
back to a keyed 127.0.0.1 port when the abstract socket is refused.
- The app's credential listener logs and retries accept errors, serves each
connection on its own thread, and logs slow requests with per-stage times.
Refs #1337, #1300
Co-authored-by: KeegantheVeganat0r <58369898+KeegantheVeganat0r@users.noreply.github.com>