diff --git a/.gitignore b/.gitignore index ea8c4bf..9e3ca59 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ /target + +# Firmware dump files +*.bin diff --git a/src/lib.rs b/src/lib.rs index b4f3f4d..8ad106c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,8 +1,7 @@ //! Library for initializing a pro controller //! -//! Code stolen from https://handheldlegend.github.io/procon2tool/ -#![allow(non_upper_case_globals)] -#![allow(dead_code)] +//! Code stolen from [HandHeldLegend procon2tool](https://handheldlegend.github.io/procon2tool/) +#![allow(non_upper_case_globals, clippy::identity_op, dead_code)] use rusb::DeviceHandle; use std::time::Duration; @@ -21,49 +20,126 @@ pub mod descriptor { pub struct Controller<'t, Ctx: rusb::UsbContext> { device: &'t DeviceHandle, + verbose: bool, } impl<'t, Ctx: rusb::UsbContext> Controller<'t, Ctx> { pub fn new(device: &'t DeviceHandle) -> Self { - Self { device } + Self { + device, + verbose: false, + } + } + + pub fn verbose(self, verbose: bool) -> Self { + Self { verbose, ..self } + } + + fn dbg_write(&self, data: &[u8], timeout: Duration) -> rusb::Result { + let mut buf = [0; 128]; + #[allow(unused_assignments)] // Not actually unused, the linter is dumb + let mut len = 0; + let out = self.write(data, timeout)?; + + len = self.read(&mut buf, Duration::from_millis(100)).unwrap_or(0); + + if self.verbose { + println!("OUT: {data:02x?}\n => {:02x?}", &buf[..len]); + } + + Ok(out) + } + + fn write(&self, data: &[u8], timeout: Duration) -> rusb::Result { + self.device.write_bulk(0x02, data, timeout) + } + + fn read(&self, buf: &mut [u8], timeout: Duration) -> rusb::Result { + self.device.read_bulk(0x82, buf, timeout) } 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)?; + let timeout = Duration::from_secs(1); + + if self.verbose { + println!("Initializing:"); + } + + out += self.dbg_write(INIT_COMMAND_0x03, timeout)?; + out += self.dbg_write(UNKNOWN_COMMAND_0x07, timeout)?; + out += self.dbg_write(UNKNOWN_COMMAND_0x16, timeout)?; + out += self.dbg_write(REQUEST_CONTROLLER_MAC, timeout)?; + out += self.dbg_write(LTK_REQUEST, timeout)?; + out += self.dbg_write(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)?; + out += self.dbg_write(IMU_COMMAND_0x02, timeout)?; + out += self.dbg_write(OUT_UNKNOWN_COMMAND_0x11, timeout)?; + out += self.dbg_write(UNKNOWN_COMMAND_0x0A, timeout)?; + out += self.dbg_write(IMU_COMMAND_0x04, timeout)?; + out += self.dbg_write(ENABLE_HAPTICS, timeout)?; + out += self.dbg_write(OUT_UNKNOWN_COMMAND_0x10, timeout)?; + out += self.dbg_write(OUT_UNKNOWN_COMMAND_0x01, timeout)?; + out += self.dbg_write(OUT_UNKNOWN_COMMAND_0x03, timeout)?; + out += self.dbg_write(OUT_UNKNOWN_COMMAND_0x0A_ALT, timeout)?; Ok(out) } pub fn set_player_led(&self, pattern: u8) -> rusb::Result { - let Self { device } = self; + let timeout = Duration::from_secs(1); + let mut resp = [0; 16]; #[rustfmt::skip] - let buf: [u8; 16] = [ + let cmd: [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)) + let out = self.write(&cmd, timeout)?; + // ACK? [09 01 00 07 00 f8 00 00] + let len = self.read(&mut resp, timeout)?; + if self.verbose { + println!("LED: {cmd:02x?}\n => {:02x?}", &resp[..len]); + } + + Ok(out) + } + + pub fn dump_spi(&self, address: usize) -> rusb::Result<[u8; 64]> { + let Self { device, .. } = self; + let mut buf = [0; 16 + 64]; + let txpoint = 2; + let rxpoint = 130; + let timeout = Duration::from_millis(100); + + #[rustfmt::skip] + let command = [ + // Command + 0x02, 0x91, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, + 0x40, // Length (64 bytes) + 0x7e, // Read command + 0x00, 0x00, + // Address (little endian) + (address >> 0) as u8, // Address field 3 + (address >> 8) as u8, // Address field 2 + (address >> 16) as u8, // Address field 1 + (address >> 24) as u8, // Address field 0 + ]; + device.write_bulk(txpoint, &command, timeout)?; + device.read_bulk(rxpoint, &mut buf, timeout)?; + + // debug-print the header + if self.verbose { + println!("HEAD: {:02x?}", &buf[..16]); + } + + let mut data = [0; 64]; + data.copy_from_slice(&buf[16..]); + + Ok(data) } } @@ -77,6 +153,7 @@ const INIT_COMMAND_0x03: &[u8] = &[ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // Console MAC Address (Little Endian) ]; +/// Probably an ACK of some kind #[rustfmt::skip] const UNKNOWN_COMMAND_0x07: &[u8] = &[ 0x07, 0x91, 0x00, 0x01, diff --git a/src/main.rs b/src/main.rs index 77d94e4..84671cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,9 @@ -use std::{thread::sleep, time::Duration}; +use std::{error::Error, fmt::Display, io::Write, path::Path, thread::sleep, time::Duration}; use procon2::{ Controller, descriptor::{VID, pid}, }; -use rusb::Result; #[rustfmt::skip] const PLAYER: [u8; 16] = [ @@ -26,7 +25,17 @@ const PLAYER: [u8; 16] = [ 0b1010, ]; -fn main() -> Result<()> { +fn main() -> Result<(), Box> { + let (mut dump, mut verbose) = (false, false); + + for arg in std::env::args() { + match arg.as_str() { + "dump" => dump = true, + "verbose" => verbose = true, + _ => {} + } + } + let mut devices = vec![]; for device in rusb::devices()?.iter() { @@ -36,25 +45,75 @@ fn main() -> Result<()> { println!("Found player {}: {device:?}!", devices.len() + 1); let device = device.open()?; let controller = Controller::new(&device); - - controller.init()?; + controller.verbose(verbose).init()?; + device.claim_interface(1)?; devices.push(device); } } + if dump { + for (player, device) in devices.iter().enumerate() { + let controller = Controller::new(device).verbose(verbose); + let descriptor = device.device().device_descriptor()?; + let name = if let Some(nid) = descriptor.product_string_index() { + device.read_string_descriptor_ascii(nid)? + } else { + String::from("procon") + }; + let path = format!( + "{name} {:04x}_{:04x}_{}-{player:x}.bin", + descriptor.vendor_id(), + descriptor.product_id(), + descriptor.device_version() + ); + procon_dump(controller, path)?; + } + return Ok(()); + } + // 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)?; + Controller::new(device) + .verbose(verbose) + .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])?; + Controller::new(device) + .verbose(verbose) + .set_player_led(PLAYER[(count + 1) & 0xf])?; } Ok(()) } + +fn procon_dump( + controller: Controller<'_, Ctx>, + path: impl AsRef + Display, +) -> Result<(), Box> { + let mut file = std::fs::File::create(path.as_ref()).unwrap(); + print!("Dumping firmware"); + std::io::stdout().flush()?; + + for addr in (0..0x200000).step_by(64) { + let data = controller.dump_spi(addr)?; + file.write_all(&data)?; + + // progress bar + if addr % 0x8000 == 0 { + controller.set_player_led(PLAYER[7 - (addr >> 15 & 7)])?; + print!("."); + std::io::stdout().flush()?; + sleep(Duration::from_millis(0)); + } + } + + controller.set_player_led(0)?; + println!("\nDumped to file {path}"); + Ok(()) +}