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);
}