From 6d6b7bdbc327528d155f0422ef096846559844c0 Mon Sep 17 00:00:00 2001 From: german77 Date: Thu, 22 Dec 2022 01:07:46 -0600 Subject: input_common: Implement joycon nfc --- src/core/hid/emulated_controller.cpp | 3 +- src/input_common/CMakeLists.txt | 2 + src/input_common/drivers/joycon.cpp | 4 +- src/input_common/helpers/joycon_driver.cpp | 44 +++ src/input_common/helpers/joycon_driver.h | 24 +- src/input_common/helpers/joycon_protocol/nfc.cpp | 414 +++++++++++++++++++++ src/input_common/helpers/joycon_protocol/nfc.h | 61 +++ .../helpers/joycon_protocol/poller.cpp | 4 + src/input_common/helpers/joycon_protocol/poller.h | 1 + 9 files changed, 544 insertions(+), 13 deletions(-) create mode 100644 src/input_common/helpers/joycon_protocol/nfc.cpp create mode 100644 src/input_common/helpers/joycon_protocol/nfc.h (limited to 'src') diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 89638cb85..1e4ec4add 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -144,7 +144,8 @@ void EmulatedController::LoadDevices() { battery_params[RightIndex].Set("battery", true); camera_params = Common::ParamPackage{"engine:camera,camera:1"}; - nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"}; + nfc_params = right_joycon; + nfc_params.Set("nfc", true); ring_params = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"}; output_params[LeftIndex] = left_joycon; diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index addecc9b3..9c901af2a 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -64,6 +64,8 @@ if (ENABLE_SDL2) helpers/joycon_protocol/generic_functions.cpp helpers/joycon_protocol/generic_functions.h helpers/joycon_protocol/joycon_types.h + helpers/joycon_protocol/nfc.cpp + helpers/joycon_protocol/nfc.h helpers/joycon_protocol/poller.cpp helpers/joycon_protocol/poller.h helpers/joycon_protocol/ringcon.cpp diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp index 049ecc4f2..29f0dc0c8 100644 --- a/src/input_common/drivers/joycon.cpp +++ b/src/input_common/drivers/joycon.cpp @@ -388,7 +388,9 @@ void Joycons::OnRingConUpdate(f32 ring_data) { void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector& amiibo_data) { const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right); - SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data}); + const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved + : Common::Input::NfcState::NewAmiibo; + SetNfc(identifier, {nfc_state, amiibo_data}); } std::shared_ptr Joycons::GetHandle(PadIdentifier identifier) const { diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp index c0a03fe2e..c3debffd1 100644 --- a/src/input_common/helpers/joycon_driver.cpp +++ b/src/input_common/helpers/joycon_driver.cpp @@ -5,6 +5,12 @@ #include "common/swap.h" #include "common/thread.h" #include "input_common/helpers/joycon_driver.h" +#include "input_common/helpers/joycon_protocol/calibration.h" +#include "input_common/helpers/joycon_protocol/generic_functions.h" +#include "input_common/helpers/joycon_protocol/nfc.h" +#include "input_common/helpers/joycon_protocol/poller.h" +#include "input_common/helpers/joycon_protocol/ringcon.h" +#include "input_common/helpers/joycon_protocol/rumble.h" namespace InputCommon::Joycon { JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} { @@ -72,6 +78,7 @@ DriverResult JoyconDriver::InitializeDevice() { // Initialize HW Protocols calibration_protocol = std::make_unique(hidapi_handle); generic_protocol = std::make_unique(hidapi_handle); + nfc_protocol = std::make_unique(hidapi_handle); ring_protocol = std::make_unique(hidapi_handle); rumble_protocol = std::make_unique(hidapi_handle); @@ -193,6 +200,25 @@ void JoyconDriver::OnNewData(std::span buffer) { .min_value = ring_calibration.min_value, }; + if (nfc_protocol->IsEnabled()) { + if (amiibo_detected) { + if (!nfc_protocol->HasAmiibo()) { + joycon_poller->updateAmiibo({}); + amiibo_detected = false; + return; + } + } + + if (!amiibo_detected) { + std::vector data(0x21C); + const auto result = nfc_protocol->ScanAmiibo(data); + if (result == DriverResult::Success) { + joycon_poller->updateAmiibo(data); + amiibo_detected = true; + } + } + } + switch (report_mode) { case InputReport::STANDARD_FULL_60HZ: joycon_poller->ReadActiveMode(buffer, motion_status, ring_status); @@ -225,6 +251,24 @@ void JoyconDriver::SetPollingMode() { generic_protocol->EnableImu(false); } + if (nfc_protocol->IsEnabled()) { + amiibo_detected = false; + nfc_protocol->DisableNfc(); + } + + if (nfc_enabled && supported_features.nfc) { + auto result = nfc_protocol->EnableNfc(); + if (result == DriverResult::Success) { + result = nfc_protocol->StartNFCPollingMode(); + } + if (result == DriverResult::Success) { + disable_input_thread = false; + return; + } + nfc_protocol->DisableNfc(); + LOG_ERROR(Input, "Error enabling NFC"); + } + if (ring_protocol->IsEnabled()) { ring_connected = false; ring_protocol->DisableRingCon(); diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h index dc5d60221..c9118ee93 100644 --- a/src/input_common/helpers/joycon_driver.h +++ b/src/input_common/helpers/joycon_driver.h @@ -8,14 +8,15 @@ #include #include -#include "input_common/helpers/joycon_protocol/calibration.h" -#include "input_common/helpers/joycon_protocol/generic_functions.h" #include "input_common/helpers/joycon_protocol/joycon_types.h" -#include "input_common/helpers/joycon_protocol/poller.h" -#include "input_common/helpers/joycon_protocol/ringcon.h" -#include "input_common/helpers/joycon_protocol/rumble.h" namespace InputCommon::Joycon { +class CalibrationProtocol; +class GenericProtocol; +class NfcProtocol; +class JoyconPoller; +class RingConProtocol; +class RumbleProtocol; class JoyconDriver final { public: @@ -84,17 +85,18 @@ private: SupportedFeatures GetSupportedFeatures(); // Protocol Features - std::unique_ptr calibration_protocol = nullptr; - std::unique_ptr generic_protocol = nullptr; - std::unique_ptr joycon_poller = nullptr; - std::unique_ptr ring_protocol = nullptr; - std::unique_ptr rumble_protocol = nullptr; + std::unique_ptr calibration_protocol; + std::unique_ptr generic_protocol; + std::unique_ptr nfc_protocol; + std::unique_ptr joycon_poller; + std::unique_ptr ring_protocol; + std::unique_ptr rumble_protocol; // Connection status bool is_connected{}; u64 delta_time; std::size_t error_counter{}; - std::shared_ptr hidapi_handle = nullptr; + std::shared_ptr hidapi_handle; std::chrono::time_point last_update; // External device status diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp new file mode 100644 index 000000000..69b2bfe05 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/nfc.cpp @@ -0,0 +1,414 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include "common/logging/log.h" +#include "input_common/helpers/joycon_protocol/nfc.h" + +namespace InputCommon::Joycon { + +NfcProtocol::NfcProtocol(std::shared_ptr handle) : JoyconCommonProtocol(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& 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 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 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 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& ntag_data) { + constexpr std::size_t timeout_limit = 10; + std::vector 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& 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 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& 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 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& 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 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& 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 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() { + return is_enabled; +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h new file mode 100644 index 000000000..0ede03d50 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol/nfc.h @@ -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 + +#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: + NfcProtocol(std::shared_ptr handle); + + DriverResult EnableNfc(); + + DriverResult DisableNfc(); + + DriverResult StartNFCPollingMode(); + + DriverResult ScanAmiibo(std::vector& data); + + bool HasAmiibo(); + + bool IsEnabled(); + +private: + struct TagFoundData { + u8 type; + std::vector uuid; + }; + + DriverResult WaitUntilNfcIsReady(); + + DriverResult StartPolling(TagFoundData& data); + + DriverResult ReadTag(const TagFoundData& data); + + DriverResult GetAmiiboData(std::vector& data); + + DriverResult SendStartPollingRequest(std::vector& output); + + DriverResult SendStopPollingRequest(std::vector& output); + + DriverResult SendStartWaitingRecieveRequest(std::vector& output); + + DriverResult SendReadAmiiboRequest(std::vector& output, std::size_t ntag_pages); + + NFCReadBlockCommand GetReadBlockCommand(std::size_t pages) const; + + bool is_enabled{}; +}; + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp index cb76e1e06..fd05d98f3 100644 --- a/src/input_common/helpers/joycon_protocol/poller.cpp +++ b/src/input_common/helpers/joycon_protocol/poller.cpp @@ -74,6 +74,10 @@ void JoyconPoller::UpdateColor(const Color& color) { callbacks.on_color_data(color); } +void JoyconPoller::updateAmiibo(const std::vector& amiibo_data) { + callbacks.on_amiibo_data(amiibo_data); +} + void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) { float normalized_value = static_cast(value - ring_status.default_value); if (normalized_value > 0) { diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h index 68b14b8ba..c40fc7bca 100644 --- a/src/input_common/helpers/joycon_protocol/poller.h +++ b/src/input_common/helpers/joycon_protocol/poller.h @@ -36,6 +36,7 @@ public: void UpdateColor(const Color& color); void UpdateRing(s16 value, const RingStatus& ring_status); + void updateAmiibo(const std::vector& amiibo_data); private: void UpdateActiveLeftPadInput(const InputReportActive& input, -- cgit v1.2.3