early-access version 3244
This commit is contained in:
187
src/input_common/helpers/joycon_protocol/calibration.cpp
Executable file
187
src/input_common/helpers/joycon_protocol/calibration.cpp
Executable file
@@ -0,0 +1,187 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/calibration.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle)
|
||||
: JoyconCommonProtocol(std::move(handle)) {}
|
||||
|
||||
DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) {
|
||||
std::vector<u8> buffer;
|
||||
DriverResult result{DriverResult::Success};
|
||||
calibration = {};
|
||||
SetBlocking();
|
||||
|
||||
result = ReadSPI(CalAddr::USER_LEFT_MAGIC, sizeof(u16), buffer);
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
|
||||
if (has_user_calibration) {
|
||||
result = ReadSPI(CalAddr::USER_LEFT_DATA, 9, buffer);
|
||||
} else {
|
||||
result = ReadSPI(CalAddr::FACT_LEFT_DATA, 9, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
calibration.x.max = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
|
||||
calibration.y.max = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
|
||||
calibration.x.center = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
|
||||
calibration.y.center = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
|
||||
calibration.x.min = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
|
||||
calibration.y.min = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
|
||||
}
|
||||
|
||||
// Nintendo fix for drifting stick
|
||||
// result = ReadSPI(0x60, 0x86 ,buffer, 16);
|
||||
// calibration.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
|
||||
|
||||
// Set a valid default calibration if data is missing
|
||||
ValidateCalibration(calibration);
|
||||
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) {
|
||||
std::vector<u8> buffer;
|
||||
DriverResult result{DriverResult::Success};
|
||||
calibration = {};
|
||||
SetBlocking();
|
||||
|
||||
result = ReadSPI(CalAddr::USER_RIGHT_MAGIC, sizeof(u16), buffer);
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
|
||||
if (has_user_calibration) {
|
||||
result = ReadSPI(CalAddr::USER_RIGHT_DATA, 9, buffer);
|
||||
} else {
|
||||
result = ReadSPI(CalAddr::FACT_RIGHT_DATA, 9, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
calibration.x.center = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
|
||||
calibration.y.center = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
|
||||
calibration.x.min = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
|
||||
calibration.y.min = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
|
||||
calibration.x.max = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
|
||||
calibration.y.max = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
|
||||
}
|
||||
|
||||
// Nintendo fix for drifting stick
|
||||
// buffer = ReadSPI(0x60, 0x98 , 16);
|
||||
// joystick.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
|
||||
|
||||
// Set a valid default calibration if data is missing
|
||||
ValidateCalibration(calibration);
|
||||
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) {
|
||||
std::vector<u8> buffer;
|
||||
DriverResult result{DriverResult::Success};
|
||||
calibration = {};
|
||||
SetBlocking();
|
||||
|
||||
result = ReadSPI(CalAddr::USER_IMU_MAGIC, sizeof(u16), buffer);
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
const bool has_user_calibration = buffer[0] == 0xB2 && buffer[1] == 0xA1;
|
||||
if (has_user_calibration) {
|
||||
result = ReadSPI(CalAddr::USER_IMU_DATA, sizeof(IMUCalibration), buffer);
|
||||
} else {
|
||||
result = ReadSPI(CalAddr::FACT_IMU_DATA, sizeof(IMUCalibration), buffer);
|
||||
}
|
||||
}
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
IMUCalibration device_calibration{};
|
||||
memcpy(&device_calibration, buffer.data(), sizeof(IMUCalibration));
|
||||
calibration.accelerometer[0].offset = device_calibration.accelerometer_offset[0];
|
||||
calibration.accelerometer[1].offset = device_calibration.accelerometer_offset[1];
|
||||
calibration.accelerometer[2].offset = device_calibration.accelerometer_offset[2];
|
||||
|
||||
calibration.accelerometer[0].scale = device_calibration.accelerometer_scale[0];
|
||||
calibration.accelerometer[1].scale = device_calibration.accelerometer_scale[1];
|
||||
calibration.accelerometer[2].scale = device_calibration.accelerometer_scale[2];
|
||||
|
||||
calibration.gyro[0].offset = device_calibration.gyroscope_offset[0];
|
||||
calibration.gyro[1].offset = device_calibration.gyroscope_offset[1];
|
||||
calibration.gyro[2].offset = device_calibration.gyroscope_offset[2];
|
||||
|
||||
calibration.gyro[0].scale = device_calibration.gyroscope_scale[0];
|
||||
calibration.gyro[1].scale = device_calibration.gyroscope_scale[1];
|
||||
calibration.gyro[2].scale = device_calibration.gyroscope_scale[2];
|
||||
}
|
||||
|
||||
ValidateCalibration(calibration);
|
||||
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration,
|
||||
s16 current_value) {
|
||||
// TODO: Get default calibration form ring itself
|
||||
if (ring_data_max == 0 && ring_data_min == 0) {
|
||||
ring_data_max = current_value + 800;
|
||||
ring_data_min = current_value - 800;
|
||||
ring_data_default = current_value;
|
||||
}
|
||||
ring_data_max = std::max(ring_data_max, current_value);
|
||||
ring_data_min = std::min(ring_data_min, current_value);
|
||||
calibration = {
|
||||
.default_value = ring_data_default,
|
||||
.max_value = ring_data_max,
|
||||
.min_value = ring_data_min,
|
||||
};
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) {
|
||||
constexpr u16 DefaultStickCenter{2048};
|
||||
constexpr u16 DefaultStickRange{1740};
|
||||
|
||||
if (calibration.x.center == 0xFFF || calibration.x.center == 0) {
|
||||
calibration.x.center = DefaultStickCenter;
|
||||
}
|
||||
if (calibration.x.max == 0xFFF || calibration.x.max == 0) {
|
||||
calibration.x.max = DefaultStickRange;
|
||||
}
|
||||
if (calibration.x.min == 0xFFF || calibration.x.min == 0) {
|
||||
calibration.x.min = DefaultStickRange;
|
||||
}
|
||||
|
||||
if (calibration.y.center == 0xFFF || calibration.y.center == 0) {
|
||||
calibration.y.center = DefaultStickCenter;
|
||||
}
|
||||
if (calibration.y.max == 0xFFF || calibration.y.max == 0) {
|
||||
calibration.y.max = DefaultStickRange;
|
||||
}
|
||||
if (calibration.y.min == 0xFFF || calibration.y.min == 0) {
|
||||
calibration.y.min = DefaultStickRange;
|
||||
}
|
||||
}
|
||||
|
||||
void CalibrationProtocol::ValidateCalibration(MotionCalibration& calibration) {
|
||||
for (auto& sensor : calibration.accelerometer) {
|
||||
if (sensor.scale == 0) {
|
||||
sensor.scale = 0x4000;
|
||||
}
|
||||
}
|
||||
for (auto& sensor : calibration.gyro) {
|
||||
if (sensor.scale == 0) {
|
||||
sensor.scale = 0x3be7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
64
src/input_common/helpers/joycon_protocol/calibration.h
Executable file
64
src/input_common/helpers/joycon_protocol/calibration.h
Executable file
@@ -0,0 +1,64 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/common_protocol.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
enum class DriverResult;
|
||||
struct JoyStickCalibration;
|
||||
struct IMUCalibration;
|
||||
struct JoyconHandle;
|
||||
} // namespace InputCommon::Joycon
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
/// Driver functions related to retrieving calibration data from the device
|
||||
class CalibrationProtocol final : private JoyconCommonProtocol {
|
||||
public:
|
||||
explicit CalibrationProtocol(std::shared_ptr<JoyconHandle> handle);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the left stick calibration from memory
|
||||
* @param is_factory_calibration if true factory values will be returned
|
||||
* @returns JoyStickCalibration of the left joystick
|
||||
*/
|
||||
DriverResult GetLeftJoyStickCalibration(JoyStickCalibration& calibration);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the right stick calibration from memory
|
||||
* @param is_factory_calibration if true factory values will be returned
|
||||
* @returns JoyStickCalibration of the right joystick
|
||||
*/
|
||||
DriverResult GetRightJoyStickCalibration(JoyStickCalibration& calibration);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the motion calibration from memory
|
||||
* @returns ImuCalibration of the motion sensor
|
||||
*/
|
||||
DriverResult GetImuCalibration(MotionCalibration& calibration);
|
||||
|
||||
/**
|
||||
* Calculates on run time the proper calibration of the ring controller
|
||||
* @returns RingCalibration of the ring sensor
|
||||
*/
|
||||
DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value);
|
||||
|
||||
private:
|
||||
void ValidateCalibration(JoyStickCalibration& calibration);
|
||||
void ValidateCalibration(MotionCalibration& calibration);
|
||||
|
||||
s16 ring_data_max = 0;
|
||||
s16 ring_data_default = 0;
|
||||
s16 ring_data_min = 0;
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
286
src/input_common/helpers/joycon_protocol/common_protocol.cpp
Executable file
286
src/input_common/helpers/joycon_protocol/common_protocol.cpp
Executable file
@@ -0,0 +1,286 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol/common_protocol.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_)
|
||||
: hidapi_handle{std::move(hidapi_handle_)} {}
|
||||
|
||||
u8 JoyconCommonProtocol::GetCounter() {
|
||||
hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F;
|
||||
return hidapi_handle->packet_counter;
|
||||
}
|
||||
|
||||
void JoyconCommonProtocol::SetBlocking() {
|
||||
SDL_hid_set_nonblocking(hidapi_handle->handle, 0);
|
||||
}
|
||||
|
||||
void JoyconCommonProtocol::SetNonBlocking() {
|
||||
SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) {
|
||||
std::vector<u8> buffer;
|
||||
const auto result = ReadSPI(CalAddr::DEVICE_TYPE, 1, buffer);
|
||||
controller_type = ControllerType::None;
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
controller_type = static_cast<ControllerType>(buffer[0]);
|
||||
// Fallback to 3rd party pro controllers
|
||||
if (controller_type == ControllerType::None) {
|
||||
controller_type = ControllerType::Pro;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) {
|
||||
ControllerType controller_type{ControllerType::None};
|
||||
const auto result = GetDeviceType(controller_type);
|
||||
if (result != DriverResult::Success || controller_type == ControllerType::None) {
|
||||
return DriverResult::UnsupportedControllerType;
|
||||
}
|
||||
|
||||
hidapi_handle->handle =
|
||||
SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
|
||||
|
||||
if (!hidapi_handle->handle) {
|
||||
LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
|
||||
device_info->vendor_id, device_info->product_id);
|
||||
return DriverResult::HandleInUse;
|
||||
}
|
||||
|
||||
SetNonBlocking();
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) {
|
||||
const std::vector<u8> buffer{static_cast<u8>(report_mode)};
|
||||
std::vector<u8> output;
|
||||
return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer, output);
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::SendData(std::span<const u8> buffer) {
|
||||
const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size());
|
||||
|
||||
if (result == -1) {
|
||||
return DriverResult::ErrorWritingData;
|
||||
}
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc, std::vector<u8>& output) {
|
||||
constexpr int timeout_mili = 100;
|
||||
constexpr int MaxTries = 10;
|
||||
int tries = 0;
|
||||
output.resize(MaxSubCommandResponseSize);
|
||||
|
||||
do {
|
||||
int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(),
|
||||
MaxSubCommandResponseSize, timeout_mili);
|
||||
|
||||
if (result < 1) {
|
||||
LOG_ERROR(Input, "No response from joycon");
|
||||
}
|
||||
if (tries++ > MaxTries) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
} while (output[0] != 0x21 && output[14] != static_cast<u8>(sc));
|
||||
|
||||
if (output[0] != 0x21 && output[14] != static_cast<u8>(sc)) {
|
||||
return DriverResult::WrongReply;
|
||||
}
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer,
|
||||
std::vector<u8>& output) {
|
||||
std::vector<u8> local_buffer(MaxResponseSize);
|
||||
|
||||
local_buffer[0] = static_cast<u8>(OutputReport::RUMBLE_AND_SUBCMD);
|
||||
local_buffer[1] = GetCounter();
|
||||
local_buffer[10] = static_cast<u8>(sc);
|
||||
for (std::size_t i = 0; i < buffer.size(); ++i) {
|
||||
local_buffer[11 + i] = buffer[i];
|
||||
}
|
||||
|
||||
auto result = SendData(local_buffer);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = GetSubCommandResponse(sc, output);
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) {
|
||||
std::vector<u8> local_buffer(MaxResponseSize);
|
||||
|
||||
local_buffer[0] = static_cast<u8>(Joycon::OutputReport::RUMBLE_ONLY);
|
||||
local_buffer[1] = GetCounter();
|
||||
|
||||
memcpy(local_buffer.data() + 2, buffer.data(), buffer.size());
|
||||
|
||||
return SendData(local_buffer);
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output) {
|
||||
constexpr std::size_t MaxTries = 10;
|
||||
std::size_t tries = 0;
|
||||
std::vector<u8> buffer = {0x00, 0x00, 0x00, 0x00, size};
|
||||
std::vector<u8> local_buffer(size + 20);
|
||||
|
||||
buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF);
|
||||
buffer[1] = static_cast<u8>((static_cast<u16>(addr) & 0xFF00) >> 8);
|
||||
do {
|
||||
const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, local_buffer);
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (tries++ > MaxTries) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
} while (local_buffer[15] != buffer[0] || local_buffer[16] != buffer[1]);
|
||||
|
||||
// Remove header from output
|
||||
output = std::vector<u8>(local_buffer.begin() + 20, local_buffer.begin() + 20 + size);
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
|
||||
std::vector<u8> output;
|
||||
|
||||
const std::vector<u8> mcu_state{static_cast<u8>(enable ? 1 : 0)};
|
||||
const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state, output);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
LOG_ERROR(Input, "SendMCUData failed with error {}", result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) {
|
||||
LOG_DEBUG(Input, "ConfigureMCU");
|
||||
std::vector<u8> output;
|
||||
|
||||
std::array<u8, sizeof(MCUConfig)> config_buffer;
|
||||
memcpy(config_buffer.data(), &config, sizeof(MCUConfig));
|
||||
config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36);
|
||||
|
||||
const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer, output);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
LOG_ERROR(Input, "Set MCU config failed with error {}", result);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode_,
|
||||
std::vector<u8>& output) {
|
||||
const int report_mode = static_cast<u8>(report_mode_);
|
||||
constexpr int TimeoutMili = 200;
|
||||
constexpr int MaxTries = 9;
|
||||
int tries = 0;
|
||||
output.resize(0x170);
|
||||
|
||||
do {
|
||||
int result = SDL_hid_read_timeout(hidapi_handle->handle, output.data(), 0x170, TimeoutMili);
|
||||
|
||||
if (result < 1) {
|
||||
LOG_ERROR(Input, "No response from joycon attempt {}", tries);
|
||||
}
|
||||
if (tries++ > MaxTries) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
} while (output[0] != report_mode || output[49] == 0xFF);
|
||||
|
||||
if (output[0] != report_mode || output[49] == 0xFF) {
|
||||
return DriverResult::WrongReply;
|
||||
}
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc,
|
||||
std::span<const u8> buffer,
|
||||
std::vector<u8>& output) {
|
||||
std::vector<u8> local_buffer(MaxResponseSize);
|
||||
|
||||
local_buffer[0] = static_cast<u8>(OutputReport::MCU_DATA);
|
||||
local_buffer[1] = GetCounter();
|
||||
local_buffer[9] = static_cast<u8>(sc);
|
||||
for (std::size_t i = 0; i < buffer.size(); ++i) {
|
||||
local_buffer[10 + i] = buffer[i];
|
||||
}
|
||||
|
||||
auto result = SendData(local_buffer);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
result = GetMCUDataResponse(report_mode, output);
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) {
|
||||
std::vector<u8> output;
|
||||
constexpr std::size_t MaxTries{8};
|
||||
std::size_t tries{};
|
||||
|
||||
do {
|
||||
const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)};
|
||||
const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (tries++ > MaxTries) {
|
||||
return DriverResult::WrongReply;
|
||||
}
|
||||
} while (output[49] != 1 || output[56] != static_cast<u8>(mode));
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
// crc-8-ccitt / polynomial 0x07 look up table
|
||||
constexpr std::array<u8, 256> mcu_crc8_table = {
|
||||
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 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const {
|
||||
u8 crc8 = 0x0;
|
||||
|
||||
for (int i = 0; i < size; ++i) {
|
||||
crc8 = mcu_crc8_table[static_cast<u8>(crc8 ^ buffer[i])];
|
||||
}
|
||||
return crc8;
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
146
src/input_common/helpers/joycon_protocol/common_protocol.h
Executable file
146
src/input_common/helpers/joycon_protocol/common_protocol.h
Executable file
@@ -0,0 +1,146 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
/// Joycon driver functions that handle low level communication
|
||||
class JoyconCommonProtocol {
|
||||
public:
|
||||
explicit JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_);
|
||||
|
||||
/**
|
||||
* Sets handle to blocking. In blocking mode, SDL_hid_read() will wait (block) until there is
|
||||
* data to read before returning.
|
||||
*/
|
||||
void SetBlocking();
|
||||
|
||||
/**
|
||||
* Sets handle to non blocking. In non-blocking mode calls to SDL_hid_read() will return
|
||||
* immediately with a value of 0 if there is no data to be read
|
||||
*/
|
||||
void SetNonBlocking();
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the joycon type from device
|
||||
* @returns controller type of the joycon
|
||||
*/
|
||||
DriverResult GetDeviceType(ControllerType& controller_type);
|
||||
|
||||
/**
|
||||
* Verifies and sets the joycon_handle if device is valid
|
||||
* @param device info from the driver
|
||||
* @returns success if the device is valid
|
||||
*/
|
||||
DriverResult CheckDeviceAccess(SDL_hid_device_info* device);
|
||||
|
||||
/**
|
||||
* Sends a request to set the polling mode of the joycon
|
||||
* @param report_mode polling mode to be set
|
||||
*/
|
||||
DriverResult SetReportMode(Joycon::ReportMode report_mode);
|
||||
|
||||
/**
|
||||
* Sends data to the joycon device
|
||||
* @param buffer data to be send
|
||||
*/
|
||||
DriverResult SendData(std::span<const u8> buffer);
|
||||
|
||||
/**
|
||||
* Waits for incoming data of the joycon device that matchs the subcommand
|
||||
* @param sub_command type of data to be returned
|
||||
* @returns a buffer containing the responce
|
||||
*/
|
||||
DriverResult GetSubCommandResponse(SubCommand sub_command, std::vector<u8>& output);
|
||||
|
||||
/**
|
||||
* Sends a sub command to the device and waits for it's reply
|
||||
* @param sc sub command to be send
|
||||
* @param buffer data to be send
|
||||
* @returns output buffer containing the responce
|
||||
*/
|
||||
DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer, std::vector<u8>& output);
|
||||
|
||||
/**
|
||||
* Sends vibration data to the joycon
|
||||
* @param buffer data to be send
|
||||
*/
|
||||
DriverResult SendVibrationReport(std::span<const u8> buffer);
|
||||
|
||||
/**
|
||||
* Reads the SPI memory stored on the joycon
|
||||
* @param Initial address location
|
||||
* @param size in bytes to be read
|
||||
* @returns output buffer containing the responce
|
||||
*/
|
||||
DriverResult ReadSPI(CalAddr addr, u8 size, std::vector<u8>& output);
|
||||
|
||||
/**
|
||||
* Enables MCU chip on the joycon
|
||||
* @param enable if true the chip will be enabled
|
||||
*/
|
||||
DriverResult EnableMCU(bool enable);
|
||||
|
||||
/**
|
||||
* Configures the MCU to the correspoinding mode
|
||||
* @param MCUConfig configuration
|
||||
*/
|
||||
DriverResult ConfigureMCU(const MCUConfig& config);
|
||||
|
||||
/**
|
||||
* Waits until there's MCU data available. On timeout returns error
|
||||
* @param report mode of the expected reply
|
||||
* @returns a buffer containing the responce
|
||||
*/
|
||||
DriverResult GetMCUDataResponse(ReportMode report_mode_, std::vector<u8>& output);
|
||||
|
||||
/**
|
||||
* Sends data to the MCU chip and waits for it's reply
|
||||
* @param report mode of the expected reply
|
||||
* @param sub command to be send
|
||||
* @param buffer data to be send
|
||||
* @returns output buffer containing the responce
|
||||
*/
|
||||
DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer,
|
||||
std::vector<u8>& output);
|
||||
|
||||
/**
|
||||
* Wait's until the MCU chip is on the specified mode
|
||||
* @param report mode of the expected reply
|
||||
* @param MCUMode configuration
|
||||
*/
|
||||
DriverResult WaitSetMCUMode(ReportMode report_mode, MCUMode mode);
|
||||
|
||||
/**
|
||||
* Calculates the checksum from the MCU data
|
||||
* @param buffer containing the data to be send
|
||||
* @param size of the buffer in bytes
|
||||
* @returns byte with the correct checksum
|
||||
*/
|
||||
u8 CalculateMCU_CRC8(u8* buffer, u8 size) const;
|
||||
|
||||
private:
|
||||
/**
|
||||
* Increments and returns the packet counter of the handle
|
||||
* @param joycon_handle device to send the data
|
||||
* @returns packet counter value
|
||||
*/
|
||||
u8 GetCounter();
|
||||
|
||||
std::shared_ptr<JoyconHandle> hidapi_handle;
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
147
src/input_common/helpers/joycon_protocol/generic_functions.cpp
Executable file
147
src/input_common/helpers/joycon_protocol/generic_functions.cpp
Executable file
@@ -0,0 +1,147 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol/generic_functions.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle)
|
||||
: JoyconCommonProtocol(std::move(handle)) {}
|
||||
|
||||
DriverResult GenericProtocol::EnablePassiveMode() {
|
||||
SetBlocking();
|
||||
const auto result = SetReportMode(ReportMode::SIMPLE_HID_MODE);
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::EnableActiveMode() {
|
||||
SetBlocking();
|
||||
const auto result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) {
|
||||
std::vector<u8> output;
|
||||
SetBlocking();
|
||||
|
||||
const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output);
|
||||
|
||||
device_info = {};
|
||||
if (result == DriverResult::Success) {
|
||||
memcpy(&device_info, output.data(), sizeof(DeviceInfo));
|
||||
}
|
||||
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type) {
|
||||
return GetDeviceType(controller_type);
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::EnableImu(bool enable) {
|
||||
const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)};
|
||||
std::vector<u8> output;
|
||||
SetBlocking();
|
||||
const auto result = SendSubCommand(SubCommand::ENABLE_IMU, buffer, output);
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
|
||||
AccelerometerSensitivity asen,
|
||||
AccelerometerPerformance afrec) {
|
||||
const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
|
||||
static_cast<u8>(gfrec), static_cast<u8>(afrec)};
|
||||
std::vector<u8> output;
|
||||
SetBlocking();
|
||||
const auto result = SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer, output);
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetBattery(u32& battery_level) {
|
||||
battery_level = 0;
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetColor(Color& color) {
|
||||
std::vector<u8> buffer;
|
||||
SetBlocking();
|
||||
const auto result = ReadSPI(CalAddr::COLOR_DATA, 12, buffer);
|
||||
SetNonBlocking();
|
||||
|
||||
color = {};
|
||||
if (result == DriverResult::Success) {
|
||||
color.body = static_cast<u32>((buffer[0] << 16) | (buffer[1] << 8) | buffer[2]);
|
||||
color.buttons = static_cast<u32>((buffer[3] << 16) | (buffer[4] << 8) | buffer[5]);
|
||||
color.left_grip = static_cast<u32>((buffer[6] << 16) | (buffer[7] << 8) | buffer[8]);
|
||||
color.right_grip = static_cast<u32>((buffer[9] << 16) | (buffer[10] << 8) | buffer[11]);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) {
|
||||
std::vector<u8> buffer;
|
||||
SetBlocking();
|
||||
const auto result = ReadSPI(CalAddr::SERIAL_NUMBER, 16, buffer);
|
||||
SetNonBlocking();
|
||||
|
||||
serial_number = {};
|
||||
if (result == DriverResult::Success) {
|
||||
memcpy(serial_number.data(), buffer.data() + 1, sizeof(SerialNumber));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetTemperature(u32& temperature) {
|
||||
// Not all devices have temperature sensor
|
||||
temperature = 25;
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) {
|
||||
DeviceInfo device_info{};
|
||||
|
||||
const auto result = GetDeviceInfo(device_info);
|
||||
version = device_info.firmware;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::SetHomeLight() {
|
||||
static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00};
|
||||
std::vector<u8> output;
|
||||
SetBlocking();
|
||||
|
||||
const auto result = SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer, output);
|
||||
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::SetLedBusy() {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::SetLedPattern(u8 leds) {
|
||||
const std::array<u8, 1> buffer{leds};
|
||||
std::vector<u8> output;
|
||||
SetBlocking();
|
||||
|
||||
const auto result = SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer, output);
|
||||
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) {
|
||||
return SetLedPattern(static_cast<u8>(leds << 4));
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
108
src/input_common/helpers/joycon_protocol/generic_functions.h
Executable file
108
src/input_common/helpers/joycon_protocol/generic_functions.h
Executable file
@@ -0,0 +1,108 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/common_protocol.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
/// Joycon driver functions that easily implemented
|
||||
class GenericProtocol final : private JoyconCommonProtocol {
|
||||
public:
|
||||
explicit GenericProtocol(std::shared_ptr<JoyconHandle> handle);
|
||||
|
||||
/// Enables passive mode. This mode only sends button data on change. Sticks will return digital
|
||||
/// data instead of analog. Motion will be disabled
|
||||
DriverResult EnablePassiveMode();
|
||||
|
||||
/// Enables active mode. This mode will return the current status every 5-15ms
|
||||
DriverResult EnableActiveMode();
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the joycon firmware and mac from handle
|
||||
* @returns controller device info
|
||||
*/
|
||||
DriverResult GetDeviceInfo(DeviceInfo& controller_type);
|
||||
|
||||
/**
|
||||
* Sends a request to obtain the joycon type from handle
|
||||
* @returns controller type of the joycon
|
||||
*/
|
||||
DriverResult GetControllerType(ControllerType& controller_type);
|
||||
|
||||
/**
|
||||
* Enables motion input
|
||||
* @param enable if true motion data will be enabled
|
||||
*/
|
||||
DriverResult EnableImu(bool enable);
|
||||
|
||||
/**
|
||||
* Configures the motion sensor with the specified parameters
|
||||
* @param gsen gyroscope sensor sensitvity in degrees per second
|
||||
* @param gfrec gyroscope sensor frequency in hertz
|
||||
* @param asen accelerometer sensitivity in G force
|
||||
* @param afrec accelerometer frequency in hertz
|
||||
*/
|
||||
DriverResult SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
|
||||
AccelerometerSensitivity asen, AccelerometerPerformance afrec);
|
||||
|
||||
/**
|
||||
* Request battery level from the device
|
||||
* @returns battery level
|
||||
*/
|
||||
DriverResult GetBattery(u32& battery_level);
|
||||
|
||||
/**
|
||||
* Request joycon colors from the device
|
||||
* @returns colors of the body and buttons
|
||||
*/
|
||||
DriverResult GetColor(Color& color);
|
||||
|
||||
/**
|
||||
* Request joycon serial number from the device
|
||||
* @returns 16 byte serial number
|
||||
*/
|
||||
DriverResult GetSerialNumber(SerialNumber& serial_number);
|
||||
|
||||
/**
|
||||
* Request joycon serial number from the device
|
||||
* @returns 16 byte serial number
|
||||
*/
|
||||
DriverResult GetTemperature(u32& temperature);
|
||||
|
||||
/**
|
||||
* Request joycon serial number from the device
|
||||
* @returns 16 byte serial number
|
||||
*/
|
||||
DriverResult GetVersionNumber(FirmwareVersion& version);
|
||||
|
||||
/**
|
||||
* Sets home led behaviour
|
||||
*/
|
||||
DriverResult SetHomeLight();
|
||||
|
||||
/**
|
||||
* Sets home led into a slow breathing state
|
||||
*/
|
||||
DriverResult SetLedBusy();
|
||||
|
||||
/**
|
||||
* Sets the 4 player leds on the joycon on a solid state
|
||||
* @params bit flag containing the led state
|
||||
*/
|
||||
DriverResult SetLedPattern(u8 leds);
|
||||
|
||||
/**
|
||||
* Sets the 4 player leds on the joycon on a blinking state
|
||||
* @returns bit flag containing the led state
|
||||
*/
|
||||
DriverResult SetLedBlinkPattern(u8 leds);
|
||||
};
|
||||
} // namespace InputCommon::Joycon
|
494
src/input_common/helpers/joycon_protocol/joycon_types.h
Executable file
494
src/input_common/helpers/joycon_protocol/joycon_types.h
Executable file
@@ -0,0 +1,494 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <SDL_hidapi.h>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
constexpr u32 MaxErrorCount = 50;
|
||||
constexpr u32 MaxBufferSize = 60;
|
||||
constexpr u32 MaxResponseSize = 49;
|
||||
constexpr u32 MaxSubCommandResponseSize = 64;
|
||||
constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
|
||||
|
||||
using MacAddress = std::array<u8, 6>;
|
||||
using SerialNumber = std::array<u8, 15>;
|
||||
|
||||
enum class ControllerType {
|
||||
None,
|
||||
Left,
|
||||
Right,
|
||||
Pro,
|
||||
Grip,
|
||||
Dual,
|
||||
};
|
||||
|
||||
enum class PadAxes {
|
||||
LeftStickX,
|
||||
LeftStickY,
|
||||
RightStickX,
|
||||
RightStickY,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
enum class PadMotion {
|
||||
LeftMotion,
|
||||
RightMotion,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
enum class PadButton : u32 {
|
||||
Down = 0x000001,
|
||||
Up = 0x000002,
|
||||
Right = 0x000004,
|
||||
Left = 0x000008,
|
||||
LeftSR = 0x000010,
|
||||
LeftSL = 0x000020,
|
||||
L = 0x000040,
|
||||
ZL = 0x000080,
|
||||
Y = 0x000100,
|
||||
X = 0x000200,
|
||||
B = 0x000400,
|
||||
A = 0x000800,
|
||||
RightSR = 0x001000,
|
||||
RightSL = 0x002000,
|
||||
R = 0x004000,
|
||||
ZR = 0x008000,
|
||||
Minus = 0x010000,
|
||||
Plus = 0x020000,
|
||||
StickR = 0x040000,
|
||||
StickL = 0x080000,
|
||||
Home = 0x100000,
|
||||
Capture = 0x200000,
|
||||
};
|
||||
|
||||
enum class PasivePadButton : u32 {
|
||||
Down_A = 0x0001,
|
||||
Right_X = 0x0002,
|
||||
Left_B = 0x0004,
|
||||
Up_Y = 0x0008,
|
||||
SL = 0x0010,
|
||||
SR = 0x0020,
|
||||
Minus = 0x0100,
|
||||
Plus = 0x0200,
|
||||
StickL = 0x0400,
|
||||
StickR = 0x0800,
|
||||
Home = 0x1000,
|
||||
Capture = 0x2000,
|
||||
L_R = 0x4000,
|
||||
ZL_ZR = 0x8000,
|
||||
};
|
||||
|
||||
enum class OutputReport : u8 {
|
||||
RUMBLE_AND_SUBCMD = 0x01,
|
||||
FW_UPDATE_PKT = 0x03,
|
||||
RUMBLE_ONLY = 0x10,
|
||||
MCU_DATA = 0x11,
|
||||
USB_CMD = 0x80,
|
||||
};
|
||||
|
||||
enum class InputReport : u8 {
|
||||
SUBCMD_REPLY = 0x21,
|
||||
STANDARD_FULL_60HZ = 0x30,
|
||||
NFC_IR_MODE_60HZ = 0x31,
|
||||
SIMPLE_HID_MODE = 0x3F,
|
||||
INPUT_USB_RESPONSE = 0x81,
|
||||
};
|
||||
|
||||
enum class FeatureReport : u8 {
|
||||
Last_SUBCMD = 0x02,
|
||||
OTA_GW_UPGRADE = 0x70,
|
||||
SETUP_MEM_READ = 0x71,
|
||||
MEM_READ = 0x72,
|
||||
ERASE_MEM_SECTOR = 0x73,
|
||||
MEM_WRITE = 0x74,
|
||||
LAUNCH = 0x75,
|
||||
};
|
||||
|
||||
enum class SubCommand : u8 {
|
||||
STATE = 0x00,
|
||||
MANUAL_BT_PAIRING = 0x01,
|
||||
REQ_DEV_INFO = 0x02,
|
||||
SET_REPORT_MODE = 0x03,
|
||||
TRIGGERS_ELAPSED = 0x04,
|
||||
GET_PAGE_LIST_STATE = 0x05,
|
||||
SET_HCI_STATE = 0x06,
|
||||
RESET_PAIRING_INFO = 0x07,
|
||||
LOW_POWER_MODE = 0x08,
|
||||
SPI_FLASH_READ = 0x10,
|
||||
SPI_FLASH_WRITE = 0x11,
|
||||
RESET_MCU = 0x20,
|
||||
SET_MCU_CONFIG = 0x21,
|
||||
SET_MCU_STATE = 0x22,
|
||||
SET_PLAYER_LIGHTS = 0x30,
|
||||
GET_PLAYER_LIGHTS = 0x31,
|
||||
SET_HOME_LIGHT = 0x38,
|
||||
ENABLE_IMU = 0x40,
|
||||
SET_IMU_SENSITIVITY = 0x41,
|
||||
WRITE_IMU_REG = 0x42,
|
||||
READ_IMU_REG = 0x43,
|
||||
ENABLE_VIBRATION = 0x48,
|
||||
GET_REGULATED_VOLTAGE = 0x50,
|
||||
SET_EXTERNAL_CONFIG = 0x58,
|
||||
UNKNOWN_RINGCON = 0x59,
|
||||
UNKNOWN_RINGCON2 = 0x5A,
|
||||
UNKNOWN_RINGCON3 = 0x5C,
|
||||
};
|
||||
|
||||
enum class UsbSubCommand : u8 {
|
||||
CONN_STATUS = 0x01,
|
||||
HADSHAKE = 0x02,
|
||||
BAUDRATE_3M = 0x03,
|
||||
NO_TIMEOUT = 0x04,
|
||||
EN_TIMEOUT = 0x05,
|
||||
RESET = 0x06,
|
||||
PRE_HANDSHAKE = 0x91,
|
||||
SEND_UART = 0x92,
|
||||
};
|
||||
|
||||
enum class CalMagic : u8 {
|
||||
USR_MAGIC_0 = 0xB2,
|
||||
USR_MAGIC_1 = 0xA1,
|
||||
USRR_MAGI_SIZE = 2,
|
||||
};
|
||||
|
||||
enum class CalAddr {
|
||||
SERIAL_NUMBER = 0X6000,
|
||||
DEVICE_TYPE = 0X6012,
|
||||
COLOR_EXIST = 0X601B,
|
||||
FACT_LEFT_DATA = 0X603d,
|
||||
FACT_RIGHT_DATA = 0X6046,
|
||||
COLOR_DATA = 0X6050,
|
||||
FACT_IMU_DATA = 0X6020,
|
||||
USER_LEFT_MAGIC = 0X8010,
|
||||
USER_LEFT_DATA = 0X8012,
|
||||
USER_RIGHT_MAGIC = 0X801B,
|
||||
USER_RIGHT_DATA = 0X801D,
|
||||
USER_IMU_MAGIC = 0X8026,
|
||||
USER_IMU_DATA = 0X8028,
|
||||
};
|
||||
|
||||
enum class ReportMode : u8 {
|
||||
ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00,
|
||||
ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01,
|
||||
ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02,
|
||||
ACTIVE_POLLING_IR_CAMERA_DATA = 0x03,
|
||||
MCU_UPDATE_STATE = 0x23,
|
||||
STANDARD_FULL_60HZ = 0x30,
|
||||
NFC_IR_MODE_60HZ = 0x31,
|
||||
SIMPLE_HID_MODE = 0x3F,
|
||||
};
|
||||
|
||||
enum class GyroSensitivity : u8 {
|
||||
DPS250,
|
||||
DPS500,
|
||||
DPS1000,
|
||||
DPS2000, // Default
|
||||
};
|
||||
|
||||
enum class AccelerometerSensitivity : u8 {
|
||||
G8, // Default
|
||||
G4,
|
||||
G2,
|
||||
G16,
|
||||
};
|
||||
|
||||
enum class GyroPerformance : u8 {
|
||||
HZ833,
|
||||
HZ208, // Default
|
||||
};
|
||||
|
||||
enum class AccelerometerPerformance : u8 {
|
||||
HZ200,
|
||||
HZ100, // Default
|
||||
};
|
||||
|
||||
enum class MCUCommand : u8 {
|
||||
ConfigureMCU = 0x21,
|
||||
ConfigureIR = 0x23,
|
||||
};
|
||||
|
||||
enum class MCUSubCommand : u8 {
|
||||
SetMCUMode = 0x0,
|
||||
SetDeviceMode = 0x1,
|
||||
ReadDeviceMode = 0x02,
|
||||
WriteDeviceRegisters = 0x4,
|
||||
};
|
||||
|
||||
enum class MCUMode : u8 {
|
||||
Suspend = 0,
|
||||
Standby = 1,
|
||||
Ringcon = 3,
|
||||
NFC = 4,
|
||||
IR = 5,
|
||||
MaybeFWUpdate = 6,
|
||||
};
|
||||
|
||||
enum class MCURequest : u8 {
|
||||
GetMCUStatus = 1,
|
||||
GetNFCData = 2,
|
||||
GetIRData = 3,
|
||||
};
|
||||
|
||||
enum class MCUReport : u8 {
|
||||
Empty = 0x00,
|
||||
StateReport = 0x01,
|
||||
IRData = 0x03,
|
||||
BusyInitializing = 0x0b,
|
||||
IRStatus = 0x13,
|
||||
IRRegisters = 0x1b,
|
||||
NFCState = 0x2a,
|
||||
NFCReadData = 0x3a,
|
||||
EmptyAwaitingCmd = 0xff,
|
||||
};
|
||||
|
||||
enum class MCUPacketFlag : u8 {
|
||||
MorePacketsRemaining = 0x00,
|
||||
LastCommandPacket = 0x08,
|
||||
};
|
||||
|
||||
enum class NFCReadCommand : u8 {
|
||||
CancelAll = 0x00,
|
||||
StartPolling = 0x01,
|
||||
StopPolling = 0x02,
|
||||
StartWaitingRecieve = 0x04,
|
||||
Ntag = 0x06,
|
||||
Mifare = 0x0F,
|
||||
};
|
||||
|
||||
enum class NFCTagType : u8 {
|
||||
AllTags = 0x00,
|
||||
Ntag215 = 0x01,
|
||||
};
|
||||
|
||||
enum class DriverResult {
|
||||
Success,
|
||||
WrongReply,
|
||||
Timeout,
|
||||
UnsupportedControllerType,
|
||||
HandleInUse,
|
||||
ErrorReadingData,
|
||||
ErrorWritingData,
|
||||
NoDeviceDetected,
|
||||
InvalidHandle,
|
||||
NotSupported,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
struct MotionSensorCalibration {
|
||||
s16 offset;
|
||||
s16 scale;
|
||||
};
|
||||
|
||||
struct MotionCalibration {
|
||||
std::array<MotionSensorCalibration, 3> accelerometer;
|
||||
std::array<MotionSensorCalibration, 3> gyro;
|
||||
};
|
||||
|
||||
// Basic motion data containing data from the sensors and a timestamp in microseconds
|
||||
struct MotionData {
|
||||
float gyro_x{};
|
||||
float gyro_y{};
|
||||
float gyro_z{};
|
||||
float accel_x{};
|
||||
float accel_y{};
|
||||
float accel_z{};
|
||||
u64 delta_timestamp{};
|
||||
};
|
||||
|
||||
struct JoyStickAxisCalibration {
|
||||
u16 max{1};
|
||||
u16 min{1};
|
||||
u16 center{0};
|
||||
};
|
||||
|
||||
struct JoyStickCalibration {
|
||||
JoyStickAxisCalibration x;
|
||||
JoyStickAxisCalibration y;
|
||||
};
|
||||
|
||||
struct RingCalibration {
|
||||
s16 default_value;
|
||||
s16 max_value;
|
||||
s16 min_value;
|
||||
};
|
||||
|
||||
struct Color {
|
||||
u32 body;
|
||||
u32 buttons;
|
||||
u32 left_grip;
|
||||
u32 right_grip;
|
||||
};
|
||||
|
||||
struct Battery {
|
||||
union {
|
||||
u8 raw{};
|
||||
|
||||
BitField<0, 4, u8> unknown;
|
||||
BitField<4, 1, u8> charging;
|
||||
BitField<5, 3, u8> status;
|
||||
};
|
||||
};
|
||||
|
||||
struct VibrationValue {
|
||||
f32 low_amplitude;
|
||||
f32 low_frequency;
|
||||
f32 high_amplitude;
|
||||
f32 high_frequency;
|
||||
};
|
||||
|
||||
struct JoyconHandle {
|
||||
SDL_hid_device* handle = nullptr;
|
||||
u8 packet_counter{};
|
||||
};
|
||||
|
||||
struct MCUConfig {
|
||||
MCUCommand command;
|
||||
MCUSubCommand sub_command;
|
||||
MCUMode mode;
|
||||
INSERT_PADDING_BYTES(0x22);
|
||||
u8 crc;
|
||||
};
|
||||
static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size");
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct InputReportPassive {
|
||||
InputReport report_mode;
|
||||
u16 button_input;
|
||||
u8 stick_state;
|
||||
std::array<u8, 10> unknown_data;
|
||||
};
|
||||
static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size");
|
||||
|
||||
struct InputReportActive {
|
||||
InputReport report_mode;
|
||||
u8 packet_id;
|
||||
Battery battery_status;
|
||||
std::array<u8, 3> button_input;
|
||||
std::array<u8, 3> left_stick_state;
|
||||
std::array<u8, 3> right_stick_state;
|
||||
u8 vibration_code;
|
||||
std::array<s16, 6 * 2> motion_input;
|
||||
INSERT_PADDING_BYTES(0x2);
|
||||
s16 ring_input;
|
||||
};
|
||||
static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size");
|
||||
|
||||
struct InputReportNfcIr {
|
||||
InputReport report_mode;
|
||||
u8 packet_id;
|
||||
Battery battery_status;
|
||||
std::array<u8, 3> button_input;
|
||||
std::array<u8, 3> left_stick_state;
|
||||
std::array<u8, 3> right_stick_state;
|
||||
u8 vibration_code;
|
||||
std::array<s16, 6 * 2> motion_input;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
};
|
||||
static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size");
|
||||
#pragma pack(pop)
|
||||
|
||||
struct IMUCalibration {
|
||||
std::array<s16, 3> accelerometer_offset;
|
||||
std::array<s16, 3> accelerometer_scale;
|
||||
std::array<s16, 3> gyroscope_offset;
|
||||
std::array<s16, 3> gyroscope_scale;
|
||||
};
|
||||
static_assert(sizeof(IMUCalibration) == 0x18, "IMUCalibration is an invalid size");
|
||||
|
||||
struct NFCReadBlock {
|
||||
u8 start;
|
||||
u8 end;
|
||||
};
|
||||
static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size");
|
||||
|
||||
struct NFCReadBlockCommand {
|
||||
u8 block_count{};
|
||||
std::array<NFCReadBlock, 4> blocks{};
|
||||
};
|
||||
static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size");
|
||||
|
||||
struct NFCReadCommandData {
|
||||
u8 unknown;
|
||||
u8 uuid_length;
|
||||
u8 unknown_2;
|
||||
std::array<u8, 6> uid;
|
||||
NFCTagType tag_type;
|
||||
NFCReadBlockCommand read_block;
|
||||
};
|
||||
static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size");
|
||||
|
||||
struct NFCPollingCommandData {
|
||||
u8 enable_mifare;
|
||||
u8 unknown_1;
|
||||
u8 unknown_2;
|
||||
u8 unknown_3;
|
||||
u8 unknown_4;
|
||||
};
|
||||
static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size");
|
||||
|
||||
struct NFCRequestState {
|
||||
MCUSubCommand sub_command;
|
||||
NFCReadCommand command_argument;
|
||||
u8 packet_id;
|
||||
INSERT_PADDING_BYTES(0x1);
|
||||
MCUPacketFlag packet_flag;
|
||||
u8 data_length;
|
||||
union {
|
||||
std::array<u8, 0x1F> raw_data;
|
||||
NFCReadCommandData nfc_read;
|
||||
NFCPollingCommandData nfc_polling;
|
||||
};
|
||||
u8 crc;
|
||||
};
|
||||
static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
|
||||
|
||||
struct FirmwareVersion {
|
||||
u8 major;
|
||||
u8 minor;
|
||||
};
|
||||
static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
|
||||
|
||||
struct DeviceInfo {
|
||||
FirmwareVersion firmware;
|
||||
MacAddress mac_address;
|
||||
};
|
||||
static_assert(sizeof(DeviceInfo) == 0x8, "DeviceInfo is an invalid size");
|
||||
|
||||
struct MotionStatus {
|
||||
bool is_enabled;
|
||||
u64 delta_time;
|
||||
GyroSensitivity gyro_sensitivity;
|
||||
AccelerometerSensitivity accelerometer_sensitivity;
|
||||
};
|
||||
|
||||
struct RingStatus {
|
||||
bool is_enabled;
|
||||
s16 default_value;
|
||||
s16 max_value;
|
||||
s16 min_value;
|
||||
};
|
||||
|
||||
struct JoyconCallbacks {
|
||||
std::function<void(Battery)> on_battery_data;
|
||||
std::function<void(Color)> on_color_data;
|
||||
std::function<void(int, bool)> on_button_data;
|
||||
std::function<void(int, f32)> on_stick_data;
|
||||
std::function<void(int, const MotionData&)> on_motion_data;
|
||||
std::function<void(f32)> on_ring_data;
|
||||
std::function<void(const std::vector<u8>&)> on_amiibo_data;
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
415
src/input_common/helpers/joycon_protocol/nfc.cpp
Executable file
415
src/input_common/helpers/joycon_protocol/nfc.cpp
Executable file
@@ -0,0 +1,415 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <thread>
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol/nfc.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle)
|
||||
: JoyconCommonProtocol(std::move(handle)) {}
|
||||
|
||||
DriverResult NfcProtocol::EnableNfc() {
|
||||
LOG_INFO(Input, "Enable NFC");
|
||||
DriverResult result{DriverResult::Success};
|
||||
SetBlocking();
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = EnableMCU(true);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
const MCUConfig config{
|
||||
.command = MCUCommand::ConfigureMCU,
|
||||
.sub_command = MCUSubCommand::SetMCUMode,
|
||||
.mode = MCUMode::NFC,
|
||||
.crc = {},
|
||||
};
|
||||
|
||||
result = ConfigureMCU(config);
|
||||
}
|
||||
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::DisableNfc() {
|
||||
LOG_DEBUG(Input, "Disable NFC");
|
||||
DriverResult result{DriverResult::Success};
|
||||
SetBlocking();
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = EnableMCU(false);
|
||||
}
|
||||
|
||||
is_enabled = false;
|
||||
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::StartNFCPollingMode() {
|
||||
LOG_DEBUG(Input, "Start NFC pooling Mode");
|
||||
DriverResult result{DriverResult::Success};
|
||||
TagFoundData tag_data{};
|
||||
SetBlocking();
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitUntilNfcIsReady();
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
is_enabled = true;
|
||||
}
|
||||
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
|
||||
LOG_DEBUG(Input, "Start NFC pooling Mode");
|
||||
DriverResult result{DriverResult::Success};
|
||||
TagFoundData tag_data{};
|
||||
SetBlocking();
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = StartPolling(tag_data);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = ReadTag(tag_data);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitUntilNfcIsReady();
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = StartPolling(tag_data);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = GetAmiiboData(data);
|
||||
}
|
||||
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
bool NfcProtocol::HasAmiibo() {
|
||||
DriverResult result{DriverResult::Success};
|
||||
TagFoundData tag_data{};
|
||||
SetBlocking();
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = StartPolling(tag_data);
|
||||
}
|
||||
|
||||
SetNonBlocking();
|
||||
return result == DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::WaitUntilNfcIsReady() {
|
||||
constexpr std::size_t timeout_limit = 10;
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
|
||||
do {
|
||||
auto result = SendStartWaitingRecieveRequest(output);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
if (tries++ > timeout_limit) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
} while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[55] != 0x31 ||
|
||||
output[56] != 0x00);
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::StartPolling(TagFoundData& data) {
|
||||
LOG_DEBUG(Input, "Start Polling for tag");
|
||||
constexpr std::size_t timeout_limit = 7;
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
|
||||
do {
|
||||
const auto result = SendStartPollingRequest(output);
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
if (tries++ > timeout_limit) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
} while (output[49] != 0x2a || (output[51] << 8) + output[50] != 0x0500 || output[56] != 0x09);
|
||||
|
||||
data.type = output[62];
|
||||
data.uuid.resize(output[64]);
|
||||
memcpy(data.uuid.data(), output.data() + 65, data.uuid.size());
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::ReadTag(const TagFoundData& data) {
|
||||
constexpr std::size_t timeout_limit = 10;
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
|
||||
std::string uuid_string;
|
||||
for (auto& content : data.uuid) {
|
||||
uuid_string += fmt::format(" {:02x}", content);
|
||||
}
|
||||
|
||||
LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string);
|
||||
|
||||
tries = 0;
|
||||
std::size_t ntag_pages = 0;
|
||||
// Read Tag data
|
||||
loop1:
|
||||
while (true) {
|
||||
auto result = SendReadAmiiboRequest(output, ntag_pages);
|
||||
|
||||
int attempt = 0;
|
||||
while (1) {
|
||||
if (attempt != 0) {
|
||||
result = GetMCUDataResponse(ReportMode::NFC_IR_MODE_60HZ, output);
|
||||
}
|
||||
if ((output[49] == 0x3a || output[49] == 0x2a) && output[56] == 0x07) {
|
||||
return DriverResult::ErrorReadingData;
|
||||
}
|
||||
if (output[49] == 0x3a && output[51] == 0x07 && output[52] == 0x01) {
|
||||
if (data.type != 2) {
|
||||
goto loop1;
|
||||
}
|
||||
switch (output[74]) {
|
||||
case 0:
|
||||
ntag_pages = 135;
|
||||
break;
|
||||
case 3:
|
||||
ntag_pages = 45;
|
||||
break;
|
||||
case 4:
|
||||
ntag_pages = 231;
|
||||
break;
|
||||
default:
|
||||
return DriverResult::ErrorReadingData;
|
||||
}
|
||||
goto loop1;
|
||||
}
|
||||
if (output[49] == 0x2a && output[56] == 0x04) {
|
||||
// finished
|
||||
SendStopPollingRequest(output);
|
||||
return DriverResult::Success;
|
||||
}
|
||||
if (output[49] == 0x2a) {
|
||||
goto loop1;
|
||||
}
|
||||
if (attempt++ > 6) {
|
||||
goto loop1;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
if (tries++ > timeout_limit) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
}
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) {
|
||||
constexpr std::size_t timeout_limit = 10;
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
|
||||
std::size_t ntag_pages = 135;
|
||||
std::size_t ntag_buffer_pos = 0;
|
||||
// Read Tag data
|
||||
loop1:
|
||||
while (true) {
|
||||
auto result = SendReadAmiiboRequest(output, ntag_pages);
|
||||
|
||||
int attempt = 0;
|
||||
while (1) {
|
||||
if (attempt != 0) {
|
||||
result = GetMCUDataResponse(ReportMode::NFC_IR_MODE_60HZ, output);
|
||||
}
|
||||
if ((output[49] == 0x3a || output[49] == 0x2a) && output[56] == 0x07) {
|
||||
return DriverResult::ErrorReadingData;
|
||||
}
|
||||
if (output[49] == 0x3a && output[51] == 0x07) {
|
||||
std::size_t payload_size = (output[54] << 8 | output[55]) & 0x7FF;
|
||||
if (output[52] == 0x01) {
|
||||
memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 116,
|
||||
payload_size - 60);
|
||||
ntag_buffer_pos += payload_size - 60;
|
||||
} else {
|
||||
memcpy(ntag_data.data() + ntag_buffer_pos, output.data() + 56, payload_size);
|
||||
}
|
||||
goto loop1;
|
||||
}
|
||||
if (output[49] == 0x2a && output[56] == 0x04) {
|
||||
LOG_INFO(Input, "Finished reading amiibo");
|
||||
return DriverResult::Success;
|
||||
}
|
||||
if (output[49] == 0x2a) {
|
||||
goto loop1;
|
||||
}
|
||||
if (attempt++ > 4) {
|
||||
goto loop1;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
if (tries++ > timeout_limit) {
|
||||
return DriverResult::Timeout;
|
||||
}
|
||||
}
|
||||
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::SendStartPollingRequest(std::vector<u8>& output) {
|
||||
NFCRequestState request{
|
||||
.sub_command = MCUSubCommand::ReadDeviceMode,
|
||||
.command_argument = NFCReadCommand::StartPolling,
|
||||
.packet_id = 0x0,
|
||||
.packet_flag = MCUPacketFlag::LastCommandPacket,
|
||||
.data_length = sizeof(NFCPollingCommandData),
|
||||
.nfc_polling =
|
||||
{
|
||||
.enable_mifare = 0x01,
|
||||
.unknown_1 = 0x00,
|
||||
.unknown_2 = 0x00,
|
||||
.unknown_3 = 0x2c,
|
||||
.unknown_4 = 0x01,
|
||||
},
|
||||
.crc = {},
|
||||
};
|
||||
|
||||
std::vector<u8> request_data(sizeof(NFCRequestState));
|
||||
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
|
||||
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
|
||||
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::SendStopPollingRequest(std::vector<u8>& output) {
|
||||
NFCRequestState request{
|
||||
.sub_command = MCUSubCommand::ReadDeviceMode,
|
||||
.command_argument = NFCReadCommand::StopPolling,
|
||||
.packet_id = 0x0,
|
||||
.packet_flag = MCUPacketFlag::LastCommandPacket,
|
||||
.data_length = 0,
|
||||
.raw_data = {},
|
||||
.crc = {},
|
||||
};
|
||||
|
||||
std::vector<u8> request_data(sizeof(NFCRequestState));
|
||||
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
|
||||
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
|
||||
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::SendStartWaitingRecieveRequest(std::vector<u8>& output) {
|
||||
NFCRequestState request{
|
||||
.sub_command = MCUSubCommand::ReadDeviceMode,
|
||||
.command_argument = NFCReadCommand::StartWaitingRecieve,
|
||||
.packet_id = 0x0,
|
||||
.packet_flag = MCUPacketFlag::LastCommandPacket,
|
||||
.data_length = 0,
|
||||
.raw_data = {},
|
||||
.crc = {},
|
||||
};
|
||||
|
||||
std::vector<u8> request_data(sizeof(NFCRequestState));
|
||||
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
|
||||
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
|
||||
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::SendReadAmiiboRequest(std::vector<u8>& output, std::size_t ntag_pages) {
|
||||
NFCRequestState request{
|
||||
.sub_command = MCUSubCommand::ReadDeviceMode,
|
||||
.command_argument = NFCReadCommand::Ntag,
|
||||
.packet_id = 0x0,
|
||||
.packet_flag = MCUPacketFlag::LastCommandPacket,
|
||||
.data_length = sizeof(NFCReadCommandData),
|
||||
.nfc_read =
|
||||
{
|
||||
.unknown = 0xd0,
|
||||
.uuid_length = 0x07,
|
||||
.unknown_2 = 0x00,
|
||||
.uid = {},
|
||||
.tag_type = NFCTagType::AllTags,
|
||||
.read_block = GetReadBlockCommand(ntag_pages),
|
||||
},
|
||||
.crc = {},
|
||||
};
|
||||
|
||||
std::vector<u8> request_data(sizeof(NFCRequestState));
|
||||
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
|
||||
request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
|
||||
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
|
||||
}
|
||||
|
||||
NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(std::size_t pages) const {
|
||||
if (pages == 0) {
|
||||
return {
|
||||
.block_count = 1,
|
||||
};
|
||||
}
|
||||
|
||||
if (pages == 45) {
|
||||
return {
|
||||
.block_count = 1,
|
||||
.blocks =
|
||||
{
|
||||
NFCReadBlock{0x00, 0x2C},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (pages == 135) {
|
||||
return {
|
||||
.block_count = 3,
|
||||
.blocks =
|
||||
{
|
||||
NFCReadBlock{0x00, 0x3b},
|
||||
{0x3c, 0x77},
|
||||
{0x78, 0x86},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (pages == 231) {
|
||||
return {
|
||||
.block_count = 4,
|
||||
.blocks =
|
||||
{
|
||||
NFCReadBlock{0x00, 0x3b},
|
||||
{0x3c, 0x77},
|
||||
{0x78, 0x83},
|
||||
{0xb4, 0xe6},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool NfcProtocol::IsEnabled() const {
|
||||
return is_enabled;
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
61
src/input_common/helpers/joycon_protocol/nfc.h
Executable file
61
src/input_common/helpers/joycon_protocol/nfc.h
Executable file
@@ -0,0 +1,61 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/common_protocol.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
class NfcProtocol final : private JoyconCommonProtocol {
|
||||
public:
|
||||
explicit NfcProtocol(std::shared_ptr<JoyconHandle> handle);
|
||||
|
||||
DriverResult EnableNfc();
|
||||
|
||||
DriverResult DisableNfc();
|
||||
|
||||
DriverResult StartNFCPollingMode();
|
||||
|
||||
DriverResult ScanAmiibo(std::vector<u8>& data);
|
||||
|
||||
bool HasAmiibo();
|
||||
|
||||
bool IsEnabled() const;
|
||||
|
||||
private:
|
||||
struct TagFoundData {
|
||||
u8 type;
|
||||
std::vector<u8> uuid;
|
||||
};
|
||||
|
||||
DriverResult WaitUntilNfcIsReady();
|
||||
|
||||
DriverResult StartPolling(TagFoundData& data);
|
||||
|
||||
DriverResult ReadTag(const TagFoundData& data);
|
||||
|
||||
DriverResult GetAmiiboData(std::vector<u8>& data);
|
||||
|
||||
DriverResult SendStartPollingRequest(std::vector<u8>& output);
|
||||
|
||||
DriverResult SendStopPollingRequest(std::vector<u8>& output);
|
||||
|
||||
DriverResult SendStartWaitingRecieveRequest(std::vector<u8>& output);
|
||||
|
||||
DriverResult SendReadAmiiboRequest(std::vector<u8>& output, std::size_t ntag_pages);
|
||||
|
||||
NFCReadBlockCommand GetReadBlockCommand(std::size_t pages) const;
|
||||
|
||||
bool is_enabled{};
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
337
src/input_common/helpers/joycon_protocol/poller.cpp
Executable file
337
src/input_common/helpers/joycon_protocol/poller.cpp
Executable file
@@ -0,0 +1,337 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol/poller.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
|
||||
JoyStickCalibration right_stick_calibration_,
|
||||
MotionCalibration motion_calibration_)
|
||||
: device_type{device_type_}, left_stick_calibration{left_stick_calibration_},
|
||||
right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {}
|
||||
|
||||
void JoyconPoller::SetCallbacks(const Joycon::JoyconCallbacks& callbacks_) {
|
||||
callbacks = std::move(callbacks_);
|
||||
}
|
||||
|
||||
void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
|
||||
const RingStatus& ring_status) {
|
||||
InputReportActive data{};
|
||||
memcpy(&data, buffer.data(), sizeof(InputReportActive));
|
||||
|
||||
switch (device_type) {
|
||||
case Joycon::ControllerType::Left:
|
||||
UpdateActiveLeftPadInput(data, motion_status);
|
||||
break;
|
||||
case Joycon::ControllerType::Right:
|
||||
UpdateActiveRightPadInput(data, motion_status);
|
||||
break;
|
||||
case Joycon::ControllerType::Pro:
|
||||
UpdateActiveProPadInput(data, motion_status);
|
||||
break;
|
||||
case Joycon::ControllerType::Grip:
|
||||
case Joycon::ControllerType::Dual:
|
||||
case Joycon::ControllerType::None:
|
||||
break;
|
||||
}
|
||||
|
||||
if (ring_status.is_enabled) {
|
||||
UpdateRing(data.ring_input, ring_status);
|
||||
}
|
||||
|
||||
callbacks.on_battery_data(data.battery_status);
|
||||
}
|
||||
|
||||
void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
|
||||
InputReportPassive data{};
|
||||
memcpy(&data, buffer.data(), sizeof(InputReportPassive));
|
||||
|
||||
switch (device_type) {
|
||||
case Joycon::ControllerType::Left:
|
||||
UpdatePasiveLeftPadInput(data);
|
||||
break;
|
||||
case Joycon::ControllerType::Right:
|
||||
UpdatePasiveRightPadInput(data);
|
||||
break;
|
||||
case Joycon::ControllerType::Pro:
|
||||
UpdatePasiveProPadInput(data);
|
||||
break;
|
||||
case Joycon::ControllerType::Grip:
|
||||
case Joycon::ControllerType::Dual:
|
||||
case Joycon::ControllerType::None:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
|
||||
// This mode is compatible with the active mode
|
||||
ReadActiveMode(buffer, motion_status, {});
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdateColor(const Color& color) {
|
||||
callbacks.on_color_data(color);
|
||||
}
|
||||
|
||||
void JoyconPoller::updateAmiibo(const std::vector<u8>& amiibo_data) {
|
||||
callbacks.on_amiibo_data(amiibo_data);
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) {
|
||||
float normalized_value = static_cast<float>(value - ring_status.default_value);
|
||||
if (normalized_value > 0) {
|
||||
normalized_value = normalized_value /
|
||||
static_cast<float>(ring_status.max_value - ring_status.default_value);
|
||||
}
|
||||
if (normalized_value < 0) {
|
||||
normalized_value = normalized_value /
|
||||
static_cast<float>(ring_status.default_value - ring_status.min_value);
|
||||
}
|
||||
callbacks.on_ring_data(normalized_value);
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status) {
|
||||
static constexpr std::array<Joycon::PadButton, 11> left_buttons{
|
||||
Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
|
||||
Joycon::PadButton::Left, Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR,
|
||||
Joycon::PadButton::L, Joycon::PadButton::ZL, Joycon::PadButton::Minus,
|
||||
Joycon::PadButton::Capture, Joycon::PadButton::StickL,
|
||||
};
|
||||
|
||||
const u32 raw_button =
|
||||
static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16));
|
||||
for (std::size_t i = 0; i < left_buttons.size(); ++i) {
|
||||
const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0;
|
||||
const int button = static_cast<int>(left_buttons[i]);
|
||||
callbacks.on_button_data(button, button_status);
|
||||
}
|
||||
|
||||
const u16 raw_left_axis_x =
|
||||
static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
|
||||
const u16 raw_left_axis_y =
|
||||
static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
|
||||
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
|
||||
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
|
||||
|
||||
if (motion_status.is_enabled) {
|
||||
auto left_motion = GetMotionInput(input, motion_status);
|
||||
// Rotate motion axis to the correct direction
|
||||
left_motion.accel_y = -left_motion.accel_y;
|
||||
left_motion.accel_z = -left_motion.accel_z;
|
||||
left_motion.gyro_x = -left_motion.gyro_x;
|
||||
callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion);
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status) {
|
||||
static constexpr std::array<Joycon::PadButton, 11> right_buttons{
|
||||
Joycon::PadButton::Y, Joycon::PadButton::X, Joycon::PadButton::B,
|
||||
Joycon::PadButton::A, Joycon::PadButton::RightSL, Joycon::PadButton::RightSR,
|
||||
Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
|
||||
Joycon::PadButton::Home, Joycon::PadButton::StickR,
|
||||
};
|
||||
|
||||
const u32 raw_button =
|
||||
static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16));
|
||||
for (std::size_t i = 0; i < right_buttons.size(); ++i) {
|
||||
const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0;
|
||||
const int button = static_cast<int>(right_buttons[i]);
|
||||
callbacks.on_button_data(button, button_status);
|
||||
}
|
||||
|
||||
const u16 raw_right_axis_x =
|
||||
static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
|
||||
const u16 raw_right_axis_y =
|
||||
static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
|
||||
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
|
||||
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
|
||||
|
||||
if (motion_status.is_enabled) {
|
||||
auto right_motion = GetMotionInput(input, motion_status);
|
||||
// Rotate motion axis to the correct direction
|
||||
right_motion.accel_x = -right_motion.accel_x;
|
||||
right_motion.accel_y = -right_motion.accel_y;
|
||||
right_motion.gyro_z = -right_motion.gyro_z;
|
||||
callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion);
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status) {
|
||||
static constexpr std::array<Joycon::PadButton, 18> pro_buttons{
|
||||
Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
|
||||
Joycon::PadButton::Left, Joycon::PadButton::L, Joycon::PadButton::ZL,
|
||||
Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y,
|
||||
Joycon::PadButton::X, Joycon::PadButton::B, Joycon::PadButton::A,
|
||||
Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
|
||||
Joycon::PadButton::Home, Joycon::PadButton::StickL, Joycon::PadButton::StickR,
|
||||
};
|
||||
|
||||
const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) |
|
||||
(input.button_input[1] << 16));
|
||||
for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
|
||||
const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0;
|
||||
const int button = static_cast<int>(pro_buttons[i]);
|
||||
callbacks.on_button_data(button, button_status);
|
||||
}
|
||||
|
||||
const u16 raw_left_axis_x =
|
||||
static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
|
||||
const u16 raw_left_axis_y =
|
||||
static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
|
||||
const u16 raw_right_axis_x =
|
||||
static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
|
||||
const u16 raw_right_axis_y =
|
||||
static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
|
||||
|
||||
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
|
||||
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
|
||||
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
|
||||
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
|
||||
callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
|
||||
|
||||
if (motion_status.is_enabled) {
|
||||
auto pro_motion = GetMotionInput(input, motion_status);
|
||||
pro_motion.gyro_x = -pro_motion.gyro_x;
|
||||
pro_motion.accel_y = -pro_motion.accel_y;
|
||||
pro_motion.accel_z = -pro_motion.accel_z;
|
||||
callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion);
|
||||
callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion);
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdatePasiveLeftPadInput(const InputReportPassive& input) {
|
||||
static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{
|
||||
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
|
||||
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
|
||||
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
|
||||
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
|
||||
Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Capture,
|
||||
Joycon::PasivePadButton::StickL,
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < left_buttons.size(); ++i) {
|
||||
const bool button_status = (input.button_input & static_cast<u32>(left_buttons[i])) != 0;
|
||||
const int button = static_cast<int>(left_buttons[i]);
|
||||
callbacks.on_button_data(button, button_status);
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) {
|
||||
static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{
|
||||
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
|
||||
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
|
||||
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
|
||||
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
|
||||
Joycon::PasivePadButton::Plus, Joycon::PasivePadButton::Home,
|
||||
Joycon::PasivePadButton::StickR,
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < right_buttons.size(); ++i) {
|
||||
const bool button_status = (input.button_input & static_cast<u32>(right_buttons[i])) != 0;
|
||||
const int button = static_cast<int>(right_buttons[i]);
|
||||
callbacks.on_button_data(button, button_status);
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) {
|
||||
static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{
|
||||
Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
|
||||
Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
|
||||
Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
|
||||
Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
|
||||
Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Plus,
|
||||
Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home,
|
||||
Joycon::PasivePadButton::StickL, Joycon::PasivePadButton::StickR,
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
|
||||
const bool button_status = (input.button_input & static_cast<u32>(pro_buttons[i])) != 0;
|
||||
const int button = static_cast<int>(pro_buttons[i]);
|
||||
callbacks.on_button_data(button, button_status);
|
||||
}
|
||||
}
|
||||
|
||||
f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const {
|
||||
const f32 value = static_cast<f32>(raw_value - calibration.center);
|
||||
if (value > 0.0f) {
|
||||
return value / calibration.max;
|
||||
}
|
||||
return value / calibration.min;
|
||||
}
|
||||
|
||||
f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
|
||||
AccelerometerSensitivity sensitivity) const {
|
||||
const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
|
||||
switch (sensitivity) {
|
||||
case Joycon::AccelerometerSensitivity::G2:
|
||||
return value / 4.0f;
|
||||
case Joycon::AccelerometerSensitivity::G4:
|
||||
return value / 2.0f;
|
||||
case Joycon::AccelerometerSensitivity::G8:
|
||||
return value;
|
||||
case Joycon::AccelerometerSensitivity::G16:
|
||||
return value * 2.0f;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal,
|
||||
GyroSensitivity sensitivity) const {
|
||||
const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
|
||||
switch (sensitivity) {
|
||||
case Joycon::GyroSensitivity::DPS250:
|
||||
return value / 8.0f;
|
||||
case Joycon::GyroSensitivity::DPS500:
|
||||
return value / 4.0f;
|
||||
case Joycon::GyroSensitivity::DPS1000:
|
||||
return value / 2.0f;
|
||||
case Joycon::GyroSensitivity::DPS2000:
|
||||
return value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis,
|
||||
const InputReportActive& input) const {
|
||||
return input.motion_input[(sensor * 3) + axis];
|
||||
}
|
||||
|
||||
MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status) const {
|
||||
MotionData motion{};
|
||||
const auto& accel_cal = motion_calibration.accelerometer;
|
||||
const auto& gyro_cal = motion_calibration.gyro;
|
||||
const s16 raw_accel_x = input.motion_input[1];
|
||||
const s16 raw_accel_y = input.motion_input[0];
|
||||
const s16 raw_accel_z = input.motion_input[2];
|
||||
const s16 raw_gyro_x = input.motion_input[4];
|
||||
const s16 raw_gyro_y = input.motion_input[3];
|
||||
const s16 raw_gyro_z = input.motion_input[5];
|
||||
|
||||
motion.delta_timestamp = motion_status.delta_time;
|
||||
motion.accel_x =
|
||||
GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity);
|
||||
motion.accel_y =
|
||||
GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity);
|
||||
motion.accel_z =
|
||||
GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity);
|
||||
motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity);
|
||||
motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity);
|
||||
motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity);
|
||||
|
||||
// TODO(German77): Return all three samples data
|
||||
return motion;
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
80
src/input_common/helpers/joycon_protocol/poller.h
Executable file
80
src/input_common/helpers/joycon_protocol/poller.h
Executable file
@@ -0,0 +1,80 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <span>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
// Handles input packages and triggers the corresponding input events
|
||||
class JoyconPoller {
|
||||
public:
|
||||
JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
|
||||
JoyStickCalibration right_stick_calibration_,
|
||||
MotionCalibration motion_calibration_);
|
||||
|
||||
void SetCallbacks(const Joycon::JoyconCallbacks& callbacks_);
|
||||
|
||||
/// Handles data from passive packages
|
||||
void ReadPassiveMode(std::span<u8> buffer);
|
||||
|
||||
/// Handles data from active packages
|
||||
void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
|
||||
const RingStatus& ring_status);
|
||||
|
||||
/// Handles data from nfc or ir packages
|
||||
void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status);
|
||||
|
||||
void UpdateColor(const Color& color);
|
||||
void UpdateRing(s16 value, const RingStatus& ring_status);
|
||||
void updateAmiibo(const std::vector<u8>& amiibo_data);
|
||||
|
||||
private:
|
||||
void UpdateActiveLeftPadInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status);
|
||||
void UpdateActiveRightPadInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status);
|
||||
void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status);
|
||||
|
||||
void UpdatePasiveLeftPadInput(const InputReportPassive& buffer);
|
||||
void UpdatePasiveRightPadInput(const InputReportPassive& buffer);
|
||||
void UpdatePasiveProPadInput(const InputReportPassive& buffer);
|
||||
|
||||
/// Returns a calibrated joystick axis from raw axis data
|
||||
f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const;
|
||||
|
||||
/// Returns a calibrated accelerometer axis from raw motion data
|
||||
f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
|
||||
AccelerometerSensitivity sensitivity) const;
|
||||
|
||||
/// Returns a calibrated gyro axis from raw motion data
|
||||
f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal,
|
||||
GyroSensitivity sensitivity) const;
|
||||
|
||||
/// Returns a raw motion value from a buffer
|
||||
s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const;
|
||||
|
||||
/// Returns motion data from a buffer
|
||||
MotionData GetMotionInput(const InputReportActive& input,
|
||||
const MotionStatus& motion_status) const;
|
||||
|
||||
ControllerType device_type{};
|
||||
|
||||
// Device calibration
|
||||
JoyStickCalibration left_stick_calibration{};
|
||||
JoyStickCalibration right_stick_calibration{};
|
||||
MotionCalibration motion_calibration{};
|
||||
|
||||
Joycon::JoyconCallbacks callbacks{};
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
129
src/input_common/helpers/joycon_protocol/ringcon.cpp
Executable file
129
src/input_common/helpers/joycon_protocol/ringcon.cpp
Executable file
@@ -0,0 +1,129 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol/ringcon.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle)
|
||||
: JoyconCommonProtocol(std::move(handle)) {}
|
||||
|
||||
DriverResult RingConProtocol::EnableRingCon() {
|
||||
LOG_DEBUG(Input, "Enable Ringcon");
|
||||
DriverResult result{DriverResult::Success};
|
||||
SetBlocking();
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = EnableMCU(true);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
const MCUConfig config{
|
||||
.command = MCUCommand::ConfigureMCU,
|
||||
.sub_command = MCUSubCommand::SetDeviceMode,
|
||||
.mode = MCUMode::Standby,
|
||||
.crc = {},
|
||||
};
|
||||
result = ConfigureMCU(config);
|
||||
}
|
||||
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult RingConProtocol::DisableRingCon() {
|
||||
LOG_DEBUG(Input, "Disable RingCon");
|
||||
DriverResult result{DriverResult::Success};
|
||||
SetBlocking();
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = EnableMCU(false);
|
||||
}
|
||||
|
||||
is_enabled = false;
|
||||
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult RingConProtocol::StartRingconPolling() {
|
||||
LOG_DEBUG(Input, "Enable Ringcon");
|
||||
bool is_connected = false;
|
||||
DriverResult result{DriverResult::Success};
|
||||
SetBlocking();
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = IsRingConnected(is_connected);
|
||||
}
|
||||
if (result == DriverResult::Success && is_connected) {
|
||||
LOG_INFO(Input, "Ringcon detected");
|
||||
result = ConfigureRing();
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
is_enabled = true;
|
||||
}
|
||||
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
|
||||
LOG_DEBUG(Input, "IsRingConnected");
|
||||
constexpr std::size_t max_tries = 28;
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
is_connected = false;
|
||||
|
||||
do {
|
||||
std::array<u8, 1> empty_data{};
|
||||
const auto result = SendSubCommand(SubCommand::UNKNOWN_RINGCON, empty_data, output);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (tries++ >= max_tries) {
|
||||
return DriverResult::NoDeviceDetected;
|
||||
}
|
||||
} while (output[14] != 0x59 || output[16] != 0x20);
|
||||
|
||||
is_connected = true;
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult RingConProtocol::ConfigureRing() {
|
||||
LOG_DEBUG(Input, "ConfigureRing");
|
||||
constexpr std::size_t max_tries = 28;
|
||||
DriverResult result{DriverResult::Success};
|
||||
std::vector<u8> output;
|
||||
std::size_t tries = 0;
|
||||
|
||||
static constexpr std::array<u8, 37> ring_config{
|
||||
0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, 0xED, 0x34, 0x36,
|
||||
0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36};
|
||||
do {
|
||||
result = SendSubCommand(SubCommand::UNKNOWN_RINGCON3, ring_config, output);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
if (tries++ >= max_tries) {
|
||||
return DriverResult::NoDeviceDetected;
|
||||
}
|
||||
} while (output[14] != 0x5C);
|
||||
|
||||
static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02};
|
||||
result = SendSubCommand(SubCommand::UNKNOWN_RINGCON2, ringcon_data, output);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool RingConProtocol::IsEnabled() const {
|
||||
return is_enabled;
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
38
src/input_common/helpers/joycon_protocol/ringcon.h
Executable file
38
src/input_common/helpers/joycon_protocol/ringcon.h
Executable file
@@ -0,0 +1,38 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/common_protocol.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
class RingConProtocol final : private JoyconCommonProtocol {
|
||||
public:
|
||||
explicit RingConProtocol(std::shared_ptr<JoyconHandle> handle);
|
||||
|
||||
DriverResult EnableRingCon();
|
||||
|
||||
DriverResult DisableRingCon();
|
||||
|
||||
DriverResult StartRingconPolling();
|
||||
|
||||
bool IsEnabled() const;
|
||||
|
||||
private:
|
||||
DriverResult IsRingConnected(bool& is_connected);
|
||||
|
||||
DriverResult ConfigureRing();
|
||||
|
||||
bool is_enabled{};
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
302
src/input_common/helpers/joycon_protocol/rumble.cpp
Executable file
302
src/input_common/helpers/joycon_protocol/rumble.cpp
Executable file
@@ -0,0 +1,302 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "input_common/helpers/joycon_protocol/rumble.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
|
||||
: JoyconCommonProtocol(std::move(handle)) {}
|
||||
|
||||
DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
|
||||
LOG_DEBUG(Input, "Enable Rumble");
|
||||
const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
|
||||
std::vector<u8> output;
|
||||
SetBlocking();
|
||||
const auto result = SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer, output);
|
||||
SetNonBlocking();
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
|
||||
std::array<u8, sizeof(DefaultVibrationBuffer)> buffer{};
|
||||
|
||||
if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) {
|
||||
return SendVibrationReport(DefaultVibrationBuffer);
|
||||
}
|
||||
|
||||
// Protect joycons from damage from strong vibrations
|
||||
const f32 clamp_amplitude =
|
||||
1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude);
|
||||
|
||||
const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency);
|
||||
const u8 encoded_high_amplitude =
|
||||
EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude);
|
||||
const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency);
|
||||
const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude);
|
||||
|
||||
buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF);
|
||||
buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01));
|
||||
buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80));
|
||||
buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF);
|
||||
|
||||
// Duplicate rumble for now
|
||||
buffer[4] = buffer[0];
|
||||
buffer[5] = buffer[1];
|
||||
buffer[6] = buffer[2];
|
||||
buffer[7] = buffer[3];
|
||||
|
||||
return SendVibrationReport(buffer);
|
||||
}
|
||||
|
||||
u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const {
|
||||
const u8 new_frequency =
|
||||
static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
|
||||
return static_cast<u16>((new_frequency - 0x60) * 4);
|
||||
}
|
||||
|
||||
u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const {
|
||||
const u8 new_frequency =
|
||||
static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
|
||||
return static_cast<u8>(new_frequency - 0x40);
|
||||
}
|
||||
|
||||
u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
|
||||
/* More information about these values can be found here:
|
||||
* https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
|
||||
*/
|
||||
static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
|
||||
std::pair<f32, int>{0.0f, 0x0},
|
||||
{0.01f, 0x2},
|
||||
{0.012f, 0x4},
|
||||
{0.014f, 0x6},
|
||||
{0.017f, 0x8},
|
||||
{0.02f, 0x0a},
|
||||
{0.024f, 0x0c},
|
||||
{0.028f, 0x0e},
|
||||
{0.033f, 0x10},
|
||||
{0.04f, 0x12},
|
||||
{0.047f, 0x14},
|
||||
{0.056f, 0x16},
|
||||
{0.067f, 0x18},
|
||||
{0.08f, 0x1a},
|
||||
{0.095f, 0x1c},
|
||||
{0.112f, 0x1e},
|
||||
{0.117f, 0x20},
|
||||
{0.123f, 0x22},
|
||||
{0.128f, 0x24},
|
||||
{0.134f, 0x26},
|
||||
{0.14f, 0x28},
|
||||
{0.146f, 0x2a},
|
||||
{0.152f, 0x2c},
|
||||
{0.159f, 0x2e},
|
||||
{0.166f, 0x30},
|
||||
{0.173f, 0x32},
|
||||
{0.181f, 0x34},
|
||||
{0.189f, 0x36},
|
||||
{0.198f, 0x38},
|
||||
{0.206f, 0x3a},
|
||||
{0.215f, 0x3c},
|
||||
{0.225f, 0x3e},
|
||||
{0.23f, 0x40},
|
||||
{0.235f, 0x42},
|
||||
{0.24f, 0x44},
|
||||
{0.245f, 0x46},
|
||||
{0.251f, 0x48},
|
||||
{0.256f, 0x4a},
|
||||
{0.262f, 0x4c},
|
||||
{0.268f, 0x4e},
|
||||
{0.273f, 0x50},
|
||||
{0.279f, 0x52},
|
||||
{0.286f, 0x54},
|
||||
{0.292f, 0x56},
|
||||
{0.298f, 0x58},
|
||||
{0.305f, 0x5a},
|
||||
{0.311f, 0x5c},
|
||||
{0.318f, 0x5e},
|
||||
{0.325f, 0x60},
|
||||
{0.332f, 0x62},
|
||||
{0.34f, 0x64},
|
||||
{0.347f, 0x66},
|
||||
{0.355f, 0x68},
|
||||
{0.362f, 0x6a},
|
||||
{0.37f, 0x6c},
|
||||
{0.378f, 0x6e},
|
||||
{0.387f, 0x70},
|
||||
{0.395f, 0x72},
|
||||
{0.404f, 0x74},
|
||||
{0.413f, 0x76},
|
||||
{0.422f, 0x78},
|
||||
{0.431f, 0x7a},
|
||||
{0.44f, 0x7c},
|
||||
{0.45f, 0x7e},
|
||||
{0.46f, 0x80},
|
||||
{0.47f, 0x82},
|
||||
{0.48f, 0x84},
|
||||
{0.491f, 0x86},
|
||||
{0.501f, 0x88},
|
||||
{0.512f, 0x8a},
|
||||
{0.524f, 0x8c},
|
||||
{0.535f, 0x8e},
|
||||
{0.547f, 0x90},
|
||||
{0.559f, 0x92},
|
||||
{0.571f, 0x94},
|
||||
{0.584f, 0x96},
|
||||
{0.596f, 0x98},
|
||||
{0.609f, 0x9a},
|
||||
{0.623f, 0x9c},
|
||||
{0.636f, 0x9e},
|
||||
{0.65f, 0xa0},
|
||||
{0.665f, 0xa2},
|
||||
{0.679f, 0xa4},
|
||||
{0.694f, 0xa6},
|
||||
{0.709f, 0xa8},
|
||||
{0.725f, 0xaa},
|
||||
{0.741f, 0xac},
|
||||
{0.757f, 0xae},
|
||||
{0.773f, 0xb0},
|
||||
{0.79f, 0xb2},
|
||||
{0.808f, 0xb4},
|
||||
{0.825f, 0xb6},
|
||||
{0.843f, 0xb8},
|
||||
{0.862f, 0xba},
|
||||
{0.881f, 0xbc},
|
||||
{0.9f, 0xbe},
|
||||
{0.92f, 0xc0},
|
||||
{0.94f, 0xc2},
|
||||
{0.96f, 0xc4},
|
||||
{0.981f, 0xc6},
|
||||
{1.003f, 0xc8},
|
||||
};
|
||||
|
||||
for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
|
||||
if (amplitude <= amplitude_value) {
|
||||
return static_cast<u8>(code);
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
|
||||
}
|
||||
|
||||
u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const {
|
||||
/* More information about these values can be found here:
|
||||
* https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
|
||||
*/
|
||||
static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
|
||||
std::pair<f32, int>{0.0f, 0x0040},
|
||||
{0.01f, 0x8040},
|
||||
{0.012f, 0x0041},
|
||||
{0.014f, 0x8041},
|
||||
{0.017f, 0x0042},
|
||||
{0.02f, 0x8042},
|
||||
{0.024f, 0x0043},
|
||||
{0.028f, 0x8043},
|
||||
{0.033f, 0x0044},
|
||||
{0.04f, 0x8044},
|
||||
{0.047f, 0x0045},
|
||||
{0.056f, 0x8045},
|
||||
{0.067f, 0x0046},
|
||||
{0.08f, 0x8046},
|
||||
{0.095f, 0x0047},
|
||||
{0.112f, 0x8047},
|
||||
{0.117f, 0x0048},
|
||||
{0.123f, 0x8048},
|
||||
{0.128f, 0x0049},
|
||||
{0.134f, 0x8049},
|
||||
{0.14f, 0x004a},
|
||||
{0.146f, 0x804a},
|
||||
{0.152f, 0x004b},
|
||||
{0.159f, 0x804b},
|
||||
{0.166f, 0x004c},
|
||||
{0.173f, 0x804c},
|
||||
{0.181f, 0x004d},
|
||||
{0.189f, 0x804d},
|
||||
{0.198f, 0x004e},
|
||||
{0.206f, 0x804e},
|
||||
{0.215f, 0x004f},
|
||||
{0.225f, 0x804f},
|
||||
{0.23f, 0x0050},
|
||||
{0.235f, 0x8050},
|
||||
{0.24f, 0x0051},
|
||||
{0.245f, 0x8051},
|
||||
{0.251f, 0x0052},
|
||||
{0.256f, 0x8052},
|
||||
{0.262f, 0x0053},
|
||||
{0.268f, 0x8053},
|
||||
{0.273f, 0x0054},
|
||||
{0.279f, 0x8054},
|
||||
{0.286f, 0x0055},
|
||||
{0.292f, 0x8055},
|
||||
{0.298f, 0x0056},
|
||||
{0.305f, 0x8056},
|
||||
{0.311f, 0x0057},
|
||||
{0.318f, 0x8057},
|
||||
{0.325f, 0x0058},
|
||||
{0.332f, 0x8058},
|
||||
{0.34f, 0x0059},
|
||||
{0.347f, 0x8059},
|
||||
{0.355f, 0x005a},
|
||||
{0.362f, 0x805a},
|
||||
{0.37f, 0x005b},
|
||||
{0.378f, 0x805b},
|
||||
{0.387f, 0x005c},
|
||||
{0.395f, 0x805c},
|
||||
{0.404f, 0x005d},
|
||||
{0.413f, 0x805d},
|
||||
{0.422f, 0x005e},
|
||||
{0.431f, 0x805e},
|
||||
{0.44f, 0x005f},
|
||||
{0.45f, 0x805f},
|
||||
{0.46f, 0x0060},
|
||||
{0.47f, 0x8060},
|
||||
{0.48f, 0x0061},
|
||||
{0.491f, 0x8061},
|
||||
{0.501f, 0x0062},
|
||||
{0.512f, 0x8062},
|
||||
{0.524f, 0x0063},
|
||||
{0.535f, 0x8063},
|
||||
{0.547f, 0x0064},
|
||||
{0.559f, 0x8064},
|
||||
{0.571f, 0x0065},
|
||||
{0.584f, 0x8065},
|
||||
{0.596f, 0x0066},
|
||||
{0.609f, 0x8066},
|
||||
{0.623f, 0x0067},
|
||||
{0.636f, 0x8067},
|
||||
{0.65f, 0x0068},
|
||||
{0.665f, 0x8068},
|
||||
{0.679f, 0x0069},
|
||||
{0.694f, 0x8069},
|
||||
{0.709f, 0x006a},
|
||||
{0.725f, 0x806a},
|
||||
{0.741f, 0x006b},
|
||||
{0.757f, 0x806b},
|
||||
{0.773f, 0x006c},
|
||||
{0.79f, 0x806c},
|
||||
{0.808f, 0x006d},
|
||||
{0.825f, 0x806d},
|
||||
{0.843f, 0x006e},
|
||||
{0.862f, 0x806e},
|
||||
{0.881f, 0x006f},
|
||||
{0.9f, 0x806f},
|
||||
{0.92f, 0x0070},
|
||||
{0.94f, 0x8070},
|
||||
{0.96f, 0x0071},
|
||||
{0.981f, 0x8071},
|
||||
{1.003f, 0x0072},
|
||||
};
|
||||
|
||||
for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
|
||||
if (amplitude <= amplitude_value) {
|
||||
return static_cast<u16>(code);
|
||||
}
|
||||
}
|
||||
|
||||
return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
33
src/input_common/helpers/joycon_protocol/rumble.h
Executable file
33
src/input_common/helpers/joycon_protocol/rumble.h
Executable file
@@ -0,0 +1,33 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/common_protocol.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
class RumbleProtocol final : private JoyconCommonProtocol {
|
||||
public:
|
||||
explicit RumbleProtocol(std::shared_ptr<JoyconHandle> handle);
|
||||
|
||||
DriverResult EnableRumble(bool is_enabled);
|
||||
|
||||
DriverResult SendVibration(const VibrationValue& vibration);
|
||||
|
||||
private:
|
||||
u16 EncodeHighFrequency(f32 frequency) const;
|
||||
u8 EncodeLowFrequency(f32 frequency) const;
|
||||
u8 EncodeHighAmplitude(f32 amplitude) const;
|
||||
u16 EncodeLowAmplitude(f32 amplitude) const;
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
Reference in New Issue
Block a user