diff --git a/crates/punktfunk-host/src/inject.rs b/crates/punktfunk-host/src/inject.rs index 867c164..21f4390 100644 --- a/crates/punktfunk-host/src/inject.rs +++ b/crates/punktfunk-host/src/inject.rs @@ -459,6 +459,11 @@ pub mod gamepad; #[cfg(target_os = "windows")] #[path = "inject/windows/gamepad_windows.rs"] pub mod gamepad; +/// Windows: small RAII wrappers (`Shm` section+view, `SwDevice` devnode) shared by the three gamepad +/// backends (DualSense / DualShock 4 / XUSB), so each per-pad resource closes deterministically on drop. +#[cfg(target_os = "windows")] +#[path = "inject/windows/gamepad_raii.rs"] +mod gamepad_raii; /// Stub — virtual gamepads need Linux uinput or the Windows UMDF drivers; events are dropped elsewhere. #[cfg(not(any(target_os = "linux", target_os = "windows")))] pub mod gamepad { diff --git a/crates/punktfunk-host/src/inject/windows/dualsense_windows.rs b/crates/punktfunk-host/src/inject/windows/dualsense_windows.rs index cbce83f..748b32c 100644 --- a/crates/punktfunk-host/src/inject/windows/dualsense_windows.rs +++ b/crates/punktfunk-host/src/inject/windows/dualsense_windows.rs @@ -29,15 +29,7 @@ use windows::core::{w, GUID, HRESULT, HSTRING, PCWSTR}; use windows::Win32::Devices::Enumeration::Pnp::{ SwDeviceClose, SwDeviceCreate, HSWDEVICE, SW_DEVICE_CREATE_INFO, }; -use windows::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE}; -use windows::Win32::Security::Authorization::{ - ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1, -}; -use windows::Win32::Security::{PSECURITY_DESCRIPTOR, SECURITY_ATTRIBUTES}; -use windows::Win32::System::Memory::{ - CreateFileMappingW, MapViewOfFile, UnmapViewOfFile, FILE_MAP_ALL_ACCESS, - MEMORY_MAPPED_VIEW_ADDRESS, PAGE_READWRITE, -}; +use windows::Win32::Foundation::{CloseHandle, HANDLE}; use windows::Win32::System::Threading::{CreateEventW, SetEvent, WaitForSingleObject}; /// Shared-section layout — the single source of truth is [`pf_driver_proto::gamepad::PadShm`] (offset @@ -60,11 +52,11 @@ pub(super) const DEVTYPE_DUALSHOCK4: u8 = pf_driver_proto::gamepad::DEVTYPE_DUAL /// loads on it and the HID DualSense appears to games) plus the shared-memory section the driver maps. /// Dropping it removes the devnode (`SwDeviceClose`) and unmaps + closes the section. struct DsWinPad { - /// Per-session devnode from SwDeviceCreate, when it succeeds. `None` falls back to an out-of-band - /// `pf_dualsense` devnode (installer/devgen). - hsw: Option, - map: HANDLE, - view: *mut u8, + /// Per-session devnode from SwDeviceCreate, when it succeeds (RAII — `SwDeviceClose` on drop). + /// `None` falls back to an out-of-band `pf_dualsense` devnode (installer/devgen). + _sw: Option, + /// The named shared section the driver maps (RAII — unmapped + closed on drop). + shm: super::gamepad_raii::Shm, seq: u8, ts: u32, last_out_seq: u32, @@ -238,62 +230,16 @@ pub(super) fn create_swdevice(p: &SwDeviceProfile) -> Result { Ok(hsw) } -/// Create + map the named section `Global\pfds-shm-`, zeroed, with a permissive DACL so the -/// WUDFHost (whatever account it runs as) can open it. Returns `(section handle, mapped base)`; the -/// caller stamps the device-type + initial input report and finally the magic. Shared by both Windows -/// pad backends (DualSense + DualShock 4). -pub(super) fn create_shm_section(index: u8) -> Result<(HANDLE, *mut u8)> { - let name = HSTRING::from(pf_driver_proto::gamepad::pad_shm_name(index)); - - let mut psd = PSECURITY_DESCRIPTOR::default(); - // SAFETY: the SDDL literal is valid; psd receives an allocated descriptor (freed by the OS when - // the process exits — acceptable for a host-lifetime object). - unsafe { - ConvertStringSecurityDescriptorToSecurityDescriptorW( - w!("D:(A;;GA;;;WD)"), - SDDL_REVISION_1, - &mut psd, - None, - )?; - } - let sa = SECURITY_ATTRIBUTES { - nLength: std::mem::size_of::() as u32, - lpSecurityDescriptor: psd.0, - bInheritHandle: false.into(), - }; - - // SAFETY: anonymous (pagefile-backed) section of SHM_SIZE bytes with the SDDL above. - let map = unsafe { - CreateFileMappingW( - INVALID_HANDLE_VALUE, - Some(&sa), - PAGE_READWRITE, - 0, - SHM_SIZE as u32, - PCWSTR(name.as_ptr()), - )? - }; - // SAFETY: map is a valid section handle; map the whole thing. - let view = unsafe { MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, SHM_SIZE) }; - if view.Value.is_null() { - // SAFETY: map is valid. - unsafe { - let _ = CloseHandle(map); - } - return Err(anyhow!("MapViewOfFile failed for {name}")); - } - let base = view.Value as *mut u8; - // SAFETY: base points at SHM_SIZE writable bytes. - unsafe { std::ptr::write_bytes(base, 0, SHM_SIZE) }; - Ok((map, base)) -} - impl DsWinPad { /// Create + map the section `Global\pfds-shm-`, stamp the magic, then spawn the /// `root\pf_dualsense` devnode (the driver loads on it and maps the section). The devnode lives /// for the pad's lifetime — dropping the pad removes it (`SwDeviceClose`). fn open(index: u8) -> Result { - let (map, base) = create_shm_section(index)?; + let shm = super::gamepad_raii::Shm::create( + &HSTRING::from(pf_driver_proto::gamepad::pad_shm_name(index)), + SHM_SIZE, + )?; + let base = shm.base(); // Stamp the neutral input report, then the magic LAST (the driver only accepts the section // once magic is set). The device-type stays 0 (DualSense — the section is already zeroed). // SAFETY: base points at SHM_SIZE writable bytes. @@ -322,10 +268,10 @@ impl DsWinPad { None } }; + let _sw = hsw.map(super::gamepad_raii::SwDevice::new); Ok(DsWinPad { - hsw, - map, - view: base, + _sw, + shm, seq: 0, ts: 0, last_out_seq: 0, @@ -338,22 +284,25 @@ impl DsWinPad { self.ts = self.ts.wrapping_add(1); let mut r = [0u8; DS_INPUT_REPORT_LEN]; serialize_state(&mut r, st, self.seq, self.ts); - // SAFETY: view points at SHM_SIZE bytes; input slot is OFF_INPUT..OFF_INPUT+64. - unsafe { std::ptr::copy_nonoverlapping(r.as_ptr(), self.view.add(OFF_INPUT), r.len()) }; + // SAFETY: base points at SHM_SIZE bytes; input slot is OFF_INPUT..OFF_INPUT+64. + unsafe { + std::ptr::copy_nonoverlapping(r.as_ptr(), self.shm.base().add(OFF_INPUT), r.len()) + }; } /// Poll the section's output slot; parse a new `0x02` report (rumble / LEDs / triggers) into a /// [`DsFeedback`] for pad `pad`. Returns empty feedback if the driver hasn't published anything new. fn service(&mut self, pad: u8) -> DsFeedback { let mut fb = DsFeedback::default(); - // SAFETY: view points at SHM_SIZE bytes. - let seq = unsafe { std::ptr::read_unaligned(self.view.add(OFF_OUT_SEQ) as *const u32) }; + // SAFETY: base points at SHM_SIZE bytes. + let seq = + unsafe { std::ptr::read_unaligned(self.shm.base().add(OFF_OUT_SEQ) as *const u32) }; if seq != self.last_out_seq { self.last_out_seq = seq; let mut out = [0u8; 64]; // SAFETY: output slot is OFF_OUTPUT..OFF_OUTPUT+64 within the section. unsafe { - std::ptr::copy_nonoverlapping(self.view.add(OFF_OUTPUT), out.as_mut_ptr(), 64) + std::ptr::copy_nonoverlapping(self.shm.base().add(OFF_OUTPUT), out.as_mut_ptr(), 64) }; parse_ds_output(pad, &out, &mut fb); } @@ -361,21 +310,6 @@ impl DsWinPad { } } -impl Drop for DsWinPad { - fn drop(&mut self) { - // SAFETY: hsw (if any) owns the devnode; view/map from MapViewOfFile/CreateFileMappingW. - unsafe { - if let Some(h) = self.hsw { - SwDeviceClose(h); - } - let _ = UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS { - Value: self.view as *mut c_void, - }); - let _ = CloseHandle(self.map); - } - } -} - /// All virtual DualSense pads of a session — the Windows analogue of /// [`DualSenseManager`](super::dualsense::DualSenseManager). Same method surface so the session input /// thread drives either backend identically. diff --git a/crates/punktfunk-host/src/inject/windows/dualshock4_windows.rs b/crates/punktfunk-host/src/inject/windows/dualshock4_windows.rs index 918c59a..b2b89dd 100644 --- a/crates/punktfunk-host/src/inject/windows/dualshock4_windows.rs +++ b/crates/punktfunk-host/src/inject/windows/dualshock4_windows.rs @@ -9,8 +9,8 @@ use super::dualsense_proto::DsState; use super::dualsense_windows::{ - create_shm_section, create_swdevice, SwDeviceProfile, DEVTYPE_DUALSHOCK4, OFF_DEVTYPE, - OFF_INPUT, OFF_OUTPUT, OFF_OUT_SEQ, SHM_MAGIC, + create_swdevice, SwDeviceProfile, DEVTYPE_DUALSHOCK4, OFF_DEVTYPE, OFF_INPUT, OFF_OUTPUT, + OFF_OUT_SEQ, SHM_MAGIC, SHM_SIZE, }; use super::dualshock4_proto::{ parse_ds4_output, serialize_state, Ds4Feedback, DS4_INPUT_REPORT_LEN, DS4_TOUCH_H, DS4_TOUCH_W, @@ -18,18 +18,16 @@ use super::dualshock4_proto::{ use crate::gamestream::gamepad::{GamepadEvent, MAX_PADS}; use anyhow::Result; use punktfunk_core::quic::{HidOutput, RichInput}; -use std::ffi::c_void; use std::time::{Duration, Instant}; -use windows::Win32::Devices::Enumeration::Pnp::{SwDeviceClose, HSWDEVICE}; -use windows::Win32::Foundation::{CloseHandle, HANDLE}; -use windows::Win32::System::Memory::{UnmapViewOfFile, MEMORY_MAPPED_VIEW_ADDRESS}; +use windows::core::HSTRING; /// A single virtual DualShock 4: the `SwDeviceCreate`'d `pf_ds4_` devnode plus the mapped /// shared section. Dropping it removes the devnode and unmaps + closes the section. struct Ds4WinPad { - hsw: Option, - map: HANDLE, - view: *mut u8, + /// Per-session devnode from SwDeviceCreate, when it succeeds (RAII — `SwDeviceClose` on drop). + _sw: Option, + /// The named shared section the driver maps (RAII — unmapped + closed on drop). + shm: super::gamepad_raii::Shm, counter: u8, ts: u16, last_out_seq: u32, @@ -39,7 +37,11 @@ impl Ds4WinPad { /// Create + map the section, stamp `device_type = DualShock 4` + a neutral report + the magic, /// then spawn the `pf_ds4_` devnode (the driver loads on it and maps the section). fn open(index: u8) -> Result { - let (map, base) = create_shm_section(index)?; + let shm = super::gamepad_raii::Shm::create( + &HSTRING::from(pf_driver_proto::gamepad::pad_shm_name(index)), + SHM_SIZE, + )?; + let base = shm.base(); // device-type FIRST (so it's visible the moment magic is), neutral report, magic LAST. // SAFETY: base points at SHM_SIZE writable bytes; OFF_DEVTYPE/OFF_INPUT are in range. unsafe { @@ -65,10 +67,10 @@ impl Ds4WinPad { None } }; + let _sw = hsw.map(super::gamepad_raii::SwDevice::new); Ok(Ds4WinPad { - hsw, - map, - view: base, + _sw, + shm, counter: 0, ts: 0, last_out_seq: 0, @@ -81,22 +83,25 @@ impl Ds4WinPad { self.ts = self.ts.wrapping_add(188); // ~1ms in the DS4's 5.33µs sensor-clock units let mut r = [0u8; DS4_INPUT_REPORT_LEN]; serialize_state(&mut r, st, self.counter, self.ts); - // SAFETY: view points at SHM_SIZE bytes; input slot is OFF_INPUT..OFF_INPUT+64. - unsafe { std::ptr::copy_nonoverlapping(r.as_ptr(), self.view.add(OFF_INPUT), r.len()) }; + // SAFETY: base points at SHM_SIZE bytes; input slot is OFF_INPUT..OFF_INPUT+64. + unsafe { + std::ptr::copy_nonoverlapping(r.as_ptr(), self.shm.base().add(OFF_INPUT), r.len()) + }; } /// Poll the section's output slot; parse a new `0x05` report (rumble / lightbar) into a /// [`Ds4Feedback`]. Returns empty feedback if the driver hasn't published anything new. fn service(&mut self) -> Ds4Feedback { let mut fb = Ds4Feedback::default(); - // SAFETY: view points at SHM_SIZE bytes. - let seq = unsafe { std::ptr::read_unaligned(self.view.add(OFF_OUT_SEQ) as *const u32) }; + // SAFETY: base points at SHM_SIZE bytes. + let seq = + unsafe { std::ptr::read_unaligned(self.shm.base().add(OFF_OUT_SEQ) as *const u32) }; if seq != self.last_out_seq { self.last_out_seq = seq; let mut out = [0u8; 64]; // SAFETY: output slot is OFF_OUTPUT..OFF_OUTPUT+64 within the section. unsafe { - std::ptr::copy_nonoverlapping(self.view.add(OFF_OUTPUT), out.as_mut_ptr(), 64) + std::ptr::copy_nonoverlapping(self.shm.base().add(OFF_OUTPUT), out.as_mut_ptr(), 64) }; parse_ds4_output(&out, &mut fb); } @@ -104,21 +109,6 @@ impl Ds4WinPad { } } -impl Drop for Ds4WinPad { - fn drop(&mut self) { - // SAFETY: hsw (if any) owns the devnode; view/map from MapViewOfFile/CreateFileMappingW. - unsafe { - if let Some(h) = self.hsw { - SwDeviceClose(h); - } - let _ = UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS { - Value: self.view as *mut c_void, - }); - let _ = CloseHandle(self.map); - } - } -} - /// All virtual DualShock 4 pads of a session — the Windows analogue of /// [`DualShock4Manager`](super::dualshock4::DualShock4Manager), with the same method surface as the /// Windows DualSense manager so the session input thread drives either backend identically. diff --git a/crates/punktfunk-host/src/inject/windows/gamepad_raii.rs b/crates/punktfunk-host/src/inject/windows/gamepad_raii.rs new file mode 100644 index 0000000..b460d8e --- /dev/null +++ b/crates/punktfunk-host/src/inject/windows/gamepad_raii.rs @@ -0,0 +1,115 @@ +//! Per-pad Windows resource RAII for the gamepad backends (DualSense / DualShock 4 / XUSB). +//! +//! Each virtual pad owns two OS resources: the named shared-memory section (+ its mapped view) the +//! `pf_dualsense`/`pf_xusb` driver reads, and the `SwDeviceCreate`'d software devnode the driver loads +//! on. Before this module, all three backends hand-rolled the same `CreateFileMappingW` + +//! `MapViewOfFile` and an identical `Drop` doing `SwDeviceClose` + `UnmapViewOfFile` + `CloseHandle` — +//! easy to drift or leak on an error path. [`Shm`] and [`SwDevice`] own those resources with RAII, so a +//! backend just holds them and the cleanup (and ordering) happens by construction. + +use anyhow::{anyhow, Result}; +use std::os::windows::io::{FromRawHandle, OwnedHandle}; +use windows::core::{w, HSTRING, PCWSTR}; +use windows::Win32::Devices::Enumeration::Pnp::{SwDeviceClose, HSWDEVICE}; +use windows::Win32::Foundation::INVALID_HANDLE_VALUE; +use windows::Win32::Security::Authorization::{ + ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1, +}; +use windows::Win32::Security::{PSECURITY_DESCRIPTOR, SECURITY_ATTRIBUTES}; +use windows::Win32::System::Memory::{ + CreateFileMappingW, MapViewOfFile, UnmapViewOfFile, FILE_MAP_ALL_ACCESS, + MEMORY_MAPPED_VIEW_ADDRESS, PAGE_READWRITE, +}; + +/// A named, anonymous (pagefile-backed) shared section + its mapped read/write view, created with the +/// permissive `D:(A;;GA;;;WD)` SDDL the restricted-token driver needs to open it. RAII: drop unmaps the +/// view, then the [`OwnedHandle`] closes the section handle (in that order). Replaces the three backends' +/// hand-duplicated `CreateFileMappingW` + `MapViewOfFile` + manual `Drop`. +pub(super) struct Shm { + /// Owns the section handle (closed on drop). Held only for ownership — never read after construction. + _handle: OwnedHandle, + view: MEMORY_MAPPED_VIEW_ADDRESS, +} + +impl Shm { + /// Create + zero a `size`-byte section named `name`, mapped read/write. The section handle is owned + /// immediately, so any failure below (or the returned `Shm`'s drop) closes it. + pub(super) fn create(name: &HSTRING, size: usize) -> Result { + let mut psd = PSECURITY_DESCRIPTOR::default(); + // SAFETY: the SDDL literal is valid; `psd` receives an OS-allocated descriptor (freed at process + // exit — acceptable for a host-lifetime object). + unsafe { + ConvertStringSecurityDescriptorToSecurityDescriptorW( + w!("D:(A;;GA;;;WD)"), + SDDL_REVISION_1, + &mut psd, + None, + )?; + } + let sa = SECURITY_ATTRIBUTES { + nLength: core::mem::size_of::() as u32, + lpSecurityDescriptor: psd.0, + bInheritHandle: false.into(), + }; + // SAFETY: an anonymous (pagefile-backed) section of `size` bytes with the SDDL above. + let map = unsafe { + CreateFileMappingW( + INVALID_HANDLE_VALUE, + Some(&sa), + PAGE_READWRITE, + 0, + size as u32, + PCWSTR(name.as_ptr()), + )? + }; + // SAFETY: `map` is a fresh section handle we own; take ownership immediately so that the early + // return below (and the eventual drop) closes it. `map` (a `Copy` `HANDLE`) stays usable for the + // `MapViewOfFile` borrow that follows — `from_raw_handle` only copies the inner pointer. + let handle = unsafe { OwnedHandle::from_raw_handle(map.0) }; + // SAFETY: `map` is a valid section handle; map the whole thing read/write. + let view = unsafe { MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, size) }; + if view.Value.is_null() { + // `handle` drops here → closes the section. No view to unmap. + return Err(anyhow!("MapViewOfFile failed for {name}")); + } + // SAFETY: `view` points at `size` writable bytes (just mapped). + unsafe { core::ptr::write_bytes(view.Value as *mut u8, 0, size) }; + Ok(Shm { + _handle: handle, + view, + }) + } + + /// The mapped section's base pointer. Stable for the `Shm`'s lifetime (moving the `Shm` does not + /// relocate the OS mapping — the view address is fixed by `MapViewOfFile`). + pub(super) fn base(&self) -> *mut u8 { + self.view.Value as *mut u8 + } +} + +impl Drop for Shm { + fn drop(&mut self) { + // SAFETY: `view` came from `MapViewOfFile`; unmap it BEFORE the `_handle` field closes the + // section (struct fields drop only after this `Drop::drop` returns). + unsafe { + let _ = UnmapViewOfFile(self.view); + } + } +} + +/// A `SwDeviceCreate`'d software devnode; drop removes it via `SwDeviceClose`. Replaces the manual +/// `SwDeviceClose` each backend used to call in its `Drop`. +pub(super) struct SwDevice(HSWDEVICE); + +impl SwDevice { + pub(super) fn new(hsw: HSWDEVICE) -> Self { + SwDevice(hsw) + } +} + +impl Drop for SwDevice { + fn drop(&mut self) { + // SAFETY: `self.0` is the handle `SwDeviceCreate` returned; `SwDeviceClose` removes the devnode. + unsafe { SwDeviceClose(self.0) }; + } +} diff --git a/crates/punktfunk-host/src/inject/windows/gamepad_windows.rs b/crates/punktfunk-host/src/inject/windows/gamepad_windows.rs index 28463cf..dd75f6c 100644 --- a/crates/punktfunk-host/src/inject/windows/gamepad_windows.rs +++ b/crates/punktfunk-host/src/inject/windows/gamepad_windows.rs @@ -21,15 +21,7 @@ use windows::core::{w, GUID, HRESULT, HSTRING, PCWSTR}; use windows::Win32::Devices::Enumeration::Pnp::{ SwDeviceClose, SwDeviceCreate, HSWDEVICE, SW_DEVICE_CREATE_INFO, }; -use windows::Win32::Foundation::{CloseHandle, HANDLE, INVALID_HANDLE_VALUE}; -use windows::Win32::Security::Authorization::{ - ConvertStringSecurityDescriptorToSecurityDescriptorW, SDDL_REVISION_1, -}; -use windows::Win32::Security::{PSECURITY_DESCRIPTOR, SECURITY_ATTRIBUTES}; -use windows::Win32::System::Memory::{ - CreateFileMappingW, MapViewOfFile, UnmapViewOfFile, FILE_MAP_ALL_ACCESS, - MEMORY_MAPPED_VIEW_ADDRESS, PAGE_READWRITE, -}; +use windows::Win32::Foundation::{CloseHandle, HANDLE}; use windows::Win32::System::Threading::{CreateEventW, SetEvent, WaitForSingleObject}; // Shared-section layout — the single source of truth is `pf_driver_proto::gamepad::XusbShm` (offset @@ -150,9 +142,10 @@ fn create_swdevice(index: u8) -> Result { /// A single virtual Xbox 360 pad: the `pf_xusb_` devnode plus the mapped shared section. struct XusbWinPad { - hsw: Option, - map: HANDLE, - view: *mut u8, + /// Owns the `pf_xusb_` devnode (dropped → `SwDeviceClose`). `None` if `SwDeviceCreate` failed. + _sw: Option, + /// Owns `Global\pfxusb-shm-` (the section + its mapped view; drop unmaps + closes). + shm: super::gamepad_raii::Shm, packet: u32, last_rumble_seq: u32, } @@ -160,45 +153,13 @@ struct XusbWinPad { impl XusbWinPad { /// Create + map `Global\pfxusb-shm-`, stamp the magic, then spawn the devnode. fn open(index: u8) -> Result { - let name = HSTRING::from(pf_driver_proto::gamepad::xusb_shm_name(index)); - - // Permissive DACL so the WUDFHost (whatever account) can open the section. - let mut psd = PSECURITY_DESCRIPTOR::default(); - // SAFETY: SDDL literal valid; psd receives an OS-freed descriptor (host-lifetime — fine). - unsafe { - ConvertStringSecurityDescriptorToSecurityDescriptorW( - w!("D:(A;;GA;;;WD)"), - SDDL_REVISION_1, - &mut psd, - None, - )?; - } - let sa = SECURITY_ATTRIBUTES { - nLength: std::mem::size_of::() as u32, - lpSecurityDescriptor: psd.0, - bInheritHandle: false.into(), - }; - // SAFETY: anonymous (pagefile-backed) section of SHM_SIZE bytes with the SDDL above. - let map = unsafe { - CreateFileMappingW( - INVALID_HANDLE_VALUE, - Some(&sa), - PAGE_READWRITE, - 0, - SHM_SIZE as u32, - PCWSTR(name.as_ptr()), - )? - }; - // SAFETY: map is a valid section handle; map the whole thing. - let view = unsafe { MapViewOfFile(map, FILE_MAP_ALL_ACCESS, 0, 0, SHM_SIZE) }; - if view.Value.is_null() { - // SAFETY: map is valid. - unsafe { - let _ = CloseHandle(map); - } - return Err(anyhow!("MapViewOfFile failed for {name}")); - } - let base = view.Value as *mut u8; + // Permissive-DACL named section the WUDFHost (whatever account) can open; `Shm` owns the + // section handle + its mapped view (zero-filled) and unmaps/closes on drop. + let shm = super::gamepad_raii::Shm::create( + &HSTRING::from(pf_driver_proto::gamepad::xusb_shm_name(index)), + SHM_SIZE, + )?; + let base = shm.base(); // Zero the section then stamp the magic LAST (the driver only accepts it once magic is set). // SAFETY: base points at SHM_SIZE writable bytes. unsafe { @@ -212,10 +173,10 @@ impl XusbWinPad { None } }; + let _sw = hsw.map(super::gamepad_raii::SwDevice::new); Ok(XusbWinPad { - hsw, - map, - view: base, + _sw, + shm, packet: 0, last_rumble_seq: 0, }) @@ -226,50 +187,36 @@ impl XusbWinPad { #[allow(clippy::too_many_arguments)] fn write_state(&mut self, buttons: u16, lt: u8, rt: u8, lx: i16, ly: i16, rx: i16, ry: i16) { self.packet = self.packet.wrapping_add(1); - // SAFETY: view points at SHM_SIZE bytes; all offsets are in range. + // SAFETY: base points at SHM_SIZE bytes; all offsets are in range. + let base = self.shm.base(); unsafe { - std::ptr::write_unaligned(self.view.add(OFF_BUTTONS) as *mut u16, buttons); - *self.view.add(OFF_LT) = lt; - *self.view.add(OFF_RT) = rt; - std::ptr::write_unaligned(self.view.add(OFF_LX) as *mut i16, lx); - std::ptr::write_unaligned(self.view.add(OFF_LY) as *mut i16, ly); - std::ptr::write_unaligned(self.view.add(OFF_RX) as *mut i16, rx); - std::ptr::write_unaligned(self.view.add(OFF_RY) as *mut i16, ry); - std::ptr::write_unaligned(self.view.add(OFF_PACKET) as *mut u32, self.packet); + std::ptr::write_unaligned(base.add(OFF_BUTTONS) as *mut u16, buttons); + *base.add(OFF_LT) = lt; + *base.add(OFF_RT) = rt; + std::ptr::write_unaligned(base.add(OFF_LX) as *mut i16, lx); + std::ptr::write_unaligned(base.add(OFF_LY) as *mut i16, ly); + std::ptr::write_unaligned(base.add(OFF_RX) as *mut i16, rx); + std::ptr::write_unaligned(base.add(OFF_RY) as *mut i16, ry); + std::ptr::write_unaligned(base.add(OFF_PACKET) as *mut u32, self.packet); } } /// Poll the section for a game's rumble (the driver bumps `rumble_seq` on each SET_STATE). Returns /// `(large, small)` motor levels (0..=255) when a new one arrived. fn service(&mut self) -> Option<(u8, u8)> { - // SAFETY: view points at SHM_SIZE bytes. - let seq = unsafe { std::ptr::read_unaligned(self.view.add(OFF_RUMBLE_SEQ) as *const u32) }; + let base = self.shm.base(); + // SAFETY: base points at SHM_SIZE bytes. + let seq = unsafe { std::ptr::read_unaligned(base.add(OFF_RUMBLE_SEQ) as *const u32) }; if seq == self.last_rumble_seq { return None; } self.last_rumble_seq = seq; // SAFETY: rumble bytes at OFF_RUMBLE / OFF_RUMBLE+1. - let (large, small) = - unsafe { (*self.view.add(OFF_RUMBLE), *self.view.add(OFF_RUMBLE + 1)) }; + let (large, small) = unsafe { (*base.add(OFF_RUMBLE), *base.add(OFF_RUMBLE + 1)) }; Some((large, small)) } } -impl Drop for XusbWinPad { - fn drop(&mut self) { - // SAFETY: hsw (if any) owns the devnode; view/map from MapViewOfFile/CreateFileMappingW. - unsafe { - if let Some(h) = self.hsw { - SwDeviceClose(h); - } - let _ = UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS { - Value: self.view as *mut c_void, - }); - let _ = CloseHandle(self.map); - } - } -} - /// All virtual Xbox 360 pads of a session — the Windows analogue of the Linux uinput-xpad manager, /// now backed by the XUSB companion driver. Same method surface (`new`/`handle`/`pump_rumble`) the /// session input thread already drives.