mirror of
https://github.com/ndeadly/MissionControl
synced 2025-02-27 12:47:15 +00:00
607 lines
26 KiB
C++
607 lines
26 KiB
C++
/*
|
|
* Copyright (c) 2020-2023 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 "emulated_switch_controller.hpp"
|
|
#include "../mcmitm_config.hpp"
|
|
|
|
namespace ams::controller {
|
|
|
|
namespace {
|
|
|
|
// Frequency in Hz rounded to nearest int
|
|
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md#frequency-table
|
|
const u16 rumble_freq_lut[] = {
|
|
0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f, 0x0030, 0x0031,
|
|
0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0039, 0x003a, 0x003b,
|
|
0x003c, 0x003e, 0x003f, 0x0040, 0x0042, 0x0043, 0x0045, 0x0046, 0x0048,
|
|
0x0049, 0x004b, 0x004d, 0x004e, 0x0050, 0x0052, 0x0054, 0x0055, 0x0057,
|
|
0x0059, 0x005b, 0x005d, 0x005f, 0x0061, 0x0063, 0x0066, 0x0068, 0x006a,
|
|
0x006c, 0x006f, 0x0071, 0x0074, 0x0076, 0x0079, 0x007b, 0x007e, 0x0081,
|
|
0x0084, 0x0087, 0x0089, 0x008d, 0x0090, 0x0093, 0x0096, 0x0099, 0x009d,
|
|
0x00a0, 0x00a4, 0x00a7, 0x00ab, 0x00ae, 0x00b2, 0x00b6, 0x00ba, 0x00be,
|
|
0x00c2, 0x00c7, 0x00cb, 0x00cf, 0x00d4, 0x00d9, 0x00dd, 0x00e2, 0x00e7,
|
|
0x00ec, 0x00f1, 0x00f7, 0x00fc, 0x0102, 0x0107, 0x010d, 0x0113, 0x0119,
|
|
0x011f, 0x0125, 0x012c, 0x0132, 0x0139, 0x0140, 0x0147, 0x014e, 0x0155,
|
|
0x015d, 0x0165, 0x016c, 0x0174, 0x017d, 0x0185, 0x018d, 0x0196, 0x019f,
|
|
0x01a8, 0x01b1, 0x01bb, 0x01c5, 0x01ce, 0x01d9, 0x01e3, 0x01ee, 0x01f8,
|
|
0x0203, 0x020f, 0x021a, 0x0226, 0x0232, 0x023e, 0x024b, 0x0258, 0x0265,
|
|
0x0272, 0x0280, 0x028e, 0x029c, 0x02ab, 0x02ba, 0x02c9, 0x02d9, 0x02e9,
|
|
0x02f9, 0x030a, 0x031b, 0x032c, 0x033e, 0x0350, 0x0363, 0x0376, 0x0389,
|
|
0x039d, 0x03b1, 0x03c6, 0x03db, 0x03f1, 0x0407, 0x041d, 0x0434, 0x044c,
|
|
0x0464, 0x047d, 0x0496, 0x04af, 0x04ca, 0x04e5
|
|
};
|
|
constexpr size_t rumble_freq_lut_size = sizeof(rumble_freq_lut) / sizeof(u16);
|
|
|
|
// Floats from dekunukem repo normalised and scaled by function used by yuzu
|
|
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md#amplitude-table
|
|
// https://github.com/yuzu-emu/yuzu/blob/d3a4a192fe26e251f521f0311b2d712f5db9918e/src/input_common/sdl/sdl_impl.cpp#L429
|
|
const float rumble_amp_lut_f[] = {
|
|
0.000000, 0.120576, 0.137846, 0.146006, 0.154745, 0.164139, 0.174246,
|
|
0.185147, 0.196927, 0.209703, 0.223587, 0.238723, 0.255268, 0.273420,
|
|
0.293398, 0.315462, 0.321338, 0.327367, 0.333557, 0.339913, 0.346441,
|
|
0.353145, 0.360034, 0.367112, 0.374389, 0.381870, 0.389564, 0.397476,
|
|
0.405618, 0.413996, 0.422620, 0.431501, 0.436038, 0.440644, 0.445318,
|
|
0.450062, 0.454875, 0.459764, 0.464726, 0.469763, 0.474876, 0.480068,
|
|
0.485342, 0.490694, 0.496130, 0.501649, 0.507256, 0.512950, 0.518734,
|
|
0.524609, 0.530577, 0.536639, 0.542797, 0.549055, 0.555413, 0.561872,
|
|
0.568436, 0.575106, 0.581886, 0.588775, 0.595776, 0.602892, 0.610127,
|
|
0.617482, 0.624957, 0.632556, 0.640283, 0.648139, 0.656126, 0.664248,
|
|
0.672507, 0.680906, 0.689447, 0.698135, 0.706971, 0.715957, 0.725098,
|
|
0.734398, 0.743857, 0.753481, 0.763273, 0.773235, 0.783370, 0.793684,
|
|
0.804178, 0.814858, 0.825726, 0.836787, 0.848044, 0.859502, 0.871165,
|
|
0.883035, 0.895119, 0.907420, 0.919943, 0.932693, 0.945673, 0.958889,
|
|
0.972345, 0.986048, 1.000000
|
|
};
|
|
constexpr size_t rumble_amp_lut_f_size = sizeof(rumble_amp_lut_f) / sizeof(float);
|
|
|
|
void DecodeRumbleValues(const u8 enc[], SwitchRumbleData *dec) {
|
|
u8 hi_freq_ind = 0x20 + (enc[0] >> 2) + ((enc[1] & 0x01) * 0x40) - 1;
|
|
u8 hi_amp_ind = (enc[1] & 0xfe) >> 1;
|
|
u8 lo_freq_ind = (enc[2] & 0x7f) - 1;
|
|
u8 lo_amp_ind = ((enc[3] - 0x40) << 1) + ((enc[2] & 0x80) >> 7);
|
|
|
|
if (!((hi_freq_ind < rumble_freq_lut_size) &&
|
|
(hi_amp_ind < rumble_amp_lut_f_size) &&
|
|
(lo_freq_ind < rumble_freq_lut_size) &&
|
|
(lo_amp_ind < rumble_amp_lut_f_size))) {
|
|
std::memset(dec, 0, sizeof(SwitchRumbleData));
|
|
return;
|
|
}
|
|
|
|
dec->high_band_freq = float(rumble_freq_lut[hi_freq_ind]);
|
|
dec->high_band_amp = rumble_amp_lut_f[hi_amp_ind];
|
|
dec->low_band_freq = float(rumble_freq_lut[lo_freq_ind]);
|
|
dec->low_band_amp = rumble_amp_lut_f[lo_amp_ind];
|
|
}
|
|
|
|
// CRC-8 with polynomial 0x7 for NFC/IR packets
|
|
u8 crc8_lut[] = {
|
|
0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
|
|
0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
|
|
0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
|
|
0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
|
|
0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
|
|
0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
|
|
0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
|
|
0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
|
|
0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
|
|
0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
|
|
0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
|
|
0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
|
|
0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
|
|
0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
|
|
0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
|
|
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3
|
|
};
|
|
|
|
u8 ComputeCrc8(const void *data, size_t size) {
|
|
auto *bytes = reinterpret_cast<const u8 *>(data);
|
|
|
|
u8 crc = 0x00;
|
|
for (size_t i = 0; i < size; ++i) {
|
|
crc = crc8_lut[crc ^ bytes[i]];
|
|
}
|
|
return crc;
|
|
}
|
|
|
|
}
|
|
|
|
EmulatedSwitchController::EmulatedSwitchController(const bluetooth::Address *address, HardwareID id)
|
|
: SwitchController(address, id)
|
|
, m_charging(false)
|
|
, m_ext_power(false)
|
|
, m_battery(BATTERY_MAX)
|
|
, m_led_pattern(0)
|
|
, m_gyro_sensitivity(2000)
|
|
, m_acc_sensitivity(8000)
|
|
, m_input_report_mode(0x30) {
|
|
this->ClearControllerState();
|
|
|
|
auto config = mitm::GetGlobalConfig();
|
|
m_enable_rumble = config->general.enable_rumble;
|
|
m_enable_motion = config->general.enable_motion;
|
|
m_trigger_threshold = config->misc.analog_trigger_activation_threshold / 100.0;
|
|
};
|
|
|
|
Result EmulatedSwitchController::Initialize() {
|
|
R_TRY(SwitchController::Initialize());
|
|
|
|
// Ensure config directory for this controller exists
|
|
std::string controller_dir = GetControllerDirectory(&m_address);
|
|
R_TRY(fs::EnsureDirectory(controller_dir.c_str()));
|
|
|
|
R_TRY(m_virtual_memory.Initialize((controller_dir + "/spi_flash.bin").c_str()));
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
void EmulatedSwitchController::ClearControllerState() {
|
|
std::memset(&m_buttons, 0, sizeof(m_buttons));
|
|
m_left_stick.SetData(STICK_CENTER, STICK_CENTER);
|
|
m_right_stick.SetData(STICK_CENTER, STICK_CENTER);
|
|
std::memset(&m_motion_data, 0, sizeof(m_motion_data));
|
|
}
|
|
|
|
void EmulatedSwitchController::UpdateControllerState(const bluetooth::HidReport *report) {
|
|
this->ProcessInputData(report);
|
|
|
|
auto input_report = reinterpret_cast<SwitchInputReport *>(m_input_report.data);
|
|
input_report->id = 0x30;
|
|
input_report->timer = (input_report->timer + 1) & 0xff;
|
|
input_report->conn_info = (0 << 1) | m_ext_power;
|
|
input_report->battery = m_battery | m_charging;
|
|
input_report->buttons = m_buttons;
|
|
input_report->left_stick = m_left_stick;
|
|
input_report->right_stick = m_right_stick;
|
|
|
|
std::memcpy(&input_report->type0x30.motion_data, &m_motion_data, sizeof(m_motion_data));
|
|
m_input_report.size = offsetof(SwitchInputReport, type0x30) + sizeof(input_report->type0x30);
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleOutputDataReport(const bluetooth::HidReport *report) {
|
|
auto output_report = reinterpret_cast<const SwitchOutputReport *>(&report->data);
|
|
|
|
switch (output_report->id) {
|
|
case 0x01:
|
|
R_TRY(this->HandleRumbleData(&output_report->rumble_data));
|
|
R_TRY(this->HandleHidCommand(&output_report->type0x01.hid_command));
|
|
break;
|
|
case 0x10:
|
|
R_TRY(this->HandleRumbleData(&output_report->rumble_data));
|
|
break;
|
|
case 0x11:
|
|
R_TRY(this->HandleRumbleData(&output_report->rumble_data));
|
|
//R_TRY(this->HandleNfcIrData(output_report->type0x11.nfc_ir_data));
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleRumbleData(const SwitchRumbleDataEncoded *encoded) {
|
|
if (m_enable_rumble) {
|
|
SwitchRumbleData rumble_data[2];
|
|
DecodeRumbleValues(encoded->left_motor, &rumble_data[0]);
|
|
DecodeRumbleValues(encoded->right_motor, &rumble_data[1]);
|
|
R_TRY(this->SetVibration(rumble_data));
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommand(const SwitchHidCommand *command) {
|
|
switch (command->id) {
|
|
case HidCommand_GetDeviceInfo:
|
|
R_TRY(this->HandleHidCommandGetDeviceInfo(command));
|
|
break;
|
|
case HidCommand_SetDataFormat:
|
|
R_TRY(this->HandleHidCommandSetDataFormat(command));
|
|
break;
|
|
case HidCommand_LRButtonDetection:
|
|
R_TRY(this->HandleHidCommandLRButtonDetection(command));
|
|
break;
|
|
case HidCommand_ClearPairingInfo:
|
|
R_TRY(this->HandleHidCommandClearPairingInfo(command));
|
|
break;
|
|
case HidCommand_Shipment:
|
|
R_TRY(this->HandleHidCommandShipment(command));
|
|
break;
|
|
case HidCommand_SerialFlashRead:
|
|
R_TRY(this->HandleHidCommandSerialFlashRead(command));
|
|
break;
|
|
case HidCommand_SerialFlashWrite:
|
|
R_TRY(this->HandleHidCommandSerialFlashWrite(command));
|
|
break;
|
|
case HidCommand_SerialFlashSectorErase:
|
|
R_TRY(this->HandleHidCommandSerialFlashSectorErase(command));
|
|
break;
|
|
case HidCommand_McuWrite:
|
|
R_TRY(this->HandleHidCommandMcuWrite(command));
|
|
break;
|
|
case HidCommand_McuResume:
|
|
R_TRY(this->HandleHidCommandMcuResume(command));
|
|
break;
|
|
case HidCommand_McuPollingEnable:
|
|
R_TRY(this->HandleHidCommandMcuPollingEnable(command));
|
|
break;
|
|
case HidCommand_McuPollingDisable:
|
|
R_TRY(this->HandleHidCommandMcuPollingDisable(command));
|
|
break;
|
|
case HidCommand_SetIndicatorLed:
|
|
R_TRY(this->HandleHidCommandSetIndicatorLed(command));
|
|
break;
|
|
case HidCommand_GetIndicatorLed:
|
|
R_TRY(this->HandleHidCommandGetIndicatorLed(command));
|
|
break;
|
|
case HidCommand_SetNotificationLed:
|
|
R_TRY(this->HandleHidCommandSetNotificationLed(command));
|
|
break;
|
|
case HidCommand_SensorSleep:
|
|
R_TRY(this->HandleHidCommandSensorSleep(command));
|
|
break;
|
|
case HidCommand_SensorConfig:
|
|
R_TRY(this->HandleHidCommandSensorConfig(command));
|
|
break;
|
|
case HidCommand_MotorEnable:
|
|
R_TRY(this->HandleHidCommandMotorEnable(command));
|
|
break;
|
|
default:
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id,
|
|
.data = {
|
|
.raw = { 0x03 }
|
|
}
|
|
};
|
|
|
|
R_TRY(this->FakeHidCommandResponse(&response));
|
|
break;
|
|
}
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandGetDeviceInfo(const SwitchHidCommand *command) {
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x82,
|
|
.id = command->id,
|
|
.data = {
|
|
.get_device_info = {
|
|
.fw_ver = {
|
|
.major = 0x03,
|
|
.minor = 0x48
|
|
},
|
|
.type = 0x03,
|
|
._unk0 = 0x02,
|
|
.address = m_address,
|
|
._unk1 = 0x01,
|
|
._unk2 = 0x02
|
|
}
|
|
}
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandSetDataFormat(const SwitchHidCommand *command) {
|
|
m_input_report_mode = command->set_data_format.id;
|
|
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandLRButtonDetection(const SwitchHidCommand *command) {
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x83,
|
|
.id = command->id
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandClearPairingInfo(const SwitchHidCommand *command) {
|
|
R_TRY(m_virtual_memory.SectorErase(0x2000));
|
|
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandShipment(const SwitchHidCommand *command) {
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id,
|
|
.data = {
|
|
.shipment = {
|
|
.enabled = false
|
|
}
|
|
}
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandSerialFlashRead(const SwitchHidCommand *command) {
|
|
// These are read from official Pro Controller
|
|
// @ 0x00006000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff <= Serial
|
|
// @ 0x00006050: 32 32 32 ff ff ff ff ff ff ff ff ff <= RGB colours (body, buttons, left grip, right grip)
|
|
// @ 0x00006080: 50 fd 00 00 c6 0f 0f 30 61 ae 90 d9 d4 14 54 41 15 54 c7 79 9c 33 36 63 <= Factory Sensor and Stick device parameters
|
|
// @ 0x00006098: 0f 30 61 ae 90 d9 d4 14 54 41 15 54 c7 79 9c 33 36 63 <= Stick device parameters 2. Normally the same with 1, even in Pro Contr.
|
|
// @ 0x00008010: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff <= User Analog sticks calibration
|
|
// @ 0x0000603d: e6 a5 67 1a 58 78 50 56 60 1a f8 7f 20 c6 63 d5 15 5e ff 32 32 32 ff ff ff <= Analog stick factory calibration + face/button colours
|
|
// @ 0x00006020: 64 ff 33 00 b8 01 00 40 00 40 00 40 17 00 d7 ff bd ff 3b 34 3b 34 3b 34 <= 6-Axis motion sensor Factory calibration
|
|
auto read_addr = command->serial_flash_read.address;
|
|
auto read_size = command->serial_flash_read.size;
|
|
|
|
SwitchHidCommandResponse response = {
|
|
.ack = 0x90,
|
|
.id = command->id,
|
|
.data = {
|
|
.serial_flash_read = {
|
|
.address = read_addr,
|
|
.size = read_size
|
|
}
|
|
}
|
|
};
|
|
|
|
R_TRY(m_virtual_memory.Read(read_addr, response.data.serial_flash_read.data, read_size));
|
|
|
|
if (read_addr == 0x6050) {
|
|
if (ams::mitm::GetSystemLanguage() == 10) {
|
|
u8 data[] = {0xff, 0xd7, 0x00, 0x00, 0x57, 0xb7, 0x00, 0x57, 0xb7, 0x00, 0x57, 0xb7};
|
|
std::memcpy(response.data.serial_flash_read.data, data, sizeof(data));
|
|
}
|
|
}
|
|
|
|
return this->FakeHidCommandResponse(&response);
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandSerialFlashWrite(const SwitchHidCommand *command) {
|
|
auto write_addr = command->serial_flash_write.address;
|
|
auto write_size = command->serial_flash_write.size;
|
|
auto write_data = command->serial_flash_write.data;
|
|
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id,
|
|
.data = {
|
|
.serial_flash_write = {
|
|
.status = m_virtual_memory.Write(write_addr, write_data, write_size).IsFailure()
|
|
}
|
|
}
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandSerialFlashSectorErase(const SwitchHidCommand *command) {
|
|
auto erase_addr = command->serial_flash_sector_erase.address;
|
|
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id,
|
|
.data = {
|
|
.serial_flash_sector_erase = {
|
|
.status = m_virtual_memory.SectorErase(erase_addr).IsFailure()
|
|
}
|
|
}
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandMcuPollingEnable(const SwitchHidCommand *command) {
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id,
|
|
.data = {
|
|
.raw = { 0x00 }
|
|
}
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandMcuPollingDisable(const SwitchHidCommand *command) {
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id,
|
|
.data = {
|
|
.raw = { 0x00 }
|
|
}
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandMcuWrite(const SwitchHidCommand *command) {
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0xa0,
|
|
.id = command->id,
|
|
.data = {
|
|
.raw = {
|
|
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
|
|
}
|
|
}
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandMcuResume(const SwitchHidCommand *command) {
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandSetIndicatorLed(const SwitchHidCommand *command) {
|
|
m_led_pattern = command->set_indicator_led.leds;
|
|
R_TRY(this->SetPlayerLed(m_led_pattern));
|
|
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandGetIndicatorLed(const SwitchHidCommand *command) {
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id,
|
|
.data = {
|
|
.get_indicator_led = {
|
|
.leds = m_led_pattern
|
|
}
|
|
}
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandSetNotificationLed(const SwitchHidCommand *command) {
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandSensorSleep(const SwitchHidCommand *command) {
|
|
if (command->sensor_sleep.disabled) {
|
|
if (!m_enable_motion) {
|
|
m_gyro_sensitivity = 2000;
|
|
m_acc_sensitivity = 8000;
|
|
}
|
|
}
|
|
|
|
m_enable_motion = mitm::GetGlobalConfig()->general.enable_motion & command->sensor_sleep.disabled;
|
|
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandSensorConfig(const SwitchHidCommand *command) {
|
|
switch (command->sensor_config.gyro_sensitivity) {
|
|
case 0: m_gyro_sensitivity = 250; break;
|
|
case 1: m_gyro_sensitivity = 500; break;
|
|
case 2: m_gyro_sensitivity = 1000; break;
|
|
case 3: m_gyro_sensitivity = 2000; break;
|
|
AMS_UNREACHABLE_DEFAULT_CASE();
|
|
}
|
|
|
|
switch (command->sensor_config.acc_sensitivity) {
|
|
case 0: m_acc_sensitivity = 8000; break;
|
|
case 1: m_acc_sensitivity = 4000; break;
|
|
case 2: m_acc_sensitivity = 2000; break;
|
|
case 3: m_acc_sensitivity = 16000; break;
|
|
AMS_UNREACHABLE_DEFAULT_CASE();
|
|
}
|
|
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleHidCommandMotorEnable(const SwitchHidCommand *command) {
|
|
m_enable_rumble = mitm::GetGlobalConfig()->general.enable_rumble & command->motor_enable.enabled;
|
|
|
|
const SwitchHidCommandResponse response = {
|
|
.ack = 0x80,
|
|
.id = command->id
|
|
};
|
|
|
|
R_RETURN(this->FakeHidCommandResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::FakeHidCommandResponse(const SwitchHidCommandResponse *response) {
|
|
std::scoped_lock lk(m_input_mutex);
|
|
|
|
auto input_report = reinterpret_cast<SwitchInputReport *>(m_input_report.data);
|
|
input_report->id = 0x21;
|
|
input_report->timer = (input_report->timer + 1) & 0xff;
|
|
input_report->conn_info = (0 << 1) | m_ext_power;
|
|
input_report->battery = m_battery | m_charging;
|
|
input_report->buttons = m_buttons;
|
|
input_report->left_stick = m_left_stick;
|
|
input_report->right_stick = m_right_stick;
|
|
input_report->vibrator = 0;
|
|
|
|
std::memcpy(&input_report->type0x21.hid_command_response, response, sizeof(SwitchHidCommandResponse));
|
|
m_input_report.size = offsetof(SwitchInputReport, type0x21) + sizeof(input_report->type0x21);
|
|
|
|
// Write a fake response into the report buffer
|
|
R_RETURN(bluetooth::hid::report::WriteHidDataReport(m_address, &m_input_report));
|
|
}
|
|
|
|
Result EmulatedSwitchController::HandleNfcIrData(const u8 *nfc_ir) {
|
|
AMS_UNUSED(nfc_ir);
|
|
|
|
SwitchNfcIrResponse response = {};
|
|
|
|
// Send device not ready response for now
|
|
response.data[0] = 0xff;
|
|
|
|
R_RETURN(this->FakeNfcIrResponse(&response));
|
|
}
|
|
|
|
Result EmulatedSwitchController::FakeNfcIrResponse(const SwitchNfcIrResponse *response) {
|
|
std::scoped_lock lk(m_input_mutex);
|
|
|
|
auto input_report = reinterpret_cast<SwitchInputReport *>(m_input_report.data);
|
|
input_report->id = 0x31;
|
|
input_report->timer = (input_report->timer + 1) & 0xff;
|
|
input_report->conn_info = (0 << 1) | m_ext_power;
|
|
input_report->battery = m_battery | m_charging;
|
|
input_report->buttons = m_buttons;
|
|
input_report->left_stick = m_left_stick;
|
|
input_report->right_stick = m_right_stick;
|
|
input_report->vibrator = 0;
|
|
|
|
std::memcpy(&input_report->type0x31.motion_data, &m_motion_data, sizeof(m_motion_data));
|
|
std::memcpy(&input_report->type0x31.nfc_ir_response, response, sizeof(SwitchNfcIrResponse));
|
|
input_report->type0x31.crc = ComputeCrc8(response, sizeof(SwitchNfcIrResponse));
|
|
m_input_report.size = offsetof(SwitchInputReport, type0x31) + sizeof(input_report->type0x31);
|
|
|
|
// Write a fake response into the report buffer
|
|
R_RETURN(bluetooth::hid::report::WriteHidDataReport(m_address, &m_input_report));
|
|
}
|
|
|
|
}
|