lib: Add shmancy firmware dumping and verbose printing
This commit is contained in:
parent
8d4238fe3e
commit
8b56a1bc5d
3
.gitignore
vendored
3
.gitignore
vendored
@ -1 +1,4 @@
|
||||
/target
|
||||
|
||||
# Firmware dump files
|
||||
*.bin
|
||||
|
127
src/lib.rs
127
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<Ctx>,
|
||||
verbose: bool,
|
||||
}
|
||||
|
||||
impl<'t, Ctx: rusb::UsbContext> Controller<'t, Ctx> {
|
||||
pub fn new(device: &'t DeviceHandle<Ctx>) -> 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<usize> {
|
||||
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<usize> {
|
||||
self.device.write_bulk(0x02, data, timeout)
|
||||
}
|
||||
|
||||
fn read(&self, buf: &mut [u8], timeout: Duration) -> rusb::Result<usize> {
|
||||
self.device.read_bulk(0x82, buf, timeout)
|
||||
}
|
||||
|
||||
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)?;
|
||||
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<usize> {
|
||||
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,
|
||||
|
73
src/main.rs
73
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<dyn Error>> {
|
||||
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<Ctx: rusb::UsbContext>(
|
||||
controller: Controller<'_, Ctx>,
|
||||
path: impl AsRef<Path> + Display,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
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(())
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user