/// /// The majority of the logic in this file is derived from HOJA-LIB-RP2040 /// https://github.com/HandHeldLegend/HOJA-LIB-RP2040 /// /// The original author gave their consent for this to be included in the NaxGCC firmware, /// and for this file to be distributed under the GPLv3, see https://git.naxdy.org/NaxdyOrg/NaxGCC-FW/pulls/26 /// use core::ops::{Deref, DerefMut}; use defmt::{info, trace, Format}; use embassy_rp::clocks::RoscRng; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal}; use embassy_time::Instant; use embassy_usb::{ class::hid::{ReportId, RequestHandler}, control::OutResponse, }; use packed_struct::{derive::PackedStruct, PackedStruct}; use rand::RngCore; use crate::{input::ControllerState, usb_comms::HidReportBuilder}; const SW_INFO_SET_MAC: u8 = 0x01; const SW_CMD_SET_INPUT_MODE: u8 = 0x03; const SW_CMD_GET_DEVINFO: u8 = 0x02; const SW_CMD_SET_SHIPMODE: u8 = 0x08; const SW_CMD_GET_SPI: u8 = 0x10; const SW_CMD_SET_PAIRING: u8 = 0x01; const SW_CMD_GET_TRIGGERET: u8 = 0x04; const ACK_GET_DEVINFO: u8 = 0x82; const ACK_GET_SPI: u8 = 0x90; const ACK_SET_PAIRING: u8 = 0x81; const ACK_GET_TRIGERET: u8 = 0x83; const ACK_GENERIC: u8 = 0x80; const RM_SEND_STATE: u8 = 0x30; const PRO_CONTROLLER_STRING: [u8; 24] = [ 0x00, 0x25, 0x08, 0x50, 0x72, 0x6F, 0x20, 0x43, 0x6F, 0x6E, 0x74, 0x72, 0x6F, 0x6C, 0x6C, 0x65, 0x72, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, ]; #[derive(Debug, Format, Clone, Copy)] struct ProconRequestInfo { report_id: ProconRequestId, response_id: ProconResponseId, command_id: u8, raw_data: [u8; 64], } #[derive(Debug, Format, Clone, Copy)] enum ProconRequestId { GetInfo = 0x80, Command = 0x01, Rumble = 0x10, } #[derive(Debug, Format, Clone, Copy)] enum ProconResponseId { GetInfo = 0x81, GetState = 0x30, Command = 0x21, } static SIGNAL_PROCON_REQUEST: Signal<CriticalSectionRawMutex, ProconRequestInfo> = Signal::new(); #[rustfmt::skip] pub const PROCON_REPORT_DESCRIPTOR: &[u8] = &[ 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 0x15, 0x00, // Logical Minimum (0) 0x09, 0x04, // Usage (Joystick) 0xA1, 0x01, // Collection (Application) 0x85, 0x30, // Report ID (48) 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 0x05, 0x09, // Usage Page (Button) 0x19, 0x01, // Usage Minimum (0x01) 0x29, 0x0A, // Usage Maximum (0x0A) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x75, 0x01, // Report Size (1) 0x95, 0x0A, // Report Count (10) 0x55, 0x00, // Unit Exponent (0) 0x65, 0x00, // Unit (None) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x05, 0x09, // Usage Page (Button) 0x19, 0x0B, // Usage Minimum (0x0B) 0x29, 0x0E, // Usage Maximum (0x0E) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x75, 0x01, // Report Size (1) 0x95, 0x04, // Report Count (4) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x75, 0x01, // Report Size (1) 0x95, 0x02, // Report Count (2) 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x0B, 0x01, 0x00, 0x01, 0x00, // Usage (0x010001) 0xA1, 0x00, // Collection (Physical) 0x0B, 0x30, 0x00, 0x01, 0x00, // Usage (0x010030) 0x0B, 0x31, 0x00, 0x01, 0x00, // Usage (0x010031) 0x0B, 0x32, 0x00, 0x01, 0x00, // Usage (0x010032) 0x0B, 0x35, 0x00, 0x01, 0x00, // Usage (0x010035) 0x15, 0x00, // Logical Minimum (0) 0x27, 0xFF, 0xFF, 0x00, 0x00, // Logical Maximum (65534) 0x75, 0x10, // Report Size (16) 0x95, 0x04, // Report Count (4) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0xC0, // End Collection 0x0B, 0x39, 0x00, 0x01, 0x00, // Usage (0x010039) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x07, // Logical Maximum (7) 0x35, 0x00, // Physical Minimum (0) 0x46, 0x3B, 0x01, // Physical Maximum (315) 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) 0x75, 0x04, // Report Size (4) 0x95, 0x01, // Report Count (1) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x05, 0x09, // Usage Page (Button) 0x19, 0x0F, // Usage Minimum (0x0F) 0x29, 0x12, // Usage Maximum (0x12) 0x15, 0x00, // Logical Minimum (0) 0x25, 0x01, // Logical Maximum (1) 0x75, 0x01, // Report Size (1) 0x95, 0x04, // Report Count (4) 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x75, 0x08, // Report Size (8) 0x95, 0x34, // Report Count (52) 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) 0x85, 0x21, // Report ID (33) 0x09, 0x01, // Usage (0x01) 0x75, 0x08, // Report Size (8) 0x95, 0x3F, // Report Count (63) 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x85, 0x81, // Report ID (-127) 0x09, 0x02, // Usage (0x02) 0x75, 0x08, // Report Size (8) 0x95, 0x3F, // Report Count (63) 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) 0x85, 0x01, // Report ID (1) 0x09, 0x03, // Usage (0x03) 0x75, 0x08, // Report Size (8) 0x95, 0x3F, // Report Count (63) 0x91, 0x83, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile) 0x85, 0x10, // Report ID (16) 0x09, 0x04, // Usage (0x04) 0x75, 0x08, // Report Size (8) 0x95, 0x3F, // Report Count (63) 0x91, 0x83, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile) 0x85, 0x80, // Report ID (-128) 0x09, 0x05, // Usage (0x05) 0x75, 0x08, // Report Size (8) 0x95, 0x3F, // Report Count (63) 0x91, 0x83, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile) 0x85, 0x82, // Report ID (-126) 0x09, 0x06, // Usage (0x06) 0x75, 0x08, // Report Size (8) 0x95, 0x3F, // Report Count (63) 0x91, 0x83, // Output (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Volatile) 0xC0, // End Collection // 203 bytes ]; #[derive(Clone, Copy, Debug, Format)] struct ProconByteReport([u8; 64]); impl Deref for ProconByteReport { type Target = [u8; 64]; fn deref(&self) -> &Self::Target { &self.0 } } impl DerefMut for ProconByteReport { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } impl ProconByteReport { fn set_report_id(&mut self, id: u8) { self[0] = id; } fn set_battery_status(&mut self) { self[2] = BatteryStatus::default() .pack() .expect("Failed to pack fake procon battery status")[0]; } fn set_timer(&mut self) { self[1] = Instant::now().as_millis() as u8; } fn set_ack(&mut self, ack: u8) { self[13] = ack; } fn set_subcommand(&mut self, cmd: u8) { self[14] = cmd; } fn set_devinfo(&mut self) { self[15] = 0x04; // NS Firmware primary (4.x) self[16] = 0x33; // NS Firmware secondary (x.21) self[17] = 0x03; // Controller ID primary (Pro Controller) self[18] = 0x02; // Controller ID secondary self[25] = 0x01; self[26] = 0x02; } fn sw_spi_readfromaddress( &mut self, offset_address: u8, address: u8, length: u8, switch_host_address: &[u8], ) { let read_info = [address, offset_address, 0x00, 0x00, length]; self[15..(15 + read_info.len())].copy_from_slice(&read_info); let mut output_spi_data = [0u8; 30]; output_spi_data.iter_mut().enumerate().for_each(|(i, e)| { *e = sw_spi_getaddressdata(offset_address, address + i as u8, switch_host_address) }); self[20..(20 + length as usize)].copy_from_slice(&output_spi_data[..(length as usize)]); } fn set_trigerret(&mut self, time_10_ms: u16) { let [upper_ms, lower_ms] = time_10_ms.to_be_bytes(); for i in 0..14 { self[15 + i] = upper_ms; self[16 + i] = lower_ms; } } } fn sw_spi_getaddressdata(offset_address: u8, address: u8, switch_host_address: &[u8]) -> u8 { match offset_address { 0x00 => 0x00, 0x20..=0x40 => match address { 0x26 | 0x00 => 0x95, // Size of pairing data 0x27 | 0x01 => 0x22, // Checksum 0x28 | 0x29 | 0x02 | 0x03 => 0x00, // Host BT address (Big-endian) 0x2A..=0x2F => switch_host_address[(address - 0x2a) as usize], 0x04..=0x09 => switch_host_address[(address - 4) as usize], // Bluetooth LTK (Little-endian) NOT IMPLEMENTED YET 0x30..=0x3F => 0x00, 0x0A..=0x19 => 0x00, // Host capability 0x68 is Nintendo Switch. 0x08 is PC 0x4A | 0x24 => 0x68, 0x4B | 0x25 => 0, _ => 0x00, }, 0x50 => 0x00, 0x60 => match address { 0x00..0x0f => 0xff, 0x12 => 0x03, 0x13 => 0x02, 0x1b => 0x01, 0x20 => 35, 0x21 => 0, 0x22 => 185, 0x23 => 255, 0x24 => 26, 0x25 => 1, 0x26 => 0, 0x27 => 64, 0x28 => 0, 0x29 => 64, 0x2A => 0, 0x2B => 64, 0x2C => 1, 0x2D => 0, 0x2E => 1, 0x2F => 0, 0x30 => 1, 0x31 => 0, 0x32 => 0x3B, 0x33 => 0x34, 0x34 => 0x3B, 0x35 => 0x34, 0x36 => 0x3B, 0x37 => 0x34, 0x3d..=0x45 => mk_switch_analog_calibration_data()[(address - 0x3d) as usize], 0x46..=0x4e => mk_switch_analog_calibration_data()[(address - 0x3d) as usize], 0x4F => 0xFF, 0x50 => 26, 0x51 => 26, 0x52 => 26, 0x53..=0x55 => 94, 0x56 => 255, 0x57 => 255, 0x58 => 255, 0x59..=0x5B => 255, 0x5C => 0x01, 0x80 => 80, 0x81 => 253, 0x82 => 0, 0x83 => 0, 0x84 => 198, 0x85 => 15, 0x98 | 0x86 => 15, 0x99 | 0x87 => 48, 0x9A | 0x88 => 97, 0x9B | 0x89 => 174, 0x9C | 0x8A => 144, 0x9D | 0x8B => 217, 0x9E | 0x8C => 212, 0x9F | 0x8D => 20, 0xA0 | 0x8E => 84, 0xA1 | 0x8F => 65, 0xA2 | 0x90 => 21, 0xA3 | 0x91 => 84, 0xA4 | 0x92 => 199, 0xA5 | 0x93 => 121, 0xA6 | 0x94 => 156, 0xA7 | 0x95 => 51, 0xA8 | 0x96 => 54, 0xA9 | 0x97 => 99, _ => 0, }, 0x80 => match address { 0x10..=0x1a => 0xff, 0x1b..=0x25 => 0xff, 0x26..=0x3f => 0xff, _ => 0xff, }, _ => 0xff, } } fn mk_switch_analog_calibration_data() -> [u8; 18] { fn switch_analog_encode(in_lower: u16, in_upper: u16) -> [u8; 3] { let mut out = [0u8; 3]; [out[0], out[1]] = in_lower.to_le_bytes(); out[1] |= ((in_upper & 0xf) << 4) as u8; out[2] = ((in_upper & 0xff0) >> 4) as u8; out } const MIN: u16 = 128 << 4; const MAXX: u16 = 128 << 4; const CENTER: u16 = 128 << 4; let mut out = [0u8; 18]; out[0..3].copy_from_slice(&switch_analog_encode(MAXX, MAXX)); out[3..6].copy_from_slice(&switch_analog_encode(CENTER, CENTER)); out[6..9].copy_from_slice(&switch_analog_encode(MIN, MIN)); out[9..12].copy_from_slice(&switch_analog_encode(CENTER, CENTER)); out[12..15].copy_from_slice(&switch_analog_encode(MIN, MIN)); out[15..18].copy_from_slice(&switch_analog_encode(MAXX, MAXX)); info!("Returning switch data: {:x}", out); out } #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)] #[packed_struct(bit_numbering = "lsb0", size_bytes = "1")] pub struct ProconButtonsRight { #[packed_field(bits = "0")] pub button_y: bool, #[packed_field(bits = "1")] pub button_x: bool, #[packed_field(bits = "2")] pub button_b: bool, #[packed_field(bits = "3")] pub button_a: bool, #[packed_field(bits = "4")] pub trigger_r_sr: bool, #[packed_field(bits = "5")] pub trigger_r_sl: bool, #[packed_field(bits = "6")] pub trigger_r: bool, #[packed_field(bits = "7")] pub trigger_zr: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)] #[packed_struct(bit_numbering = "lsb0", size_bytes = "1")] pub struct ProconButtonsShared { #[packed_field(bits = "0")] pub button_minus: bool, #[packed_field(bits = "1")] pub button_plus: bool, #[packed_field(bits = "2")] pub button_sb_right: bool, #[packed_field(bits = "3")] pub button_sb_left: bool, #[packed_field(bits = "4")] pub button_home: bool, #[packed_field(bits = "5")] pub button_capture: bool, #[packed_field(bits = "6")] pub none: bool, #[packed_field(bits = "7")] pub change_grip_active: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)] #[packed_struct(bit_numbering = "lsb0", size_bytes = "1")] pub struct ProconButtonsLeft { #[packed_field(bits = "0")] pub dpad_down: bool, #[packed_field(bits = "1")] pub dpad_up: bool, #[packed_field(bits = "2")] pub dpad_right: bool, #[packed_field(bits = "3")] pub dped_left: bool, #[packed_field(bits = "4")] pub trigger_l_sr: bool, #[packed_field(bits = "5")] pub trigger_l_sl: bool, #[packed_field(bits = "6")] pub trigger_l: bool, #[packed_field(bits = "7")] pub trigger_zl: bool, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)] #[packed_struct(bit_numbering = "msb0", endian = "lsb", size_bytes = "9")] pub struct ProconState { #[packed_field(bits = "0..=7")] pub buttons_right: ProconButtonsRight, #[packed_field(bits = "8..=15")] pub buttons_shared: ProconButtonsShared, #[packed_field(bits = "16..=23")] pub buttons_left: ProconButtonsLeft, #[packed_field(bits = "24..=39")] pub lstick_x: u16, #[packed_field(bits = "40..=47")] pub lstick_y: u8, #[packed_field(bits = "48..=63")] pub rstick_x: u16, #[packed_field(bits = "64..=71")] pub rstick_y: u8, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PackedStruct, Format)] #[packed_struct(bit_numbering = "lsb0", endian = "msb", size_bytes = "1")] struct BatteryStatus { #[packed_field(bits = "0..=3")] connection: u8, #[packed_field(bits = "4..=7")] battery_level: u8, } impl Default for BatteryStatus { fn default() -> Self { Self { connection: 1, battery_level: 8, } } } impl From<&ControllerState> for ProconState { fn from(value: &ControllerState) -> Self { Self { buttons_left: ProconButtonsLeft { dpad_down: value.dpad_down, dpad_right: value.dpad_right, dpad_up: value.dpad_up, dped_left: value.dpad_left, trigger_l: value.trigger_zl, trigger_zl: value.trigger_l, ..Default::default() }, buttons_right: ProconButtonsRight { button_a: value.button_a, button_b: value.button_b, button_x: value.button_x, button_y: value.button_y, trigger_r: value.trigger_zr, trigger_zr: value.trigger_r, ..Default::default() }, buttons_shared: ProconButtonsShared { button_plus: value.button_start && !value.trigger_zr, button_home: value.button_start && value.trigger_zr, ..Default::default() }, lstick_x: value.stick_state.ax as u16 * 16, lstick_y: value.stick_state.ay, rstick_x: value.stick_state.cx as u16 * 16, rstick_y: value.stick_state.cy, } } } pub struct ProconReportBuilder { switch_reporting_mode: u8, switch_mac_address: [u8; 6], switch_host_address: [u8; 6], switch_ltk: [u8; 16], } impl Default for ProconReportBuilder { fn default() -> Self { Self { switch_reporting_mode: 0, switch_mac_address: gen_switch_mac_address(), switch_host_address: [0u8; 6], switch_ltk: gen_switch_ltk(), } } } fn gen_switch_mac_address() -> [u8; 6] { let mut mac_addr = [0u8; 6]; mac_addr.iter_mut().for_each(|e| { *e = RoscRng.next_u64() as u8; }); mac_addr[0] &= 0xfe; mac_addr[5] = 0x9b; mac_addr } fn gen_switch_ltk() -> [u8; 16] { let mut switch_ltk = [0u8; 16]; switch_ltk.iter_mut().for_each(|e| { *e = RoscRng.next_u64() as u8; }); switch_ltk } impl ProconReportBuilder { fn get_info_report(&self, current_report_info: &ProconRequestInfo) -> [u8; 64] { let mut report = ProconByteReport([0u8; 64]); report.set_report_id(ProconResponseId::GetInfo as u8); if current_report_info.command_id == SW_INFO_SET_MAC { report[1] = SW_INFO_SET_MAC; report[3] = 0x03; self.switch_mac_address .iter() .rev() .enumerate() .for_each(|(i, e)| { report[4 + i] = *e; }); } else { report[1] = current_report_info.command_id; } *report } fn get_state_report(&self, state: &ProconState) -> [u8; 64] { static mut UNKNOWN: u8 = 0xA; unsafe { UNKNOWN = match UNKNOWN { 0xA => 0xB, 0xB => 0xC, _ => 0xA, }; } let mut report = ProconByteReport([0u8; 64]); let data = state .pack() .expect("Failed to pack pro controller input data"); report.set_report_id(ProconResponseId::GetState as u8); report.set_timer(); report.set_battery_status(); report[3..=11].copy_from_slice(&data); report[12] = unsafe { UNKNOWN }; *report } fn get_command_report(&mut self, current_report_info: &ProconRequestInfo) -> [u8; 64] { let mut report = ProconByteReport([0u8; 64]); report.set_report_id(ProconResponseId::Command as u8); report.set_timer(); report.set_battery_status(); report.set_subcommand(current_report_info.command_id); match current_report_info.command_id { SW_CMD_SET_INPUT_MODE => { report.set_ack(ACK_GENERIC); self.switch_reporting_mode = current_report_info.raw_data[11]; info!( "Switch reporting mode is now {:x}", self.switch_reporting_mode ); } SW_CMD_GET_DEVINFO => { report.set_ack(ACK_GET_DEVINFO); report.set_devinfo(); } SW_CMD_GET_SPI => { report.set_ack(ACK_GET_SPI); report.sw_spi_readfromaddress( current_report_info.raw_data[12], current_report_info.raw_data[11], current_report_info.raw_data[15], &self.switch_host_address, ); } SW_CMD_SET_SHIPMODE => { report.set_ack(ACK_GENERIC); } SW_CMD_SET_PAIRING => { report.set_ack(ACK_SET_PAIRING); self.perform_pairing(&mut report, current_report_info); } SW_CMD_GET_TRIGGERET => { report.set_ack(ACK_GET_TRIGERET); report.set_trigerret(100); } _ => { report.set_ack(ACK_GENERIC); } } *report } fn perform_pairing( &mut self, report: &mut ProconByteReport, current_report_info: &ProconRequestInfo, ) { let pairing_phase = current_report_info.raw_data[11]; let host_address = ¤t_report_info.raw_data[12..]; match pairing_phase { 1 => { self.switch_host_address .iter_mut() .enumerate() .for_each(|(i, e)| *e = host_address[5 - i]); report[16..=21].copy_from_slice(&self.switch_mac_address); report[22..(22 + PRO_CONTROLLER_STRING.len())] .copy_from_slice(&PRO_CONTROLLER_STRING); } 2 => { report[15] = 2; report[16..(16 + self.switch_ltk.len())].copy_from_slice(&self.switch_ltk); } 3 => { report[15] = 3; } _ => {} } } } impl HidReportBuilder<64> for ProconReportBuilder { async fn get_hid_report(&mut self, state: &ControllerState) -> [u8; 64] { let current_report_info = if self.switch_reporting_mode == RM_SEND_STATE { SIGNAL_PROCON_REQUEST.try_take() } else { Some(SIGNAL_PROCON_REQUEST.wait().await) }; if let Some(current_report_info) = current_report_info { match current_report_info.report_id { ProconRequestId::GetInfo => self.get_info_report(¤t_report_info), ProconRequestId::Command => self.get_command_report(¤t_report_info), ProconRequestId::Rumble => self.get_state_report(&ProconState::from(state)), } } else { self.get_state_report(&ProconState::from(state)) } } } pub struct ProconRequestHandler; impl RequestHandler for ProconRequestHandler { fn get_report(&mut self, id: ReportId, _buf: &mut [u8]) -> Option<usize> { info!("Get report for {:?}", id); None } fn set_report(&mut self, id: ReportId, data: &[u8]) -> OutResponse { trace!("Set report for {:?}: {:x}", id, data); let mut buf = [0u8; 64]; let len_to_copy = buf.len().min(data.len()); buf[..len_to_copy].copy_from_slice(&data[..len_to_copy]); if let ReportId::Out(id) = id { if id == ProconRequestId::GetInfo as u8 { SIGNAL_PROCON_REQUEST.signal(ProconRequestInfo { command_id: buf[1], report_id: ProconRequestId::GetInfo, response_id: ProconResponseId::GetInfo, raw_data: buf, }); } else if id == ProconRequestId::Command as u8 { SIGNAL_PROCON_REQUEST.signal(ProconRequestInfo { command_id: buf[10], report_id: ProconRequestId::Command, response_id: ProconResponseId::Command, raw_data: buf, }); } else if id == ProconRequestId::Rumble as u8 { // TODO: handle rumble } } OutResponse::Accepted } fn set_idle_ms(&mut self, id: Option<ReportId>, dur: u32) { info!("Set idle rate for {:?} to {:?}", id, dur); } fn get_idle_ms(&mut self, id: Option<ReportId>) -> Option<u32> { info!("Get idle rate for {:?}", id); None } }