#include "device/tundra_config.h" #include "device/tundra_imu.h" #include // clang-format off: hidsdi.h must precede hidpi.h (USAGE typedefs). #include #include #include // clang-format on #include #include extern "C" { #include "puff/puff.h" } namespace sauna { namespace { constexpr uint8_t kReportConfigReadMode = 0x10; constexpr uint8_t kReportConfigRead = 0x11; // Overlapped GET_FEATURE — see header for why HidD_GetFeature won't do. bool getFeature(HANDLE dev, uint8_t* buf, DWORD len) { OVERLAPPED ol{}; ol.hEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr); DWORD got = 0; BOOL ok = DeviceIoControl(dev, IOCTL_HID_GET_FEATURE, buf, len, buf, len, &got, &ol); if (!ok && GetLastError() == ERROR_IO_PENDING) ok = GetOverlappedResult(dev, &ol, &got, TRUE); CloseHandle(ol.hEvent); return ok != FALSE; } // Retry-on-stall wrapper. A stalled control transfer delivers no data, so // retrying cannot duplicate a chunk; the read-twice-identical gate catches // anything weirder. bool getFeatureRetry(HANDLE dev, uint8_t id, uint8_t* buf, DWORD len, int tries = 20) { for (int i = 0; i < tries; i++) { memset(buf, 0, len); buf[0] = id; if (getFeature(dev, buf, len)) return true; Sleep(20); } return false; } // One full compressed-config dump. Mirrors libsurvive's whole-read retry on // a short stream (up to 3 attempts). bool dumpOnce(HANDLE dev, USHORT featLen, std::vector* out, std::string* err) { std::vector buf(featLen); for (int attempt = 0; attempt < 3; attempt++) { out->clear(); if (!getFeatureRetry(dev, kReportConfigReadMode, buf.data(), (DWORD)buf.size())) { *err = "CONFIG_READMODE failed (err " + std::to_string(GetLastError()) + ")"; return false; } const size_t expected = (size_t)(buf[1] << 8 | buf[2]); if (expected == 0 || expected > 65536) { *err = "implausible config length " + std::to_string(expected); return false; } for (;;) { if (!getFeatureRetry(dev, kReportConfigRead, buf.data(), (DWORD)buf.size())) { *err = "CONFIG_READ failed (err " + std::to_string(GetLastError()) + ") at offset " + std::to_string(out->size()); return false; } const size_t size = buf[1]; if (size > (size_t)featLen - 2) { *err = "chunk size " + std::to_string(size) + " exceeds report"; return false; } if (size == 0) break; out->insert(out->end(), buf.begin() + 2, buf.begin() + 2 + size); } // libsurvive note: actual length is sometimes 2 bytes over advertised. if (out->size() >= expected) return true; } *err = "config stream persistently short"; return false; } } // namespace bool ReadWatchmanConfig(const char* serial, std::string* jsonText, std::string* err, std::string* outSerial, std::vector* rawZ) { std::string chosen; HANDLE dev = (HANDLE)OpenWatchmanHid(serial, &chosen); if (!dev) { *err = "no Watchman device (Beyond unplugged? wrong serial?)"; return false; } PHIDP_PREPARSED_DATA pp = nullptr; HIDP_CAPS caps{}; if (!HidD_GetPreparsedData(dev, &pp) || HidP_GetCaps(pp, &caps) != HIDP_STATUS_SUCCESS || caps.FeatureReportByteLength < 3) { if (pp) HidD_FreePreparsedData(pp); CloseHandle(dev); *err = "no usable feature reports on MI_00"; return false; } HidD_FreePreparsedData(pp); // Read twice; byte-identical or fail (backup discipline). std::vector d1, d2; bool ok = dumpOnce(dev, caps.FeatureReportByteLength, &d1, err) && dumpOnce(dev, caps.FeatureReportByteLength, &d2, err); CloseHandle(dev); if (!ok) return false; if (d1 != d2) { *err = "config reads differ (" + std::to_string(d1.size()) + " vs " + std::to_string(d2.size()) + " bytes) — retry"; return false; } if (d1.size() < 3 || d1[0] != 0x78) { *err = "unexpected stream header"; return false; } // puff is raw-deflate: skip the 2-byte zlib header (libsurvive does too). std::vector json(256 * 1024); unsigned long ol = (unsigned long)json.size(); unsigned long il = (unsigned long)d1.size() - 2; if (puff(json.data(), &ol, d1.data() + 2, &il) != 0) { *err = "config inflate failed"; return false; } jsonText->assign((const char*)json.data(), ol); if (outSerial) *outSerial = chosen; if (rawZ) *rawZ = std::move(d1); return true; } } // namespace sauna