From 701e70bd3edf2fef473dfe3f880000d4fb16b4c5 Mon Sep 17 00:00:00 2001 From: Kurt Himebauch <136133082+xXJSONDeruloXx@users.noreply.github.com> Date: Thu, 22 Oct 2026 11:43:05 -0400 Subject: [PATCH] fix: the emulated Steam Controller reports the firmware build Steam ships (#1323) * fix: the emulated Steam Controller reports the firmware build Steam ships * fix Triton firmware lookup for Steam root binary --- app/src/main/cpp/fakeinput_steam.cpp | 34 +++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/app/src/main/cpp/fakeinput_steam.cpp b/app/src/main/cpp/fakeinput_steam.cpp index 05d0138dd..c446170fe 100644 --- a/app/src/main/cpp/fakeinput_steam.cpp +++ b/app/src/main/cpp/fakeinput_steam.cpp @@ -2650,6 +2650,38 @@ __attribute__((visibility("hidden"))) static int triton_get_feature(DeckHidraw & static constexpr uint32_t TRITON_HWID = 72; static constexpr uint32_t TRITON_FW_TIMESTAMP = 0x6AC686B3; +// Steam compares the controller's firmware with TRITON_FW_TS in its bundled hardwareupdater.cfg (and blocks +// with a required update below MUST_UPDATE_TRITON_FW_TS), so the emulated pad reports at least the build +// Steam ships. +static uint32_t triton_fw_timestamp() { + static const uint32_t timestamp = [] { + std::vector roots; + char exe[PATH_MAX]; + ssize_t n = readlink("/proc/self/exe", exe, sizeof(exe) - 1); + if (n > 0) { + std::string path(exe, static_cast(n)); + size_t bin = path.rfind('/'); + size_t root = bin == std::string::npos || bin == 0 ? std::string::npos : path.rfind('/', bin - 1); + if (bin != std::string::npos && bin > 0) roots.push_back(path.substr(0, bin)); + if (root != std::string::npos) roots.push_back(path.substr(0, root)); + } + if (const char *home = getenv("HOME")) roots.push_back(std::string(home) + "/.local/share/Steam"); + uint32_t newest = TRITON_FW_TIMESTAMP; + for (const std::string &root : roots) { + std::ifstream cfg(root + "/bin/hardwareupdater/hardwareupdater.cfg"); + std::string line; + while (std::getline(cfg, line)) { + if (line.rfind("TRITON_FW_TS:", 0) != 0) continue; + uint32_t shipped = static_cast(strtoul(line.c_str() + 13, nullptr, 16)); + if (shipped > newest) newest = shipped; + } + } + if (newest != TRITON_FW_TIMESTAMP) Logger::log("triton: reporting firmware %08x, the build Steam ships\n", newest); + return newest; + }(); + return timestamp; +} + __attribute__((visibility("hidden"))) static int triton_pad_feature(DeckHidraw &triton, uint8_t *buf, size_t size, bool set) { if (!buf || size < 2) { errno = EINVAL; @@ -2670,7 +2702,7 @@ __attribute__((visibility("hidden"))) static int triton_pad_feature(DeckHidraw & switch (triton.pending_feature) { case DECK_GET_ATTRIBUTES_VALUES: { static const uint8_t kIds[] = {0x01, 0x02, 0x04, 0x09, 0x0a, 0x0b}; - const uint32_t values[] = {static_cast(TRITON_WIRED_PRODUCT), 0, TRITON_FW_TIMESTAMP, TRITON_HWID, 0, 4000}; + const uint32_t values[] = {static_cast(TRITON_WIRED_PRODUCT), 0, triton_fw_timestamp(), TRITON_HWID, 0, 4000}; reply[2] = sizeof(kIds) * 5; for (size_t i = 0; i < sizeof(kIds); i++) { reply[3 + i * 5] = kIds[i];