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
|
/target
|
||||||
|
|
||||||
|
# Firmware dump files
|
||||||
|
*.bin
|
||||||
|
127
src/lib.rs
127
src/lib.rs
@ -1,8 +1,7 @@
|
|||||||
//! Library for initializing a pro controller
|
//! Library for initializing a pro controller
|
||||||
//!
|
//!
|
||||||
//! Code stolen from https://handheldlegend.github.io/procon2tool/
|
//! Code stolen from [HandHeldLegend procon2tool](https://handheldlegend.github.io/procon2tool/)
|
||||||
#![allow(non_upper_case_globals)]
|
#![allow(non_upper_case_globals, clippy::identity_op, dead_code)]
|
||||||
#![allow(dead_code)]
|
|
||||||
|
|
||||||
use rusb::DeviceHandle;
|
use rusb::DeviceHandle;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
@ -21,49 +20,126 @@ pub mod descriptor {
|
|||||||
|
|
||||||
pub struct Controller<'t, Ctx: rusb::UsbContext> {
|
pub struct Controller<'t, Ctx: rusb::UsbContext> {
|
||||||
device: &'t DeviceHandle<Ctx>,
|
device: &'t DeviceHandle<Ctx>,
|
||||||
|
verbose: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t, Ctx: rusb::UsbContext> Controller<'t, Ctx> {
|
impl<'t, Ctx: rusb::UsbContext> Controller<'t, Ctx> {
|
||||||
pub fn new(device: &'t DeviceHandle<Ctx>) -> Self {
|
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> {
|
pub fn init(&self) -> rusb::Result<usize> {
|
||||||
let Self { device } = self;
|
|
||||||
let mut out: usize = 0;
|
let mut out: usize = 0;
|
||||||
let timeout = Duration::from_millis(100);
|
let timeout = Duration::from_secs(1);
|
||||||
let endpoint: u8 = 2;
|
|
||||||
out += device.write_bulk(endpoint, INIT_COMMAND_0x03, timeout)?;
|
if self.verbose {
|
||||||
out += device.write_bulk(endpoint, UNKNOWN_COMMAND_0x07, timeout)?;
|
println!("Initializing:");
|
||||||
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 += self.dbg_write(INIT_COMMAND_0x03, timeout)?;
|
||||||
out += device.write_bulk(endpoint, UNKNOWN_COMMAND_0x15_ARG_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 += self.set_player_led(0)?;
|
||||||
out += device.write_bulk(endpoint, IMU_COMMAND_0x02, timeout)?;
|
out += self.dbg_write(IMU_COMMAND_0x02, timeout)?;
|
||||||
out += device.write_bulk(endpoint, OUT_UNKNOWN_COMMAND_0x11, timeout)?;
|
out += self.dbg_write(OUT_UNKNOWN_COMMAND_0x11, timeout)?;
|
||||||
out += device.write_bulk(endpoint, UNKNOWN_COMMAND_0x0A, timeout)?;
|
out += self.dbg_write(UNKNOWN_COMMAND_0x0A, timeout)?;
|
||||||
out += device.write_bulk(endpoint, IMU_COMMAND_0x04, timeout)?;
|
out += self.dbg_write(IMU_COMMAND_0x04, timeout)?;
|
||||||
out += device.write_bulk(endpoint, ENABLE_HAPTICS, timeout)?;
|
out += self.dbg_write(ENABLE_HAPTICS, timeout)?;
|
||||||
out += device.write_bulk(endpoint, OUT_UNKNOWN_COMMAND_0x10, timeout)?;
|
out += self.dbg_write(OUT_UNKNOWN_COMMAND_0x10, timeout)?;
|
||||||
out += device.write_bulk(endpoint, OUT_UNKNOWN_COMMAND_0x01, timeout)?;
|
out += self.dbg_write(OUT_UNKNOWN_COMMAND_0x01, timeout)?;
|
||||||
out += device.write_bulk(endpoint, OUT_UNKNOWN_COMMAND_0x03, timeout)?;
|
out += self.dbg_write(OUT_UNKNOWN_COMMAND_0x03, timeout)?;
|
||||||
out += device.write_bulk(endpoint, OUT_UNKNOWN_COMMAND_0x0A_ALT, timeout)?;
|
out += self.dbg_write(OUT_UNKNOWN_COMMAND_0x0A_ALT, timeout)?;
|
||||||
|
|
||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_player_led(&self, pattern: u8) -> rusb::Result<usize> {
|
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]
|
#[rustfmt::skip]
|
||||||
let buf: [u8; 16] = [
|
let cmd: [u8; 16] = [
|
||||||
0x09, 0x91, 0x00, 0x07, 0x00, 0x08, 0x00, 0x00,
|
0x09, 0x91, 0x00, 0x07, 0x00, 0x08, 0x00, 0x00,
|
||||||
pattern, // LED bitfield - replace 0x00 with desired LED pattern
|
pattern, // LED bitfield - replace 0x00 with desired LED pattern
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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)
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF // Console MAC Address (Little Endian)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/// Probably an ACK of some kind
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
const UNKNOWN_COMMAND_0x07: &[u8] = &[
|
const UNKNOWN_COMMAND_0x07: &[u8] = &[
|
||||||
0x07, 0x91, 0x00, 0x01,
|
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::{
|
use procon2::{
|
||||||
Controller,
|
Controller,
|
||||||
descriptor::{VID, pid},
|
descriptor::{VID, pid},
|
||||||
};
|
};
|
||||||
use rusb::Result;
|
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
const PLAYER: [u8; 16] = [
|
const PLAYER: [u8; 16] = [
|
||||||
@ -26,7 +25,17 @@ const PLAYER: [u8; 16] = [
|
|||||||
0b1010,
|
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![];
|
let mut devices = vec![];
|
||||||
|
|
||||||
for device in rusb::devices()?.iter() {
|
for device in rusb::devices()?.iter() {
|
||||||
@ -36,25 +45,75 @@ fn main() -> Result<()> {
|
|||||||
println!("Found player {}: {device:?}!", devices.len() + 1);
|
println!("Found player {}: {device:?}!", devices.len() + 1);
|
||||||
let device = device.open()?;
|
let device = device.open()?;
|
||||||
let controller = Controller::new(&device);
|
let controller = Controller::new(&device);
|
||||||
|
controller.verbose(verbose).init()?;
|
||||||
controller.init()?;
|
device.claim_interface(1)?;
|
||||||
|
|
||||||
devices.push(device);
|
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
|
// Play a shmancy pattern in-sync on all controllers
|
||||||
for pattern in &PLAYER[0..8] {
|
for pattern in &PLAYER[0..8] {
|
||||||
for device in devices.iter() {
|
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));
|
sleep(Duration::from_millis(100));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the controller IDs
|
// Set the controller IDs
|
||||||
for (count, device) in devices.iter().enumerate() {
|
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(())
|
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