Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow all Switch controllers except Joycons to be reported as Official Pro Controllers #535

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions mc_mitm/config.ini
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@
;dualsense_enable_player_leds=false
; Set Dualsense vibration intensity, 12.5% per increment. Valid range [1-8] where 1=12.5%, 8=100% [default 4(50%)]
;dualsense_vibration_intensity=4
; Change the controller type/PID reported by every official controller but Joycons to be a Pro Controller, allowing full button remapping [default false]
;force_pro_controller=false
9 changes: 9 additions & 0 deletions mc_mitm/source/btm_mitm/btm_mitm_service.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "btm_mitm_service.hpp"
#include "btm_shim.h"
#include "../controllers/controller_management.hpp"
#include "../mcmitm_config.hpp"

namespace ams::mitm::btm {

Expand Down Expand Up @@ -55,6 +56,10 @@ namespace ams::mitm::btm {
if (!controller::IsOfficialSwitchControllerName(device->name)) {
std::strncpy(device->name, controller::ProControllerName, sizeof(device->name) - 1);
}
else if (mitm::GetGlobalConfig()->misc.force_pro_controller && controller::IsNotJoyconOrProController(device->name)) {
device->profile_info.hid_device_info.vid = 0x057e; //This may not have any effect, it's just to fix problems with a Licensed Pro Controller
device->profile_info.hid_device_info.pid = 0x2009; //Change Pid to a Pro controller one
}
}

R_SUCCEED();
Expand Down Expand Up @@ -108,6 +113,10 @@ namespace ams::mitm::btm {
if (!controller::IsOfficialSwitchControllerName(device->name.name)) {
std::strncpy(device->name.name, controller::ProControllerName, sizeof(device->name) - 1);
}
else if (mitm::GetGlobalConfig()->misc.force_pro_controller && controller::IsNotJoyconOrProController(device->name.name)) {
device->profile_info.hid_device_info.vid = 0x057e; //This may not have any effect, it's just to fix problems with a Licensed Pro Controller
device->profile_info.hid_device_info.pid = 0x2009; //Change Pid to a Pro controller one
}
}

R_SUCCEED();
Expand Down
16 changes: 15 additions & 1 deletion mc_mitm/source/controllers/controller_management.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "controller_management.hpp"
#include <stratosphere.hpp>
#include "../utils.hpp"
#include "../mcmitm_config.hpp"

namespace ams::controller {

Expand Down Expand Up @@ -57,6 +58,7 @@ namespace ams::controller {
if (IsOfficialSwitchControllerName(hos::GetVersion() < hos::Version_13_0_0 ? device->name.name : device->name2))
return ControllerType_Switch;


for (auto hwId : WiiController::hardware_ids) {
if ( (device->vid == hwId.vid) && (device->pid == hwId.pid) ) {
return ControllerType_Wii;
Expand Down Expand Up @@ -224,6 +226,16 @@ namespace ams::controller {
return false;
}

bool IsNotJoyconOrProController(const std::string& name) {
std::string JoyconName = "Joy-Con";
std::string ProControllerName = "Pro Controller";
if (name.rfind(JoyconName) == 0 || name.rfind(ProControllerName) == 0) {
return false;
}

return true;
}

void AttachHandler(const bluetooth::Address *address) {
bluetooth::DevicesSettings device_settings;
R_ABORT_UNLESS(btdrvGetPairedDeviceInfo(*address, &device_settings));
Expand All @@ -234,7 +246,9 @@ namespace ams::controller {

switch (Identify(&device_settings)) {
case ControllerType_Switch:
controller = std::make_shared<SwitchController>(address, id);
if (mitm::GetGlobalConfig()->misc.force_pro_controller && IsNotJoyconOrProController(hos::GetVersion() < hos::Version_13_0_0 ? device_settings.name.name : device_settings.name2))
controller = std::make_shared<ForcedProController>(address, id);
else controller = std::make_shared<SwitchController>(address, id);
break;
case ControllerType_Wii:
controller = std::make_shared<WiiController>(address, id);
Expand Down
2 changes: 2 additions & 0 deletions mc_mitm/source/controllers/controller_management.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <string>

#include "switch_controller.hpp"
#include "forced_pro_controller.hpp"
#include "wii_controller.hpp"
#include "dualshock3_controller.hpp"
#include "dualshock4_controller.hpp"
Expand Down Expand Up @@ -88,6 +89,7 @@ namespace ams::controller {
ControllerType Identify(const bluetooth::DevicesSettings *device);
bool IsAllowedDeviceClass(const bluetooth::DeviceClass *cod);
bool IsOfficialSwitchControllerName(const std::string& name);
bool IsNotJoyconOrProController(const std::string& name);

void AttachHandler(const bluetooth::Address *address);
void RemoveHandler(const bluetooth::Address *address);
Expand Down
154 changes: 154 additions & 0 deletions mc_mitm/source/controllers/forced_pro_controller.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
/*
* Copyright (c) 2020-2022 ndeadly
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#include "forced_pro_controller.hpp"
#include "../mcmitm_config.hpp"

namespace ams::controller {

namespace {

bool ApplyN64Remapping(SwitchButtonData *buttons) {
bool alternatescheme = buttons->Y; //C-Up
buttons->Y = buttons->ZR; //C-Down -> Y
buttons->ZR = buttons->lstick_press; //ZR -> ZR
buttons->lstick_press = 0;
if (alternatescheme) {
buttons->lstick_press = buttons->L; //L -> LEFT STICK PRESS
buttons->rstick_press = buttons->R; //R -> RIGHT STICK PRESS
buttons->L = 0;
buttons->R = 0;
}
return alternatescheme;
}

}

ForcedProController::ForcedProController(const bluetooth::Address *address, HardwareID id)
: SwitchController(address, id)
, m_is_n64_controller(false) {
if (m_id.pid == 0x2019)
m_is_n64_controller = true;
}


Result ForcedProController::HandleDataReportEvent(const bluetooth::HidReportEventInfo *event_info) {
const bluetooth::HidReport *report;
if (hos::GetVersion() >= hos::Version_9_0_0) {
report = &event_info->data_report.v9.report;
} else if (hos::GetVersion() >= hos::Version_7_0_0) {
report = reinterpret_cast<const bluetooth::HidReport *>(&event_info->data_report.v7.report);
} else {
report = reinterpret_cast<const bluetooth::HidReport *>(&event_info->data_report.v1.report);
}

if (!m_future_responses.empty()) {
if ((m_future_responses.back()->GetType() == BtdrvHidEventType_Data) && (m_future_responses.back()->GetUserData() == report->data[0])) {
m_future_responses.back()->SetData(*event_info);
}
}

std::scoped_lock lk(m_input_mutex);

this->UpdateControllerState(report);

auto input_report = reinterpret_cast<SwitchInputReport *>(m_input_report.data);
if (input_report->id == 0x21) {
auto response = &input_report->type0x21.hid_command_response;
switch (response->id) {
case HidCommand_SerialFlashRead:
if (response->data.serial_flash_read.address == 0x6050) {
if (ams::mitm::GetSystemLanguage() == 10) {
uint8_t data[] = {0xff, 0xd7, 0x00, 0x00, 0x57, 0xb7, 0x00, 0x57, 0xb7, 0x00, 0x57, 0xb7};
std::memcpy(response->data.serial_flash_read.data, data, sizeof(data));
}
else {
uint8_t data[] = {0x32, 0x32, 0x32, 0xe6, 0xe6, 0xe6, 0x46, 0x46, 0x46, 0x46, 0x46, 0x46};
std::memcpy(response->data.serial_flash_read.data, data, sizeof(data));
}
}
else if (m_is_n64_controller) {
switch (response->data.serial_flash_read.address) {
case 0x603d: //Factory stick calibration
memcpy(&response->data.serial_flash_read.data[9], &response->data.serial_flash_read.data[3], 3); //Swap calibration triplets for the right stick (https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/spi_flash_notes.md)
memcpy(&m_n64_calibrated_stick_zero, &response->data.serial_flash_read.data[3], 3);
memcpy(&response->data.serial_flash_read.data[12], &response->data.serial_flash_read.data[6], 3);
memcpy(&response->data.serial_flash_read.data[15], &response->data.serial_flash_read.data[0], 3);
break;
case 0x8010: //User stick calibration
if (response->data.serial_flash_read.data[0] != 0xFF && response->data.serial_flash_read.data[1] != 0xFF) {
memcpy(&response->data.serial_flash_read.data[11], &response->data.serial_flash_read.data[0], 2); //Copy magic numbers that signal the presence of a user calibration
memcpy(&response->data.serial_flash_read.data[13], &response->data.serial_flash_read.data[5], 3); //Swap calibration triplets for the right stick
memcpy(&m_n64_calibrated_stick_zero, &response->data.serial_flash_read.data[5], 3);
memcpy(&response->data.serial_flash_read.data[16], &response->data.serial_flash_read.data[8], 3);
memcpy(&response->data.serial_flash_read.data[19], &response->data.serial_flash_read.data[2], 3);
}
break;
case 0x6080: //Left stick parameters (always read first) (first 6 bytes are 6-Axis Horizontal Offsets which we don't need)
memcpy(&m_n64_left_stick_param, &response->data.serial_flash_read.data[6],sizeof(SwitchAnalogStickParameters));
break;
case 0x6098: //Right stick parameters (always read after the left ones)
memcpy(&response->data.serial_flash_read.data, &m_n64_left_stick_param, sizeof(SwitchAnalogStickParameters));
break;
}
}
break;
case HidCommand_GetDeviceInfo:
response->data.get_device_info.type = 0x03;
response->data.get_device_info._unk2 = 0x02;
break;
}
}

if (m_is_n64_controller) {
if (ApplyN64Remapping(&input_report->buttons)) {
input_report->right_stick.SetData(input_report->left_stick.GetX(), input_report->left_stick.GetY());
memcpy(&input_report->left_stick.m_xy, &m_n64_calibrated_stick_zero, 3);
}
else memcpy(&input_report->right_stick.m_xy, &m_n64_calibrated_stick_zero, 3);
}

this->ApplyButtonCombos(&input_report->buttons);

return bluetooth::hid::report::WriteHidDataReport(m_address, &m_input_report);
}

Result ForcedProController::HandleOutputDataReport(const bluetooth::HidReport *report) {
auto output_report = reinterpret_cast<const SwitchOutputReport *>(&report->data);
if (output_report->id == 0x01 && (output_report->type0x01.hid_command.id == HidCommand_SerialFlashWrite || output_report->type0x01.hid_command.id == HidCommand_SerialFlashSectorErase)) {
SwitchInputReport input_report = {
.id = 0x21,
.timer = 1,
.conn_info = 1,
.battery = BATTERY_MAX,
.vibrator = 0,
};

input_report.type0x21.hid_command_response = {
.ack = 0x80,
.id = output_report->type0x01.hid_command.id,
.data = {
.serial_flash_write = {
.status = 0x1 //Force write protected response just to be safe
}
}
};

return bluetooth::hid::report::WriteHidDataReport(m_address, &m_input_report);
}
return this->WriteDataReport(report);
}

}
36 changes: 36 additions & 0 deletions mc_mitm/source/controllers/forced_pro_controller.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2020-2022 ndeadly
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "switch_controller.hpp"

namespace ams::controller {

class ForcedProController : public SwitchController {

public:
ForcedProController(const bluetooth::Address *address, HardwareID id);
virtual ~ForcedProController() {};

Result HandleDataReportEvent(const bluetooth::HidReportEventInfo *event_info);
Result HandleOutputDataReport(const bluetooth::HidReport *report);
private:
bool m_is_n64_controller;
SwitchAnalogStickParameters m_n64_left_stick_param;
uint8_t m_n64_calibrated_stick_zero[3];

};

}
11 changes: 7 additions & 4 deletions mc_mitm/source/mcmitm_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,16 @@ namespace ams::mitm {
.dualshock4_lightbar_brightness = 5,
.dualsense_lightbar_brightness = 5,
.dualsense_enable_player_leds = true,
.dualsense_vibration_intensity = 4
.dualsense_vibration_intensity = 4,
.force_pro_controller = false
}
};

void ParseBoolean(const char *value, bool *out) {
if (strcasecmp(value, "true") == 0)
*out = true;
else if (strcasecmp(value, "false") == 0)
*out = false;
*out = false;
}

void ParseInt(const char *value, int *out, int min=INT_MIN, int max=INT_MAX) {
Expand Down Expand Up @@ -107,8 +108,10 @@ namespace ams::mitm {
ParseBoolean(value, &config->misc.dualsense_enable_player_leds);
} else if (strcasecmp(name, "dualsense_vibration_intensity") == 0) {
ParseInt(value, &config->misc.dualsense_vibration_intensity, 1, 8);
}
} else {
} else if (strcasecmp(name, "force_pro_controller") == 0)
ParseBoolean(value, &config->misc.force_pro_controller);
}
else {
return 0;
}

Expand Down
1 change: 1 addition & 0 deletions mc_mitm/source/mcmitm_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ namespace ams::mitm {
int dualsense_lightbar_brightness;
bool dualsense_enable_player_leds;
int dualsense_vibration_intensity;
bool force_pro_controller;
} misc;
};

Expand Down