/* * Copyright (C) 2020-2023 ndeadly * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "dualshock3_controller.hpp" #include "../bluetooth_mitm/bluetooth/bluetooth_core.hpp" #include "../mcmitm_config.hpp" #include #include namespace ams::controller { namespace { const char *ds3_device_name = "PLAYSTATION(R)3 Controller"; constexpr u16 ds3_vendor_id = 0x054c; constexpr u16 ds3_product_id = 0x0268; enum Dualshock3LedMode { Dualshock3LedMode_Switch = 0, Dualshock3LedMode_Ps3 = 1, Dualshock3LedMode_Hybrid = 2, }; const u8 enable_payload[] = { 0xf4, 0x42, 0x03, 0x00, 0x00 }; const u8 led_config[] = { 0xff, 0x27, 0x10, 0x00, 0x32 }; const u8 player_led_patterns[] = { 0b1000, 0b1100, 0b1110, 0b1111, 0b1001, 0b0101, 0b1101, 0b0110 }; constexpr float stick_scale_factor = float(UINT12_MAX) / UINT8_MAX; constexpr float accel_scale_factor = 65535 / 16000.0f * 1000 / 113; alignas(os::MemoryPageSize) constinit u8 g_usb_buffer[0x1000]; const UsbHsInterfaceFilter g_interface_filter = { .Flags = UsbHsInterfaceFilterFlags_idVendor | UsbHsInterfaceFilterFlags_idProduct | UsbHsInterfaceFilterFlags_bInterfaceClass, .idVendor = ds3_vendor_id, .idProduct = ds3_product_id, .bInterfaceClass = USB_CLASS_HID, }; Result SetMasterAddress(UsbHsClientIfSession *if_session, const BtdrvAddress *address) { const struct { u8 unk1; u8 unk2; BtdrvAddress address; } data = {0x01, 0x00, *address}; std::memcpy(&g_usb_buffer, &data, sizeof(data)); u32 rx_size = 0; R_TRY(usbHsIfCtrlXfer(if_session, USB_ENDPOINT_OUT | (0x01 << 5) | 0x01, USB_REQUEST_SET_CONFIGURATION, 0x3f5, 0, sizeof(data), &g_usb_buffer, &rx_size )); R_SUCCEED(); } Result GetSlaveAddress(UsbHsClientIfSession *if_session, BtdrvAddress *address) { u32 tx_size = 0; R_TRY(usbHsIfCtrlXfer(if_session, USB_ENDPOINT_IN | (0x01 << 5) | 0x01, USB_REQUEST_CLEAR_FEATURE, 0x3f2, 0, 18, &g_usb_buffer, &tx_size )); *address = *reinterpret_cast(&g_usb_buffer[4]); R_SUCCEED(); } Result TrustDevice(const BtdrvAddress *address) { SetSysBluetoothDevicesSettings device = {}; device.addr = *address; device.class_of_device = {0x00, 0x05, 0x08}; device.link_key_present = false; device.trusted_services = 0x100000; device.vid = ds3_vendor_id; device.pid = ds3_product_id; device.sub_class = 0x08; device.attribute_mask = 0xff; if (hos::GetVersion() < hos::Version_13_0_0) { std::strncpy(device.name.name, ds3_device_name, sizeof(device.name)); } else { std::strncpy(device.name2, ds3_device_name, sizeof(device.name2)); } R_RETURN(btdrvAddPairedDeviceInfo(&device)); } void SignalBondComplete(const BtdrvAddress *address) { if (hos::GetVersion() < hos::Version_9_0_0) { const struct { BtdrvAddress addr; u8 pad[2]; u32 status; u32 type; } bond_event = { *address, {0}, 0, BtdrvConnectionEventType_Suspended }; bluetooth::core::SignalFakeEvent(BtdrvEventTypeOld_Connection, &bond_event, sizeof(bond_event)); } else if (hos::GetVersion() < hos::Version_12_0_0) { const struct { u32 status; BtdrvAddress addr; u8 pad[2]; u32 type; } bond_event = { 0, *address, {0}, BtdrvConnectionEventType_Suspended }; bluetooth::core::SignalFakeEvent(BtdrvEventTypeOld_Connection, &bond_event, sizeof(bond_event)); } else { const struct { u32 type; BtdrvAddress addr; u8 reserved[0xfe]; } bond_event = { BtdrvConnectionEventType_Suspended, *address, {0} }; bluetooth::core::SignalFakeEvent(BtdrvEventType_Connection, &bond_event, sizeof(bond_event)); } } } const UsbHsInterfaceFilter *Dualshock3Controller::GetUsbInterfaceFilter() { return &g_interface_filter; } bool Dualshock3Controller::UsbIdentify(UsbHsInterface *iface) { return (iface->device_desc.idVendor == ds3_vendor_id) && (iface->device_desc.idProduct == ds3_product_id); } Result Dualshock3Controller::UsbPair(UsbHsInterface *iface) { // Acquire usb:hs client interface session UsbHsClientIfSession if_session; R_TRY(usbHsAcquireUsbIf(&if_session, iface)); // Close session on function exit ON_SCOPE_EXIT { if (usbHsIfIsActive(&if_session)) { usbHsIfClose(&if_session); } }; // Fetch the console bluetooth address BtdrvAdapterProperty property; R_TRY(btdrvGetAdapterProperty(BtdrvAdapterPropertyType_Address, &property)); // Set the console address as the master on the DS3 BtdrvAddress master_address = *reinterpret_cast(property.data); R_TRY(SetMasterAddress(&if_session, &master_address)); // Get the address of the DS3 BtdrvAddress slave_address; R_TRY(GetSlaveAddress(&if_session, &slave_address)); // Add DS3 to list of trusted devices R_TRY(TrustDevice(&slave_address)); // Signal fake bonding success event for btm SignalBondComplete(&slave_address); R_SUCCEED(); } Result Dualshock3Controller::Initialize() { R_TRY(EmulatedSwitchController::Initialize()); R_TRY(this->SendEnablePayload()); R_SUCCEED(); } Result Dualshock3Controller::SetVibration(const SwitchRumbleData *rumble_data) { m_rumble_state.amp_motor_left = static_cast(255 * std::max(rumble_data[0].low_band_amp, rumble_data[1].low_band_amp)); m_rumble_state.amp_motor_right = static_cast(255 * std::max(rumble_data[0].high_band_amp, rumble_data[1].high_band_amp)); R_RETURN(this->PushRumbleLedState()); } Result Dualshock3Controller::CancelVibration() { m_rumble_state.amp_motor_left = 0; m_rumble_state.amp_motor_right = 0; R_RETURN(this->PushRumbleLedState()); } Result Dualshock3Controller::SetPlayerLed(u8 led_mask) { u8 player_index; R_TRY(LedsMaskToPlayerNumber(led_mask, &player_index)); auto config = mitm::GetGlobalConfig(); switch(config->misc.dualshock3_led_mode) { case Dualshock3LedMode_Switch: m_led_mask = player_led_patterns[player_index]; break; case Dualshock3LedMode_Ps3: m_led_mask = player_index < 4 ? 1 << player_index : ~(1 << player_index) & 0x0f; break; case Dualshock3LedMode_Hybrid: m_led_mask = led_mask; break; default: break; }; R_RETURN(this->PushRumbleLedState()); } void Dualshock3Controller::ProcessInputData(const bluetooth::HidReport *report) { auto ds3_report = reinterpret_cast(&report->data); switch(ds3_report->id) { case 0x01: this->MapInputReport0x01(ds3_report); break; default: break; } } void Dualshock3Controller::MapInputReport0x01(const Dualshock3ReportData *src) { m_charging = src->input0x01.charge == 0x02; m_battery = std::clamp(src->input0x01.battery, 0, 4) * 2; // Workaround for controller reporting battery empty and being disconnected under certain conditions if (m_battery == 0) { m_battery = 1; } m_left_stick.SetData( static_cast(stick_scale_factor * src->input0x01.left_stick.x) & UINT12_MAX, static_cast(stick_scale_factor * (UINT8_MAX - src->input0x01.left_stick.y)) & UINT12_MAX ); m_right_stick.SetData( static_cast(stick_scale_factor * src->input0x01.right_stick.x) & UINT12_MAX, static_cast(stick_scale_factor * (UINT8_MAX - src->input0x01.right_stick.y)) & UINT12_MAX ); m_buttons.dpad_down = src->input0x01.buttons.dpad_down; m_buttons.dpad_up = src->input0x01.buttons.dpad_up; m_buttons.dpad_right = src->input0x01.buttons.dpad_right; m_buttons.dpad_left = src->input0x01.buttons.dpad_left; m_buttons.A = src->input0x01.buttons.circle; m_buttons.B = src->input0x01.buttons.cross; m_buttons.X = src->input0x01.buttons.triangle; m_buttons.Y = src->input0x01.buttons.square; m_buttons.R = src->input0x01.buttons.R1; m_buttons.ZR = src->input0x01.right_trigger > (m_trigger_threshold * UINT8_MAX); m_buttons.L = src->input0x01.buttons.L1; m_buttons.ZL = src->input0x01.left_trigger > (m_trigger_threshold * UINT8_MAX); m_buttons.minus = src->input0x01.buttons.select; m_buttons.plus = src->input0x01.buttons.start; m_buttons.lstick_press = src->input0x01.buttons.L3; m_buttons.rstick_press = src->input0x01.buttons.R3; m_buttons.home = src->input0x01.buttons.ps; if (m_enable_motion) { s16 acc_x = -static_cast(accel_scale_factor * (511 - util::SwapEndian(src->input0x01.accel_y))); s16 acc_y = -static_cast(accel_scale_factor * (util::SwapEndian(src->input0x01.accel_x) - 511)); s16 acc_z = static_cast(accel_scale_factor * (511 - util::SwapEndian(src->input0x01.accel_z))); m_motion_data[0].accel_x = acc_x; m_motion_data[0].accel_y = acc_y; m_motion_data[0].accel_z = acc_z; m_motion_data[1].accel_x = acc_x; m_motion_data[1].accel_y = acc_y; m_motion_data[1].accel_z = acc_z; m_motion_data[2].accel_x = acc_x; m_motion_data[2].accel_y = acc_y; m_motion_data[2].accel_z = acc_z; } else { std::memset(&m_motion_data, 0, sizeof(m_motion_data)); } } Result Dualshock3Controller::SendEnablePayload() { m_output_report.size = sizeof(enable_payload); std::memcpy(m_output_report.data, enable_payload, m_output_report.size); R_RETURN(this->SetReport(BtdrvBluetoothHhReportType_Feature, &m_output_report)); } Result Dualshock3Controller::PushRumbleLedState() { std::scoped_lock lk(m_output_mutex); Dualshock3ReportData report = {}; report.id = 0x01; report.output0x01.data[1] = 10; report.output0x01.data[2] = m_rumble_state.amp_motor_right ? 1 : 0; report.output0x01.data[3] = 10; report.output0x01.data[4] = m_rumble_state.amp_motor_left; report.output0x01.data[9] = m_led_mask << 1; std::memcpy(&report.output0x01.data[10], led_config, sizeof(led_config)); std::memcpy(&report.output0x01.data[15], led_config, sizeof(led_config)); std::memcpy(&report.output0x01.data[20], led_config, sizeof(led_config)); std::memcpy(&report.output0x01.data[25], led_config, sizeof(led_config)); m_output_report.size = sizeof(report.output0x01) + sizeof(report.id); std::memcpy(m_output_report.data, &report, m_output_report.size); R_RETURN(this->SetReport(BtdrvBluetoothHhReportType_Output, &m_output_report)); } }