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.
This commit is contained in:
The412Banner committed 2026-10-17 23:42:33 -04:00
1 parent a53fd11779
commit eb8634ea22
13 files changed
+66 -12

No files matched your search

@@ -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
@@ -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"))
@@ -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"))
}
@@ -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<File>): List<String> {
val used = HashSet<String>()
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 }
+1 -1
View File
@@ -2341,7 +2341,7 @@
<string name="mode_client_arch_arm64">ARM64</string>
<string name="mode_client_arch_x64">x86-64</string>
<string name="setup_vac_diag">Диагностика на VAC</string>
<string name="setup_vac_diag_hint">Игрите на Source записват конзолата си (-condebug); vac.txt в дневниците на сесията показва дали е защитено или не</string>
<string name="setup_vac_diag_hint">Игрите на Source записват конзолата си (-condebug); vac.txt в дневниците на сесията показва дали играта е защитена от VAC</string>
<string name="load_starting_steam_x64">Стартиране на Steam x86-64</string>
<string name="storage_game_x64">%1$s · x86-64</string>
<string name="storage_tool_x64_client">Използва се от клиента на Steam x86-64</string>
+1 -1
View File
@@ -2391,7 +2391,7 @@
<string name="mode_client_arch_arm64">ARM64</string>
<string name="mode_client_arch_x64">x86-64</string>
<string name="setup_vac_diag">Diagnostika VAC</string>
<string name="setup_vac_diag_hint">Hry na Source zaznamenávají svou konzoli (-condebug); vac.txt v protokolech relace uvádí, zda je zabezpečeno, nebo ne</string>
<string name="setup_vac_diag_hint">Hry na Source zaznamenávají svou konzoli (-condebug); vac.txt v protokolech relace uvádí, zda je hra chráněna VAC</string>
<string name="load_starting_steam_x64">Spouštění Steamu x86-64</string>
<string name="storage_game_x64">%1$s · x86-64</string>
<string name="storage_tool_x64_client">Používá klient Steam x86-64</string>
+1 -1
View File
@@ -2391,7 +2391,7 @@
<string name="mode_client_arch_arm64">ARM64</string>
<string name="mode_client_arch_x64">x86-64</string>
<string name="setup_vac_diag">Diagnostyka VAC</string>
<string name="setup_vac_diag_hint">Gry na Source zapisują swoją konsolę (-condebug); vac.txt w logach sesji pokazuje, czy jest zabezpieczone</string>
<string name="setup_vac_diag_hint">Gry na Source zapisują swoją konsolę (-condebug); vac.txt w logach sesji pokazuje, czy gra jest chroniona przez VAC</string>
<string name="load_starting_steam_x64">Uruchamianie Steam x86-64</string>
<string name="storage_game_x64">%1$s · x86-64</string>
<string name="storage_tool_x64_client">Używane przez klienta Steam x86-64</string>
+1 -1
View File
@@ -2385,7 +2385,7 @@
<string name="mode_client_arch_arm64">ARM64</string>
<string name="mode_client_arch_x64">x86-64</string>
<string name="setup_vac_diag">Диагностика VAC</string>
<string name="setup_vac_diag_hint">Игры на Source записывают консоль (-condebug); vac.txt в журналах сессии показывает, защищено или нет</string>
<string name="setup_vac_diag_hint">Игры на Source записывают консоль (-condebug); vac.txt в журналах сессии показывает, защищена ли игра VAC</string>
<string name="load_starting_steam_x64">Запуск Steam x86-64</string>
<string name="storage_game_x64">%1$s · x86-64</string>
<string name="storage_tool_x64_client">Используется клиентом Steam x86-64</string>
+1 -1
View File
@@ -2391,7 +2391,7 @@
<string name="mode_client_arch_arm64">ARM64</string>
<string name="mode_client_arch_x64">x86-64</string>
<string name="setup_vac_diag">Діагностика VAC</string>
<string name="setup_vac_diag_hint">Ігри на Source записують свою консоль (-condebug); vac.txt у журналах сеансу показує, захищено чи ні</string>
<string name="setup_vac_diag_hint">Ігри на Source записують свою консоль (-condebug); vac.txt у журналах сеансу показує, чи захищена гра VAC</string>
<string name="load_starting_steam_x64">Запуск Steam x86-64</string>
<string name="storage_game_x64">%1$s · x86-64</string>
<string name="storage_tool_x64_client">Використовується клієнтом Steam x86-64</string>
@@ -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))
}
}
@@ -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 {
@@ -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/}
@@ -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