// punktfunk virtual Xbox 360 XUSB companion — UMDF2 driver presenting the XUSB device interface so // classic XInput (XInputGetState) reads the pad with no kernel bus driver (the HIDMaestro approach). // // xinput1_4.dll enumerates GUID_DEVINTERFACE_XUSB, opens the Nth instance (= player slot), and polls // it with buffered IOCTLs. We register the interface and answer those IOCTLs from controller state the // host publishes into a shared DATA section; a game's rumble (SET_STATE) is published back for the // host to forward. Byte formats are the source-verified xusb22 wire layout (HIDMaestro // driver/companion.c + nefarius/XInputHooker XUSB.h + ViGEm XUSB_REPORT). // // The host channel is the **sealed pad channel** (design/gamepad-channel-sealing.md, proto v2): the // DATA section (`pf_driver_proto::gamepad::XusbShm`) is UNNAMED — we reach it only through a handle // the SYSTEM host duplicated into this WUDFHost, bootstrapped over the named `Global\pfxusb-boot-` // mailbox. The whole handshake + all shared-memory access lives in `pf_umdf_util` (audited unsafe // layer): this crate's channel/IOCTL/state logic is 100% SAFE Rust. The only `unsafe` here is the // unavoidable WDF setup FFI in DriverEntry/EvtDeviceAdd, each with a `// SAFETY:` proof. // // We answer the WAIT_* IOCTLs with STATUS_INVALID_DEVICE_REQUEST, which makes xinput1_4 fall back to // synchronous GET_STATE polling — so no manual queue / timer is needed for classic XInput. #![allow(non_snake_case, non_upper_case_globals, clippy::missing_safety_doc)] // Every remaining `unsafe {}` (all WDF setup FFI) must carry a `// SAFETY:` proof. #![deny(unsafe_op_in_unsafe_fn)] #![deny(clippy::undocumented_unsafe_blocks)] use pf_driver_proto::gamepad::XusbShm; use pf_umdf_util::channel::{ChannelClient, ChannelConfig}; use pf_umdf_util::nt_success; use pf_umdf_util::section::MappedView; use pf_umdf_util::wdf::{self, Request}; use wdk_sys::{ GUID, NTSTATUS, PCUNICODE_STRING, PDRIVER_OBJECT, PWDFDEVICE_INIT, ULONG, WDF_DRIVER_CONFIG, WDF_IO_QUEUE_CONFIG, WDF_NO_HANDLE, WDF_NO_OBJECT_ATTRIBUTES, WDFDEVICE, WDFDRIVER, WDFQUEUE, WDFREQUEST, call_unsafe_wdf_function_binding, windows::OutputDebugStringA, }; // ---- NTSTATUS ---- const STATUS_SUCCESS: NTSTATUS = 0; const STATUS_INVALID_DEVICE_REQUEST: NTSTATUS = 0xC000_0010u32 as NTSTATUS; // GUID_DEVINTERFACE_XUSB {EC87F1E3-C13B-4100-B5F7-8B84D54260CB} — what xinput1_4 enumerates + opens. const GUID_DEVINTERFACE_XUSB: GUID = GUID { Data1: 0xEC87_F1E3, Data2: 0xC13B, Data3: 0x4100, Data4: [0xB5, 0xF7, 0x8B, 0x84, 0xD5, 0x42, 0x60, 0xCB], }; // ---- XUSB IOCTLs (METHOD_BUFFERED) ---- const IOCTL_XUSB_GET_INFORMATION: u32 = 0x8000_6000; const IOCTL_XUSB_GET_CAPABILITIES: u32 = 0x8000_E004; const IOCTL_XUSB_GET_LED_STATE: u32 = 0x8000_E008; const IOCTL_XUSB_GET_STATE: u32 = 0x8000_E00C; const IOCTL_XUSB_SET_STATE: u32 = 0x8000_A010; const IOCTL_XUSB_WAIT_GUIDE_BUTTON: u32 = 0x8000_E014; const IOCTL_XUSB_GET_BATTERY_INFORMATION: u32 = 0x8000_E018; const IOCTL_XUSB_POWER_DOWN: u32 = 0x8000_A01C; const IOCTL_XUSB_GET_XINPUT_MANAGEMENT_DRIVER: u32 = 0x8000_6380; const IOCTL_XUSB_WAIT_FOR_INPUT: u32 = 0x8000_E3AC; const IOCTL_XUSB_GET_INFORMATION_EX: u32 = 0x8000_E3FC; // Xbox 360 wired identity (what GET_INFORMATION reports). 0x0103 unblocks SET_STATE (vibration). const XUSB_VID: u16 = 0x045E; const XUSB_PID: u16 = 0x028E; const XUSB_VERSION: u16 = 0x0103; // ---- WDF enum values ---- const WdfIoQueueDispatchParallel: i32 = 2; const WdfUseDefault: i32 = 2; // WDF_TRI_STATE // ---- the sealed host channel: layouts + offsets from pf_driver_proto (drift = compile error) ---- const SHM_MAGIC: u32 = pf_driver_proto::gamepad::XUSB_MAGIC; // "PFXU" const SHM_SIZE: usize = core::mem::size_of::(); const GAMEPAD_PROTO_VERSION: u32 = pf_driver_proto::gamepad::GAMEPAD_PROTO_VERSION; // XusbShm field offsets (host writes state, we answer XInput; we write rumble + health marks). const OFF_PACKET: usize = core::mem::offset_of!(XusbShm, packet); const OFF_BUTTONS: usize = core::mem::offset_of!(XusbShm, buttons); const OFF_LT: usize = core::mem::offset_of!(XusbShm, left_trigger); const OFF_RT: usize = core::mem::offset_of!(XusbShm, right_trigger); const OFF_LX: usize = core::mem::offset_of!(XusbShm, thumb_lx); const OFF_LY: usize = core::mem::offset_of!(XusbShm, thumb_ly); const OFF_RX: usize = core::mem::offset_of!(XusbShm, thumb_rx); const OFF_RY: usize = core::mem::offset_of!(XusbShm, thumb_ry); const OFF_RUMBLE_SEQ: usize = core::mem::offset_of!(XusbShm, rumble_seq); const OFF_RUMBLE_LARGE: usize = core::mem::offset_of!(XusbShm, rumble_large); const OFF_RUMBLE_SMALL: usize = core::mem::offset_of!(XusbShm, rumble_small); const OFF_DRIVER_PROTO: usize = core::mem::offset_of!(XusbShm, driver_proto); const OFF_DRIVER_HEARTBEAT: usize = core::mem::offset_of!(XusbShm, driver_heartbeat); const OFF_PAD_INDEX: usize = core::mem::offset_of!(XusbShm, pad_index); /// The sealed-channel client (per-pad: `ProcessSharingDisabled` gives each pad its own WUDFHost, so /// this static is per-pad). All shared-memory access + the bootstrap handshake live in `pf_umdf_util`. static CHANNEL: ChannelClient = ChannelClient::new(); /// This pad's channel config (magic/size/pad_index offset + our logger). fn channel_cfg() -> ChannelConfig { ChannelConfig { tag: "pf-xusb", boot_name_prefix: "Global\\pfxusb-boot-", data_magic: SHM_MAGIC, data_size: SHM_SIZE, pad_index_off: OFF_PAD_INDEX, log, } } /// Whether the world-writable bring-up file log is enabled (resolved once). OPT-IN — debug builds, /// or the `PFXUSB_DEBUG_LOG` (system-wide) env var — the same treatment pf-vdisplay got in audit /// §4.4: a RELEASE driver never writes the Public file (info-leak/DoS surface), and the per-rumble /// SET_STATE hex dumps stop being a sustained disk-write path during gameplay. DebugView can't see /// the UMDF host across session 0, so the file stays the bring-up diagnostic when enabled. fn file_log_enabled() -> bool { use std::sync::OnceLock; static ON: OnceLock = OnceLock::new(); *ON.get_or_init(|| cfg!(debug_assertions) || std::env::var_os("PFXUSB_DEBUG_LOG").is_some()) } /// Process-lifetime append handle to the bring-up log, opened ONCE and shared via a `Mutex` /// (pf-vdisplay's pattern) — no per-line open/close. fn file_appender() -> Option<&'static std::sync::Mutex> { use std::sync::OnceLock; static APPENDER: OnceLock>> = OnceLock::new(); APPENDER .get_or_init(|| { if !file_log_enabled() { return None; } std::fs::OpenOptions::new() .create(true) .append(true) .open("C:\\Users\\Public\\pfxusb-driver.log") .ok() .map(std::sync::Mutex::new) }) .as_ref() } fn log(s: &str) { if let Ok(c) = std::ffi::CString::new(s) { // SAFETY: `c` is a valid NUL-terminated string for the duration of the call. unsafe { OutputDebugStringA(c.as_ptr().cast()) }; } use std::io::Write; if let Some(m) = file_appender() && let Ok(mut f) = m.lock() { let _ = writeln!(f, "{s}"); } } macro_rules! dbglog { ($($a:tt)*) => { log(&format!($($a)*)) } } #[unsafe(export_name = "DriverEntry")] pub unsafe extern "system" fn driver_entry( driver: PDRIVER_OBJECT, registry_path: PCUNICODE_STRING, ) -> NTSTATUS { log("[pf-xusb] DriverEntry"); // SAFETY: a zeroed WDF_DRIVER_CONFIG is a valid all-null config; we then set Size + the callback. let mut config: WDF_DRIVER_CONFIG = unsafe { core::mem::zeroed() }; config.Size = core::mem::size_of::() as ULONG; config.EvtDriverDeviceAdd = Some(evt_device_add); // SAFETY: `driver`/`registry_path` are the loader-provided pointers; the config is valid. unsafe { call_unsafe_wdf_function_binding!( WdfDriverCreate, driver, registry_path, WDF_NO_OBJECT_ATTRIBUTES, &mut config, WDF_NO_HANDLE.cast::() ) } } extern "C" fn evt_device_add(_driver: WDFDRIVER, mut device_init: PWDFDEVICE_INIT) -> NTSTATUS { log("[pf-xusb] EvtDeviceAdd"); let mut device: WDFDEVICE = core::ptr::null_mut(); // SAFETY: `device_init` is the framework-provided init; attributes null; `device` receives it. let st = unsafe { call_unsafe_wdf_function_binding!( WdfDeviceCreate, &mut device_init, WDF_NO_OBJECT_ATTRIBUTES, &mut device ) }; if !nt_success(st) { dbglog!("[pf-xusb] WdfDeviceCreate failed 0x{:08x}", st as u32); return st; } // SAFETY: `device` is the live device just created — the exact contract `query_location_index` // requires. let idx = unsafe { wdf::query_location_index(device) }; CHANNEL.set_index(idx); dbglog!("[pf-xusb] shm index = {idx}"); // Register the XUSB device interface (no reference string) — what xinput1_4 enumerates + opens. // SAFETY: `device` is live; the GUID is a static; null reference string. let st = unsafe { call_unsafe_wdf_function_binding!( WdfDeviceCreateDeviceInterface, device, &GUID_DEVINTERFACE_XUSB, core::ptr::null() ) }; if !nt_success(st) { dbglog!( "[pf-xusb] WdfDeviceCreateDeviceInterface failed 0x{:08x}", st as u32 ); return st; } // Default parallel queue: all the XUSB IOCTLs land here. // SAFETY: a zeroed WDF_IO_QUEUE_CONFIG is valid; we then set Size + the fields we use. let mut qcfg: WDF_IO_QUEUE_CONFIG = unsafe { core::mem::zeroed() }; qcfg.Size = core::mem::size_of::() as ULONG; qcfg.DispatchType = WdfIoQueueDispatchParallel; qcfg.PowerManaged = WdfUseDefault; qcfg.DefaultQueue = 1; qcfg.EvtIoDeviceControl = Some(evt_io_device_control); qcfg.Settings.Parallel.NumberOfPresentedRequests = u32::MAX; let mut queue: WDFQUEUE = core::ptr::null_mut(); // SAFETY: `device` + `qcfg` are valid; attributes null; `queue` receives the handle. let st = unsafe { call_unsafe_wdf_function_binding!( WdfIoQueueCreate, device, &mut qcfg, WDF_NO_OBJECT_ATTRIBUTES, &mut queue ) }; if !nt_success(st) { dbglog!("[pf-xusb] WdfIoQueueCreate failed 0x{:08x}", st as u32); return st; } // Run the sealed-channel handshake on a worker (must NOT block EvtDeviceAdd): publish our pid in // the bootstrap mailbox and poll for the host's delivered DATA handle, so the pad attaches (and // the host's driver-attach health check goes green) even before any game polls XInput. Bounded; // a later host (or a re-delivery) is still picked up by the per-IOCTL pump. This closure is 100% // safe — the whole channel state machine lives in pf_umdf_util. std::thread::spawn(|| { let cfg = channel_cfg(); for _ in 0..500 { if let Some(v) = CHANNEL.pump(&cfg) { touch_driver_marks(v); return; } std::thread::sleep(std::time::Duration::from_millis(20)); } log( "[pf-xusb] no sealed-channel delivery within 10s (host absent, or host/driver version mismatch — see above)", ); }); log("[pf-xusb] device ready (XUSB interface registered)"); STATUS_SUCCESS } /// The current controller state from the attached DATA section (zeros / neutral when unattached). /// Returns `(dwPacketNumber, wButtons, lt, rt, lx, ly, rx, ry)`. fn read_state(data: Option<&MappedView>) -> (u32, u16, u8, u8, i16, i16, i16, i16) { match data { Some(v) => ( v.read_u32(OFF_PACKET), v.read_u16(OFF_BUTTONS), v.read_u8(OFF_LT), v.read_u8(OFF_RT), v.read_i16(OFF_LX), v.read_i16(OFF_LY), v.read_i16(OFF_RX), v.read_i16(OFF_RY), ), None => (0, 0, 0, 0, 0, 0, 0, 0), } } /// Stamp the driver health marks the host watches: `driver_proto` (the attach signal, idempotent) /// and `driver_heartbeat` (+1). Called once the channel attaches and on every serviced IOCTL, so the /// host can tell "driver bound and alive" apart from "driver package missing/failed to bind" and see /// the game-visible polling path advance. fn touch_driver_marks(data: &MappedView) { data.write_u32(OFF_DRIVER_PROTO, GAMEPAD_PROTO_VERSION); let hb = data.read_u32(OFF_DRIVER_HEARTBEAT).wrapping_add(1); data.write_u32(OFF_DRIVER_HEARTBEAT, hb); } /// Publish a game's rumble (from SET_STATE) into the DATA section for the host to forward. fn publish_rumble(data: Option<&MappedView>, large: u8, small: u8) { let Some(v) = data else { return }; v.write_u8(OFF_RUMBLE_LARGE, large); v.write_u8(OFF_RUMBLE_SMALL, small); let seq = v.read_u32(OFF_RUMBLE_SEQ).wrapping_add(1); v.write_u32(OFF_RUMBLE_SEQ, seq); } // Build the 29-byte GET_STATE buffer (the layout xinput1_4 parses). fn build_get_state(data: Option<&MappedView>) -> [u8; 29] { let (packet, buttons, lt, rt, lx, ly, rx, ry) = read_state(data); let mut s = [0u8; 29]; s[0..2].copy_from_slice(&XUSB_VERSION.to_le_bytes()); s[2] = 0x01; // device count s[5..9].copy_from_slice(&packet.to_le_bytes()); s[0x0B..0x0D].copy_from_slice(&buttons.to_le_bytes()); s[0x0D] = lt; s[0x0E] = rt; s[0x0F..0x11].copy_from_slice(&lx.to_le_bytes()); s[0x11..0x13].copy_from_slice(&ly.to_le_bytes()); s[0x13..0x15].copy_from_slice(&rx.to_le_bytes()); s[0x15..0x17].copy_from_slice(&ry.to_le_bytes()); s } // GET_INFORMATION: 12 bytes — version, device count, VID/PID. Marks the slot connected. fn build_information() -> [u8; 12] { let mut info = [0u8; 12]; info[0..2].copy_from_slice(&XUSB_VERSION.to_le_bytes()); info[2] = 0x01; // one device/port info[8..10].copy_from_slice(&XUSB_VID.to_le_bytes()); info[10..12].copy_from_slice(&XUSB_PID.to_le_bytes()); info } // GET_CAPABILITIES V1 (24 bytes): Type=0x03 SubType=0x01 (gamepad), button/stick masks, motor max // = 0xFFFF (advertise rumble). The V2 (36-byte) form prepends a 16-byte header when WGI asks for 36. #[rustfmt::skip] const CAPS_V1: [u8; 24] = [ 0x03, 0x01, 0x00, 0x01, 0xFF, 0xF7, 0xFF, 0xFF, 0xC0, 0xFF, 0xC0, 0xFF, 0xC0, 0xFF, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, ]; fn build_caps_v2() -> [u8; 36] { let mut c = [0u8; 36]; c[0..6].copy_from_slice(&[0x03, 0x01, 0x01, 0x01, 0x0C, 0x00]); c[6..8].copy_from_slice(&XUSB_VID.to_le_bytes()); c[8..10].copy_from_slice(&XUSB_PID.to_le_bytes()); c[10..16].copy_from_slice(&[0x10, 0x01, 0x00, 0xFA, 0x34, 0x22]); c[16..36].copy_from_slice(&CAPS_V1[4..24]); // the XINPUT_CAPABILITIES struct body c } extern "C" fn evt_io_device_control( _queue: WDFQUEUE, request: WDFREQUEST, output_len: usize, input_len: usize, ioctl: ULONG, ) { // SAFETY: `request` is the live request for THIS EvtIoDeviceControl invocation — exactly the // contract `Request::new` requires. From here everything is safe (the token owns completion). let request = unsafe { Request::new(request) }; // Sealed-channel pump + health marks first: adopt a (late) delivery, detach when the host's // mailbox is gone, and stamp the attach/heartbeat marks the host watches (also covers a host // started after this device — the pump attaches on the next XInput poll). let data = CHANNEL.pump(&channel_cfg()); if let Some(v) = data { touch_driver_marks(v); } let status: NTSTATUS = match ioctl { IOCTL_XUSB_GET_INFORMATION => request.copy_to_output(&build_information()), IOCTL_XUSB_GET_INFORMATION_EX => { let mut ex = [0u8; 64]; ex[0..2].copy_from_slice(&XUSB_VERSION.to_le_bytes()); ex[2] = 0x01; ex[3] = 0x01; ex[8..10].copy_from_slice(&XUSB_VID.to_le_bytes()); ex[10..12].copy_from_slice(&XUSB_PID.to_le_bytes()); let n = output_len.min(64); request.copy_to_output(&ex[..n]) } IOCTL_XUSB_GET_CAPABILITIES => { if output_len >= 36 { request.copy_to_output(&build_caps_v2()) } else { request.copy_to_output(&CAPS_V1) } } IOCTL_XUSB_GET_STATE => request.copy_to_output(&build_get_state(data)), IOCTL_XUSB_GET_LED_STATE => request.copy_to_output(&[0x00, 0x00, 0x06]), IOCTL_XUSB_GET_BATTERY_INFORMATION => request.copy_to_output(&[0x00, 0x01, 0x03, 0x00]), IOCTL_XUSB_SET_STATE => on_set_state(&request, data), IOCTL_XUSB_POWER_DOWN | IOCTL_XUSB_GET_XINPUT_MANAGEMENT_DRIVER => STATUS_SUCCESS, // Decline the async waits → xinput1_4 falls back to synchronous GET_STATE polling. IOCTL_XUSB_WAIT_GUIDE_BUTTON | IOCTL_XUSB_WAIT_FOR_INPUT => STATUS_INVALID_DEVICE_REQUEST, other => { dbglog!("[pf-xusb] unhandled IOCTL 0x{other:08x} in={input_len} out={output_len}"); STATUS_INVALID_DEVICE_REQUEST } }; request.complete(status); } // SET_STATE: the rumble packet. Classic xusb22 layout is small; the motor bytes sit near the end. // We publish a best-effort (large = byte 2, small = byte 3 for the 5-byte form) and log the raw bytes // so the exact offsets can be confirmed against a real pad. fn on_set_state(request: &Request, data: Option<&MappedView>) -> NTSTATUS { if let Ok((bytes, len)) = request.input_bytes(8) && len >= 2 { let mut hex = String::new(); for b in &bytes { hex.push_str(&format!("{b:02x} ")); } dbglog!("[pf-xusb] SET_STATE len={len} data: {hex}"); // Observed 5-byte form {00, led, largeMotor, smallMotor, subcmd}: subcmd 0x02 = rumble // (large/low-freq at [2], small/high-freq at [3]); 0x01 = player-LED set (ignored). // 4-byte = raw XINPUT_VIBRATION → the two motor hi bytes. if len >= 5 && bytes[4] == 0x02 { publish_rumble(data, bytes[2], bytes[3]); } else if len == 4 { publish_rumble(data, bytes[1], bytes[3]); } } STATUS_SUCCESS }