From 272cd60b198d46e5515ffbca7ebed26fcd1969b6 Mon Sep 17 00:00:00 2001 From: The412Banner Date: Fri, 9 Oct 2026 04:30:53 -0400 Subject: [PATCH] Stores: a game's web links reach Android - the hand-off was never packaged EOS's browser flow never reached Android because the hand-off script was not in the APK: the build copies only usr/local/bin/droiddeck-*, steam-compatibility and usr/bin/** of the overlay into the assets, so usr/local/lib/droiddeck/browser/xdg-open was skipped, the session staged nothing, the wrapper left the PATH alone, and Wine's winebrowser ran the runtime's own xdg-open, which has no browser to open in a Steam session. GE-Proton's winebrowser is Wine's own: its Browsers list, then xdg-open by name, with no steam://openurl route. The script is now usr/local/bin/droiddeck-open-url: named as Wine's browser in the prefix by its absolute path, linked as xdg-open first on the game's PATH, and reading the session channel from a file when Wine does not pass BL_LAUNCH_DIR on. BL_DEBUG_BROWSER=1 adds Wine's shell and winebrowser traces. The Epic card offers Resolve Epic sign-in, Epic's account page behind its sign-in, where a pending corrective action is presented. --- .../launcher/session/SessionFiles.kt | 4 ++-- .../droiddeck/launcher/ui/FrontEndSetup.kt | 11 ++++++++++ app/src/main/res/values/strings.xml | 1 + docs/progress-log.md | 17 ++++++++++++++- .../xdg-open => bin/droiddeck-open-url} | 21 +++++++++++++------ .../usr/local/bin/droiddeck-store-launch | 9 +++++--- .../overlay/usr/local/bin/steam-compatibility | 15 ++++++++++--- tools/tests/test_store_launch.py | 4 ++-- 8 files changed, 65 insertions(+), 17 deletions(-) rename tools/linuxfs/overlay/usr/local/{lib/droiddeck/browser/xdg-open => bin/droiddeck-open-url} (59%) diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionFiles.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionFiles.kt index 4274ad23f..5cf7348f0 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionFiles.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionFiles.kt @@ -62,8 +62,8 @@ object SessionFiles { "usr/local/bin/droiddeck-steam-shortcuts" to "usr/local/bin/droiddeck-steam-shortcuts", // Asks the app for an Epic game's sign-in code at launch, from the compat tool (StoreLaunchRequests). "usr/local/bin/droiddeck-store-launch" to "usr/local/bin/droiddeck-store-launch", - // A game's web links to Android's browser (winebrowser -> xdg-open), on game PATHs only. - "usr/local/lib/droiddeck/browser/xdg-open" to "usr/local/lib/droiddeck/browser/xdg-open", + // A game's web links to Android's browser (Wine's winebrowser starts it). + "usr/local/bin/droiddeck-open-url" to "usr/local/bin/droiddeck-open-url", "usr/local/bin/droiddeck-steam-games" to "usr/local/bin/droiddeck-steam-games", "usr/local/bin/droiddeck-pad-defaults" to "usr/local/bin/droiddeck-pad-defaults", // Flatpak: the bwrap stand-in, the store's helper and setup, and the front end's launcher. diff --git a/app/src/main/java/com/droiddeck/launcher/ui/FrontEndSetup.kt b/app/src/main/java/com/droiddeck/launcher/ui/FrontEndSetup.kt index f2c408c0b..ce5503bec 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/FrontEndSetup.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/FrontEndSetup.kt @@ -505,6 +505,9 @@ internal fun LaunchSettings(s: FrontEndState, a: FrontEndActions, host: MenuHost } } +/** Epic's account page behind its sign-in: signing in there presents any step the account still owes. */ +private const val EPIC_RESOLVE_URL = "https://www.epicgames.com/id/login?redirectUrl=https%3A%2F%2Fwww.epicgames.com%2Faccount%2Fpersonal" + /** The Epic card: sign-in, offline and overlay switches, read from and written to the game's sidecar. */ @Composable private fun EpicLaunchCard(host: MenuHost, folder: java.io.File) { @@ -535,6 +538,14 @@ private fun EpicLaunchCard(host: MenuHost, folder: java.io.File) { MenuItem(stringResource(R.string.epic_eos), checked = options.eos, focusRequester = first) { set { it.copy(eos = !it.eos) } } MenuItem(stringResource(R.string.epic_offline), checked = options.offline) { set { it.copy(offline = !it.offline) } } MenuItem(stringResource(R.string.epic_overlay), checked = options.overlay) { set { it.copy(overlay = !it.overlay) } } + // Epic asks some accounts to accept something once (privacy policy, EULA) before a game may + // sign in - EOS's "corrective action". Signing in on Epic's site shows it. + MenuItem(stringResource(R.string.epic_resolve), checked = false) { + host.open = null + runCatching { + appContext.startActivity(android.content.Intent(android.content.Intent.ACTION_VIEW, android.net.Uri.parse(EPIC_RESOLVE_URL)).addFlags(android.content.Intent.FLAG_ACTIVITY_NEW_TASK)) + } + } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c2abb6201..08ae047f7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2020,6 +2020,7 @@ Epic sign-in (EOS) Launch offline Epic overlay + Resolve Epic sign-in installed in your library store diff --git a/docs/progress-log.md b/docs/progress-log.md index 0392a353c..37f42d81f 100644 --- a/docs/progress-log.md +++ b/docs/progress-log.md @@ -144,6 +144,21 @@ without it and Setup says "Stores engine not built"). (`token`, `__token__`, `f_token`, `hdnts`, access/refresh/id tokens, `code=`) and Authorization / Cookie headers are blanked outside URLs too. `StoreLogTest` covers a GOG secure link and an Epic Akamai URL. The engine's own logcat lines (tag `EpicNative` etc.) are written on the native side. +- **Why EOS's browser flow never reached Android:** the hand-off script was never in the APK. The + build copies only `usr/local/bin/droiddeck-*`, `steam-compatibility` and `usr/bin/**` of the overlay + into the assets, so `usr/local/lib/droiddeck/browser/xdg-open` was skipped, the session staged + nothing, the wrapper did not put it on the PATH, and Wine's winebrowser ran the runtime's own + `/usr/bin/xdg-open`, which has no browser to open in a Steam session. GE-Proton 11-7's winebrowser + (Metalstorm's tool) is Wine's own - registry `Browsers` list, then `xdg-open` by name - with no + steam://openurl route. The script is now `usr/local/bin/droiddeck-open-url`: named as Wine's + browser in the prefix (`"Browsers"="/usr/local/bin/droiddeck-open-url,xdg-open"`, an absolute path + that does not depend on Wine's PATH), linked as `xdg-open` first on the game's PATH, and reading + the session channel from `/tmp/droiddeck-browser/launch-dir` when Wine does not pass + `BL_LAUNCH_DIR` on. Whether EOS calls ShellExecute at all in its browser flow (rather than handing + a verification URI and code to the game to show) is what the next run's `droiddeck xdg-open:` line, + or `BL_DEBUG_BROWSER=1` in droiddeck-env (`WINEDEBUG=+shell,+winebrowser`), will show. +- **Resolve Epic sign-in** (the Epic card): opens Epic's account page behind its sign-in in Android's + browser, where a pending corrective action (privacy policy, EULA) is presented. - **Epic overlay off by default** (device A/B, Metalstorm: overlay pointer set → exits while loading, twice; off → signs in to its Starform account and runs at 118 fps). `EpicOptions.overlay` defaults to false; the sidecar's `epic` block carries `"v": 2`, and an older block (the old default @@ -186,7 +201,7 @@ without it and Setup says "Stores engine not built"). (`STEAM_COMPAT_DATA_PATH/pfx/user.reg`, appended as DirectAudio's key is; a prefix Proton has not created yet gets it on the next launch). Logged as `epic overlay installed= prefix=`. A game's web links now open on Android: Wine's browser is set to xdg-open, and on a game's PATH - `/usr/local/lib/droiddeck/browser/xdg-open` hands http(s) addresses to the app, which opens them + `droiddeck-open-url` hands http(s) addresses to the app, which opens them with Android's browser - so a device-code sign-in can be finished on the phone. - **Epic sign-in from Steam's own Play button (device, Metalstorm "Guest Account"):** the game was started from the Steam client, so the app never minted its exchange code - the code was only diff --git a/tools/linuxfs/overlay/usr/local/lib/droiddeck/browser/xdg-open b/tools/linuxfs/overlay/usr/local/bin/droiddeck-open-url similarity index 59% rename from tools/linuxfs/overlay/usr/local/lib/droiddeck/browser/xdg-open rename to tools/linuxfs/overlay/usr/local/bin/droiddeck-open-url index bc8c2c7fe..69d5c65aa 100755 --- a/tools/linuxfs/overlay/usr/local/lib/droiddeck/browser/xdg-open +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-open-url @@ -1,11 +1,15 @@ #!/usr/bin/python3 -"""xdg-open for games: a web address a game opens (Wine's winebrowser runs xdg-open) goes to -Android's browser through the app, so an EOS device sign-in or a store page can be finished on -the phone. On a game's PATH only (droiddeck-proton puts this directory first); anything that is not -http(s), or with no session channel, goes on to the system's own xdg-open.""" +"""A game's web links to Android: Wine's winebrowser starts this (named as the browser in the +prefix's HKCU\\Software\\Wine\\WineBrowser, and as xdg-open first on a game's PATH), and an +http(s) address goes to the app, which opens it in Android's browser - so an EOS sign-in step or a +store page can be finished on the phone. Anything else goes on to the system's own xdg-open. + +The session channel is $BL_LAUNCH_DIR/stores; Wine may not pass that variable on to what it +starts, so droiddeck-proton also leaves it in LAUNCH_DIR_FILE.""" import json import os +LAUNCH_DIR_FILE = "/tmp/droiddeck-browser/launch-dir" import sys import time from pathlib import Path @@ -36,14 +40,19 @@ def main(argv): if url.startswith("steam://openurl/"): url = url[len("steam://openurl/"):] launch_dir = os.environ.get("BL_LAUNCH_DIR", "") + if not launch_dir: + try: + launch_dir = open(LAUNCH_DIR_FILE).read().strip() + except OSError: + launch_dir = "" print("droiddeck xdg-open: %s" % where(url), file=sys.stderr, flush=True) if url.startswith(("https://", "http://")) and launch_dir: hand_over(url, launch_dir) return 0 - here = os.path.dirname(os.path.abspath(__file__)) + here = os.path.dirname(os.path.realpath(sys.argv[0])) for d in os.environ.get("PATH", "").split(os.pathsep): candidate = os.path.join(d, "xdg-open") - if os.path.abspath(d) != here and os.access(candidate, os.X_OK): + if os.access(candidate, os.X_OK) and os.path.realpath(candidate) != os.path.realpath(__file__) and os.path.abspath(d) != here: os.execv(candidate, [candidate] + argv) return 1 diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-store-launch b/tools/linuxfs/overlay/usr/local/bin/droiddeck-store-launch index fdc6672e1..2c5c0d473 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-store-launch +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-store-launch @@ -21,8 +21,8 @@ an earlier launch is never used. The game's prefix also gets the EOS overlay's pointer, HKCU\Software\Epic Games\EOS "OverlayPath" (the mechanism from Legendary's lfs/eos.py), naming the one copy the app keeps at OVERLAY_DIR; with it the game's EOS SDK can show Epic's own sign-in steps in game (a consent Epic asks for once per -product) instead of waiting on a browser. Wine's browser is pointed at xdg-open, which this -runtime's /usr/local/lib/droiddeck/browser/xdg-open hands to Android. +product) instead of waiting on a browser. Wine's browser is set to droiddeck-open-url, which hands +web addresses to Android. droiddeck-store-launch """ @@ -40,6 +40,7 @@ TIMEOUT = 5.0 STALE_SECONDS = 300 OVERLAY_DIR = "/root/.local/share/droiddeck/epic-overlay" SERVER_WAIT = 8.0 +BROWSER = "/usr/local/bin/droiddeck-open-url" OVERLAY_DLL = "EOSOVH-Win64-Shipping.dll" EOS_KEY = "[Software\\\\Epic Games\\\\EOS]" BROWSER_KEY = "[Software\\\\Wine\\\\WineBrowser]" @@ -228,7 +229,9 @@ def provision_prefix(compat_data, overlay_dir=OVERLAY_DIR, overlay=False, wait=S return "prefix-not-created" # A wineserver still up for this prefix would write its own copy of user.reg over these lines. busy = not wait_for_server(pfx, wait) - set_value(reg, BROWSER_KEY, "Browsers", "xdg-open") + # winebrowser starts the first entry it can; an absolute path does not depend on the PATH Wine + # hands it. + set_value(reg, BROWSER_KEY, "Browsers", BROWSER + ",xdg-open") if not overlay: remove_value(reg, EOS_KEY, "OverlayPath") state = "overlay-off" if not has_value(reg, EOS_KEY, "OverlayPath") else "overlay-still-set" diff --git a/tools/linuxfs/overlay/usr/local/bin/steam-compatibility b/tools/linuxfs/overlay/usr/local/bin/steam-compatibility index a2a9b542f..9698b0f20 100755 --- a/tools/linuxfs/overlay/usr/local/bin/steam-compatibility +++ b/tools/linuxfs/overlay/usr/local/bin/steam-compatibility @@ -103,9 +103,18 @@ TOOL_MANIFEST_VDF = """"manifest" BL_STORE_SETUP = """ bl_store_launch() { [ "${verb:-}" = waitforexitandrun ] || return 0 - # A web address the game opens (Wine's winebrowser runs xdg-open) goes to Android's browser. - if [ -x /usr/local/lib/droiddeck/browser/xdg-open ]; then - PATH="/usr/local/lib/droiddeck/browser:$PATH"; export PATH + # A web address the game opens (Wine's winebrowser runs xdg-open) goes to Android's browser: + # droiddeck-open-url as xdg-open, first on the game's PATH, with the session channel left in a + # file in case Wine does not pass BL_LAUNCH_DIR on. (The prefix also names it as Wine's browser.) + if [ -x /usr/local/bin/droiddeck-open-url ]; then + mkdir -p /tmp/droiddeck-browser + ln -sfn /usr/local/bin/droiddeck-open-url /tmp/droiddeck-browser/xdg-open + [ -n "${BL_LAUNCH_DIR:-}" ] && printf '%s\n' "$BL_LAUNCH_DIR" > /tmp/droiddeck-browser/launch-dir + PATH="/tmp/droiddeck-browser:$PATH"; export PATH + fi + # BL_DEBUG_BROWSER=1 (droiddeck-env): Wine traces how the game opens a web address. + if [ "${BL_DEBUG_BROWSER:-}" = 1 ]; then + WINEDEBUG="${WINEDEBUG:+$WINEDEBUG,}+shell,+winebrowser"; export WINEDEBUG fi [ -x /usr/local/bin/droiddeck-store-launch ] || return 0 /usr/local/bin/droiddeck-store-launch "$@" || true diff --git a/tools/tests/test_store_launch.py b/tools/tests/test_store_launch.py index e55880f99..e83bfbf9f 100644 --- a/tools/tests/test_store_launch.py +++ b/tools/tests/test_store_launch.py @@ -132,7 +132,7 @@ class Prefix(unittest.TestCase): text = self.reg.read_text() self.assertIn('[Software\\\\Epic Games\\\\EOS] ', text) self.assertIn('"OverlayPath"="Z:' + str(self.overlay).replace('/', '\\\\') + '"', text) - self.assertIn('"Browsers"="xdg-open"', text) + self.assertIn('"Browsers"="/usr/local/bin/droiddeck-open-url,xdg-open"', text) # A second launch adds nothing: the last word already says so. store.provision_prefix(str(self.compat), str(self.overlay), overlay=True) self.assertEqual(text, self.reg.read_text()) @@ -178,7 +178,7 @@ class Choices(unittest.TestCase): class Browser(unittest.TestCase): def test_a_web_address_is_handed_to_the_app(self): - xdg = load_path(BIN.parent / 'lib/droiddeck/browser/xdg-open', 'xdg_open') + xdg = load_path(BIN / 'droiddeck-open-url', 'open_url') with tempfile.TemporaryDirectory() as root: os.environ['BL_LAUNCH_DIR'] = root try: