Files
DroidDeck/app/src/main/cpp/fakeinput_steam.cpp
T
The412Banner b74aa97996 Bring the runtime up to date with Bannerlator, and end a session the way proot can survive
From Bannerlator's feat/linux-gamescope-runtime since this repo was cut:

- Session teardown (4509d788): proot was SIGKILLed, which never lets its
  --kill-on-exit cleanup run; its tracees lived on with no tracer, every
  seccomp-trapped syscall failing with ENOSYS, spinning at a full core for over
  an hour. Now SIGTERM, a grace for proot's own cleanup, SIGKILL only if it
  will not go, then a sweep of the tree it had — snapshotted first, each pid
  checked against its start time.
- The Steam client's controller interposer is its own source
  (fakeinput_steam.cpp, 28922e7c), separate from the one preloaded into Wine.
- The seeder runs on a beat so a prefix the client creates mid-session is
  covered before its install scripts run (6ee86bff).
- GE-Proton and proton-cachyos as compatibility tools: the registrar
  bannerlator-proton-extra and the compat script's adopt_extras (03867656).
- space.c in the session shim: a library's own free space, not the phone's.

The env-vs-args ordering bug (98d38931) does not apply here: nothing is added
after the script name.
2026-09-20 00:55:37 -04:00

1702 lines
57 KiB
C++

