From e3e17e495116e3ed6cd09102914c84fa889afa01 Mon Sep 17 00:00:00 2001 From: Fabian Hamm Date: Thu, 13 Aug 2026 20:14:29 +0000 Subject: [PATCH] [HID] file-pad: nanosecond change detection, and log every state change Two things that only show up once you actually script this pad. st_mtime is whole seconds. Combined with size it looked like enough and is not: a script stepping a menu writes several same-length states per second (`press=A` then `press=B`, both 8 bytes), and every one after the first was silently dropped -- the emulator simply did not react, with nothing in any log to say why. Compare st_mtim.tv_nsec as well, and track whether the file existed at all so a delete is registered once rather than every frame. Also log one line per state change (not per frame, so it stays quiet). Driving the emulator headless means there is nothing to watch; this line is the only proof that a scripted press was picked up, which turns "did my input land?" from a guess into a grep. --- src/xenia/hid/file/file_input_driver.h | 29 ++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/xenia/hid/file/file_input_driver.h b/src/xenia/hid/file/file_input_driver.h index 7e4eacb81..bb8cf333b 100644 --- a/src/xenia/hid/file/file_input_driver.h +++ b/src/xenia/hid/file/file_input_driver.h @@ -139,16 +139,31 @@ class FileInputDriver final : public InputDriver { private: // Re-parse only when the file actually changed: `GetState` is polled every // frame and a stat is far cheaper than a read+parse. + // + // The change test uses **nanosecond** mtime, not `st_mtime`. Whole-second + // granularity plus size looked sufficient and is not: a script that steps a + // menu writes several same-length states per second (`press=A` then `press=B`, + // both 8 bytes), and every one of those after the first would be silently + // dropped. That failure is invisible — the emulator just does not react — so + // it is worth the extra field. void Refresh() { struct stat st; if (::stat(cvars::pad_file.c_str(), &st) != 0) { - Neutral(); + if (present_) { + present_ = false; + Neutral(); + ++packet_; + XELOGI("[file-pad] {} gone -> neutral", cvars::pad_file); + } return; } - if (st.st_mtime == mtime_ && st.st_size == size_) { + if (present_ && st.st_mtim.tv_sec == mtime_sec_ && + st.st_mtim.tv_nsec == mtime_nsec_ && st.st_size == size_) { return; } - mtime_ = st.st_mtime; + present_ = true; + mtime_sec_ = st.st_mtim.tv_sec; + mtime_nsec_ = st.st_mtim.tv_nsec; size_ = st.st_size; std::FILE* f = std::fopen(cvars::pad_file.c_str(), "rb"); if (!f) { @@ -161,6 +176,10 @@ class FileInputDriver final : public InputDriver { buf[n] = '\0'; Parse(buf); ++packet_; + // One line per change (not per frame): with no display to watch, this log is + // the only proof that a scripted press was actually picked up. + XELOGI("[file-pad] #{} buttons={:04X} lt={} rt={} lx={} ly={} rx={} ry={}", + packet_, buttons_, lt_, rt_, lx_, ly_, rx_, ry_); } void Neutral() { @@ -230,7 +249,9 @@ class FileInputDriver final : public InputDriver { uint8_t lt_ = 0, rt_ = 0; int16_t lx_ = 0, ly_ = 0, rx_ = 0, ry_ = 0; uint32_t packet_ = 1; - time_t mtime_ = 0; + bool present_ = false; + time_t mtime_sec_ = 0; + long mtime_nsec_ = -1; off_t size_ = -1; };