Initial commit
This commit is contained in:
commit
8d4238fe3e
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/target
|
65
Cargo.lock
generated
Normal file
65
Cargo.lock
generated
Normal 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
7
Cargo.toml
Normal 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
192
src/lib.rs
Normal 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
60
src/main.rs
Normal 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(())
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user