Initial commit

This commit is contained in:
John 2025-08-02 02:27:58 -04:00
commit 8d4238fe3e
5 changed files with 325 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

65
Cargo.lock generated Normal file
View File

@ -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"

7
Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "procon2"
version = "0.1.0"
edition = "2024"
[dependencies]
rusb = "0.9.4"

192
src/lib.rs Normal file
View File

@ -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<Ctx>,
}
impl<'t, Ctx: rusb::UsbContext> Controller<'t, Ctx> {
pub fn new(device: &'t DeviceHandle<Ctx>) -> Self {
Self { device }
}
pub fn init(&self) -> rusb::Result<usize> {
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<usize> {
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
];

60
src/main.rs Normal file
View File

@ -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(())
}