diff --git a/README.md b/README.md index cc6d8c3..af02d85 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ Connect the Plug & Play to a PC or console using the data cable. Press one of th | Two handle controller (PC) | RIGHT | D-Pad is mapped to SELECT+ABCD | | Two handle controller (PS1) | DOWN + Power handle at 0 | Hold D to disable handles and enable D-Pad | | Two handle controller (N64) | DOWN + Power handle at 1 | | +| Two handle controller (SAT) | DOWN + Power handle at 2 | | | Two handle controller "Type 2" (PS2) | D | | | Shinkansen controller (PS2) | B | Power notches are mapped to P2-P4-P7-P10-P13 | | Multi Train Controller (PS2) - P4/B7 | C + Power handle at 0 | SELECT+A=A2, SELECT+D=ATS, SELECT+D-Pad=Reverser | @@ -54,7 +55,7 @@ If you need more information regarding each controller and supported software, p ### Nintendo 64 -Use mode *Two handle controller (N64)* In the emulator's settings, assign the controller to **port 3**. The controller should map automatically. Make sure to enable the setting **Independent C-Buttons controls**. +Use mode *Two handle controller (N64)*. In the emulator's settings, assign the controller to **port 3**. The controller should map automatically. Make sure to enable the setting **Independent C-Buttons controls**. ### PlayStation @@ -64,6 +65,10 @@ Use mode *Two handle controller (PS1)*. In the emulator's settings, configure a Use mode *Generic Train Controller*. In the emulator's settings, configure a USB Densha de GO! controller and map the buttons/axes manually. +### Sega Saturn + +Use mode *Two handle controller (SAT)*.The controller should map automatically. + ## RNDIS access (advanced users) When no controller is selected, RNDIS access is enabled in the device. You can access SSH on the Plug & Play at 169.254.215.100. SFTP is not supported out of the box, but SCP is available. Keep in mind the root filesystem is mounted read-only by default. diff --git a/src/controller/emulated.rs b/src/controller/emulated.rs index c149793..e7ab56b 100644 --- a/src/controller/emulated.rs +++ b/src/controller/emulated.rs @@ -15,6 +15,7 @@ mod sotp031201_p4b2b7; mod sotp031201_p4b7; mod sotp031201_p5b5; mod sotp031201_p5b7; +mod tc5175290; mod tcpp20003; mod tcpp20009; mod tcpp20011; @@ -29,15 +30,16 @@ const ANDROID_GADGET: &str = "/sys/class/android_usb/android0"; #[derive(PartialEq, Debug, Clone, Copy)] pub enum ControllerModel { DGOC44U, - TCPP20003, - TCPP20009, - TCPP20011, + SLPH00051, SOTP031201P4B7, SOTP031201P4B2B7, SOTP031201P5B5, SOTP031201P5B7, + TC5175290, + TCPP20003, + TCPP20009, + TCPP20011, ZKNS001, - SLPH00051, GENERIC, } @@ -88,6 +90,14 @@ pub fn set_model(state: &ControllerState) -> Option { &tcpp20003::DESCRIPTORS, &tcpp20003::STRINGS, ); + } else if state.button_down && state.power == 2 { + model_name = "TC-5175290"; + model = ControllerModel::TC5175290; + descriptors = ( + &tc5175290::DEVICE_DESCRIPTOR, + &tc5175290::DESCRIPTORS, + &tc5175290::STRINGS, + ); } else if state.button_d { model_name = "TCPP-20009"; model = ControllerModel::TCPP20009; @@ -159,6 +169,9 @@ pub fn set_state(state: &mut ControllerState, model: &ControllerModel) { ControllerModel::DGOC44U => { dgoc44u::update_gadget(state); } + ControllerModel::TC5175290 => { + tc5175290::update_gadget(state); + } ControllerModel::TCPP20003 => { tcpp20003::update_gadget(state); } @@ -207,6 +220,9 @@ pub fn handle_ctrl_transfer(model: ControllerModel, data: &[u8]) { ControllerModel::SLPH00051 => { report = Some(&slph00051::HID_REPORT_DESCRIPTOR); } + ControllerModel::TC5175290 => { + report = Some(&tc5175290::HID_REPORT_DESCRIPTOR); + } ControllerModel::TCPP20003 => { report = Some(&tcpp20003::HID_REPORT_DESCRIPTOR); } @@ -230,6 +246,9 @@ pub fn handle_ctrl_transfer(model: ControllerModel, data: &[u8]) { ControllerModel::SLPH00051 => { slph00051::handle_ctrl_transfer(data); } + ControllerModel::TC5175290 => { + tc5175290::handle_ctrl_transfer(data); + } ControllerModel::TCPP20003 => { tcpp20003::handle_ctrl_transfer(data); } diff --git a/src/controller/emulated/tc5175290.rs b/src/controller/emulated/tc5175290.rs new file mode 100644 index 0000000..82e5c3c --- /dev/null +++ b/src/controller/emulated/tc5175290.rs @@ -0,0 +1,361 @@ +use crate::controller::emulated::{DeviceDescriptor, ENDPOINT0, ENDPOINT1}; +use crate::controller::physical::ControllerState; +use bitflags::bitflags; +use std::fs::File; +use std::io::Write; + +pub const DESCRIPTORS: [u8; 80] = [ + 0x01, 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, + 0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, + 0x94, 0x00, 0x07, 0x05, 0x02, 0x03, 0x40, 0x00, 0x05, 0x07, 0x05, 0x81, 0x03, 0x40, 0x00, 0x05, + 0x09, 0x04, 0x00, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x09, 0x21, 0x11, 0x01, 0x00, 0x01, 0x22, + 0x94, 0x00, 0x07, 0x05, 0x02, 0x03, 0x40, 0x00, 0x05, 0x07, 0x05, 0x81, 0x03, 0x40, 0x00, 0x05, +]; +pub const STRINGS: [u8; 16] = [ + 0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +]; + +pub const DEVICE_DESCRIPTOR: DeviceDescriptor = DeviceDescriptor { + b_device_class: 0x0, + b_device_sub_class: 0x0, + id_vendor: 0x054C, + id_product: 0x0268, + bcd_device: 0x0100, + i_manufacturer: "TAITO", + i_product: "Densha de Go! Plug & Play (SAT Two Handle mode)", + i_serial_number: "TC-5175290", +}; + +pub const HID_REPORT_DESCRIPTOR: [u8; 148] = [ + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x04, // Usage (Joystick) + 0xA1, 0x01, // Collection (Physical) + 0xA1, 0x02, // Collection (Application) + 0x85, 0x01, // Report ID (1) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + // NOTE: reserved byte + 0x75, 0x01, // Report Size (1) + 0x95, 0x13, // Report Count (19) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x35, 0x00, // Physical Minimum (0) + 0x45, 0x01, // Physical Maximum (1) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0x13, // Usage Maximum (0x13) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0D, // Report Count (13) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x81, 0x03, // Input (Const,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + // NOTE: 32 bit integer, where 0:18 are buttons and 19:31 are reserved + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x01, // Usage (Pointer) + 0xA1, 0x00, // Collection (Undefined) + 0x75, 0x08, // Report Size (8) + 0x95, 0x04, // Report Count (4) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0xFF, 0x00, // Physical Maximum (255) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x09, 0x32, // Usage (Z) + 0x09, 0x35, // Usage (Rz) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + // NOTE: four joysticks + 0xC0, // End Collection + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x75, 0x08, // Report Size (8) + 0x95, 0x27, // Report Count (39) + 0x09, 0x01, // Usage (Pointer) + 0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x75, 0x08, // Report Size (8) + 0x95, 0x30, // Report Count (48) + 0x09, 0x01, // Usage (Pointer) + 0x91, + 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0x75, 0x08, // Report Size (8) + 0x95, 0x30, // Report Count (48) + 0x09, 0x01, // Usage (Pointer) + 0xB1, + 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0xC0, // End Collection + 0xA1, 0x02, // Collection (Application) + 0x85, 0x02, // Report ID (2) + 0x75, 0x08, // Report Size (8) + 0x95, 0x30, // Report Count (48) + 0x09, 0x01, // Usage (Pointer) + 0xB1, + 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0xC0, // End Collection + 0xA1, 0x02, // Collection (Application) + 0x85, 0xEE, // Report ID (238) + 0x75, 0x08, // Report Size (8) + 0x95, 0x30, // Report Count (48) + 0x09, 0x01, // Usage (Pointer) + 0xB1, + 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0xC0, // End Collection + 0xA1, 0x02, // Collection (Application) + 0x85, 0xEF, // Report ID (239) + 0x75, 0x08, // Report Size (8) + 0x95, 0x30, // Report Count (48) + 0x09, 0x01, // Usage (Pointer) + 0xB1, + 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile) + 0xC0, // End Collection + 0xC0, // End Collection +]; + +const F2_REPORT: [u8; 64] = [ + 0xF2, 0xFF, 0xFF, 0x0, 0x0, 0x6, 0xF5, 0x48, 0xE2, 0x49, 0x0, 0x3, 0x50, 0x81, 0xD8, 0x1, 0x8A, + 0x13, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x0, + 0x0, 0x4, 0x0, 0x1, 0x2, 0x7, 0x0, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +]; + +const F5_REPORT: [u8; 64] = [ + 0x1, 0x0, 0x0, 0x23, 0x6, 0x7C, 0xB9, 0xB, 0xE2, 0x49, 0x0, 0x3, 0x50, 0x81, 0xD8, 0x1, 0x8A, + 0x13, 0x0, 0x0, 0x0, 0x0, 0x4, 0x0, 0x2, 0x2, 0x2, 0x2, 0x0, 0x0, 0x0, 0x4, 0x4, 0x4, 0x4, 0x0, + 0x0, 0x4, 0x0, 0x1, 0x2, 0x7, 0x0, 0x17, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, +]; + +bitflags! { + struct Buttons1: u8 { + const NONE = 0; + const SELECT = 1; + const L3 = 2; + const R3 = 4; + const START = 8; + const UP = 16; + const RIGHT = 32; + const DOWN = 64; + const LEFT = 128; + } + struct Buttons2: u8 { + const NONE = 0; + const L2 = 1; + const R2 = 2; + const L1 = 4; + const R1 = 8; + const TRIANGLE = 16; + const CIRCLE = 32; + const CROSS = 64; + const SQUARE = 128; + } +} + +pub fn update_gadget(state: &mut ControllerState) { + let mut buttons1 = Buttons1::NONE; + let mut buttons2 = Buttons2::NONE; + + // Calculate data for handles + match state.power { + 0 => { + buttons2.insert(Buttons2::TRIANGLE | Buttons2::L1); + } + 1 => { + buttons2.insert(Buttons2::SQUARE | Buttons2::L1); + } + 2 => { + buttons2.insert(Buttons2::L1); + } + 3 => { + buttons2.insert(Buttons2::TRIANGLE | Buttons2::SQUARE); + } + 4 => { + buttons2.insert(Buttons2::TRIANGLE); + } + _ => { + buttons2.insert(Buttons2::SQUARE); + } + } + match state.brake { + 0 => { + buttons1.insert(Buttons1::DOWN | Buttons1::LEFT); + buttons2.insert(Buttons2::R2); + } + 1 => { + buttons1.insert(Buttons1::DOWN | Buttons1::LEFT); + buttons2.insert(Buttons2::L2); + } + 2 => { + buttons1.insert(Buttons1::DOWN | Buttons1::LEFT); + } + 3 => { + buttons1.insert(Buttons1::LEFT); + buttons2.insert(Buttons2::R2 | Buttons2::L2); + } + 4 => { + buttons1.insert(Buttons1::LEFT); + buttons2.insert(Buttons2::R2); + } + 5 => { + buttons1.insert(Buttons1::LEFT); + buttons2.insert(Buttons2::L2); + } + 6 => { + buttons1.insert(Buttons1::LEFT); + } + 7 => { + buttons1.insert(Buttons1::DOWN); + buttons2.insert(Buttons2::R2 | Buttons2::L2); + } + 8 => { + buttons1.insert(Buttons1::DOWN); + buttons2.insert(Buttons2::R2); + } + _ => (), + } + + // Calculate data for buttons + if state.button_a { + buttons2.insert(Buttons2::CROSS) + } + if state.button_b { + buttons2.insert(Buttons2::CIRCLE) + } + if state.button_c { + buttons2.insert(Buttons2::R1) + } + if state.button_start { + buttons1.insert(Buttons1::START) + } + + let btn_up = if buttons1.contains(Buttons1::UP) { + 0xFF + } else { + 0x0 + }; + let btn_right = if buttons1.contains(Buttons1::RIGHT) { + 0xFF + } else { + 0x0 + }; + let btn_down = if buttons1.contains(Buttons1::DOWN) { + 0xFF + } else { + 0x0 + }; + let btn_left = if buttons1.contains(Buttons1::LEFT) { + 0xFF + } else { + 0x0 + }; + let btn_l2 = if buttons2.contains(Buttons2::L2) { + 0xFF + } else { + 0x0 + }; + let btn_r2 = if buttons2.contains(Buttons2::R2) { + 0xFF + } else { + 0x0 + }; + let btn_l1 = if buttons2.contains(Buttons2::L1) { + 0xFF + } else { + 0x0 + }; + let btn_r1 = if buttons2.contains(Buttons2::R1) { + 0xFF + } else { + 0x0 + }; + let btn_triangle = if buttons2.contains(Buttons2::TRIANGLE) { + 0xFF + } else { + 0x0 + }; + let btn_circle = if buttons2.contains(Buttons2::CIRCLE) { + 0xFF + } else { + 0x0 + }; + let btn_cross = if buttons2.contains(Buttons2::CROSS) { + 0xFF + } else { + 0x0 + }; + let btn_square = if buttons2.contains(Buttons2::SQUARE) { + 0xFF + } else { + 0x0 + }; + + // Assemble data and send it to gadget + let data = [ + 0x1, + 0x0, + buttons1.bits, + buttons2.bits, + 0x0, + 0x0, + 0x80, + 0x80, + 0x80, + 0x80, + 0x0, + 0x0, + 0x0, + 0x0, + btn_up, + btn_right, + btn_down, + btn_left, + btn_l2, + btn_r2, + btn_l1, + btn_r1, + btn_triangle, + btn_circle, + btn_cross, + btn_square, + 0x0, + 0x0, + 0x0, + 0x3, + 0xEF, + 0x14, + 0x0, + 0x0, + 0x0, + 0x0, + 0x23, + 0x1A, + 0x77, + 0x1, + 0x81, + 0x1, + 0xFE, + 0x1, + 0xFE, + 0x1, + 0xFE, + 0x1, + 0xFE, + ]; + if let Ok(mut file) = File::create(ENDPOINT1) { + file.write(&data).ok(); + } +} + +pub fn handle_ctrl_transfer(data: &[u8]) { + if data[1] == 1 && data[2] == 0xF2 { + // Init 1 + if let Ok(mut file) = File::create(ENDPOINT0) { + file.write(&F2_REPORT).unwrap(); + } + } else if data[1] == 1 && data[2] == 0xF5 { + // Init 2 + if let Ok(mut file) = File::create(ENDPOINT0) { + file.write(&F5_REPORT).unwrap(); + } + } +}