From eb8634ea22b8aaddb66d436c654a217a6b3b620f Mon Sep 17 00:00:00 2001 From: The412Banner Date: Sat, 17 Oct 2026 23:42:33 -0400 Subject: [PATCH] fix: review fixes for the x86-64 Steam client - The library cache is saved only by the newest scan, like the list it feeds. - A session records the Steam client it ran (steam-client.txt), and an abandoned session's Steam logs are copied from that client. - Console logs from the same game and mod in two libraries get distinct names in the VAC report. - Client CPU pinning also recognises FEXInterpreter as the launcher. - pl, ru, uk, cs, bg: the VAC hint names the game as what is protected. --- .../com/droiddeck/launcher/MainActivity.kt | 2 +- .../launcher/session/SessionArtifacts.kt | 12 ++++++++++- .../launcher/session/SessionService.kt | 5 ++++- .../launcher/session/VacDiagnostics.kt | 14 +++++++++++-- app/src/main/res/values-bg/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- .../session/SessionClientRecordTest.kt | 20 +++++++++++++++++++ .../launcher/session/VacDiagnosticsTest.kt | 11 ++++++++++ .../overlay/usr/local/bin/droiddeck-session | 2 +- .../overlay/usr/local/bin/droiddeck-steam-x64 | 2 +- 13 files changed, 66 insertions(+), 12 deletions(-) create mode 100644 app/src/test/java/com/droiddeck/launcher/session/SessionClientRecordTest.kt diff --git a/app/src/main/java/com/droiddeck/launcher/MainActivity.kt b/app/src/main/java/com/droiddeck/launcher/MainActivity.kt index 80ca83c07..4c41ea269 100644 --- a/app/src/main/java/com/droiddeck/launcher/MainActivity.kt +++ b/app/src/main/java/com/droiddeck/launcher/MainActivity.kt @@ -1926,7 +1926,7 @@ class MainActivity : ComponentActivity() { val scannedX64 = scannedArch == "x86_64" val games = if (ready) Library.launchableGames(this, arch = scannedArch) else emptyList() val all = games.distinctBy { it.gameId } - if (ready) com.droiddeck.launcher.frontend.LibraryCache.save(this, all, scannedX64) + if (ready && scanGeneration == libraryScanGeneration) com.droiddeck.launcher.frontend.LibraryCache.save(this, all, scannedX64) val endedSession = SessionState.sessionId.takeIf { !SessionState.running && it != null && it != componentSweepSession } if (ready && (!componentSweepDone || endedSession != null)) { componentSweepDone = true diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionArtifacts.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionArtifacts.kt index 57e032bbd..e0e855742 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionArtifacts.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionArtifacts.kt @@ -188,6 +188,16 @@ object SessionArtifacts { return record } + private const val CLIENT_FILE = "steam-client.txt" + + fun recordClient(dir: File, arch: String) { + runCatching { File(dir, CLIENT_FILE).writeText("$arch\n") } + } + + /** The Steam client a session ran, when it recorded one. */ + internal fun recordedClient(dir: File): String? = + runCatching { File(dir, CLIENT_FILE).readText().trim() }.getOrNull()?.takeIf { it == "arm64" || it == "x86_64" } + private fun copySteamLogs(context: Context, dir: File, record: Record, steamArch: String? = null) { val logs = File(steamArch?.let { SessionPrefs.steamRoot(context, it) } ?: SessionPrefs.activeSteamRoot(context), "logs") if (!logs.isDirectory) return @@ -231,7 +241,7 @@ object SessionArtifacts { ) val record = Record(dir) if (newest) { - copySteamLogs(context, dir, record) + copySteamLogs(context, dir, record, recordedClient(dir)) VacDiagnostics.collect(context, dir) } SessionLogCapture.dumpCrashBuffer(File(dir, "crash.log")) diff --git a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt index b6cc8703a..1d8db3d53 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/SessionService.kt @@ -587,7 +587,10 @@ class SessionService : Service() { guest.add("GLIBC_TUNABLES=glibc.pthread.rseq=0:glibc.malloc.top_pad=16777216") if (SessionState.mode == MODE_STEAM) guest.add("BL_MANGOAPP=" + (if (SessionPrefs.mangoapp(this)) "1" else "0")) SessionState.steamClientArch = if (steamHere) SessionPrefs.steamClientArch(this) else null - SessionState.steamClientArch?.let { guest.add("BL_STEAM_CLIENT_ARCH=$it") } + SessionState.steamClientArch?.let { arch -> + guest.add("BL_STEAM_CLIENT_ARCH=$arch") + SessionPaths.current()?.let { SessionArtifacts.recordClient(it, arch) } + } if (SessionState.mode == MODE_STEAM) { guest.add("BL_GAMESCOPE_STRETCH_16X9=" + (if (SessionPrefs.stretch16x9(this)) "1" else "0")) } diff --git a/app/src/main/java/com/droiddeck/launcher/session/VacDiagnostics.kt b/app/src/main/java/com/droiddeck/launcher/session/VacDiagnostics.kt index b73d746c1..568188209 100644 --- a/app/src/main/java/com/droiddeck/launcher/session/VacDiagnostics.kt +++ b/app/src/main/java/com/droiddeck/launcher/session/VacDiagnostics.kt @@ -103,8 +103,9 @@ object VacDiagnostics { report.appendLine(" No console.log was written this session. Only Source-engine games write one,") report.appendLine(" and only when started with -condebug (this setting adds it).") } - consoles.forEach { log -> - val name = consoleName(log) + val names = consoleNames(consoles) + consoles.forEachIndexed { i, log -> + val name = names[i] val lines = tail(log, armedSizes[canonical(log)] ?: 0L) File(out, "$name-console.log").bufferedWriter().use { w -> lines.forEach { w.write(LogRedactor.redact(it)); w.newLine() } @@ -173,6 +174,15 @@ object VacDiagnostics { return roots.filter { it.isDirectory }.distinctBy { runCatching { it.canonicalPath }.getOrDefault(it.path) } } + /** Two libraries can hold the same game and mod folder; a repeated name takes a tag from the log's path. */ + internal fun consoleNames(logs: List): List { + val used = HashSet() + return logs.map { log -> + val base = consoleName(log) + if (used.add(base)) base else "$base-%08x".format(canonical(log).hashCode()).also { used.add(it) } + } + } + private fun consoleName(log: File): String { val mod = log.parentFile val game = mod?.parentFile?.let { if (it.name == "game") it.parentFile else it } diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 4c9dbad8f..9f3afeff5 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -2341,7 +2341,7 @@ ARM64 x86-64 Диагностика на VAC - Игрите на Source записват конзолата си (-condebug); vac.txt в дневниците на сесията показва дали е защитено или не + Игрите на Source записват конзолата си (-condebug); vac.txt в дневниците на сесията показва дали играта е защитена от VAC Стартиране на Steam x86-64 %1$s · x86-64 Използва се от клиента на Steam x86-64 diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 3ea57ae2f..67fa3ad17 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -2391,7 +2391,7 @@ ARM64 x86-64 Diagnostika VAC - Hry na Source zaznamenávají svou konzoli (-condebug); vac.txt v protokolech relace uvádí, zda je zabezpečeno, nebo ne + Hry na Source zaznamenávají svou konzoli (-condebug); vac.txt v protokolech relace uvádí, zda je hra chráněna VAC Spouštění Steamu x86-64 %1$s · x86-64 Používá klient Steam x86-64 diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 3918a0dc0..61c698987 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -2391,7 +2391,7 @@ ARM64 x86-64 Diagnostyka VAC - Gry na Source zapisują swoją konsolę (-condebug); vac.txt w logach sesji pokazuje, czy jest zabezpieczone + Gry na Source zapisują swoją konsolę (-condebug); vac.txt w logach sesji pokazuje, czy gra jest chroniona przez VAC Uruchamianie Steam x86-64 %1$s · x86-64 Używane przez klienta Steam x86-64 diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index f7dc90341..901ffbc8d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -2385,7 +2385,7 @@ ARM64 x86-64 Диагностика VAC - Игры на Source записывают консоль (-condebug); vac.txt в журналах сессии показывает, защищено или нет + Игры на Source записывают консоль (-condebug); vac.txt в журналах сессии показывает, защищена ли игра VAC Запуск Steam x86-64 %1$s · x86-64 Используется клиентом Steam x86-64 diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 8ec9b0c9e..6e305d204 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -2391,7 +2391,7 @@ ARM64 x86-64 Діагностика VAC - Ігри на Source записують свою консоль (-condebug); vac.txt у журналах сеансу показує, захищено чи ні + Ігри на Source записують свою консоль (-condebug); vac.txt у журналах сеансу показує, чи захищена гра VAC Запуск Steam x86-64 %1$s · x86-64 Використовується клієнтом Steam x86-64 diff --git a/app/src/test/java/com/droiddeck/launcher/session/SessionClientRecordTest.kt b/app/src/test/java/com/droiddeck/launcher/session/SessionClientRecordTest.kt new file mode 100644 index 000000000..60c37d0c2 --- /dev/null +++ b/app/src/test/java/com/droiddeck/launcher/session/SessionClientRecordTest.kt @@ -0,0 +1,20 @@ +package com.droiddeck.launcher.session + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNull +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +class SessionClientRecordTest { + @get:Rule val tmp = TemporaryFolder() + + @Test fun aSessionFolderKeepsTheClientItRan() { + val dir = tmp.newFolder("2026-10-18-01-steam") + assertNull(SessionArtifacts.recordedClient(dir)) + SessionArtifacts.recordClient(dir, "x86_64") + assertEquals("x86_64", SessionArtifacts.recordedClient(dir)) + java.io.File(dir, "steam-client.txt").writeText("something else\n") + assertNull(SessionArtifacts.recordedClient(dir)) + } +} diff --git a/app/src/test/java/com/droiddeck/launcher/session/VacDiagnosticsTest.kt b/app/src/test/java/com/droiddeck/launcher/session/VacDiagnosticsTest.kt index 8e8ec5ed9..614a7dbae 100644 --- a/app/src/test/java/com/droiddeck/launcher/session/VacDiagnosticsTest.kt +++ b/app/src/test/java/com/droiddeck/launcher/session/VacDiagnosticsTest.kt @@ -18,6 +18,17 @@ class VacDiagnosticsTest { } } + @Test fun theSameGameInTwoLibrariesGetsTwoConsoleNames() { + val a = java.io.File("/lib1/steamapps/common/Team Fortress 2/tf/console.log") + val b = java.io.File("/lib2/steamapps/common/Team Fortress 2/tf/console.log") + val c = java.io.File("/lib1/steamapps/common/Half-Life 2/hl2/console.log") + val names = VacDiagnostics.consoleNames(listOf(a, b, c)) + assertEquals("Team Fortress 2-tf", names[0]) + assertTrue(names[1].startsWith("Team Fortress 2-tf-") && names[1] != names[0]) + assertEquals("Half-Life 2-hl2", names[2]) + assertEquals(names, VacDiagnostics.consoleNames(listOf(a, b, c))) + } + @Test fun tailReadsAllOfAReplacedFile() { val log = java.io.File.createTempFile("console", ".log") try { diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-session b/tools/linuxfs/overlay/usr/local/bin/droiddeck-session index 9452d7336..4fd738b89 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-session +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-session @@ -1032,7 +1032,7 @@ BUSCONF if [ "$beat" -le 720 ] && [ -n "${BL_CLIENT_CPUS:-}" ]; then for _d in /proc/[0-9]*; do { IFS= read -r -d '' _c && { IFS= read -r -d '' _c1 || _c1=; }; } < "$_d/cmdline" 2>/dev/null || continue - case $_c in FEX|*/FEX) _c=$_c1 ;; esac + case $_c in FEX|*/FEX|FEXInterpreter|*/FEXInterpreter) _c=$_c1 ;; esac case $_c in *steamwebhelper*|*steamrtarm64/steam|*steamrt64/steam|*gamescope*) ;; *) continue ;; esac _t=("$_d"/task/*) _p=${_d#/proc/} diff --git a/tools/linuxfs/overlay/usr/local/bin/droiddeck-steam-x64 b/tools/linuxfs/overlay/usr/local/bin/droiddeck-steam-x64 index 35bf9beaf..f4b71d0da 100755 --- a/tools/linuxfs/overlay/usr/local/bin/droiddeck-steam-x64 +++ b/tools/linuxfs/overlay/usr/local/bin/droiddeck-steam-x64 @@ -950,7 +950,7 @@ fi if [ -n "${BL_CLIENT_CPUS:-}" ] && command -v taskset >/dev/null 2>&1; then ( while sleep 5; do - for pid in $(ps -A -o PID,ARGS | awk '($2 == "FEX" || $2 ~ /\/FEX$/) && ($3 ~ /steamrt64\/steam$/ || $3 ~ /steamwebhelper$/) {print $1} $2 ~ /steamrtarm64\/steamwebhelper$/ {print $1}'); do + for pid in $(ps -A -o PID,ARGS | awk '($2 ~ /^(.*\/)?FEX(Interpreter)?$/) && ($3 ~ /steamrt64\/steam$/ || $3 ~ /steamwebhelper$/) {print $1} $2 ~ /steamrtarm64\/steamwebhelper$/ {print $1}'); do taskset -a -p -c "$BL_CLIENT_CPUS" "$pid" >/dev/null 2>&1 done done