committed by
Narr the Reg
9 changed files with 544 additions and 13 deletions
-
3src/core/hid/emulated_controller.cpp
-
2src/input_common/CMakeLists.txt
-
4src/input_common/drivers/joycon.cpp
-
44src/input_common/helpers/joycon_driver.cpp
-
24src/input_common/helpers/joycon_driver.h
-
414src/input_common/helpers/joycon_protocol/nfc.cpp
-
61src/input_common/helpers/joycon_protocol/nfc.h
-
4src/input_common/helpers/joycon_protocol/poller.cpp
-
1src/input_common/helpers/joycon_protocol/poller.h
@ -0,0 +1,414 @@ |
|||||
|
// 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(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() { |
||||
|
return is_enabled; |
||||
|
} |
||||
|
|
||||
|
} // namespace InputCommon::Joycon
|
||||
@ -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: |
||||
|
NfcProtocol(std::shared_ptr<JoyconHandle> handle); |
||||
|
|
||||
|
DriverResult EnableNfc(); |
||||
|
|
||||
|
DriverResult DisableNfc(); |
||||
|
|
||||
|
DriverResult StartNFCPollingMode(); |
||||
|
|
||||
|
DriverResult ScanAmiibo(std::vector<u8>& data); |
||||
|
|
||||
|
bool HasAmiibo(); |
||||
|
|
||||
|
bool IsEnabled(); |
||||
|
|
||||
|
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 |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue