From c50d424e92fe7eaf2cad9abade47fd9fe76af804 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Sun, 8 Nov 2026 23:37:57 -0500 Subject: [PATCH] fix(native): controllers reach Steam and the install-script evaluator no longer blocks game moves --- app/src/main/cpp/fakeinput_steam.cpp | 10 +++++++++- .../overlay/usr/local/bin/droiddeck-game-env | 4 ++++ tools/nativeguest/hook/fileops.c | 2 ++ tools/nativeguest/hook/files.c | 3 +++ tools/nativeguest/hook/paths.c | Bin 17775 -> 24580 bytes 5 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/cpp/fakeinput_steam.cpp b/app/src/main/cpp/fakeinput_steam.cpp index 9558d780c..f67515ba6 100644 --- a/app/src/main/cpp/fakeinput_steam.cpp +++ b/app/src/main/cpp/fakeinput_steam.cpp @@ -2311,7 +2311,15 @@ __attribute__((visibility("hidden"))) static bool is_deck_sysfs_path(const char if (!path) return false; if (!strncmp(path, "/sys/devices/droiddeck", 22) || !strncmp(path, "/sys/class/hidraw", 17)) return true; const char *listing = deck_listing_dir(); - return listing && !strncmp(path, listing, strlen(listing)); + if (!listing) return false; + if (!strncmp(path, listing, strlen(listing))) return true; + // Without proot, a descriptor's /proc/self/fd link names the stand-in where it really is (the + // listing's parent holds devices/ and class-hidraw/), not /sys + const char *slash = strrchr(listing, '/'); + if (!slash || slash == listing) return false; + size_t root = static_cast(slash - listing); + return !strncmp(path, listing, root) && path[root] == '/' && + (!strncmp(path + root, "/devices", 8) || !strncmp(path + root, "/class-hidraw", 13)); } __attribute__((visibility("hidden"))) static bool is_deck_sysfs_fd(int fd) { diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env b/tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env index 9fb3c39c6..e382dfd87 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-game-env @@ -1147,6 +1147,10 @@ def main(): if scripts and install_scripts_done(scripts, env): print("droiddeck-game-env: install scripts already run in this prefix; evaluator skipped", file=sys.stderr) return 0 + if scripts and not env.get("DBUS_SESSION_BUS_ADDRESS"): + # libdbus would autolaunch a session bus under the evaluator's reaper, which then waits for it + # forever: Steam keeps seeing the app as running and cannot move or update it + env["DBUS_SESSION_BUS_ADDRESS"] = "disabled:" try: digest = scripts_digest(scripts) if scripts else None except OSError: diff --git a/tools/nativeguest/hook/fileops.c b/tools/nativeguest/hook/fileops.c index fdad4e024..7913a281d 100644 --- a/tools/nativeguest/hook/fileops.c +++ b/tools/nativeguest/hook/fileops.c @@ -13,6 +13,7 @@ int faccessat(int dirfd, const char *pathname, int mode, int flags) { errno = EFAULT; return -1; } + DDNATV_AT_REMAP(dirfd, pathname); struct stat st; int stat_flags = (flags & AT_SYMLINK_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0; @@ -328,6 +329,7 @@ ssize_t readlinkat(int dirfd, const char *pathname, char *buf, size_t bufsiz) { } static ssize_t (*orig_readlinkat)(int, const char *, char *, size_t) = NULL; if (!orig_readlinkat) orig_readlinkat = (ssize_t (*)(int, const char *, char *, size_t))dlsym(RTLD_NEXT, "readlinkat"); + DDNATV_AT_REMAP(dirfd, pathname); char pbuf[PATH_MAX]; const char *target = (pathname && pathname[0] == '/') ? rewrite_path(pathname, pbuf, sizeof(pbuf)) : pathname; ssize_t ret = orig_readlinkat ? orig_readlinkat(dirfd, target, buf, bufsiz) : -1; diff --git a/tools/nativeguest/hook/files.c b/tools/nativeguest/hook/files.c index 1643f988d..0e5ee1a08 100644 --- a/tools/nativeguest/hook/files.c +++ b/tools/nativeguest/hook/files.c @@ -121,6 +121,7 @@ int openat(int dirfd, const char *pathname, int flags, ...) { static int (*orig_openat)(int, const char *, int, ...) = NULL; if (!orig_openat) orig_openat = (int (*)(int, const char *, int, ...))dlsym(RTLD_NEXT, "openat"); init_ddnatv_hook(); + DDNATV_AT_REMAP(dirfd, pathname); char exe_buf[PATH_MAX]; if (is_proc_self_exe(pathname)) { if (g_real_exe[0] != '\0') { @@ -513,6 +514,7 @@ int lstat(const char *pathname, struct stat *statbuf) { int fstatat(int dirfd, const char *pathname, struct stat *statbuf, int flags) { static int (*orig_fstatat)(int, const char *, struct stat *, int) = NULL; if (!orig_fstatat) orig_fstatat = (int (*)(int, const char *, struct stat *, int))dlsym(RTLD_NEXT, "fstatat"); + DDNATV_AT_REMAP(dirfd, pathname); char buf[PATH_MAX]; const char *target = (pathname && pathname[0] == '/') ? rewrite_path(pathname, buf, sizeof(buf)) : pathname; int ret = orig_fstatat ? orig_fstatat(dirfd, target, statbuf, flags) : -1; @@ -607,6 +609,7 @@ int statx(int dirfd, const char *pathname, int flags, unsigned int mask, struct __asm__ volatile("" : "+r"(pathname), "+r"(statxbuf)); static int (*orig_statx)(int, const char *, int, unsigned int, struct statx *) = NULL; if (!orig_statx) orig_statx = (int (*)(int, const char *, int, unsigned int, struct statx *))dlsym(RTLD_NEXT, "statx"); + DDNATV_AT_REMAP(dirfd, pathname); char buf[PATH_MAX]; const char *target = (pathname && pathname[0] == '/') ? rewrite_path(pathname, buf, sizeof(buf)) : pathname; int ret = orig_statx ? orig_statx(dirfd, target, flags, mask, statxbuf) : -1; diff --git a/tools/nativeguest/hook/paths.c b/tools/nativeguest/hook/paths.c index 79d9746c254e4e36e5732dab91fe3f97607fd7ac..8a5c2a322e8b933252ec75b7c7af066f2878e26a 100644 GIT binary patch literal 24580 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 delta 3136 zcmaJ@YfPKh85R&OaR@hKTnd8gJ3a*~Jwy;p7ayqZQ9frD~uRw(}1wp(?qr5Oke~CgXt{1k^ z*R8^SJS=<+bw#HTF6zMdikg{<(X$?kMw4OrsZctVl0yk8$p#p`y{bJG{#=y6*pYCA zEU-i>wJAl}&NhoDnYk(h(c8L^HA%6~#>&u@Xhc1`dUEfb;!%8FY=@=9U&N8$Dy+q`B|6+FX~AupKPqWPS!tj4pzZKe z5~&EBo0{@sx%4vLFSWqEqCrDhxtIsa7*@;lc&2Oycgng@dQ^vY8e>Nd*r4(J(G`3| zV@%aiC-m-#cPuSQrbuf0yxEIus`shT}1OoyNz<$4S*` zu7(2hRZlH`TA>*cD7K`n$o6?tYDcy)5qe6r*(;WawoHFPLT6lY85LsISgsr@Dp#;s z*@ssvheVJ+tsH@?st55ZKi;UCd#T#0U{vCe6&CU3DLGX=hwsyPP<=v4LuNNFM>d#g zJ1lQ3yMtzS5rZ||*sbZs+citLT5GS1hUKuq#yYbgBjJq1j0s#->#?m?a+CCHR4|kOc7wTCeB)uU4*x1UUK9^{@x0*-{%Ww`^Qm?r zyTz{p~Jf+S~uX%3ro`k*cZ|H$K)I=quD>!E44U zW$R`!nnHNdG>y9^9gX^uIcjXogGTdYuW&h%N@irHv@#04g8m>eEm3O| zth{k;hlvM2Fxzp*Jc937n$cmY#+YS-zPs=n%ebIftTh<0PPPfLFIC7RWsA40Gx);V zbA)r_*VYDn#ip$lqWQ3LVC2xVwpIMYwvK80I(}|n)9}WVmUh!|S*E5>dwgBqaKPc1 z$7?+X+;DW_eMbi>oo5ktHi`Xy#i_@uPBZS%{J_~_5@xcETnK2 z!7%Kq#e&O>XI%OQrC_sC)PlF=G;t|=>Rp$kMkw9gh$kdwl2epWBXC+?qxobD0?#wA&T?oHG|YVsI)}1r_hP?fH_g_SC`T(NXEw<4MmkBs@lZ z*VC#*kh9a-#H`ARh~ImRB?8_a9(wEw4?n!}J(HsB@7TbPJ7TCgv4M*xB>ervJi0oa zh;{bslz1Je86F>A_SDI{ycp`%;7^@3Rf3J%-sYA&y;K#CLEWWmIlSQZD6Zz*6sx$@ zZN%j+8{X*BtM~cy%?KZKt&kymxmXhhI=r~pZNd*}{H|Lo68%Yc1c4rYe5881sT>CJ zVUHQry;E52?M3&}aop%_+;3wXD{qrYX;-;GP}UE&L%z-*+Fi>oal$yW2aG~j-r0I} z?k9YtVYvaDm2MZv?~ro!IS9an|4nOhP=*-dJp0B<> zZ$Ae%=Yinp8B{CRuG}i06{I=J9+HD*TptYJ!@+jmv4%!4PviQK3%7@;2M@W0s;B#^ zCL`i#&PnFxNA%?po*>KY&y2H!EvvlsM@x`!_Y?}QpJmDV{#qf%M9i<}!7#lJA zgk&ZYzaWKVMpp)9e{;JzVtk*J-NbR$&$|oX$Zw7e;ogW7&7%$kMmO=R(Y~{JbX*Hp zNi|QGzck(bIE6p^{=^?yRaUaDkg#xNdh7}OXzWQG9S@45{Ni{MULNnqAIFEFov^?^ zVZ!Fb6n-&r<_O&{Rj51}#Ma5Pc=u#I{&KQiT>dnZ8dxX$5S#Rm<$;L1fRv1yA3cou zMkYG#<81jJ0!A&6I>Tp^%Q$mt6>pu2Kz%xjr%#(rrg5${du z>8uT5W%?X$PJ0h_uK!MtV<=FE*8?tmGhm>3#v;HSGRN