diff --git a/Makefile b/Makefile index 69dc8c8a..d5787b54 100644 --- a/Makefile +++ b/Makefile @@ -39,6 +39,7 @@ dist: all mkdir -p dist/config/MissionControl mkdir -p dist/config/MissionControl/controllers cp mc_mitm/config.ini dist/config/MissionControl/missioncontrol.ini.template + cp mc_mitm/default.ini dist/config/MissionControl/controllers/default.ini cd dist; zip -r $(PROJECT_NAME)-$(BUILD_VERSION).zip ./*; cd ../; diff --git a/common/controller_profiles.hpp b/common/controller_profiles.hpp new file mode 100644 index 00000000..589c975f --- /dev/null +++ b/common/controller_profiles.hpp @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2020-2021 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 . + */ +#pragma once + +struct ControllerProfileConfig { + struct { + uint8_t controller_type; + bool enable_rumble; + bool enable_motion; + } general; + + struct { + bool use_western_layout; + uint32_t sony_led_brightness; + uint32_t dualshock_reportrate; + bool swap_dpad_lstick; + bool invert_lstick_xaxis; + bool invert_lstick_yaxis; + bool invert_rstick_xaxis; + bool invert_rstick_yaxis; + bool disable_home_button; + } misc; +}; + +constexpr ControllerProfileConfig g_cp_global_config = { + .general = { + .controller_type = 3, + .enable_rumble = true, + .enable_motion = true + }, + .misc = { + .use_western_layout = false, + .sony_led_brightness = 8, + .dualshock_reportrate = 8, + .swap_dpad_lstick = false, + .invert_lstick_xaxis = false, + .invert_lstick_yaxis = false, + .invert_rstick_xaxis = false, + .invert_rstick_yaxis = false, + .disable_home_button = false + } +}; diff --git a/mc_mitm/Makefile b/mc_mitm/Makefile index eb7c01e8..a690f9f6 100644 --- a/mc_mitm/Makefile +++ b/mc_mitm/Makefile @@ -13,6 +13,8 @@ ifneq ($(BUILD),$(notdir $(CURDIR))) export OUTPUT := $(CURDIR)/$(TARGET) export TOPDIR := $(CURDIR) +SOURCES += ../common + export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ $(foreach dir,$(DATA),$(CURDIR)/$(dir)) diff --git a/mc_mitm/config.ini b/mc_mitm/config.ini index 83ff7651..4a9721a7 100644 --- a/mc_mitm/config.ini +++ b/mc_mitm/config.ini @@ -1,15 +1,9 @@ [general] -; Enable vibration support for unofficial controllers [default true] -;enable_rumble=true -; Enable motion controls support for unoffical controllers [default true] -;enable_motion=true +; Completely disable the use of any controller profiles present on the SD card and fall back to program defaults +;disable_custom_profiles=false [bluetooth] ; Override host name of Bluetooth adapter ;host_name=Nintendo Switch! ; Override host mac address of Bluetooth adapter ;host_address=04:20:69:04:20:69 - -[misc] -; Disable the LED lightbar on Sony Dualshock 4 and Dualsense controllers [default false] -;disable_sony_leds=false diff --git a/mc_mitm/default.ini b/mc_mitm/default.ini new file mode 100644 index 00000000..95fe6946 --- /dev/null +++ b/mc_mitm/default.ini @@ -0,0 +1,24 @@ +[general] +; Specifies which kind of controller should be emulated (1 = LeftJoyCon, 2 = RightJoyCon, 3 = ProController) [default 3] +;controller_type=3 +; Enable vibration support for unofficial controllers [default true] +;enable_rumble=true +; Enable motion controls support for unoffical controllers [default true] +;enable_motion=true + +[misc] +; Automatically remaps the controller buttons to behave similarly to how they do on western consoles (i.e A to confirm on Xbox controllers, X on Playstation ones) +;use_western_layout=false +; Specify the LED lightbar brightness on Sony controllers (range 0(off)-63(max)) [default 8] +;sony_led_brightness=8 +; Specifies a different polling rate value for DualShock 4 controllers (see https://github.com/ndeadly/MissionControl/blob/master/mc_mitm/source/controllers/dualshock4_controller.hpp#L21 for a list of valid values) [default 8(125hz)] +;dualshock_reportrate=8 +; Map the dpad of the controller to the left stick and viceversa [default false] +;swap_dpad_lstick=false +; A series of stick axis reversal settings [default false] +;invert_lstick_xaxis=false +;invert_lstick_yaxis=false +;invert_rstick_xaxis=false +;invert_rstick_yaxis=false +; Disable the use of the HOME button system-wide (still accessible via button combos) [default false] +;disable_home_button=false \ No newline at end of file diff --git a/mc_mitm/source/bluetooth_mitm/bluetooth/bluetooth_hid.cpp b/mc_mitm/source/bluetooth_mitm/bluetooth/bluetooth_hid.cpp index 099169e2..689f7b2f 100644 --- a/mc_mitm/source/bluetooth_mitm/bluetooth/bluetooth_hid.cpp +++ b/mc_mitm/source/bluetooth_mitm/bluetooth/bluetooth_hid.cpp @@ -67,6 +67,51 @@ namespace ams::bluetooth::hid { g_system_event_fwd.Signal(); } + Result VirtualReconnect(const bluetooth::Address *address) { + + if (hos::GetVersion() >= hos::Version_12_0_0) { + struct ConnectionState { + BtdrvHidConnectionStatus status; + BtdrvAddress address; + }; + + // Signal fake disconnection event + ConnectionState disconnected = { BtdrvHidConnectionStatus_Closed, *address }; + SignalFakeEvent(BtdrvHidEventType_Connection, &disconnected, sizeof(disconnected)); + g_data_read_event.Wait(); + + // If we don't wait a bit the console disconnects the controller for real + svcSleepThread(100'000'000ULL); + + // Signal fake connection event + ConnectionState connected = { BtdrvHidConnectionStatus_Opened, *address }; + SignalFakeEvent(BtdrvHidEventType_Connection, &connected, sizeof(connected)); + g_data_read_event.Wait(); + } + else { + struct ConnectionState { + BtdrvAddress address; + uint8_t pad[2]; + BtdrvHidConnectionStatus status; + }; + + // Signal fake disconnection event + ConnectionState disconnected = { *address, {0}, BtdrvHidConnectionStatusOld_Closed }; + SignalFakeEvent(BtdrvHidEventTypeOld_Connection, &disconnected, sizeof(disconnected)); + g_data_read_event.Wait(); + + // If we don't wait a bit the console disconnects the controller for real + svcSleepThread(100'000'000ULL); + + // Signal fake connection event + ConnectionState connected = { *address, {0}, BtdrvHidConnectionStatusOld_Opened }; + SignalFakeEvent(BtdrvHidEventTypeOld_Connection, &connected, sizeof(connected)); + g_data_read_event.Wait(); + } + + return ams::ResultSuccess(); + } + Result GetEventInfo(bluetooth::HidEventType *type, void *buffer, size_t size) { std::scoped_lock lk(g_event_info_lock); diff --git a/mc_mitm/source/bluetooth_mitm/bluetooth/bluetooth_hid.hpp b/mc_mitm/source/bluetooth_mitm/bluetooth/bluetooth_hid.hpp index 22d89446..eebfa0e5 100644 --- a/mc_mitm/source/bluetooth_mitm/bluetooth/bluetooth_hid.hpp +++ b/mc_mitm/source/bluetooth_mitm/bluetooth/bluetooth_hid.hpp @@ -28,6 +28,7 @@ namespace ams::bluetooth::hid { os::SystemEvent *GetForwardEvent(void); os::SystemEvent *GetUserForwardEvent(void); + Result VirtualReconnect(const bluetooth::Address *address); void SignalFakeEvent(bluetooth::HidEventType type, const void *data, size_t size); Result GetEventInfo(bluetooth::HidEventType *type, void *buffer, size_t size); void HandleEvent(void); diff --git a/mc_mitm/source/btm_mitm/btm_mitm_service.cpp b/mc_mitm/source/btm_mitm/btm_mitm_service.cpp index 191acc6e..89027e19 100644 --- a/mc_mitm/source/btm_mitm/btm_mitm_service.cpp +++ b/mc_mitm/source/btm_mitm/btm_mitm_service.cpp @@ -16,6 +16,7 @@ #include "btm_mitm_service.hpp" #include "btm_shim.h" #include "../controllers/controller_management.hpp" +//#include "../controllers/switch_controller.hpp" #include namespace ams::mitm::btm { @@ -24,9 +25,14 @@ namespace ams::mitm::btm { void RenameConnectedDevices(BtmConnectedDeviceV1 devices[], size_t count) { for (unsigned int i = 0; i < count; ++i) { - auto device = &devices[i]; - if (!controller::IsOfficialSwitchControllerName(device->name)) { - std::strncpy(device->name, controller::pro_controller_name, sizeof(device->name) - 1); + auto device = controller::LocateHandler(&devices[i].address); + if (device) { + if (!device->IsOfficialController()) { + if (device->GetControllerType() == controller::SwitchControllerType_ProController) + std::strncpy(devices[i].name, controller::pro_controller_name, sizeof(devices[i].name) - 1); + else + std::strncpy(devices[i].name, "Joy-Con (R)", sizeof(devices[i].name) - 1); + } } } } @@ -80,9 +86,14 @@ namespace ams::mitm::btm { R_TRY(btmGetDeviceInfoDeprecatedFwd(m_forward_service.get(), device_info)); for (unsigned int i = 0; i < device_info->device_count; ++i) { - auto device = &device_info->devices[i]; - if (!controller::IsOfficialSwitchControllerName(device->name.name)) { - std::strncpy(device->name.name, controller::pro_controller_name, sizeof(device->name) - 1); + auto device = controller::LocateHandler(&device_info->devices[i].addr); + if (device) { + if (!device->IsOfficialController()) { + if (device->GetControllerType() == controller::SwitchControllerType_ProController) + std::strncpy(device_info->devices[i].name.name, controller::pro_controller_name, sizeof(device_info->devices[i].name) - 1); + else + std::strncpy(device_info->devices[i].name.name, "Joy-Con (R)", sizeof(device_info->devices[i].name) - 1); + } } } diff --git a/mc_mitm/source/controllers/controller_management.hpp b/mc_mitm/source/controllers/controller_management.hpp index 79bd9470..8a745a5b 100644 --- a/mc_mitm/source/controllers/controller_management.hpp +++ b/mc_mitm/source/controllers/controller_management.hpp @@ -75,7 +75,7 @@ namespace ams::controller { public: UnknownController(const bluetooth::Address *address, HardwareID id) : EmulatedSwitchController(address, id) { - m_colours.buttons = {0xff, 0x00, 0x00}; + //m_colours.buttons = {0xff, 0x00, 0x00}; TODO: Replicate this behaviour with the SPI flash into account }; }; diff --git a/mc_mitm/source/controllers/controller_profiles_management.cpp b/mc_mitm/source/controllers/controller_profiles_management.cpp new file mode 100644 index 00000000..127f0032 --- /dev/null +++ b/mc_mitm/source/controllers/controller_profiles_management.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2020-2021 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 . + */ +#include "controller_profiles_management.hpp" +#include "../utils.hpp" +#include "../mcmitm_config.hpp" + +namespace ams::controller { + + namespace { + constexpr const char cp_default_path[] = "sdmc:/config/MissionControl/controllers/default.ini"; + constexpr const char controllers_folder[] = "sdmc:/config/MissionControl/controllers/"; + constexpr const char profile_container[] = "profile.ini"; + + void ValidateBrightness(const char *value, uint32_t *out){ + uint32_t temp=8; + utils::ParseUInt32(value, &temp); + if(temp <= 63) + *out = temp; + else *out = 8; + } + + void ValidateReportRate(const char *value, uint32_t *out){ + uint32_t temp=8; + utils::ParseUInt32(value, &temp); + if(temp <= 16) + *out = temp; + else *out = 8; + } + + void ValidateControllerType(const char *value, uint8_t *out){ + uint32_t temp=3; + utils::ParseUInt32(value, &temp); + if(temp>=1 && temp <= 3) + *out = static_cast(temp); + else *out = 3; + } + + int ControllerProfileIniHandler(void *user, const char *section, const char *name, const char *value) { + auto config = reinterpret_cast(user); + + if (strcasecmp(section, "general") == 0) { + if (strcasecmp(name, "controller_type") == 0) + ValidateControllerType(value, &config->general.controller_type); + else if (strcasecmp(name, "enable_rumble") == 0) + utils::ParseBoolean(value, &config->general.enable_rumble); + else if (strcasecmp(name, "enable_motion") == 0) + utils::ParseBoolean(value, &config->general.enable_motion); + } + else if (strcasecmp(section, "misc") == 0) { + if (strcasecmp(name, "use_western_layout") == 0) + utils::ParseBoolean(value, &config->misc.use_western_layout); + else if (strcasecmp(name, "sony_led_brightness") == 0) + ValidateBrightness(value, &config->misc.sony_led_brightness); + else if (strcasecmp(name, "dualshock_reportrate") == 0) + ValidateReportRate(value, &config->misc.dualshock_reportrate); + else if (strcasecmp(name, "swap_dpad_lstick") == 0) + utils::ParseBoolean(value, &config->misc.swap_dpad_lstick); + else if (strcasecmp(name, "invert_lstick_xaxis") == 0) + utils::ParseBoolean(value, &config->misc.invert_lstick_xaxis); + else if (strcasecmp(name, "invert_lstick_yaxis") == 0) + utils::ParseBoolean(value, &config->misc.invert_lstick_yaxis); + else if (strcasecmp(name, "invert_rstick_xaxis") == 0) + utils::ParseBoolean(value, &config->misc.invert_rstick_xaxis); + else if (strcasecmp(name, "invert_rstick_yaxis") == 0) + utils::ParseBoolean(value, &config->misc.invert_rstick_yaxis); + else if (strcasecmp(name, "disable_home_button") == 0) + utils::ParseBoolean(value, &config->misc.disable_home_button); + } + else { + return 0; + } + + return 1; + } + } + + Result GetControllerConfig(const bluetooth::Address *address, ControllerProfileConfig *config) { + *config = g_cp_global_config; + if (mitm::GetGlobalConfig()->general.disable_custom_profiles) + return 1; + char custom_config_path[100] = ""; + std::strcat(custom_config_path, controllers_folder); + char btaddress_string[2 * sizeof(bluetooth::Address) + 1] = ""; + utils::BluetoothAddressToString(address, btaddress_string, 2 * sizeof(bluetooth::Address) + 1); + std::strcat(custom_config_path, btaddress_string); + std::strcat(custom_config_path, "/"); + std::strcat(custom_config_path, profile_container); + bool has_file = false; + R_TRY(fs::HasFile(&has_file, custom_config_path)); + if (!has_file) { + std::strcpy(custom_config_path, cp_default_path); + } + /* Open the file. */ + fs::FileHandle file; + { + if (R_FAILED(fs::OpenFile(std::addressof(file), custom_config_path, fs::OpenMode_Read))) { + return 1; + } + } + ON_SCOPE_EXIT { fs::CloseFile(file); }; + util::ini::ParseFile(file, config, ControllerProfileIniHandler); + return ams::ResultSuccess(); + } +} diff --git a/mc_mitm/source/controllers/controller_profiles_management.hpp b/mc_mitm/source/controllers/controller_profiles_management.hpp new file mode 100644 index 00000000..e1834758 --- /dev/null +++ b/mc_mitm/source/controllers/controller_profiles_management.hpp @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020-2021 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 . + */ +#pragma once +#include +#include "../../../common/controller_profiles.hpp" +#include "../bluetooth_mitm/bluetooth/bluetooth_types.hpp" + +namespace ams::controller { + + Result GetControllerConfig(const bluetooth::Address *address, ControllerProfileConfig *config); + +} diff --git a/mc_mitm/source/controllers/dualsense_controller.cpp b/mc_mitm/source/controllers/dualsense_controller.cpp index a2b30eae..06fe12f6 100644 --- a/mc_mitm/source/controllers/dualsense_controller.cpp +++ b/mc_mitm/source/controllers/dualsense_controller.cpp @@ -35,19 +35,17 @@ namespace ams::controller { 0x0A }; - const constexpr RGBColour led_disable = {0x00, 0x00, 0x00}; - const RGBColour player_led_colours[] = { // Same colours used by PS4 - {0x00, 0x00, 0x40}, // blue - {0x40, 0x00, 0x00}, // red - {0x00, 0x40, 0x00}, // green - {0x20, 0x00, 0x20}, // pink + {0x00, 0x00, 0x04}, // blue + {0x04, 0x00, 0x00}, // red + {0x00, 0x04, 0x00}, // green + {0x02, 0x00, 0x02}, // pink // New colours for controllers 5-8 - {0x00, 0x20, 0x20}, // cyan - {0x30, 0x10, 0x00}, // orange - {0x20, 0x20, 0x00}, // yellow - {0x10, 0x00, 0x30} // purple + {0x00, 0x02, 0x02}, // cyan + {0x03, 0x01, 0x00}, // orange + {0x02, 0x02, 0x00}, // yellow + {0x01, 0x00, 0x03} // purple }; } @@ -80,8 +78,9 @@ namespace ams::controller { } Result DualsenseController::SetLightbarColour(RGBColour colour) { - auto config = mitm::GetGlobalConfig(); - m_led_colour = config->misc.disable_sony_leds ? led_disable : colour; + m_led_colour.r = colour.r * m_profile.misc.sony_led_brightness; + m_led_colour.g = colour.g * m_profile.misc.sony_led_brightness; + m_led_colour.b = colour.b * m_profile.misc.sony_led_brightness; return this->PushRumbleLedState(); } @@ -128,7 +127,7 @@ namespace ams::controller { battery_level = 10; m_battery = static_cast(8 * (battery_level + 1) / 10) & 0x0e; - + m_left_stick.SetData( static_cast(stick_scale_factor * src->input0x31.left_stick.x) & 0xfff, static_cast(stick_scale_factor * (UINT8_MAX - src->input0x31.left_stick.y)) & 0xfff diff --git a/mc_mitm/source/controllers/dualshock4_controller.cpp b/mc_mitm/source/controllers/dualshock4_controller.cpp index 068284d9..b9206404 100644 --- a/mc_mitm/source/controllers/dualshock4_controller.cpp +++ b/mc_mitm/source/controllers/dualshock4_controller.cpp @@ -25,19 +25,17 @@ namespace ams::controller { const constexpr float stick_scale_factor = float(UINT12_MAX) / UINT8_MAX; - const constexpr RGBColour led_disable = {0x00, 0x00, 0x00}; - const RGBColour player_led_colours[] = { // Same colours used by PS4 - {0x00, 0x00, 0x40}, // blue - {0x40, 0x00, 0x00}, // red - {0x00, 0x40, 0x00}, // green - {0x20, 0x00, 0x20}, // pink + {0x00, 0x00, 0x04}, // blue + {0x04, 0x00, 0x00}, // red + {0x00, 0x04, 0x00}, // green + {0x02, 0x00, 0x02}, // pink // New colours for controllers 5-8 - {0x00, 0x20, 0x20}, // cyan - {0x30, 0x10, 0x00}, // orange - {0x20, 0x20, 0x00}, // yellow - {0x10, 0x00, 0x30} // purple + {0x00, 0x02, 0x02}, // cyan + {0x03, 0x01, 0x00}, // orange + {0x02, 0x02, 0x00}, // yellow + {0x01, 0x00, 0x03} // purple }; } @@ -45,7 +43,7 @@ namespace ams::controller { Result Dualshock4Controller::Initialize(void) { R_TRY(this->PushRumbleLedState()); R_TRY(EmulatedSwitchController::Initialize()); - + return ams::ResultSuccess(); } @@ -69,8 +67,9 @@ namespace ams::controller { } Result Dualshock4Controller::SetLightbarColour(RGBColour colour) { - auto config = mitm::GetGlobalConfig(); - m_led_colour = config->misc.disable_sony_leds ? led_disable : colour; + m_led_colour.r = colour.r * m_profile.misc.sony_led_brightness; + m_led_colour.g = colour.g * m_profile.misc.sony_led_brightness; + m_led_colour.b = colour.b * m_profile.misc.sony_led_brightness; return this->PushRumbleLedState(); } @@ -89,7 +88,7 @@ namespace ams::controller { } } - void Dualshock4Controller::HandleInputReport0x01(const Dualshock4ReportData *src) { + void Dualshock4Controller::HandleInputReport0x01(const Dualshock4ReportData *src) { m_left_stick.SetData( static_cast(stick_scale_factor * src->input0x01.left_stick.x) & 0xfff, static_cast(stick_scale_factor * (UINT8_MAX - src->input0x01.left_stick.y)) & 0xfff @@ -165,7 +164,7 @@ namespace ams::controller { } Result Dualshock4Controller::PushRumbleLedState(void) { - Dualshock4OutputReport0x11 report = {0xa2, 0x11, static_cast(0xc0 | (m_report_rate & 0xff)), 0x20, 0xf3, 0x04, 0x00, + Dualshock4OutputReport0x11 report = {0xa2, 0x11, static_cast(0xc0 | (static_cast(m_profile.misc.dualshock_reportrate) & 0xff)), 0x20, 0xf3, 0x04, 0x00, m_rumble_state.amp_motor_right, m_rumble_state.amp_motor_left, m_led_colour.r, m_led_colour.g, m_led_colour.b }; diff --git a/mc_mitm/source/controllers/dualshock4_controller.hpp b/mc_mitm/source/controllers/dualshock4_controller.hpp index df2dc45f..858594d0 100644 --- a/mc_mitm/source/controllers/dualshock4_controller.hpp +++ b/mc_mitm/source/controllers/dualshock4_controller.hpp @@ -67,7 +67,7 @@ namespace ams::controller { uint8_t cross : 1; uint8_t circle : 1; uint8_t triangle : 1; - + uint8_t L1 : 1; uint8_t R1 : 1; uint8_t L2 : 1; @@ -76,7 +76,7 @@ namespace ams::controller { uint8_t options : 1; uint8_t L3 : 1; uint8_t R3 : 1; - + uint8_t ps : 1; uint8_t tpad : 1; uint8_t counter : 6; @@ -151,7 +151,6 @@ namespace ams::controller { Dualshock4Controller(const bluetooth::Address *address, HardwareID id) : EmulatedSwitchController(address, id) - , m_report_rate(Dualshock4ReportRate_125Hz) , m_led_colour({0, 0, 0}) , m_rumble_state({0, 0}) { } @@ -168,11 +167,10 @@ namespace ams::controller { void HandleInputReport0x11(const Dualshock4ReportData *src); void MapButtons(const Dualshock4ButtonData *buttons); - + Result PushRumbleLedState(void); - Dualshock4ReportRate m_report_rate; - RGBColour m_led_colour; + RGBColour m_led_colour; Dualshock4RumbleData m_rumble_state; }; diff --git a/mc_mitm/source/controllers/emulated_switch_controller.cpp b/mc_mitm/source/controllers/emulated_switch_controller.cpp index 754480c6..d272d0e8 100644 --- a/mc_mitm/source/controllers/emulated_switch_controller.cpp +++ b/mc_mitm/source/controllers/emulated_switch_controller.cpp @@ -18,10 +18,15 @@ #include "../mcmitm_config.hpp" #include +#include "../bluetooth_mitm/bluetooth/bluetooth_hid.hpp" + namespace ams::controller { namespace { + constexpr auto DPAD_THRESHOLD_BEGIN = STICK_ZERO - UINT12_MAX / 4; + constexpr auto DPAD_THRESHOLD_END = STICK_ZERO + UINT12_MAX / 4; + // Factory calibration data representing analog stick ranges that span the entire 12-bit data type in x and y SwitchAnalogStickFactoryCalibration lstick_factory_calib = {0xff, 0xf7, 0x7f, 0x00, 0x08, 0x80, 0x00, 0x08, 0x80}; SwitchAnalogStickFactoryCalibration rstick_factory_calib = {0x00, 0x08, 0x80, 0x00, 0x08, 0x80, 0xff, 0xf7, 0x7f}; @@ -140,6 +145,37 @@ namespace ams::controller { return ams::ResultSuccess(); } + + const uint8_t mcu_config_pro_controller[] = { + 0x01, 0x00, 0xff, 0x00, 0x03, 0x00, 0x05, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x5c + }; + + const uint8_t mcu_config_joycon[] = { + 0x01, 0x00, 0xff, 0x00, 0x08, 0x00, 0x1b, 0x06, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xf6 + }; + + const SwitchControllerColours default_colours_pro_controller = { + .body = {0x32, 0x32, 0x32}, + .buttons = {0xe6, 0xe6, 0xe6}, + .left_grip = {0x46, 0x46, 0x46}, + .right_grip = {0x46, 0x46, 0x46} + }; + + const SwitchControllerColours default_colours_joycon = { + .body = {0x82, 0x82, 0x82}, + .buttons = {0x0f, 0x0f, 0x0f}, + .left_grip = {0xff, 0xff, 0xff}, + .right_grip = {0xff, 0xff, 0xff} + }; + } EmulatedSwitchController::EmulatedSwitchController(const bluetooth::Address *address, HardwareID id) @@ -149,15 +185,9 @@ namespace ams::controller { , m_battery(BATTERY_MAX) , m_led_pattern(0) { this->ClearControllerState(); + GetControllerConfig(address, &m_profile); - m_colours.body = {0x32, 0x32, 0x32}; - m_colours.buttons = {0xe6, 0xe6, 0xe6}; - m_colours.left_grip = {0x46, 0x46, 0x46}; - m_colours.right_grip = {0x46, 0x46, 0x46}; - - auto config = mitm::GetGlobalConfig(); - - m_enable_rumble = config->general.enable_rumble; + m_enable_rumble = m_profile.general.enable_rumble; }; EmulatedSwitchController::~EmulatedSwitchController() { @@ -211,8 +241,110 @@ namespace ams::controller { switch_report->input0x30.right_stick = m_right_stick; std::memcpy(&switch_report->input0x30.motion, &m_motion_data, sizeof(m_motion_data)); + if (m_profile.misc.disable_home_button) + switch_report->input0x30.buttons.home = 0; + this->ApplyButtonCombos(&switch_report->input0x30.buttons); + if(m_profile.misc.use_western_layout) { + uint8_t temp = switch_report->input0x30.buttons.A; + switch_report->input0x30.buttons.A = switch_report->input0x30.buttons.B; + switch_report->input0x30.buttons.B = temp; + temp = switch_report->input0x30.buttons.X; + switch_report->input0x30.buttons.X = switch_report->input0x30.buttons.Y; + switch_report->input0x30.buttons.Y = temp; + } + + if (m_profile.misc.swap_dpad_lstick) { + uint16_t temp_lstick_x = STICK_ZERO; //Start in a neutral position + uint16_t temp_lstick_y = STICK_ZERO; + if (switch_report->input0x30.buttons.dpad_down) + temp_lstick_y -= (UINT12_MAX / 2); + if (switch_report->input0x30.buttons.dpad_up) //Should be if else, but some controllers don't have an actual dpad, so both states are allowed + temp_lstick_y += (UINT12_MAX / 2); + if (switch_report->input0x30.buttons.dpad_right) + temp_lstick_x += (UINT12_MAX / 2); + if (switch_report->input0x30.buttons.dpad_left) + temp_lstick_x -= (UINT12_MAX / 2); + + switch_report->input0x30.buttons.dpad_left = switch_report->input0x30.left_stick.GetX() < DPAD_THRESHOLD_BEGIN ? 1 : 0; + switch_report->input0x30.buttons.dpad_right = switch_report->input0x30.left_stick.GetX() > DPAD_THRESHOLD_END ? 1 : 0; + switch_report->input0x30.buttons.dpad_down = switch_report->input0x30.left_stick.GetY() < DPAD_THRESHOLD_BEGIN ? 1 : 0; + switch_report->input0x30.buttons.dpad_up = switch_report->input0x30.left_stick.GetY() > DPAD_THRESHOLD_END ? 1 : 0; + + switch_report->input0x30.left_stick.SetData(temp_lstick_x, temp_lstick_y); + } + + if (m_profile.misc.invert_lstick_xaxis) + switch_report->input0x30.left_stick.InvertX(); + if (m_profile.misc.invert_lstick_yaxis) + switch_report->input0x30.left_stick.InvertY(); + if (m_profile.misc.invert_rstick_xaxis) + switch_report->input0x30.right_stick.InvertX(); + if (m_profile.misc.invert_rstick_yaxis) + switch_report->input0x30.right_stick.InvertY(); + + uint8_t playernumber = 0xff; + HidNpadSystemProperties out; + switch (m_profile.general.controller_type) { + case SwitchControllerType_RightJoyCon: + switch_report->input0x30.buttons.SL_R = switch_report->input0x30.buttons.L | switch_report->input0x30.buttons.ZL; + switch_report->input0x30.buttons.SR_R = switch_report->input0x30.buttons.R | switch_report->input0x30.buttons.ZR; + LedsMaskToPlayerNumber(m_led_pattern, &playernumber); //There might be a better way to do this, but as of now, it works + if(playernumber<8){ + hidGetNpadSystemProperties(static_cast(playernumber), &out); + + if(out.is_sl_sr_button_oriented){ + if (switch_report->input0x30.buttons.dpad_down | switch_report->input0x30.buttons.dpad_up | switch_report->input0x30.buttons.dpad_right | switch_report->input0x30.buttons.dpad_left){ + switch_report->input0x30.right_stick.SetData( + switch_report->input0x30.buttons.dpad_down ? UINT12_MAX : (switch_report->input0x30.buttons.dpad_up ? 0 : STICK_ZERO), + switch_report->input0x30.buttons.dpad_right ? UINT12_MAX : (switch_report->input0x30.buttons.dpad_left ? 0 : STICK_ZERO) + ); + } + else { + switch_report->input0x30.left_stick.InvertY(); + switch_report->input0x30.right_stick.SetData(switch_report->input0x30.left_stick.GetY(), switch_report->input0x30.left_stick.GetX()); + } + uint8_t temp = switch_report->input0x30.buttons.Y; + switch_report->input0x30.buttons.Y = switch_report->input0x30.buttons.X; + switch_report->input0x30.buttons.X = switch_report->input0x30.buttons.A; + switch_report->input0x30.buttons.A = switch_report->input0x30.buttons.B; + switch_report->input0x30.buttons.B = temp; + + } + else { + switch_report->input0x30.right_stick.SetData(switch_report->input0x30.left_stick.GetX(), switch_report->input0x30.left_stick.GetY()); + } + } + break; + case SwitchControllerType_LeftJoyCon: + switch_report->input0x30.buttons.SL_L = switch_report->input0x30.buttons.L | switch_report->input0x30.buttons.ZL; + switch_report->input0x30.buttons.SR_L = switch_report->input0x30.buttons.R | switch_report->input0x30.buttons.ZR; + LedsMaskToPlayerNumber(m_led_pattern, &playernumber); + if(playernumber<8){ + hidGetNpadSystemProperties(static_cast(playernumber), &out); + if(out.is_sl_sr_button_oriented){ + if (switch_report->input0x30.buttons.dpad_down | switch_report->input0x30.buttons.dpad_up | switch_report->input0x30.buttons.dpad_right | switch_report->input0x30.buttons.dpad_left){ + switch_report->input0x30.left_stick.SetData( + switch_report->input0x30.buttons.dpad_up ? UINT12_MAX : (switch_report->input0x30.buttons.dpad_down ? 0 : STICK_ZERO), + switch_report->input0x30.buttons.dpad_left ? UINT12_MAX : (switch_report->input0x30.buttons.dpad_right ? 0 : STICK_ZERO) + ); + } + else { + switch_report->input0x30.left_stick.InvertX(); + switch_report->input0x30.left_stick.SetData(switch_report->input0x30.left_stick.GetY(), switch_report->input0x30.left_stick.GetX()); + } + switch_report->input0x30.buttons.dpad_down = switch_report->input0x30.buttons.A; + switch_report->input0x30.buttons.dpad_left = switch_report->input0x30.buttons.B; + switch_report->input0x30.buttons.dpad_up = switch_report->input0x30.buttons.Y; + switch_report->input0x30.buttons.dpad_right = switch_report->input0x30.buttons.X; + } + } + break; + default: + break; + } + switch_report->input0x30.timer = os::ConvertToTimeSpan(os::GetSystemTick()).GetMilliSeconds() & 0xff; return bluetooth::hid::report::WriteHidReportBuffer(&m_address, &m_input_report); } @@ -327,19 +459,21 @@ namespace ams::controller { Result EmulatedSwitchController::SubCmdRequestDeviceInfo(const bluetooth::HidReport *report) { AMS_UNUSED(report); + const FirmwareVersion fw = (m_profile.general.controller_type == SwitchControllerType_ProController) ? pro_controller_fw_version : joycon_fw_version; + const SwitchSubcommandResponse response = { .ack = 0x82, .id = SubCmd_RequestDeviceInfo, .device_info = { .fw_ver = { - .major = 0x03, - .minor = 0x48 + .major = fw.major, + .minor = fw.minor }, - .type = 0x03, + .type = m_profile.general.controller_type, ._unk0 = 0x02, .address = m_address, - ._unk1 = 0x01, - ._unk2 = 0x02 + ._unk1 = static_cast(m_profile.general.controller_type == SwitchControllerType_LeftJoyCon ? 0x03 : 0x01), + ._unk2 = static_cast(m_profile.general.controller_type == SwitchControllerType_ProController ? 0x02 : 0x01) } }; @@ -475,14 +609,14 @@ namespace ams::controller { .id = SubCmd_0x25, .data = { 0x00 } }; - + return this->FakeSubCmdResponse(&response); } Result EmulatedSwitchController::SubCmdSetMcuConfig(const bluetooth::HidReport *report) { AMS_UNUSED(report); - const SwitchSubcommandResponse response = { + SwitchSubcommandResponse response = { .ack = 0xa0, .id = SubCmd_SetMcuConfig, .data = {0x01, 0x00, 0xff, 0x00, 0x03, 0x00, 0x05, 0x01, @@ -492,6 +626,11 @@ namespace ams::controller { 0x00, 0x5c} }; + if (m_profile.general.controller_type == SwitchControllerType_ProController) + std::memcpy(response.data, mcu_config_pro_controller, sizeof(mcu_config_pro_controller)); + else + std::memcpy(response.data, mcu_config_joycon, sizeof(mcu_config_joycon)); + return this->FakeSubCmdResponse(&response); } @@ -559,7 +698,7 @@ namespace ams::controller { Result EmulatedSwitchController::SubCmdEnableVibration(const bluetooth::HidReport *report) { auto switch_report = reinterpret_cast(&report->data); - m_enable_rumble = mitm::GetGlobalConfig()->general.enable_rumble & switch_report->output0x01.subcmd.set_vibration.enabled; + m_enable_rumble = m_profile.general.enable_rumble & switch_report->output0x01.subcmd.set_vibration.enabled; const SwitchSubcommandResponse response = { .ack = 0x80, diff --git a/mc_mitm/source/controllers/emulated_switch_controller.hpp b/mc_mitm/source/controllers/emulated_switch_controller.hpp index 8d374235..f5a7b1b2 100644 --- a/mc_mitm/source/controllers/emulated_switch_controller.hpp +++ b/mc_mitm/source/controllers/emulated_switch_controller.hpp @@ -30,6 +30,7 @@ namespace ams::controller { virtual Result Initialize(void); bool IsOfficialController(void) { return false; } + SwitchControllerType GetControllerType(void) { return static_cast(m_profile.general.controller_type); }; Result HandleIncomingReport(const bluetooth::HidReport *report); Result HandleOutgoingReport(const bluetooth::HidReport *report); @@ -77,7 +78,6 @@ namespace ams::controller { SwitchAnalogStick m_right_stick; Switch6AxisData m_motion_data[3]; - ProControllerColours m_colours; bool m_enable_rumble; fs::FileHandle m_spi_flash_file; diff --git a/mc_mitm/source/controllers/switch_controller.hpp b/mc_mitm/source/controllers/switch_controller.hpp index 6684c48d..1d0411de 100644 --- a/mc_mitm/source/controllers/switch_controller.hpp +++ b/mc_mitm/source/controllers/switch_controller.hpp @@ -15,6 +15,7 @@ */ #pragma once #include "switch_analog_stick.hpp" +#include "controller_profiles_management.hpp" #include "../bluetooth_mitm/bluetooth/bluetooth_types.hpp" #include "../bluetooth_mitm/bluetooth/bluetooth_hid_report.hpp" @@ -32,6 +33,17 @@ namespace ams::controller { SwitchPlayerNumber_Seven, SwitchPlayerNumber_Eight, SwitchPlayerNumber_Unknown = 0xf + }; + + enum SwitchControllerType : uint8_t { + SwitchControllerType_LeftJoyCon = 1, + SwitchControllerType_RightJoyCon = 2, + SwitchControllerType_ProController = 3, + }; + + struct FirmwareVersion { + uint8_t major; + uint8_t minor; }; struct HardwareID { @@ -45,19 +57,20 @@ namespace ams::controller { uint8_t b; } __attribute__ ((__packed__)); - struct ProControllerColours { + struct SwitchControllerColours { RGBColour body; RGBColour buttons; RGBColour left_grip; RGBColour right_grip; } __attribute__ ((__packed__)); - + struct SwitchButtonData { uint8_t Y : 1; uint8_t X : 1; uint8_t B : 1; uint8_t A : 1; - uint8_t : 2; // SR, SL (Right Joy) + uint8_t SR_R : 1; + uint8_t SL_R : 1; uint8_t R : 1; uint8_t ZR : 1; @@ -73,7 +86,8 @@ namespace ams::controller { uint8_t dpad_up : 1; uint8_t dpad_right : 1; uint8_t dpad_left : 1; - uint8_t : 2; // SR, SL (Left Joy) + uint8_t SR_L : 1; + uint8_t SL_L : 1; uint8_t L : 1; uint8_t ZL : 1; } __attribute__ ((__packed__)); @@ -286,12 +300,15 @@ namespace ams::controller { Result LedsMaskToPlayerNumber(uint8_t led_mask, uint8_t *player_number); + constexpr const FirmwareVersion joycon_fw_version = {0x04, 0x07}; + constexpr const FirmwareVersion pro_controller_fw_version = {0x03, 0x48}; + std::string GetControllerDirectory(const bluetooth::Address *address); class SwitchController { - public: - static constexpr const HardwareID hardware_ids[] = { + public: + static constexpr const HardwareID hardware_ids[] = { {0x057e, 0x2006}, // Official Joycon(L) Controller {0x057e, 0x2007}, // Official Joycon(R) Controller/NES Online Controller {0x057e, 0x2009}, // Official Switch Pro Controller @@ -311,6 +328,7 @@ namespace ams::controller { virtual bool IsOfficialController(void) { return true; } virtual bool SupportsSetTsiCommand(void) { return m_settsi_supported; } + virtual SwitchControllerType GetControllerType(void) { return SwitchControllerType_ProController; }; //.Todo: handle this properly for official controllers virtual Result Initialize(void); virtual Result HandleIncomingReport(const bluetooth::HidReport *report); @@ -326,6 +344,7 @@ namespace ams::controller { HardwareID m_id; bool m_settsi_supported; + ControllerProfileConfig m_profile; bluetooth::HidReport m_input_report; bluetooth::HidReport m_output_report; diff --git a/mc_mitm/source/mcmitm_config.cpp b/mc_mitm/source/mcmitm_config.cpp index cbc8b112..99284205 100644 --- a/mc_mitm/source/mcmitm_config.cpp +++ b/mc_mitm/source/mcmitm_config.cpp @@ -16,6 +16,7 @@ #include #include #include "mcmitm_config.hpp" +#include "utils.hpp" namespace ams::mitm { @@ -25,59 +26,22 @@ namespace ams::mitm { MissionControlConfig g_global_config = { .general = { - .enable_rumble = true, - .enable_motion = true - }, - .misc = { - .disable_sony_leds = false + .disable_custom_profiles=false } }; - void ParseBoolean(const char *value, bool *out) { - if (strcasecmp(value, "true") == 0) - *out = true; - else if (strcasecmp(value, "false") == 0) - *out = false; - } - - void ParseBluetoothAddress(const char *value, bluetooth::Address *out) { - // Check length of address string is correct - if (std::strlen(value) != 3*sizeof(bluetooth::Address) - 1) return; - - // Parse bluetooth mac address - char buf[2 + 1]; - bluetooth::Address address = {}; - for (uint32_t i = 0; i < sizeof(bluetooth::Address); ++i) { - // Convert hex pair to number - std::memcpy(buf, &value[i*3], 2); - address.address[i] = static_cast(std::strtoul(buf, nullptr, 16)); - - // Check for colon separator - if ((i < sizeof(bluetooth::Address) - 1) && (value[i*3 + 2] != ':')) - return; - } - - *out = address; - } - int ConfigIniHandler(void *user, const char *section, const char *name, const char *value) { auto config = reinterpret_cast(user); if (strcasecmp(section, "general") == 0) { - if (strcasecmp(name, "enable_rumble") == 0) - ParseBoolean(value, &config->general.enable_rumble); - else if (strcasecmp(name, "enable_motion") == 0) - ParseBoolean(value, &config->general.enable_motion); + if (strcasecmp(name, "disable_custom_profiles") == 0) + utils::ParseBoolean(value, &config->general.disable_custom_profiles); } else if (strcasecmp(section, "bluetooth") == 0) { if (strcasecmp(name, "host_name") == 0) std::strncpy(config->bluetooth.host_name, value, sizeof(config->bluetooth.host_name)); else if (strcasecmp(name, "host_address") == 0) - ParseBluetoothAddress(value, &config->bluetooth.host_address); - } - else if (strcasecmp(section, "misc") == 0) { - if (strcasecmp(name, "disable_sony_leds") == 0) - ParseBoolean(value, &config->misc.disable_sony_leds); + utils::ParseBluetoothAddress(value, &config->bluetooth.host_address); } else { return 0; diff --git a/mc_mitm/source/mcmitm_config.hpp b/mc_mitm/source/mcmitm_config.hpp index 3555efd0..bb60d46a 100644 --- a/mc_mitm/source/mcmitm_config.hpp +++ b/mc_mitm/source/mcmitm_config.hpp @@ -18,19 +18,14 @@ namespace ams::mitm { struct MissionControlConfig { - struct { - bool enable_rumble; - bool enable_motion; + struct{ + bool disable_custom_profiles; } general; struct { char host_name[0x20]; bluetooth::Address host_address; } bluetooth; - - struct { - bool disable_sony_leds; - } misc; }; MissionControlConfig *GetGlobalConfig(void); diff --git a/mc_mitm/source/mcmitm_initialization.cpp b/mc_mitm/source/mcmitm_initialization.cpp index 15d72f7b..3c30ad3a 100644 --- a/mc_mitm/source/mcmitm_initialization.cpp +++ b/mc_mitm/source/mcmitm_initialization.cpp @@ -79,6 +79,7 @@ namespace ams::mitm { } g_init_event.Signal(); + R_ABORT_UNLESS(hidInitialize()); } } diff --git a/mc_mitm/source/utils.cpp b/mc_mitm/source/utils.cpp index df3ddf3f..9f5c967f 100644 --- a/mc_mitm/source/utils.cpp +++ b/mc_mitm/source/utils.cpp @@ -53,4 +53,35 @@ namespace ams::utils { return ams::ResultSuccess(); } + void ParseBoolean(const char *value, bool *out) { + if (strcasecmp(value, "true") == 0) + *out = true; + else if (strcasecmp(value, "false") == 0) + *out = false; + } + + void ParseUInt32(const char *value, uint32_t *out) { + *out = atoi(value); + } + + void ParseBluetoothAddress(const char *value, bluetooth::Address *out) { + // Check length of address string is correct + if (std::strlen(value) != 3*sizeof(bluetooth::Address) - 1) return; + + // Parse bluetooth mac address + char buf[2 + 1]; + bluetooth::Address address = {}; + for (uint32_t i = 0; i < sizeof(bluetooth::Address); ++i) { + // Convert hex pair to number + std::memcpy(buf, &value[i*3], 2); + address.address[i] = static_cast(std::strtoul(buf, nullptr, 16)); + + // Check for colon separator + if ((i < sizeof(bluetooth::Address) - 1) && (value[i*3 + 2] != ':')) + return; + } + + *out = address; + } + } diff --git a/mc_mitm/source/utils.hpp b/mc_mitm/source/utils.hpp index 2b18ea39..0dba3687 100644 --- a/mc_mitm/source/utils.hpp +++ b/mc_mitm/source/utils.hpp @@ -22,5 +22,8 @@ namespace ams::utils { s32 ConvertToUserPriority(s32 horizon_priority); Result BluetoothAddressToString(const bluetooth::Address *address, char *out, size_t out_size); + void ParseBoolean(const char *value, bool *out); + void ParseUInt32(const char *value, uint32_t *out); + void ParseBluetoothAddress(const char *value, bluetooth::Address *out); }