commit 8d4238fe3e48cc340bca7032cd06ce691dc63daf Author: John Date: Sat Aug 2 02:27:58 2025 -0400 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..e91aa0c --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,65 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cc" +version = "1.2.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3a42d84bb6b69d3a8b3eaacf0d88f179e1929695e1ad012b6cf64d9caaa5fd2" +dependencies = [ + "shlex", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + +[[package]] +name = "libusb1-sys" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da050ade7ac4ff1ba5379af847a10a10a8e284181e060105bf8d86960ce9ce0f" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "procon2" +version = "0.1.0" +dependencies = [ + "rusb", +] + +[[package]] +name = "rusb" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab9f9ff05b63a786553a4c02943b74b34a988448671001e9a27e2f0565cc05a4" +dependencies = [ + "libc", + "libusb1-sys", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..0263a3d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "procon2" +version = "0.1.0" +edition = "2024" + +[dependencies] +rusb = "0.9.4" diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..b4f3f4d --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,192 @@ +//! Library for initializing a pro controller +//! +//! Code stolen from https://handheldlegend.github.io/procon2tool/ +#![allow(non_upper_case_globals)] +#![allow(dead_code)] + +use rusb::DeviceHandle; +use std::time::Duration; + +pub mod descriptor { + pub const VID: u16 = 0x057e; + + pub mod pid { + pub const JOYCON2_R: u16 = 0x2066; + pub const JOYCON2_L: u16 = 0x2067; + pub const PROCON2: u16 = 0x2069; + pub const GCNSO: u16 = 0x2073; + pub const PIDS: [u16; 4] = [JOYCON2_L, JOYCON2_R, PROCON2, GCNSO]; + } +} + +pub struct Controller<'t, Ctx: rusb::UsbContext> { + device: &'t DeviceHandle, +} + +impl<'t, Ctx: rusb::UsbContext> Controller<'t, Ctx> { + pub fn new(device: &'t DeviceHandle) -> Self { + Self { device } + } + + pub fn init(&self) -> rusb::Result { + let Self { device } = self; + let mut out: usize = 0; + let timeout = Duration::from_millis(100); + let endpoint: u8 = 2; + out += device.write_bulk(endpoint, INIT_COMMAND_0x03, timeout)?; + out += device.write_bulk(endpoint, UNKNOWN_COMMAND_0x07, timeout)?; + out += device.write_bulk(endpoint, UNKNOWN_COMMAND_0x16, timeout)?; + out += device.write_bulk(endpoint, REQUEST_CONTROLLER_MAC, timeout)?; + // out += device.write_bulk(endpoint, LTK_REQUEST, timeout)?; + out += device.write_bulk(endpoint, UNKNOWN_COMMAND_0x15_ARG_0x03, timeout)?; + out += self.set_player_led(0)?; + out += device.write_bulk(endpoint, IMU_COMMAND_0x02, timeout)?; + out += device.write_bulk(endpoint, OUT_UNKNOWN_COMMAND_0x11, timeout)?; + out += device.write_bulk(endpoint, UNKNOWN_COMMAND_0x0A, timeout)?; + out += device.write_bulk(endpoint, IMU_COMMAND_0x04, timeout)?; + out += device.write_bulk(endpoint, ENABLE_HAPTICS, timeout)?; + out += device.write_bulk(endpoint, OUT_UNKNOWN_COMMAND_0x10, timeout)?; + out += device.write_bulk(endpoint, OUT_UNKNOWN_COMMAND_0x01, timeout)?; + out += device.write_bulk(endpoint, OUT_UNKNOWN_COMMAND_0x03, timeout)?; + out += device.write_bulk(endpoint, OUT_UNKNOWN_COMMAND_0x0A_ALT, timeout)?; + + Ok(out) + } + + pub fn set_player_led(&self, pattern: u8) -> rusb::Result { + let Self { device } = self; + + #[rustfmt::skip] + let buf: [u8; 16] = [ + 0x09, 0x91, 0x00, 0x07, 0x00, 0x08, 0x00, 0x00, + pattern, // LED bitfield - replace 0x00 with desired LED pattern + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + + device.write_bulk(2, &buf, Duration::from_millis(16)) + } +} + +// --- Here there be raw USB bullshit --- \\ + +/// Initialization Command 0x03 - Starts HID output at 4ms intervals +#[rustfmt::skip] +const INIT_COMMAND_0x03: &[u8] = &[ + 0x03, 0x91, 0x00, 0x0d, 0x00, 0x08, + 0x00, 0x00, 0x01, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // Console MAC Address (Little Endian) +]; + +#[rustfmt::skip] +const UNKNOWN_COMMAND_0x07: &[u8] = &[ + 0x07, 0x91, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, +]; + +#[rustfmt::skip] +const UNKNOWN_COMMAND_0x16: &[u8] = &[ + 0x16, 0x91, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, +]; + +/// LTK Request Command 0x15 Arg 0x02 +#[rustfmt::skip] +const LTK_REQUEST: &[u8] = &[ + 0x15, 0x91, 0x00, 0x02, 0x00, 0x11, + 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // LTK - 16 byte key + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF +]; + +/// Unknown Command 0x15 Arg 0x03 +#[rustfmt::skip] +const UNKNOWN_COMMAND_0x15_ARG_0x03: &[u8] = &[ + 0x15, 0x91, 0x00, 0x03, 0x00, 0x01, + 0x00, 0x00, 0x00 +]; + +/// Unknown Command 0x09 + #[rustfmt::skip] +const UNKNOWN_COMMAND_0x09: &[u8] = &[ + 0x09, 0x91, 0x00, 0x07, 0x00, 0x08, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +]; + +/// IMU Command 0x0C Arg 0x02 - No ACK needed + #[rustfmt::skip] +const IMU_COMMAND_0x02: &[u8] = &[ + 0x0c, 0x91, 0x00, 0x02, 0x00, 0x04, + 0x00, 0x00, 0x27, + 0x00, 0x00, 0x00 +]; +/// Request Controller MAC Command 0x15 Arg 0x01 +#[rustfmt::skip] +const REQUEST_CONTROLLER_MAC: &[u8] = &[ + 0x15, 0x91, 0x00, 0x01, 0x00, 0x0e, + 0x00, 0x00, 0x00, 0x02, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Console MAC Address (Little Endian) + 0xFF, // Byte 14 with bit 0 masked off + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // Remainder of Console MAC Address +]; + +/// OUT Unknown Command 0x11 +#[rustfmt::skip] +const OUT_UNKNOWN_COMMAND_0x11: &[u8] = &[ + 0x11, 0x91, 0x00, 0x03, + 0x00, 0x00, 0x00, 0x00 +]; + +/// Unknown Command 0x0A +#[rustfmt::skip] +const UNKNOWN_COMMAND_0x0A: &[u8] = &[ + 0x0a, 0x91, 0x00, 0x08, 0x00, 0x14, + 0x00, 0x00, 0x01, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0x35, 0x00, 0x46, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 +]; + +/// IMU Command 0x0C Arg 0x04 +#[rustfmt::skip] +const IMU_COMMAND_0x04: &[u8] = &[ + 0x0c, 0x91, 0x00, 0x04, 0x00, 0x04, + 0x00, 0x00, 0x27, + 0x00, 0x00, 0x00 +]; + +/// Enable Haptics (Probably) 0x03 +#[rustfmt::skip] +const ENABLE_HAPTICS: &[u8] = &[ + 0x03, 0x91, 0x00, 0x0a, 0x00, 0x04, + 0x00, 0x00, 0x09, + 0x00, 0x00, 0x00 +]; + +/// OUT Unknown Command 0x10 - No ACK +#[rustfmt::skip] +const OUT_UNKNOWN_COMMAND_0x10: &[u8] = &[ + 0x10, 0x91, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00 +]; + +/// OUT Unknown Command 0x01 +#[rustfmt::skip] +const OUT_UNKNOWN_COMMAND_0x01: &[u8] = &[ + 0x01, 0x91, 0x00, 0x0c, + 0x00, 0x00, 0x00, 0x00 +]; + +/// OUT Unknown Command 0x03 (different from init command) +#[rustfmt::skip] +const OUT_UNKNOWN_COMMAND_0x03: &[u8] = &[ + 0x03, 0x91, 0x00, 0x01, + 0x00, 0x00, 0x00 +]; + +/// OUT Unknown Command 0x0A (different from earlier 0x0A) +#[rustfmt::skip] +const OUT_UNKNOWN_COMMAND_0x0A_ALT: &[u8] = &[ + 0x0a, 0x91, 0x00, 0x02, 0x00, 0x04, + 0x00, 0x00, 0x03, + 0x00, 0x00 +]; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..77d94e4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,60 @@ +use std::{thread::sleep, time::Duration}; + +use procon2::{ + Controller, + descriptor::{VID, pid}, +}; +use rusb::Result; + +#[rustfmt::skip] +const PLAYER: [u8; 16] = [ + 0b0000, + 0b0001, + 0b0011, + 0b0111, + 0b1111, + 0b1110, + 0b1100, + 0b1000, + 0b0010, + 0b0110, + 0b0100, + 0b0101, + 0b1001, + 0b1011, + 0b1101, + 0b1010, +]; + +fn main() -> Result<()> { + let mut devices = vec![]; + + for device in rusb::devices()?.iter() { + let desc = device.device_descriptor()?; + + if desc.vendor_id() == VID && pid::PIDS.contains(&desc.product_id()) { + println!("Found player {}: {device:?}!", devices.len() + 1); + let device = device.open()?; + let controller = Controller::new(&device); + + controller.init()?; + + devices.push(device); + } + } + + // Play a shmancy pattern in-sync on all controllers + for pattern in &PLAYER[0..8] { + for device in devices.iter() { + Controller::new(device).set_player_led(*pattern)?; + } + sleep(Duration::from_millis(100)); + } + + // Set the controller IDs + for (count, device) in devices.iter().enumerate() { + Controller::new(device).set_player_led(PLAYER[(count + 1) & 0xf])?; + } + + Ok(()) +}