//! Human input device system. //! //! Holds the guest-facing `X_INPUT_*` struct layouts (big-endian, matching the //! Xbox 360 ABI exactly) and the host-neutral `GamepadState` snapshot used by //! both the UI (writer) and the kernel's `XamInputGetState` handler (reader). use xenia_memory::{GuestMemory, MemoryAccess}; /// Human input device system stub. pub struct InputSystem { pub gamepad: GamepadState, } /// Host-side gamepad snapshot. /// /// Kept as POD so it can be dropped into an `AtomicCell` for /// lock-free UI→CPU state transfer. Layout intentionally matches the in-memory /// subset of `X_INPUT_GAMEPAD` (minus endianness), but the wire serializer in /// [`write_input_state`] handles the big-endian conversion explicitly. #[derive(Default, Clone, Copy, Debug)] #[repr(C)] pub struct GamepadState { pub buttons: u16, pub left_trigger: u8, pub right_trigger: u8, pub left_stick_x: i16, pub left_stick_y: i16, pub right_stick_x: i16, pub right_stick_y: i16, } /// Xbox 360 button flags. Values match `X_INPUT_GAMEPAD_BUTTON` in /// `xenia-canary/src/xenia/hid/input.h`. pub mod buttons { pub const DPAD_UP: u16 = 0x0001; pub const DPAD_DOWN: u16 = 0x0002; pub const DPAD_LEFT: u16 = 0x0004; pub const DPAD_RIGHT: u16 = 0x0008; pub const START: u16 = 0x0010; pub const BACK: u16 = 0x0020; pub const LEFT_THUMB: u16 = 0x0040; pub const RIGHT_THUMB: u16 = 0x0080; pub const LEFT_SHOULDER: u16 = 0x0100; pub const RIGHT_SHOULDER: u16 = 0x0200; pub const GUIDE: u16 = 0x0400; pub const A: u16 = 0x1000; pub const B: u16 = 0x2000; pub const X: u16 = 0x4000; pub const Y: u16 = 0x8000; } /// Xbox guest error codes returned by `XamInput*`. pub mod errors { /// ERROR_SUCCESS pub const SUCCESS: u32 = 0; /// ERROR_DEVICE_NOT_CONNECTED pub const DEVICE_NOT_CONNECTED: u32 = 0x48F; /// ERROR_EMPTY (used by XamInputGetKeystroke when no event queued) pub const EMPTY: u32 = 0x10D2; } /// Sub-types that games query via `X_INPUT_CAPABILITIES::SubType`. pub mod subtype { pub const GAMEPAD: u8 = 0x01; } impl InputSystem { pub fn new() -> Self { Self { gamepad: GamepadState::default(), } } } impl Default for InputSystem { fn default() -> Self { Self::new() } } /// Serialize a [`GamepadState`] into the 16-byte big-endian `X_INPUT_STATE` /// struct at `out_ptr` in guest memory. /// /// Layout (matches `xenia-canary/src/xenia/hid/input.h`): /// /// | Offset | Size | Field | Endianness | /// |--------|------|----------------|------------| /// | 0x00 | 4 | packet_number | BE u32 | /// | 0x04 | 2 | buttons | BE u16 | /// | 0x06 | 1 | left_trigger | u8 | /// | 0x07 | 1 | right_trigger | u8 | /// | 0x08 | 2 | thumb_lx | BE i16 | /// | 0x0A | 2 | thumb_ly | BE i16 | /// | 0x0C | 2 | thumb_rx | BE i16 | /// | 0x0E | 2 | thumb_ry | BE i16 | pub fn write_input_state( mem: &GuestMemory, out_ptr: u32, packet_number: u32, state: &GamepadState, ) { if out_ptr == 0 { return; } mem.write_u32(out_ptr, packet_number); mem.write_u16(out_ptr + 0x04, state.buttons); mem.write_u8(out_ptr + 0x06, state.left_trigger); mem.write_u8(out_ptr + 0x07, state.right_trigger); mem.write_u16(out_ptr + 0x08, state.left_stick_x as u16); mem.write_u16(out_ptr + 0x0A, state.left_stick_y as u16); mem.write_u16(out_ptr + 0x0C, state.right_stick_x as u16); mem.write_u16(out_ptr + 0x0E, state.right_stick_y as u16); } /// Serialize an `X_INPUT_CAPABILITIES` block for a standard wired controller. /// /// Layout (20 bytes, matches `xenia-canary`): /// /// | Offset | Size | Field | /// |--------|------|------------------| /// | 0x00 | 1 | Type (gamepad=1) | /// | 0x01 | 1 | SubType | /// | 0x02 | 2 | Flags | /// | 0x04 | 12 | Gamepad state | /// | 0x10 | 2 | Vibration.left | /// | 0x12 | 2 | Vibration.right | pub fn write_input_capabilities(mem: &GuestMemory, out_ptr: u32) { if out_ptr == 0 { return; } // Type = DEVTYPE_GAMEPAD (1), SubType = STANDARD (1), Flags = 0 mem.write_u8(out_ptr, 1); mem.write_u8(out_ptr + 0x01, subtype::GAMEPAD); mem.write_u16(out_ptr + 0x02, 0); // Gamepad capabilities: buttons = all standard bits, triggers+sticks = full range. // Games typically AND the gamepad mask to decide which controls exist; advertising // everything is the safe default. mem.write_u16(out_ptr + 0x04, 0xF3FF); // buttons: all except GUIDE mem.write_u8(out_ptr + 0x06, 0xFF); // left_trigger range mem.write_u8(out_ptr + 0x07, 0xFF); // right_trigger range mem.write_u16(out_ptr + 0x08, 0xFFFFu16); // lx mem.write_u16(out_ptr + 0x0A, 0xFFFFu16); // ly mem.write_u16(out_ptr + 0x0C, 0xFFFFu16); // rx mem.write_u16(out_ptr + 0x0E, 0xFFFFu16); // ry // Vibration: both motors full range mem.write_u16(out_ptr + 0x10, 0xFFFFu16); mem.write_u16(out_ptr + 0x12, 0xFFFFu16); } #[cfg(test)] mod tests { use super::*; use xenia_memory::page_table::MemoryProtect; #[test] fn write_input_state_layout_is_big_endian() { let mut mem = GuestMemory::new().unwrap(); let rw = MemoryProtect::READ | MemoryProtect::WRITE; mem.alloc(0x4000_0000, 0x1000, rw).unwrap(); let state = GamepadState { buttons: buttons::A | buttons::START, // 0x1010 left_trigger: 0x80, right_trigger: 0x40, left_stick_x: 0x0102, left_stick_y: -1, right_stick_x: 0x0304, right_stick_y: 0x0506, }; write_input_state(&mut mem, 0x4000_0000, 0xDEAD_BEEF, &state); // packet_number BE assert_eq!(mem.read_u8(0x4000_0000), 0xDE); assert_eq!(mem.read_u8(0x4000_0001), 0xAD); assert_eq!(mem.read_u8(0x4000_0002), 0xBE); assert_eq!(mem.read_u8(0x4000_0003), 0xEF); // buttons BE (0x1010 → high byte 0x10 first) assert_eq!(mem.read_u8(0x4000_0004), 0x10); assert_eq!(mem.read_u8(0x4000_0005), 0x10); // triggers assert_eq!(mem.read_u8(0x4000_0006), 0x80); assert_eq!(mem.read_u8(0x4000_0007), 0x40); // thumb_lx BE assert_eq!(mem.read_u8(0x4000_0008), 0x01); assert_eq!(mem.read_u8(0x4000_0009), 0x02); // thumb_ly = -1 → 0xFFFF assert_eq!(mem.read_u8(0x4000_000A), 0xFF); assert_eq!(mem.read_u8(0x4000_000B), 0xFF); // thumb_rx BE assert_eq!(mem.read_u8(0x4000_000C), 0x03); assert_eq!(mem.read_u8(0x4000_000D), 0x04); assert_eq!(mem.read_u8(0x4000_000E), 0x05); assert_eq!(mem.read_u8(0x4000_000F), 0x06); } }