760 lines
24 KiB
Rust
760 lines
24 KiB
Rust
///
|
|
/// 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
|
|
}
|
|
}
|