/*
* The fake evdev layer. Taken from WinNative (maxjivi05, main 0cfc070b + feature/wayland-gamescope
* through f1f34cd7, 2026-09-14..19), GPL-3.0: the locking, the fork handlers, the shared device
* table, the Steam virtual-gamepad identity and the Z/RZ trigger placement are that author's.
* Added here: an opt-in Xbox 360 identity (FAKE_EVDEV_IDENTITY=xbox360), which is what gets SDL's
* built-in mapping applied with no configuration when the pad is read by the Steam client itself.
*/
#include <algorithm>
#include <cerrno>
#include <climits>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include <dirent.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <linux/input.h>
#include <linux/joystick.h>
#include <poll.h>
#include <pthread.h>
#include <signal.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <sys/inotify.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/sysmacros.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <unistd.h>
#define EXPORT __attribute__((visibility("default"))) extern "C"
// glibc declares the request as unsigned long; bionic as int. The same source also serves the
// Linux runtime's preload (tools/linuxfs), where Steam and SDL run against glibc.
#ifdef __GLIBC__
typedef unsigned long ioctl_request_t;
#else
typedef int ioctl_request_t;
#endif
static constexpr uint16_t GAMEPAD_VENDOR_ID_BASE = 0x1234;
static constexpr uint16_t GAMEPAD_PRODUCT_ID_BASE = 0x5678;
static constexpr uint16_t GAMEPAD_VERSION = 0x0110;
static constexpr const char *GAMEPAD_NAME_TEMPLATE = "Generic HID Gamepad %d";
// The gamepad Steam Input presents to a game, and the name SDL reads its slot from.
static constexpr uint16_t STEAM_VIRTUAL_VENDOR_ID = 0x28de;
static constexpr uint16_t STEAM_VIRTUAL_PRODUCT_ID = 0x11ff;
static constexpr const char *STEAM_VIRTUAL_NAME_TEMPLATE = "Microsoft X-Box 360 pad %d";
// Opt-in identity for the process that reads the pad directly (the Steam client): SDL keys its
// mapping database on bus+vendor+product, and 045E:028E is the most widely mapped id there is.
// Not offset per slot: a real pad reports the same ids on every port.
static constexpr uint16_t X360_VENDOR_ID = 0x045E;
static constexpr uint16_t X360_PRODUCT_ID = 0x028E;
static constexpr const char *X360_NAME_TEMPLATE = "Xbox 360 Controller (%d)";
static constexpr const char *GAMEPAD_PHYS_TEMPLATE = "usb-fakeinput/input%d";
static constexpr const char *GAMEPAD_UNIQ_TEMPLATE = "0000000000%02d";
static constexpr uint8_t GAMEPAD_AXIS_COUNT = 8;
static constexpr uint8_t GAMEPAD_BUTTON_COUNT = 11;
static constexpr uint32_t FAKE_INPUT_RING_MAGIC = 0x46494252;
static constexpr uint32_t FAKE_INPUT_RING_VERSION = 2;
static constexpr uint32_t FAKE_INPUT_EVENT_SIZE = sizeof(struct input_event);
static constexpr uint32_t FAKE_INPUT_RING_CAPACITY = 4096;
static constexpr unsigned int FAKE_INPUT_MAJOR = 13;
static constexpr unsigned int FAKE_INPUT_EVENT_MINOR_BASE = 64;
static constexpr unsigned int FAKE_INPUT_JS_MINOR_BASE = 0;
struct FakeInputRingHeader {
uint32_t magic; // 0
uint32_t version; // 4
uint32_t event_size; // 8
uint32_t capacity; // 12
uint64_t write_seq; // 16
uint64_t generation; // 24
// Authoritative absolute-state snapshot, published by the writer under a
// seqlock (odd snapshot_seq = write in progress). The reader replays it as a
// full keyframe whenever the delta stream could have desynced (open, ring
// overflow) so dropped events can recover without periodic duplicate input.
uint64_t snapshot_seq; // 32
uint32_t snapshot_buttons; // 40 bit i -> kSnapshotButtons[i] pressed
int16_t snapshot_axes[8]; // 44 values in kSnapshotAxisCodes order
uint32_t resync_seq; // 60
};
static_assert(sizeof(FakeInputRingHeader) == 64,
"fake input ring header must stay ABI-stable");
static constexpr size_t FAKE_INPUT_RING_HEADER_SIZE =
sizeof(FakeInputRingHeader);
static constexpr size_t FAKE_INPUT_RING_SIZE =
FAKE_INPUT_RING_HEADER_SIZE +
(FAKE_INPUT_RING_CAPACITY * FAKE_INPUT_EVENT_SIZE);
struct FakeController {
char *event = nullptr;
int slot = -1;
FakeInputRingHeader *ring = nullptr;
uint64_t read_seq = 0;
uint64_t generation = 0;
uint32_t resync_seq = 0;
size_t mapping_size = 0;
bool closed = false;
bool needs_keyframe = true;
~FakeController() {
if (ring) munmap(ring, mapping_size);
free(event);
}
// Pending keyframe (full absolute-state baseline) currently streaming to the
// guest. The axis/button values are captured from the snapshot when the
// keyframe starts so the frame stays consistent across multi-read delivery.
size_t keyframe_remaining = 0;
int32_t keyframe_axes[8] = {0, 0, 0, 0, 0, 0, 0, 0};
uint32_t keyframe_buttons = 0;
};
struct NeutralEventSpec {
uint16_t type;
uint16_t code;
};
// Event template for a full keyframe: every button, every axis/hat, then a
// SYN_REPORT, in this fixed order. The value carried by each event is filled
// from the authoritative snapshot (see keyframe_value); an all-zero snapshot
// yields the neutral baseline. Replayed on open and ring overflow so dropped
// events cannot leave a guest stuck.
static const NeutralEventSpec kNeutralEvents[] = {
{EV_KEY, BTN_A}, {EV_KEY, BTN_B}, {EV_KEY, BTN_X},
{EV_KEY, BTN_Y}, {EV_KEY, BTN_TL}, {EV_KEY, BTN_TR},
{EV_KEY, BTN_SELECT}, {EV_KEY, BTN_START}, {EV_KEY, BTN_MODE},
{EV_KEY, BTN_THUMBL}, {EV_KEY, BTN_THUMBR}, {EV_ABS, ABS_X},
{EV_ABS, ABS_Y}, {EV_ABS, ABS_RX}, {EV_ABS, ABS_RY},
{EV_ABS, ABS_GAS}, {EV_ABS, ABS_BRAKE}, {EV_ABS, ABS_HAT0X},
{EV_ABS, ABS_HAT0Y}, {EV_SYN, SYN_REPORT},
};
static constexpr size_t kNeutralEventCount =
sizeof(kNeutralEvents) / sizeof(kNeutralEvents[0]);
// Axis layout of FakeInputRingHeader::snapshot_axes (mirrors the Java writer).
static const uint16_t kSnapshotAxisCodes[8] = {
ABS_X, ABS_Y, ABS_RX, ABS_RY, ABS_GAS, ABS_BRAKE, ABS_HAT0X, ABS_HAT0Y};
// Bit i of FakeInputRingHeader::snapshot_buttons maps to this button code.
static const uint16_t kSnapshotButtons[11] = {
BTN_A, BTN_B, BTN_X, BTN_Y, BTN_TL, BTN_TR,
BTN_SELECT, BTN_START, BTN_THUMBL, BTN_THUMBR,
// Bit 10 is the Steam button: the client's in-game menu is bound to it, and SDL reports it
// as button 8 from this position in the key bits.
BTN_MODE};
static constexpr size_t kSnapshotButtonCount =
sizeof(kSnapshotButtons) / sizeof(kSnapshotButtons[0]);
// An LD_PRELOAD interposer is called outside its own lifetime. The loader initialises libraries
// in dependency order, and the hooks below belong to whichever library gets there first: on the
// Linux runtime, libpython's initialiser runs before ours and calls close() and read(), so a hook
// fires before this library has been initialised at all. A file-scope std::unordered_map or
// std::recursive_mutex has no object yet at that moment - touching one is undefined behaviour,
// and in practice the process dies before main(). The same window reopens at exit, after static
// destructors have run. (Proton is a Python script, so this killed every game launched from the
// native Steam client, and the compatibility-tool registrar with it.)
//
// So everything with a constructor lives behind an accessor that builds it on first use and
// never destroys it. The leak is deliberate and bounded: one map or mutex for the process.
namespace Logger {
void init();
}
static void controller_fork_prepare();
static void controller_fork_parent();
static void controller_fork_child();
struct FakeConfig {
const char *hook_dir;
const char *udev_data_dir;
bool vibration_enabled;
bool xbox360_identity;
};
static FakeConfig &config() {
static FakeConfig *cfg = [] {
auto *c = new FakeConfig();
const char *dir = getenv("FAKE_EVDEV_DIR");
c->hook_dir = dir ? dir : "/data/data/com.termux/files/home/fake-input";
c->udev_data_dir = getenv("FAKE_UDEV_DATA_DIR");
c->vibration_enabled =
getenv("FAKE_EVDEV_VIBRATION") && atoi(getenv("FAKE_EVDEV_VIBRATION"));
const char *identity = getenv("FAKE_EVDEV_IDENTITY");
c->xbox360_identity = identity && !strcmp(identity, "xbox360");
pthread_atfork(controller_fork_prepare, controller_fork_parent,
controller_fork_child);
Logger::init();
return c;
}();
return *cfg;
}
static std::recursive_mutex &controller_mutex() {
static auto *mutex = new std::recursive_mutex();
return *mutex;
}
static std::unordered_map<int, std::shared_ptr<FakeController>> &controller_map() {
static auto *map = new std::unordered_map<int, std::shared_ptr<FakeController>>();
return *map;
}
static std::unordered_map<int, std::string> &ring_paths() {
static auto *map = new std::unordered_map<int, std::string>();
return *map;
}
static std::unordered_map<int, struct ff_effect> &ff_effects() {
static auto *map = new std::unordered_map<int, struct ff_effect>();
return *map;
}
static const char *fake_hook_dir() { return config().hook_dir; }
static const char *fake_udev_data_dir() { return config().udev_data_dir; }
static bool fake_vibration_enabled() { return config().vibration_enabled; }
static bool fake_xbox360_identity() { return config().xbox360_identity; }
static bool ring_paths_loaded = false;
static int next_ff_id = 0;
// fork() carries over only the calling thread, so a lock another thread was holding at that
// instant stays held in the child by a thread that is not there to release it. Every hook below
// takes controller_mutex, and the Steam client forks while its other threads are inside them, so
// the child blocked on its first open() and never reached exec: the spawn never completed, the
// client's main loop stalled past its own watchdog and it tore the session down.
//
// Taking the lock before the fork is what makes the copy consistent - no thread is part way
// through the tables it guards. The parent then unlocks it. The child cannot: a recursive mutex
// records the owning thread, the child's one thread has a new id, and unlocking one it does not
// own is refused, which would leave the lock held for good. It gets a fresh mutex instead, which
// is sound precisely because the fork was taken with the lock held.
static void controller_fork_prepare() { controller_mutex().lock(); }
static void controller_fork_parent() { controller_mutex().unlock(); }
static void controller_fork_child() {
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(controller_mutex().native_handle(), &attr);
pthread_mutexattr_destroy(&attr);
}
namespace Logger {
int log_enabled;
void init() {
log_enabled = getenv("FAKE_EVDEV_LOG") && atoi(getenv("FAKE_EVDEV_LOG"));
}
void log(const char *message, ...) {
if (!log_enabled)
return;
va_list args;
va_start(args, message);
vfprintf(stderr, message, args);
va_end(args);
std::cerr.flush();
}
} // namespace Logger
__attribute__((constructor)) static void library_init() {
// Warming the lazily built state, not owning it: when the loader runs this before anything
// calls a hook - the ordinary case - the first hook finds everything already there. Nothing
// below depends on it having run, which is the whole point.
config();
controller_mutex();
controller_map();
}
__attribute__((visibility("hidden"))) static void
send_vibration(int strong, int weak, uint16_t duration_ms, uint16_t slot) {
if (!fake_vibration_enabled())
return;
// Rumble is best effort. A full Android listener backlog must never park
// winebus (or every input hook through controller_mutex()), and a closing
// listener must not terminate the guest with SIGPIPE.
int sock = socket(AF_UNIX, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, 0);
if (sock < 0)
return;
struct sockaddr_un addr = {};
addr.sun_family = AF_UNIX;
const char *name = "winlator_vibration";
memcpy(addr.sun_path + 1, name, strlen(name));
socklen_t addrlen = offsetof(struct sockaddr_un, sun_path) + 1 + strlen(name);
if (connect(sock, reinterpret_cast<struct sockaddr *>(&addr), addrlen) < 0) {
syscall(SYS_close, sock);
return;
}
uint16_t data[4];
data[0] = static_cast<uint16_t>(strong);
data[1] = static_cast<uint16_t>(weak);
data[2] = duration_ms;
data[3] = slot;
send(sock, data, sizeof(data), MSG_DONTWAIT | MSG_NOSIGNAL);
syscall(SYS_close, sock);
}
__attribute__((visibility("hidden"))) static void
check_ff_event(const struct input_event *ev, uint16_t slot) {
if (ev->type != EV_FF)
return;
int id = ev->code;
if (ev->value > 0) {
auto it = ff_effects().find(id);
if (it == ff_effects().end())
return;
uint16_t duration = it->second.replay.length;
if (it->second.type == FF_RUMBLE) {
send_vibration(it->second.u.rumble.strong_magnitude,
it->second.u.rumble.weak_magnitude, duration, slot);
} else if (it->second.type == FF_PERIODIC) {
send_vibration(it->second.u.periodic.magnitude,
it->second.u.periodic.magnitude, duration, slot);
}
} else {
send_vibration(0, 0, 0, slot);
}
}
__attribute__((visibility("hidden"))) char *
from_real_to_fake_path(const char *pathname) {
const char *event = strrchr(pathname, '/') + 1;
char *fake_path = nullptr;
if (asprintf(&fake_path, "%s/%s", fake_hook_dir(), event) < 0)
fake_path = nullptr;
return fake_path;
}
__attribute__((visibility("hidden"))) static bool path_exists(const char *path) {
return path && faccessat(AT_FDCWD, path, F_OK, 0) == 0;
}
__attribute__((visibility("hidden"))) static bool
is_fake_input_node_path(const char *pathname) {
return pathname && (!strncmp(pathname, "/dev/input/event", 16) ||
!strncmp(pathname, "/dev/input/js", 13));
}
__attribute__((visibility("hidden"))) static bool
is_fake_udev_data_path(const char *pathname) {
return pathname && fake_udev_data_dir() && *fake_udev_data_dir() &&
!strncmp(pathname, "/run/udev/data/c13:", 19);
}
__attribute__((visibility("hidden"))) char *
from_real_to_fake_udev_data_path(const char *pathname) {
const char *name = strrchr(pathname, '/') + 1;
char *fake_path = nullptr;
if (asprintf(&fake_path, "%s/%s", fake_udev_data_dir(), name) < 0)
fake_path = nullptr;
return fake_path;
}
__attribute__((visibility("hidden"))) const char *
get_event(const char *pathname) {
const char *event = strrchr(pathname, '/') + 1;
return event;
}
__attribute__((visibility("hidden"))) int get_event_number(const char *event) {
if (!event)
return -1;
const char *digits = event;
while (*digits && (*digits < '0' || *digits > '9'))
digits++;
return *digits ? atoi(digits) : -1;
}
__attribute__((visibility("hidden"))) static dev_t
get_fake_input_rdev(const char *event) {
int event_number = get_event_number(event);
if (event_number < 0)
return makedev(FAKE_INPUT_MAJOR, 0);
if (!strncmp(event, "event", 5))
return makedev(FAKE_INPUT_MAJOR, FAKE_INPUT_EVENT_MINOR_BASE + event_number);
if (!strncmp(event, "js", 2))
return makedev(FAKE_INPUT_MAJOR, FAKE_INPUT_JS_MINOR_BASE + event_number);
return makedev(FAKE_INPUT_MAJOR, event_number);
}
__attribute__((visibility("hidden"))) static void load_ring_paths() {
std::lock_guard<std::recursive_mutex> guard(controller_mutex());
if (ring_paths_loaded)
return;
const char *spec = getenv("FAKE_EVDEV_MEMFD_PATHS");
if (!spec || !*spec) {
ring_paths_loaded = true;
return;
}
char *copy = strdup(spec);
if (!copy)
return;
char *saveptr = nullptr;
for (char *token = strtok_r(copy, ";", &saveptr); token;
token = strtok_r(nullptr, ";", &saveptr)) {
char *equals = strchr(token, '=');
if (!equals)
continue;
*equals = '\0';
int slot = atoi(token);
const char *path = equals + 1;
if (slot >= 0 && *path)
ring_paths()[slot] = path;
}
free(copy);
ring_paths_loaded = true;
}
__attribute__((visibility("hidden"))) static std::string
get_ring_path_for_slot(int slot) {
std::lock_guard<std::recursive_mutex> guard(controller_mutex());
load_ring_paths();
auto it = ring_paths().find(slot);
return it == ring_paths().end() ? std::string() : it->second;
}
__attribute__((visibility("hidden"))) static uint64_t
ring_write_seq(const FakeInputRingHeader *ring) {
return __atomic_load_n(&ring->write_seq, __ATOMIC_ACQUIRE);
}
__attribute__((visibility("hidden"))) static uint64_t
ring_generation(const FakeInputRingHeader *ring) {
return __atomic_load_n(&ring->generation, __ATOMIC_ACQUIRE);
}
__attribute__((visibility("hidden"))) static uint32_t
ring_resync_seq(const FakeInputRingHeader *ring) {
return __atomic_load_n(&ring->resync_seq, __ATOMIC_ACQUIRE);
}
__attribute__((visibility("hidden"))) static bool
ring_header_is_valid(const FakeInputRingHeader *ring) {
return ring && ring->magic == FAKE_INPUT_RING_MAGIC &&
ring->version == FAKE_INPUT_RING_VERSION &&
ring->event_size == FAKE_INPUT_EVENT_SIZE &&
ring->capacity == FAKE_INPUT_RING_CAPACITY;
}
struct SnapshotState {
uint64_t sequence = 0;
uint64_t write_seq = 0;
uint64_t generation = 0;
uint32_t resync_seq = 0;
uint32_t buttons = 0;
int32_t axes[8] = {};
};
static long long monotonic_ms();
// The writer protects the events, cursor and snapshot in one publication.
// A busy writer is retried later; inventing a neutral state would lose holds.
__attribute__((visibility("hidden"))) static bool
read_snapshot(const FakeInputRingHeader *ring, SnapshotState &out) {
for (int attempt = 0; attempt < 8; attempt++) {
uint64_t sequence = __atomic_load_n(&ring->snapshot_seq, __ATOMIC_ACQUIRE);
if (sequence & 1ULL) continue;
out.sequence = sequence;
out.write_seq = ring_write_seq(ring);
out.generation = ring_generation(ring);
out.resync_seq = ring_resync_seq(ring);
out.buttons = __atomic_load_n(&ring->snapshot_buttons, __ATOMIC_RELAXED);
for (int i = 0; i < 8; i++)
out.axes[i] = __atomic_load_n(&ring->snapshot_axes[i], __ATOMIC_RELAXED);
__atomic_thread_fence(__ATOMIC_ACQUIRE);
if (sequence == __atomic_load_n(&ring->snapshot_seq, __ATOMIC_RELAXED))
return true;
}
return false;
}
// This baseline supersedes all deltas through its cursor. Replaying older
// deltas after it can reassert a control that the snapshot already released.
__attribute__((visibility("hidden"))) static void
capture_keyframe(FakeController &fake, const SnapshotState &snap) {
fake.keyframe_buttons = snap.buttons;
for (int i = 0; i < 8; i++) fake.keyframe_axes[i] = snap.axes[i];
fake.keyframe_remaining = kNeutralEventCount;
fake.read_seq = snap.write_seq;
fake.resync_seq = snap.resync_seq;
fake.needs_keyframe = false;
}
// Resolve the value a keyframe event should carry from the captured snapshot.
__attribute__((visibility("hidden"))) static int32_t
keyframe_value(const FakeController &fake, uint16_t type, uint16_t code) {
if (type == EV_KEY) {
for (size_t i = 0; i < kSnapshotButtonCount; i++)
if (kSnapshotButtons[i] == code)
return (fake.keyframe_buttons >> i) & 1u;
return 0; // a code the snapshot word does not carry
}
if (type == EV_ABS) {
for (int i = 0; i < 8; i++)
if (kSnapshotAxisCodes[i] == code)
return fake.keyframe_axes[i];
}
return 0; // SYN / unknown
}
__attribute__((visibility("hidden"))) static int
open_fake_input_ring(const char *event, int flags) {
int slot = get_event_number(event);
std::string ring_path = get_ring_path_for_slot(slot);
if (ring_path.empty()) {
Logger::log("No input ring is configured for %s (slot %d)\n", event, slot);
errno = ENODEV;
return -1;
}
static auto my_open = reinterpret_cast<int (*)(const char *, int, ...)>(dlsym(RTLD_NEXT, "open"));
int fd = my_open(ring_path.c_str(), O_RDWR | (flags & (O_NONBLOCK | O_CLOEXEC)));
if (fd < 0)
return -1;
void *mapping =
mmap(nullptr, FAKE_INPUT_RING_SIZE, PROT_READ, MAP_SHARED, fd, 0);
if (mapping == MAP_FAILED) {
int saved_errno = errno;
syscall(SYS_close, fd);
errno = saved_errno;
return -1;
}
FakeInputRingHeader *ring =
reinterpret_cast<FakeInputRingHeader *>(mapping);
if (!ring_header_is_valid(ring)) {
Logger::log("Input ring %s has no valid header\n", ring_path.c_str());
munmap(mapping, FAKE_INPUT_RING_SIZE);
syscall(SYS_close, fd);
errno = ENODEV;
return -1;
}
auto controller = std::make_shared<FakeController>();
controller->event = strdup(event);
controller->slot = slot;
controller->ring = ring;
controller->mapping_size = FAKE_INPUT_RING_SIZE;
controller->generation = ring_generation(ring);
// Establish the cursor at open so taps arriving before the first read stay
// queued. If publication is busy, read() will finish establishing it later.
SnapshotState snap;
if (read_snapshot(ring, snap) && snap.generation == controller->generation)
capture_keyframe(*controller, snap);
{
std::lock_guard<std::recursive_mutex> guard(controller_mutex());
controller_map()[fd] = controller;
}
Logger::log("Adding ring-backed controller, fd %d event %s slot %d\n", fd,
event, slot);
return fd;
}
// Under the Steam client a pad it manages is hidden from the game: the client lists it in
// SDL_GAMECONTROLLER_IGNORE_DEVICES, its overlay refuses the game's open() of the node, and the
// game is meant to see Steam Input's virtual gamepad instead. That one is a uinput device, which
// does not exist here, so the game was left with no controller at all. With
// FAKE_EVDEV_STEAM_VIRTUAL set the pad carries the virtual gamepad's identity for every process
// but the client's own, which must keep seeing the pad as the physical one it reads.
__attribute__((visibility("hidden"))) static bool presents_steam_virtual() {
static int known;
if (known == 0) {
const char *enabled = getenv("FAKE_EVDEV_STEAM_VIRTUAL");
char exe[PATH_MAX];
ssize_t length = enabled && atoi(enabled) ? readlink("/proc/self/exe", exe, sizeof(exe) - 1) : -1;
bool client = false;
if (length > 0) {
exe[length] = '\0';
const char *name = strrchr(exe, '/');
client = !strncmp(name ? name + 1 : exe, "steam", 5);
}
known = length > 0 && !client ? 1 : -1;
}
return known == 1;
}
// The virtual gamepad is an X-Box 360 pad, whose triggers are ABS_Z and ABS_RZ. Wine places a
// gamepad's axes by their position among the ones it advertises, so the triggers have to sit
// where that pad has them: as ABS_GAS and ABS_BRAKE they follow the sticks, the right stick
// lands on the left trigger, and a released right trigger reads as the right stick held up.
__attribute__((visibility("hidden"))) static uint16_t presented_abs_code(uint16_t code) {
if (!presents_steam_virtual()) return code;
if (code == ABS_BRAKE) return ABS_Z;
if (code == ABS_GAS) return ABS_RZ;
return code;
}
__attribute__((visibility("hidden"))) static uint16_t ring_abs_code(uint16_t code) {
if (!presents_steam_virtual()) return code;
if (code == ABS_Z) return ABS_BRAKE;
if (code == ABS_RZ) return ABS_GAS;
return code;
}
__attribute__((visibility("hidden"))) static void
copy_slot_ioctl_string(int op, void *argp, const char *format, int event_number) {
size_t size = _IOC_SIZE(op);
if (!argp || size == 0)
return;
snprintf(static_cast<char *>(argp), size, format, event_number);
}
__attribute__((visibility("hidden"))) static bool is_fake_input_fd(int fd) {
std::lock_guard<std::recursive_mutex> guard(controller_mutex());
return controller_map().find(fd) != controller_map().end();
}
__attribute__((visibility("hidden"))) static bool fake_fd_is_stale(int fd) {
std::lock_guard<std::recursive_mutex> guard(controller_mutex());
auto controller = controller_map().find(fd);
return controller != controller_map().end() &&
ring_generation(controller->second->ring) != controller->second->generation;
}
// Caller holds controller_mutex().
static bool fake_has_unread_data(const FakeController &fake) {
if (ring_generation(fake.ring) != fake.generation)
return false;
// Readiness never consumes resync requests or changes the read cursor.
// Do not spin a nonblocking guest on a producer's unfinished publication.
if (fake.keyframe_remaining > 0) return true;
uint64_t sequence = __atomic_load_n(&fake.ring->snapshot_seq, __ATOMIC_ACQUIRE);
if (sequence & 1ULL) return false;
bool ready = fake.needs_keyframe ||
ring_resync_seq(fake.ring) != fake.resync_seq ||
ring_write_seq(fake.ring) != fake.read_seq;
__atomic_thread_fence(__ATOMIC_ACQUIRE);
return ready && sequence == __atomic_load_n(&fake.ring->snapshot_seq, __ATOMIC_RELAXED);
}
static bool fake_fd_has_unread_data(int fd) {
std::lock_guard<std::recursive_mutex> guard(controller_mutex());
auto it = controller_map().find(fd);
return it != controller_map().end() && fake_has_unread_data(*it->second);
}
static short fake_poll_revents(const std::shared_ptr<FakeController> &fake, short events) {
std::lock_guard<std::recursive_mutex> guard(controller_mutex());
if (fake->closed) return POLLNVAL;
if (ring_generation(fake->ring) != fake->generation) return POLLHUP;
short ready = events & (POLLOUT | POLLWRNORM);
if ((events & (POLLIN | POLLRDNORM)) && fake_has_unread_data(*fake))
ready |= events & (POLLIN | POLLRDNORM);
return ready;
}
__attribute__((visibility("hidden"))) static long long
timespec_to_ms(const struct timespec *timeout) {
if (!timeout)
return -1;
return static_cast<long long>(timeout->tv_sec) * 1000LL +
timeout->tv_nsec / 1000000LL;
}
__attribute__((visibility("hidden"))) static long long
timeval_to_ms(const struct timeval *timeout) {
if (!timeout)
return -1;
return static_cast<long long>(timeout->tv_sec) * 1000LL +
timeout->tv_usec / 1000LL;
}
__attribute__((visibility("hidden"))) static long long monotonic_ms() {
struct timespec now = {};
clock_gettime(CLOCK_MONOTONIC, &now);
return static_cast<long long>(now.tv_sec) * 1000LL + now.tv_nsec / 1000000LL;
}
EXPORT int open(const char *pathname, int flags, ...) {
va_list va;
mode_t mode;
int fd;
bool hasMode;
va_start(va, flags);
hasMode = flags & O_CREAT;
if (hasMode) {
mode = va_arg(va, mode_t);
}
va_end(va);
static auto my_open = reinterpret_cast<int (*)(const char *, int, ...)>(dlsym(RTLD_NEXT, "open"));
char *fake_path = nullptr;
const char *event = nullptr;
if (pathname) {
if (is_fake_input_node_path(pathname)) {
event = get_event(pathname);
fake_path = from_real_to_fake_path(pathname);
if (!fake_path) {
errno = ENOMEM;
return -1;
}
if (path_exists(fake_path)) {
fd = open_fake_input_ring(event, flags);
if (fd >= 0) {
free(fake_path);
return fd;
}
int saved_errno = errno;
free(fake_path);
errno = saved_errno;
return -1;
}
pathname = fake_path;
} else if (is_fake_udev_data_path(pathname)) {
fake_path = from_real_to_fake_udev_data_path(pathname);
if (!fake_path) {
errno = ENOMEM;
return -1;
}
pathname = fake_path;
} else if (!strcmp(pathname, "/dev/input")) {
pathname = fake_hook_dir();
}
}
if (hasMode)
fd = my_open(pathname, flags, mode);
else
fd = my_open(pathname, flags);
if (fake_path)
free(fake_path);
return fd;
}
EXPORT int openat(int dirfd, const char *pathname, int flags, ...) {
va_list va;
mode_t mode;
int fd;
bool hasMode;
va_start(va, flags);
hasMode = flags & O_CREAT;
if (hasMode) {
mode = va_arg(va, mode_t);
}
va_end(va);
static auto my_openat = reinterpret_cast<int (*)(int, const char *, int, ...)>(dlsym(RTLD_NEXT, "openat"));
char *fake_path = nullptr;
const char *event = nullptr;
if (pathname) {
if (is_fake_input_node_path(pathname)) {
event = get_event(pathname);
fake_path = from_real_to_fake_path(pathname);
if (!fake_path) {
errno = ENOMEM;
return -1;
}
if (path_exists(fake_path)) {
fd = open_fake_input_ring(event, flags);
if (fd >= 0) {
free(fake_path);
return fd;
}
int saved_errno = errno;
free(fake_path);
errno = saved_errno;
return -1;
}
pathname = fake_path;
} else if (is_fake_udev_data_path(pathname)) {
fake_path = from_real_to_fake_udev_data_path(pathname);
if (!fake_path) {
errno = ENOMEM;
return -1;
}
pathname = fake_path;
} else if (!strcmp(pathname, "/dev/input")) {
pathname = fake_hook_dir();
}
}
if (hasMode)
fd = my_openat(dirfd, pathname, flags, mode);
else
fd = my_openat(dirfd, pathname, flags);
if (fake_path)
free(fake_path);
return fd;
}
EXPORT int stat(const char *pathname, struct stat *statbuf) {
static auto my_stat = reinterpret_cast<decltype(&::stat)>(dlsym(RTLD_NEXT, "stat"));
const char *event = nullptr;
char *fake_path = nullptr;
if (pathname) {
if (is_fake_input_node_path(pathname)) {
event = get_event(pathname);
fake_path = from_real_to_fake_path(pathname);
if (!fake_path) {
errno = ENOMEM;
return -1;
}
pathname = fake_path;
} else if (is_fake_udev_data_path(pathname)) {
fake_path = from_real_to_fake_udev_data_path(pathname);
if (!fake_path) {
errno = ENOMEM;
return -1;
}
pathname = fake_path;
} else if (!strcmp(pathname, "/dev/input")) {
pathname = fake_hook_dir();
}
}
int ret = my_stat(pathname, statbuf);
if (ret == 0 && event && get_event_number(event) >= 0) {
statbuf->st_mode = (statbuf->st_mode & ~S_IFMT) | S_IFCHR;
statbuf->st_rdev = get_fake_input_rdev(event);
}
if (fake_path)
free(fake_path);
return ret;
}
EXPORT int fstat(int fd, struct stat *buf) {
static auto my_fstat = reinterpret_cast<decltype(&::fstat)>(dlsym(RTLD_NEXT, "fstat"));
int ret = my_fstat(fd, buf);
std::lock_guard<std::recursive_mutex> guard(controller_mutex());
auto controller = controller_map().find(fd);
if (ret == 0 && controller != controller_map().end()) {
buf->st_mode = (buf->st_mode & ~S_IFMT) | S_IFCHR;
buf->st_rdev = get_fake_input_rdev(controller->second->event);
}
return ret;
}
EXPORT int access(const char *pathname, int mode) {
static auto my_access = reinterpret_cast<decltype(&::access)>(dlsym(RTLD_NEXT, "access"));
char *fake_path = nullptr;
if (pathname) {
if (is_fake_input_node_path(pathname)) {
fake_path = from_real_to_fake_path(pathname);
if (!fake_path) {
errno = ENOMEM;
return -1;
}
pathname = fake_path;
} else if (is_fake_udev_data_path(pathname)) {
fake_path = from_real_to_fake_udev_data_path(pathname);
if (!fake_path) {
errno = ENOMEM;
return -1;
}
pathname = fake_path;
} else if (!strcmp(pathname, "/dev/input")) {
pathname = fake_hook_dir();
}
}
int ret = my_access(pathname, mode);
if (fake_path)
free(fake_path);
return ret;
}
EXPORT int faccessat(int dirfd, const char *pathname, int mode, int flags) {
static auto my_faccessat = reinterpret_cast<decltype(&::faccessat)>(dlsym(RTLD_NEXT, "faccessat"));
char *fake_path = nullptr;
if (pathname) {
if (is_fake_input_node_path(pathname)) {
fake_path = from_real_to_fake_path(pathname);
if (!fake_path) {
errno = ENOMEM;
return -1;
}
pathname = fake_path;
} else if (is_fake_udev_data_path(pathname)) {
fake_path = from_real_to_fake_udev_data_path(pathname);
if (!fake_path) {
errno = ENOMEM;
return -1;
}
pathname = fake_path;
} else if (!strcmp(pathname, "/dev/input")) {
pathname = fake_hook_dir();
}
}
int ret = my_faccessat(dirfd, pathname, mode, flags);
if (fake_path)
free(fake_path);
return ret;
}
EXPORT int scandir(const char *dirp, struct dirent ***namelist,
int (*filter)(const struct dirent *),
int (*compar)(const struct dirent **,
const struct dirent **)) {
static auto my_scandir = reinterpret_cast<decltype(&::scandir)>(dlsym(RTLD_NEXT, "scandir"));
if (dirp) {
if (!strcmp(dirp, "/dev/input")) {
dirp = fake_hook_dir();
} else if (fake_udev_data_dir() && !strcmp(dirp, "/run/udev/data")) {
dirp = fake_udev_data_dir();
}
}
return my_scandir(dirp, namelist, filter, compar);
}
EXPORT int inotify_add_watch(int fd, const char *pathname, uint32_t mask) {
static auto my_inotify_add_watch = reinterpret_cast<decltype(&::inotify_add_watch)>(dlsym(RTLD_NEXT, "inotify_add_watch"));
char *fake_path = nullptr;
if (pathname) {
if (is_fake_input_node_path(pathname)) {
fake_path = from_real_to_fake_path(pathname);
if (!fake_path) {
errno = ENOMEM;
return -1;
}
pathname = fake_path;
} else if (!strcmp(pathname, "/dev/input")) {
pathname = fake_hook_dir();
}
}
int ret = my_inotify_add_watch(fd, pathname, mask);
if (fake_path)
free(fake_path);
return ret;
}
template <size_t N>
static int copy_ioctl_bits(int op, void *destination, const unsigned char (&bits)[N]) {
size_t size = std::min<size_t>(_IOC_SIZE(op), N);
if (size) memcpy(destination, bits, size);
return static_cast<int>(size);
}
static bool wait_snapshot(std::shared_ptr<FakeController> fake,
std::unique_lock<std::recursive_mutex> &guard,
SnapshotState &snap) {
for (;;) {
if (fake->closed) {
errno = EBADF;
return false;
}
if (ring_generation(fake->ring) != fake->generation) {
errno = ENODEV;
return false;
}
if (read_snapshot(fake->ring, snap)) return true;
// Enumeration queries must not fail merely because Android is publishing
// an input frame. Other hooks (including Binder) can proceed while we wait.
guard.unlock();
struct timespec wait = {0, 1000000};
int result = nanosleep(&wait, nullptr);
int saved_errno = errno;
guard.lock();
if (result < 0) {
errno = saved_errno;
return false;
}
}
}
EXPORT int ioctl(int fd, ioctl_request_t op, ...) {
va_list va;
void *argp;
va_start(va, op);
argp = va_arg(va, void *);
va_end(va);
// The lock must not be held across the passthrough: binder's transport is a
// blocking ioctl(BINDER_WRITE_READ), so a parked binder pool thread would own
// controller_mutex() for as long as it waits and deadlock every other ioctl.
std::unique_lock<std::recursive_mutex> guard(controller_mutex());
auto controller = controller_map().find(fd);
if (controller == controller_map().end()) {
guard.unlock();
return syscall(SYS_ioctl, fd, op, argp);
}
int type = (op >> 8 & 0xFF);
int number = (op >> 0 & 0xFF);
const char *event = controller->second->event ? controller->second->event : "event0";
int event_number = controller->second->slot;
if (type == 0x45 && number == 0x1) {
Logger::log("Hooking ioctl EVIOCGVERSION for event %s\n", event);
int version = 65536;
memcpy(argp, (void *)&version, sizeof(int));
return 0;
} else if (type == 0x45 && number == 0x2) {
Logger::log("Hooking ioctl EVIOCGID for event %s\n", event);
struct input_id id;
memset(&id, 0, sizeof(id));
id.bustype = 0x03;
if (presents_steam_virtual()) {
id.vendor = STEAM_VIRTUAL_VENDOR_ID;
id.product = STEAM_VIRTUAL_PRODUCT_ID;
} else if (fake_xbox360_identity()) {
id.vendor = X360_VENDOR_ID;
id.product = X360_PRODUCT_ID;
} else {
id.vendor = static_cast<uint16_t>(GAMEPAD_VENDOR_ID_BASE + event_number);
id.product = static_cast<uint16_t>(GAMEPAD_PRODUCT_ID_BASE + event_number);
}
id.version = GAMEPAD_VERSION;
memcpy(argp, (void *)&id, sizeof(id));
return 0;
} else if (type == 0x45 && number == 0x6) {
Logger::log("Hooking ioctl EVIOCGNAME for event %s\n", event);
copy_slot_ioctl_string(op, argp,
presents_steam_virtual() ? STEAM_VIRTUAL_NAME_TEMPLATE
: (fake_xbox360_identity() ? X360_NAME_TEMPLATE : GAMEPAD_NAME_TEMPLATE),
event_number);
return 0;
} else if (type == 0x45 && number == 0x7) {
Logger::log("Hooking ioctl EVIOCGPHYS for event %s\n", event);
copy_slot_ioctl_string(op, argp, GAMEPAD_PHYS_TEMPLATE, event_number);
return 0;
} else if (type == 0x45 && number == 0x8) {
Logger::log("Hooking ioctl EVIOCGUNIQ for event %s\n", event);
copy_slot_ioctl_string(op, argp, GAMEPAD_UNIQ_TEMPLATE, event_number);
return 0;
} else if (type == 0x45 && number == 0x9) {
unsigned char bitmask[(INPUT_PROP_MAX + 8) / 8] = {};
return copy_ioctl_bits(op, argp, bitmask);
} else if (type == 0x45 && number == 0x18) {
SnapshotState snap;
if (!wait_snapshot(controller->second, guard, snap)) return -1;
unsigned char bitmask[(KEY_MAX + 8) / 8] = {};
for (size_t i = 0; i < kSnapshotButtonCount; ++i) {
if (snap.buttons & (1u << i))
bitmask[kSnapshotButtons[i] / 8] |= 1u << (kSnapshotButtons[i] % 8);
}
return copy_ioctl_bits(op, argp, bitmask);
} else if (type == 0x45 && number == 0x20) {
Logger::log("Hooking ioctl EVIOCGBIT(0, len) for event %s\n", event);
unsigned char bitmask[(EV_MAX + 8) / 8] = {};
bitmask[EV_SYN / 8] |= (1 << (EV_SYN % 8));
bitmask[EV_KEY / 8] |= (1 << (EV_KEY % 8));
bitmask[EV_ABS / 8] |= (1 << (EV_ABS % 8));
return copy_ioctl_bits(op, argp, bitmask);
} else if (type == 0x45 && number == 0x21) {
Logger::log("Hooking ioctl EVIOCGBIT(EV_KEY, len) for event %s\n", event);
unsigned char bitmask[(KEY_MAX + 8) / 8] = {};
const int xbox_buttons[] = {BTN_A, BTN_B, BTN_X, BTN_Y,
BTN_TL, BTN_TR, BTN_SELECT, BTN_START,
BTN_MODE, BTN_THUMBL, BTN_THUMBR};
for (int button : xbox_buttons)
bitmask[button / 8] |= (1 << (button % 8));
return copy_ioctl_bits(op, argp, bitmask);
} else if (type == 0x45 && number == 0x22) {
Logger::log("Hooking ioctl EVIOCGBIT(EV_REL, len) for event %s\n", event);
unsigned char bitmask[(REL_MAX + 8) / 8] = {};
return copy_ioctl_bits(op, argp, bitmask);
} else if (type == 0x45 && number == 0x23) {
Logger::log("Hooking ioctl EVIOCGBIT(EV_ABS, len) for event %s\n", event);
unsigned char bitmask[(ABS_MAX + 8) / 8] = {};
bitmask[ABS_X / 8] |= (1 << (ABS_X % 8));
bitmask[ABS_Y / 8] |= (1 << (ABS_Y % 8));
bitmask[ABS_RX / 8] |= (1 << (ABS_RX % 8));
bitmask[ABS_RY / 8] |= (1 << (ABS_RY % 8));
for (uint16_t trigger : {ABS_GAS, ABS_BRAKE}) {
uint16_t code = presented_abs_code(trigger);
bitmask[code / 8] |= (1 << (code % 8));
}
bitmask[ABS_HAT0X / 8] |= (1 << (ABS_HAT0X % 8));
bitmask[ABS_HAT0Y / 8] |= (1 << (ABS_HAT0Y % 8));
return copy_ioctl_bits(op, argp, bitmask);
} else if (type == 0x45 && number == 0x35) {
Logger::log("Hooking ioctl EVIOCGBIT(EV_FF, len) for event %s\n", event);
unsigned char bitmask[(FF_MAX + 8) / 8] = {};
bitmask[FF_RUMBLE / 8] |= (1 << (FF_RUMBLE % 8));
bitmask[FF_PERIODIC / 8] |= (1 << (FF_PERIODIC % 8));
return copy_ioctl_bits(op, argp, bitmask);
} else if (type == 0x45 && number == 0x80) {
struct ff_effect *effect = static_cast<struct ff_effect *>(argp);
if (effect->id == -1)
effect->id = next_ff_id++;
ff_effects()[effect->id] = *effect;
uint16_t duration = effect->replay.length;
uint16_t slot = static_cast<uint16_t>(event_number);
if (effect->type == FF_RUMBLE) {
send_vibration(effect->u.rumble.strong_magnitude,
effect->u.rumble.weak_magnitude, duration, slot);
} else if (effect->type == FF_PERIODIC) {
send_vibration(effect->u.periodic.magnitude, effect->u.periodic.magnitude,
duration, slot);
}
return 0;
} else if (type == 0x45 && number == 0x81) {
int id = (intptr_t)argp;
ff_effects().erase(id);
return 0;
} else if (type == 0x45 && number == 0x84) {
int max_effects = 16;
memcpy(argp, &max_effects, sizeof(int));
return 0;
} else if (type == 0x45 && number >= 0x40 && number <= 0x51) {
Logger::log("Hooking ioctl EVIOCGABS(ABS) for event %s\n", event);
struct input_absinfo abs_info;
memset(&abs_info, 0, sizeof(abs_info));
uint16_t code = ring_abs_code(number - 0x40);
if (code == ABS_GAS || code == ABS_BRAKE) {
abs_info.value = 0;
abs_info.minimum = 0;
abs_info.maximum = 255;
} else if (code <= ABS_RY) {
abs_info.value = 0;
abs_info.minimum = -32768;
abs_info.maximum = 32767;
} else if (code == ABS_HAT0X || code == ABS_HAT0Y) {
abs_info.value = 0;
abs_info.minimum = -1;
abs_info.maximum = 1;
}
SnapshotState snap;
if (!wait_snapshot(controller->second, guard, snap)) return -1;
for (int i = 0; i < 8; ++i)
if (kSnapshotAxisCodes[i] == code) abs_info.value = snap.axes[i];
memcpy(argp, &abs_info, std::min<size_t>(_IOC_SIZE(op), sizeof(abs_info)));
return 0;
} else if (type == 0x45 && number == 0x90) {
Logger::log("Hooking ioctl EVIOCGRAB for event %s\n", event);
return 0;
} else if (type == 0x6A && number == 0x1) {
Logger::log("Hooking ioctl JSIOCGVERSION for event %s\n", event);
int version = JS_VERSION;
memcpy(argp, (void *)&version, sizeof(version));
return 0;
} else if (type == 0x6A && number == 0x11) {
Logger::log("Hooking ioctl JSIOCGAXES for event %s\n", event);
uint8_t axes = GAMEPAD_AXIS_COUNT;
memcpy(argp, (void *)&axes, sizeof(axes));
return 0;
} else if (type == 0x6A && number == 0x12) {
Logger::log("Hooking ioctl JSIOCGBUTTONS for event %s\n", event);
uint8_t buttons = GAMEPAD_BUTTON_COUNT;
memcpy(argp, (void *)&buttons, sizeof(buttons));
return 0;
} else if (type == 0x6A && number == 0x13) {
Logger::log("Hooking ioctl JSIOCGNAME(len) for event %s\n", event);
copy_slot_ioctl_string(op, argp,
presents_steam_virtual() ? STEAM_VIRTUAL_NAME_TEMPLATE
: (fake_xbox360_identity() ? X360_NAME_TEMPLATE : GAMEPAD_NAME_TEMPLATE),
event_number);
return 0;
} else {
Logger::log("Unhandled evdev ioctl, type %d number %d\n", type, number);
guard.unlock();
return syscall(SYS_ioctl, fd, op, argp);
}
}
EXPORT int close(int fd) {
static auto my_close = reinterpret_cast<decltype(&::close)>(dlsym(RTLD_NEXT, "close"));
std::unique_lock<std::recursive_mutex> guard(controller_mutex());
auto controller = controller_map().find(fd);
if (controller != controller_map().end()) {
Logger::log("Removing controller, fd %d event %s\n", controller->first,
controller->second->event ? controller->second->event : "(unknown)");
controller->second->closed = true;
controller_map().erase(fd);
}
guard.unlock();
return my_close(fd);
}
EXPORT ssize_t read(int fd, void *buf, size_t count) {
std::unique_lock<std::recursive_mutex> guard(controller_mutex());
auto controller = controller_map().find(fd);
if (controller == controller_map().end()) {
guard.unlock();
return syscall(SYS_read, fd, buf, count);
}
// Keep the mapping alive across waits, even if close() removes the fd and
// another open reuses its number. Every state access still holds the lock.
auto handle = controller->second;
FakeController &fake = *handle;
if (count < FAKE_INPUT_EVENT_SIZE) {
errno = EINVAL;
return -1;
}
int flags = fcntl(fd, F_GETFL);
bool nonblock = flags >= 0 && (flags & O_NONBLOCK);
long backoff_ns = 1000 * 1000;
for (;;) {
if (fake.closed) {
errno = EBADF;
return -1;
}
if (ring_generation(fake.ring) != fake.generation) {
errno = ENODEV;
return -1;
}
for (int attempt = 0; attempt < 8; ++attempt) {
size_t requested = count / FAKE_INPUT_EVENT_SIZE;
if (fake.keyframe_remaining == 0) {
SnapshotState snap;
if (!read_snapshot(fake.ring, snap)) break;
if (snap.generation != fake.generation) {
errno = ENODEV;
return -1;
}
if (fake.needs_keyframe || snap.resync_seq != fake.resync_seq ||
snap.write_seq < fake.read_seq ||
snap.write_seq - fake.read_seq > FAKE_INPUT_RING_CAPACITY) {
capture_keyframe(fake, snap);
} else {
size_t events = std::min<uint64_t>(requested, snap.write_seq - fake.read_seq);
if (events == 0) break;
const uint8_t *ring_events = reinterpret_cast<const uint8_t *>(fake.ring) +
FAKE_INPUT_RING_HEADER_SIZE;
for (size_t i = 0; i < events; ++i) {
size_t index = (fake.read_seq + i) % FAKE_INPUT_RING_CAPACITY;
struct input_event ev;
memcpy(&ev, ring_events + index * FAKE_INPUT_EVENT_SIZE, FAKE_INPUT_EVENT_SIZE);
if (ev.type == EV_ABS) ev.code = presented_abs_code(ev.code);
memcpy(static_cast<uint8_t *>(buf) + i * FAKE_INPUT_EVENT_SIZE, &ev,
FAKE_INPUT_EVENT_SIZE);
}
// A producer can lap the reader while it copies. Discard that copy
// instead of delivering torn/overwritten events or losing a release.
__atomic_thread_fence(__ATOMIC_ACQUIRE);
if (snap.sequence != __atomic_load_n(&fake.ring->snapshot_seq, __ATOMIC_RELAXED))
continue;
fake.read_seq += events;
return static_cast<ssize_t>(events * FAKE_INPUT_EVENT_SIZE);
}
}
// Finish a captured frame before handling a newer resync. Its counter is
// acknowledged only when that newer snapshot is actually captured.
size_t events = std::min(requested, fake.keyframe_remaining);
struct timeval now = {};
gettimeofday(&now, nullptr);
for (size_t i = 0; i < events; ++i) {
size_t index = kNeutralEventCount - fake.keyframe_remaining;
struct input_event ev = {};
ev.time = now;
ev.type = kNeutralEvents[index].type;
ev.code = kNeutralEvents[index].code;
ev.value = keyframe_value(fake, ev.type, ev.code);
if (ev.type == EV_ABS) ev.code = presented_abs_code(ev.code);
memcpy(static_cast<uint8_t *>(buf) + i * FAKE_INPUT_EVENT_SIZE,
&ev, FAKE_INPUT_EVENT_SIZE);
--fake.keyframe_remaining;
}
return static_cast<ssize_t>(events * FAKE_INPUT_EVENT_SIZE);
}
if (nonblock) {
errno = EAGAIN;
return -1;
}
guard.unlock();
struct timespec sleep_time = {0, backoff_ns};
int result = nanosleep(&sleep_time, nullptr);
guard.lock();
if (result < 0) return -1;
if (backoff_ns < 16 * 1000 * 1000) backoff_ns *= 2;
}
}
EXPORT ssize_t write(int fd, const void *buf, size_t count) {
static auto my_write = reinterpret_cast<decltype(&::write)>(dlsym(RTLD_NEXT, "write"));
std::unique_lock<std::recursive_mutex> guard(controller_mutex());
auto controller = controller_map().find(fd);
if (controller != controller_map().end()) {
if (fake_fd_is_stale(fd)) {
errno = ENODEV;
return -1;
}
const struct input_event *ev = nullptr;
uint16_t slot = static_cast<uint16_t>(controller->second->slot);
if (count == sizeof(struct input_event)) {
ev = static_cast<const struct input_event *>(buf);
check_ff_event(ev, slot);
}
return static_cast<ssize_t>(count);
}
guard.unlock();
return my_write(fd, buf, count);
}
EXPORT ssize_t writev(int fd, const struct iovec *iov, int iovcnt) {
std::unique_lock<std::recursive_mutex> guard(controller_mutex());
auto controller = controller_map().find(fd);
if (controller != controller_map().end()) {
if (fake_fd_is_stale(fd)) {
errno = ENODEV;
return -1;
}
uint16_t slot = static_cast<uint16_t>(controller->second->slot);
ssize_t total = 0;
for (int i = 0; i < iovcnt; i++) {
if (iov[i].iov_len == sizeof(struct input_event)) {
const struct input_event *ev =
static_cast<const struct input_event *>(iov[i].iov_base);
check_ff_event(ev, slot);
}
total += static_cast<ssize_t>(iov[i].iov_len);
}
return total;
}
guard.unlock();
return syscall(SYS_writev, fd, iov, iovcnt);
}
static int poll_fake(struct pollfd *fds, nfds_t nfds, int timeout,
const sigset_t *sigmask) {
static auto my_poll = reinterpret_cast<decltype(&::poll)>(dlsym(RTLD_NEXT, "poll"));
static auto my_ppoll = reinterpret_cast<decltype(&::ppoll)>(dlsym(RTLD_NEXT, "ppoll"));
bool has_fake_fds = false;
std::vector<struct pollfd> real_fds;
real_fds.reserve(nfds);
std::vector<std::shared_ptr<FakeController>> fake_fds(nfds);
{
std::lock_guard<std::recursive_mutex> guard(controller_mutex());
for (nfds_t i = 0; i < nfds; i++) {
real_fds.push_back(fds[i]);
auto it = controller_map().find(fds[i].fd);
if (it != controller_map().end()) {
fake_fds[i] = it->second;
has_fake_fds = true;
real_fds[i].fd = -1;
real_fds[i].revents = 0;
}
}
}
if (!has_fake_fds) {
if (sigmask) {
struct timespec wait = {timeout / 1000, (timeout % 1000) * 1000000L};
return my_ppoll(fds, nfds, timeout < 0 ? nullptr : &wait, sigmask);
}
return my_poll(fds, nfds, timeout);
}
const long long deadline_ms = timeout < 0 ? -1 : monotonic_ms() + timeout;
int backoff_ms = 1;
while (true) {
int ready = 0;
for (nfds_t i = 0; i < nfds; i++)
fds[i].revents = 0;
for (nfds_t i = 0; i < nfds; i++) {
if (!fake_fds[i]) continue;
short revents = fake_poll_revents(fake_fds[i], fds[i].events);
fds[i].revents = revents;
if (revents)
ready++;
}
int real_timeout = ready > 0 ? 0 : [&] {
if (timeout == 0) return 0;
int remaining = deadline_ms < 0
? backoff_ms
: std::min(backoff_ms, (int)(deadline_ms - monotonic_ms()));
return std::max(remaining, 0);
}();
int real_ready;
if (sigmask) {
struct timespec wait = {real_timeout / 1000, (real_timeout % 1000) * 1000000L};
real_ready = my_ppoll(real_fds.data(), nfds, &wait, sigmask);
} else {
real_ready = my_poll(real_fds.data(), nfds, real_timeout);
}
if (real_ready < 0) return -1;
if (real_ready > 0) {
for (nfds_t i = 0; i < nfds; i++) {
if (!fake_fds[i]) {
fds[i].revents = real_fds[i].revents;
if (fds[i].revents)
ready++;
}
}
}
if (ready == 0 && real_timeout > 0) {
for (nfds_t i = 0; i < nfds; i++) {
if (!fake_fds[i]) continue;
short revents = fake_poll_revents(fake_fds[i], fds[i].events);
fds[i].revents = revents;
if (revents)
ready++;
}
}
if (ready > 0)
return ready;
if (timeout == 0)
return 0;
if (deadline_ms >= 0 && monotonic_ms() >= deadline_ms)
return 0;
if (backoff_ms < 16)
backoff_ms *= 2;
}
}
EXPORT int poll(struct pollfd *fds, nfds_t nfds, int timeout) {
return poll_fake(fds, nfds, timeout, nullptr);
}
EXPORT int ppoll(struct pollfd *fds, nfds_t nfds,
const struct timespec *timeout, const sigset_t *sigmask) {
static auto my_ppoll = reinterpret_cast<decltype(&::ppoll)>(dlsym(RTLD_NEXT, "ppoll"));
if (timeout && (timeout->tv_sec < 0 || timeout->tv_nsec < 0 || timeout->tv_nsec >= 1000000000L)) {
errno = EINVAL;
return -1;
}
for (nfds_t i = 0; i < nfds; ++i) {
if (!is_fake_input_fd(fds[i].fd)) continue;
if (!sigmask)
return poll_fake(fds, nfds, static_cast<int>(timespec_to_ms(timeout)), nullptr);
// Keep signals pending between polling slices. Each kernel ppoll applies
// the requested mask atomically with its wait, just like a single ppoll.
sigset_t all_signals, original_mask;
sigfillset(&all_signals);
int error = pthread_sigmask(SIG_SETMASK, &all_signals, &original_mask);
if (error) {
errno = error;
return -1;
}
int result = poll_fake(fds, nfds, static_cast<int>(timespec_to_ms(timeout)), sigmask);
int saved_errno = errno;
pthread_sigmask(SIG_SETMASK, &original_mask, nullptr);
errno = saved_errno;
return result;
}
return my_ppoll(fds, nfds, timeout, sigmask);
}
EXPORT int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout) {
static auto my_select = reinterpret_cast<decltype(&::select)>(dlsym(RTLD_NEXT, "select"));
fd_set original_readfds;
fd_set original_writefds;
fd_set original_exceptfds;
fd_set real_readfds;
fd_set real_writefds;
fd_set real_exceptfds;
bool has_fake_fds = false;
if (readfds) {
original_readfds = *readfds;
real_readfds = *readfds;
} else {
FD_ZERO(&original_readfds);
FD_ZERO(&real_readfds);
}
if (writefds) {
original_writefds = *writefds;
real_writefds = *writefds;
} else {
FD_ZERO(&original_writefds);
FD_ZERO(&real_writefds);
}
if (exceptfds) {
original_exceptfds = *exceptfds;
real_exceptfds = *exceptfds;
} else {
FD_ZERO(&original_exceptfds);
FD_ZERO(&real_exceptfds);
}
for (int fd = 0; fd < nfds; fd++) {
if (!is_fake_input_fd(fd))
continue;
has_fake_fds = true;
FD_CLR(fd, &real_readfds);
FD_CLR(fd, &real_writefds);
FD_CLR(fd, &real_exceptfds);
}
if (!has_fake_fds)
return my_select ? my_select(nfds, readfds, writefds, exceptfds, timeout)
: -1;
const long long timeout_ms = timeval_to_ms(timeout);
const long long deadline_ms =
timeout_ms < 0 ? -1 : monotonic_ms() + timeout_ms;
int backoff_ms = 1;
while (true) {
int ready = 0;
if (readfds)
FD_ZERO(readfds);
if (writefds)
FD_ZERO(writefds);
if (exceptfds)
FD_ZERO(exceptfds);
for (int fd = 0; fd < nfds; fd++) {
if (!is_fake_input_fd(fd))
continue;
if (readfds && FD_ISSET(fd, &original_readfds) && fake_fd_is_stale(fd)) {
FD_SET(fd, readfds);
ready++;
} else if (readfds && FD_ISSET(fd, &original_readfds) &&
fake_fd_has_unread_data(fd)) {
FD_SET(fd, readfds);
ready++;
}
}
int wait_ms = ready > 0 ? 0 : [&] {
if (timeout_ms == 0) return 0;
int remaining = deadline_ms < 0
? backoff_ms
: std::min(backoff_ms, (int)(deadline_ms - monotonic_ms()));
return std::max(remaining, 0);
}();
struct timeval wait_tv = {wait_ms / 1000, (wait_ms % 1000) * 1000};
fd_set iter_readfds = real_readfds;
fd_set iter_writefds = real_writefds;
fd_set iter_exceptfds = real_exceptfds;
int real_ready =
my_select
? my_select(nfds, readfds ? &iter_readfds : nullptr,
writefds ? &iter_writefds : nullptr,
exceptfds ? &iter_exceptfds : nullptr, &wait_tv)
: 0;
if (real_ready < 0) return -1;
if (real_ready > 0) {
for (int fd = 0; fd < nfds; fd++) {
if (readfds && FD_ISSET(fd, &iter_readfds)) {
FD_SET(fd, readfds);
ready++;
}
if (writefds && FD_ISSET(fd, &iter_writefds)) {
FD_SET(fd, writefds);
ready++;
}
if (exceptfds && FD_ISSET(fd, &iter_exceptfds)) {
FD_SET(fd, exceptfds);
ready++;
}
}
}
if (ready == 0 && wait_ms > 0) {
for (int fd = 0; fd < nfds; fd++) {
if (!is_fake_input_fd(fd))
continue;
if (readfds && FD_ISSET(fd, &original_readfds) && fake_fd_is_stale(fd)) {
FD_SET(fd, readfds);
ready++;
} else if (readfds && FD_ISSET(fd, &original_readfds) &&
fake_fd_has_unread_data(fd)) {
FD_SET(fd, readfds);
ready++;
}
}
}
if (ready > 0)
return ready;
if (timeout_ms == 0)
return 0;
if (deadline_ms >= 0 && monotonic_ms() >= deadline_ms)
return 0;
if (backoff_ms < 16)
backoff_ms *= 2;
}
}
#ifdef __GLIBC__
// Steam's binaries were linked against an older glibc and reach the calls above through these
// names instead.
static mode_t open_mode(int flags, va_list va) {
return (flags & (O_CREAT | O_TMPFILE)) ? va_arg(va, mode_t) : 0;
}
EXPORT int open64(const char *pathname, int flags, ...) {
va_list va;
va_start(va, flags);
mode_t mode = open_mode(flags, va);
va_end(va);
return open(pathname, flags, mode);
}
EXPORT int openat64(int dirfd, const char *pathname, int flags, ...) {
va_list va;
va_start(va, flags);
mode_t mode = open_mode(flags, va);
va_end(va);
return openat(dirfd, pathname, flags, mode);
}
EXPORT int stat64(const char *pathname, struct stat64 *statbuf) {
return stat(pathname, reinterpret_cast<struct stat *>(statbuf));
}
EXPORT int fstat64(int fd, struct stat64 *buf) {
return fstat(fd, reinterpret_cast<struct stat *>(buf));
}
EXPORT int __xstat(int version, const char *pathname, struct stat *statbuf) {
(void)version;
return stat(pathname, statbuf);
}
EXPORT int __xstat64(int version, const char *pathname, struct stat64 *statbuf) {
(void)version;
return stat(pathname, reinterpret_cast<struct stat *>(statbuf));
}
EXPORT int __fxstat(int version, int fd, struct stat *buf) {
(void)version;
return fstat(fd, buf);
}
EXPORT int __fxstat64(int version, int fd, struct stat64 *buf) {
(void)version;
return fstat(fd, reinterpret_cast<struct stat *>(buf));
}
#endif