Merge pull request #10842 from german77/native_mifare

input_common: Implement native mifare/skylander support for joycons/pro controller
This commit is contained in:
liamwhite 2023-06-23 09:27:00 -04:00 committed by GitHub
commit 87b9b5d10f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1170 additions and 198 deletions

View file

@ -86,7 +86,7 @@ enum class NfcState {
NewAmiibo, NewAmiibo,
WaitingForAmiibo, WaitingForAmiibo,
AmiiboRemoved, AmiiboRemoved,
NotAnAmiibo, InvalidTagType,
NotSupported, NotSupported,
WrongDeviceState, WrongDeviceState,
WriteFailed, WriteFailed,
@ -218,8 +218,22 @@ struct CameraStatus {
}; };
struct NfcStatus { struct NfcStatus {
NfcState state{}; NfcState state{NfcState::Unknown};
std::vector<u8> data{}; u8 uuid_length;
u8 protocol;
u8 tag_type;
std::array<u8, 10> uuid;
};
struct MifareData {
u8 command;
u8 sector;
std::array<u8, 0x6> key;
std::array<u8, 0x10> data;
};
struct MifareRequest {
std::array<MifareData, 0x10> data;
}; };
// List of buttons to be passed to Qt that can be translated // List of buttons to be passed to Qt that can be translated
@ -294,7 +308,7 @@ struct CallbackStatus {
BatteryStatus battery_status{}; BatteryStatus battery_status{};
VibrationStatus vibration_status{}; VibrationStatus vibration_status{};
CameraFormat camera_status{CameraFormat::None}; CameraFormat camera_status{CameraFormat::None};
NfcState nfc_status{NfcState::Unknown}; NfcStatus nfc_status{};
std::vector<u8> raw_data{}; std::vector<u8> raw_data{};
}; };
@ -356,9 +370,30 @@ public:
return NfcState::NotSupported; return NfcState::NotSupported;
} }
virtual NfcState StartNfcPolling() {
return NfcState::NotSupported;
}
virtual NfcState StopNfcPolling() {
return NfcState::NotSupported;
}
virtual NfcState ReadAmiiboData([[maybe_unused]] std::vector<u8>& out_data) {
return NfcState::NotSupported;
}
virtual NfcState WriteNfcData([[maybe_unused]] const std::vector<u8>& data) { virtual NfcState WriteNfcData([[maybe_unused]] const std::vector<u8>& data) {
return NfcState::NotSupported; return NfcState::NotSupported;
} }
virtual NfcState ReadMifareData([[maybe_unused]] const MifareRequest& request,
[[maybe_unused]] MifareRequest& out_data) {
return NfcState::NotSupported;
}
virtual NfcState WriteMifareData([[maybe_unused]] const MifareRequest& request) {
return NfcState::NotSupported;
}
}; };
/// An abstract class template for a factory that can create input devices. /// An abstract class template for a factory that can create input devices.

View file

@ -149,12 +149,16 @@ void EmulatedController::LoadDevices() {
camera_params[0] = right_joycon; camera_params[0] = right_joycon;
camera_params[0].Set("camera", true); camera_params[0].Set("camera", true);
camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
nfc_params[1] = right_joycon; nfc_params[1] = right_joycon;
nfc_params[1].Set("nfc", true); nfc_params[1].Set("nfc", true);
// Only map virtual devices to the first controller
if (npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld) {
camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
}
output_params[LeftIndex] = left_joycon; output_params[LeftIndex] = left_joycon;
output_params[RightIndex] = right_joycon; output_params[RightIndex] = right_joycon;
output_params[2] = camera_params[1]; output_params[2] = camera_params[1];
@ -1176,10 +1180,7 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
return; return;
} }
controller.nfc_state = { controller.nfc_state = controller.nfc_values;
controller.nfc_values.state,
controller.nfc_values.data,
};
} }
bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
@ -1308,6 +1309,73 @@ bool EmulatedController::HasNfc() const {
return is_connected && (has_virtual_nfc && is_virtual_nfc_supported); return is_connected && (has_virtual_nfc && is_virtual_nfc_supported);
} }
bool EmulatedController::AddNfcHandle() {
nfc_handles++;
return SetPollingMode(EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::NFC) ==
Common::Input::DriverResult::Success;
}
bool EmulatedController::RemoveNfcHandle() {
nfc_handles--;
if (nfc_handles <= 0) {
return SetPollingMode(EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::Active) ==
Common::Input::DriverResult::Success;
}
return true;
}
bool EmulatedController::StartNfcPolling() {
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3];
return nfc_output_device->StartNfcPolling() == Common::Input::NfcState::Success ||
nfc_virtual_output_device->StartNfcPolling() == Common::Input::NfcState::Success;
}
bool EmulatedController::StopNfcPolling() {
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3];
return nfc_output_device->StopNfcPolling() == Common::Input::NfcState::Success ||
nfc_virtual_output_device->StopNfcPolling() == Common::Input::NfcState::Success;
}
bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3];
if (nfc_output_device->ReadAmiiboData(data) == Common::Input::NfcState::Success) {
return true;
}
return nfc_virtual_output_device->ReadAmiiboData(data) == Common::Input::NfcState::Success;
}
bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request,
Common::Input::MifareRequest& out_data) {
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3];
if (nfc_output_device->ReadMifareData(request, out_data) == Common::Input::NfcState::Success) {
return true;
}
return nfc_virtual_output_device->ReadMifareData(request, out_data) ==
Common::Input::NfcState::Success;
}
bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) {
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3];
if (nfc_output_device->WriteMifareData(request) == Common::Input::NfcState::Success) {
return true;
}
return nfc_virtual_output_device->WriteMifareData(request) == Common::Input::NfcState::Success;
}
bool EmulatedController::WriteNfc(const std::vector<u8>& data) { bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_virtual_output_device = output_devices[3]; auto& nfc_virtual_output_device = output_devices[3];

View file

@ -97,10 +97,7 @@ struct RingSensorForce {
f32 force; f32 force;
}; };
struct NfcState { using NfcState = Common::Input::NfcStatus;
Common::Input::NfcState state{};
std::vector<u8> data{};
};
struct ControllerMotion { struct ControllerMotion {
Common::Vec3f accel{}; Common::Vec3f accel{};
@ -393,9 +390,31 @@ public:
/// Returns true if the device has nfc support /// Returns true if the device has nfc support
bool HasNfc() const; bool HasNfc() const;
/// Sets the joycon in nfc mode and increments the handle count
bool AddNfcHandle();
/// Decrements the handle count if zero sets the joycon in active mode
bool RemoveNfcHandle();
/// Start searching for nfc tags
bool StartNfcPolling();
/// Stop searching for nfc tags
bool StopNfcPolling();
/// Returns true if the nfc tag was readable
bool ReadAmiiboData(std::vector<u8>& data);
/// Returns true if the nfc tag was written /// Returns true if the nfc tag was written
bool WriteNfc(const std::vector<u8>& data); bool WriteNfc(const std::vector<u8>& data);
/// Returns true if the nfc tag was readable
bool ReadMifareData(const Common::Input::MifareRequest& request,
Common::Input::MifareRequest& out_data);
/// Returns true if the nfc tag was written
bool WriteMifareData(const Common::Input::MifareRequest& request);
/// Returns the led pattern corresponding to this emulated controller /// Returns the led pattern corresponding to this emulated controller
LedPattern GetLedPattern() const; LedPattern GetLedPattern() const;
@ -532,6 +551,7 @@ private:
bool system_buttons_enabled{true}; bool system_buttons_enabled{true};
f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard}; f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
u32 turbo_button_state{0}; u32 turbo_button_state{0};
std::size_t nfc_handles{0};
// Temporary values to avoid doing changes while the controller is in configuring mode // Temporary values to avoid doing changes while the controller is in configuring mode
NpadStyleIndex tmp_npad_type{NpadStyleIndex::None}; NpadStyleIndex tmp_npad_type{NpadStyleIndex::None};

View file

@ -299,11 +299,7 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal
Common::Input::NfcStatus nfc{}; Common::Input::NfcStatus nfc{};
switch (callback.type) { switch (callback.type) {
case Common::Input::InputType::Nfc: case Common::Input::InputType::Nfc:
nfc = { return callback.nfc_status;
.state = callback.nfc_status,
.data = callback.raw_data,
};
break;
default: default:
LOG_ERROR(Input, "Conversion from type {} to NFC not implemented", callback.type); LOG_ERROR(Input, "Conversion from type {} to NFC not implemented", callback.type);
break; break;

View file

@ -141,7 +141,7 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
applet_output.device_handle = applet_input_common.device_handle; applet_output.device_handle = applet_input_common.device_handle;
applet_output.result = CabinetResult::Cancel; applet_output.result = CabinetResult::Cancel;
const auto reg_result = nfp_device->GetRegisterInfo(applet_output.register_info); const auto reg_result = nfp_device->GetRegisterInfo(applet_output.register_info);
const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info, false); const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info);
nfp_device->Finalize(); nfp_device->Finalize();
if (reg_result.IsSuccess()) { if (reg_result.IsSuccess()) {

View file

@ -93,7 +93,8 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
const auto nfc_status = npad_device->GetNfc(); const auto nfc_status = npad_device->GetNfc();
switch (nfc_status.state) { switch (nfc_status.state) {
case Common::Input::NfcState::NewAmiibo: case Common::Input::NfcState::NewAmiibo:
LoadNfcTag(nfc_status.data); LoadNfcTag(nfc_status.protocol, nfc_status.tag_type, nfc_status.uuid_length,
nfc_status.uuid);
break; break;
case Common::Input::NfcState::AmiiboRemoved: case Common::Input::NfcState::AmiiboRemoved:
if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) { if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
@ -108,28 +109,46 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
} }
} }
bool NfcDevice::LoadNfcTag(std::span<const u8> data) { bool NfcDevice::LoadNfcTag(u8 protocol, u8 tag_type, u8 uuid_length, UniqueSerialNumber uuid) {
if (device_state != DeviceState::SearchingForTag) { if (device_state != DeviceState::SearchingForTag) {
LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state); LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state);
return false; return false;
} }
if ((protocol & static_cast<u8>(allowed_protocols)) == 0) {
LOG_ERROR(Service_NFC, "Protocol not supported {}", protocol);
return false;
}
real_tag_info = {
.uuid = uuid,
.uuid_length = uuid_length,
.protocol = static_cast<NfcProtocol>(protocol),
.tag_type = static_cast<TagType>(tag_type),
};
device_state = DeviceState::TagFound;
deactivate_event->GetReadableEvent().Clear();
activate_event->Signal();
return true;
}
bool NfcDevice::LoadAmiiboData() {
std::vector<u8> data{};
if (!npad_device->ReadAmiiboData(data)) {
return false;
}
if (data.size() < sizeof(NFP::EncryptedNTAG215File)) { if (data.size() < sizeof(NFP::EncryptedNTAG215File)) {
LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size()); LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size());
return false; return false;
} }
mifare_data.resize(data.size());
memcpy(mifare_data.data(), data.data(), data.size());
memcpy(&tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); memcpy(&tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
is_plain_amiibo = NFP::AmiiboCrypto::IsAmiiboValid(tag_data); is_plain_amiibo = NFP::AmiiboCrypto::IsAmiiboValid(tag_data);
is_write_protected = false; is_write_protected = false;
device_state = DeviceState::TagFound;
deactivate_event->GetReadableEvent().Clear();
activate_event->Signal();
// Fallback for plain amiibos // Fallback for plain amiibos
if (is_plain_amiibo) { if (is_plain_amiibo) {
LOG_INFO(Service_NFP, "Using plain amiibo"); LOG_INFO(Service_NFP, "Using plain amiibo");
@ -147,6 +166,7 @@ bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
return true; return true;
} }
LOG_INFO(Service_NFP, "Using encrypted amiibo");
tag_data = {}; tag_data = {};
memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
return true; return true;
@ -162,7 +182,6 @@ void NfcDevice::CloseNfcTag() {
device_state = DeviceState::TagRemoved; device_state = DeviceState::TagRemoved;
encrypted_tag_data = {}; encrypted_tag_data = {};
tag_data = {}; tag_data = {};
mifare_data = {};
activate_event->GetReadableEvent().Clear(); activate_event->GetReadableEvent().Clear();
deactivate_event->Signal(); deactivate_event->Signal();
} }
@ -179,8 +198,12 @@ void NfcDevice::Initialize() {
device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable; device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable;
encrypted_tag_data = {}; encrypted_tag_data = {};
tag_data = {}; tag_data = {};
mifare_data = {};
is_initalized = true; if (device_state != DeviceState::Initialized) {
return;
}
is_initalized = npad_device->AddNfcHandle();
} }
void NfcDevice::Finalize() { void NfcDevice::Finalize() {
@ -190,6 +213,11 @@ void NfcDevice::Finalize() {
if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
StopDetection(); StopDetection();
} }
if (device_state != DeviceState::Unavailable) {
npad_device->RemoveNfcHandle();
}
device_state = DeviceState::Unavailable; device_state = DeviceState::Unavailable;
is_initalized = false; is_initalized = false;
} }
@ -200,10 +228,8 @@ Result NfcDevice::StartDetection(NfcProtocol allowed_protocol) {
return ResultWrongDeviceState; return ResultWrongDeviceState;
} }
if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, if (!npad_device->StartNfcPolling()) {
Common::Input::PollingMode::NFC) != LOG_ERROR(Service_NFC, "Nfc polling not supported");
Common::Input::DriverResult::Success) {
LOG_ERROR(Service_NFC, "Nfc not supported");
return ResultNfcDisabled; return ResultNfcDisabled;
} }
@ -213,9 +239,6 @@ Result NfcDevice::StartDetection(NfcProtocol allowed_protocol) {
} }
Result NfcDevice::StopDetection() { Result NfcDevice::StopDetection() {
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::Active);
if (device_state == DeviceState::Initialized) { if (device_state == DeviceState::Initialized) {
return ResultSuccess; return ResultSuccess;
} }
@ -225,6 +248,7 @@ Result NfcDevice::StopDetection() {
} }
if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
npad_device->StopNfcPolling();
device_state = DeviceState::Initialized; device_state = DeviceState::Initialized;
return ResultSuccess; return ResultSuccess;
} }
@ -233,7 +257,7 @@ Result NfcDevice::StopDetection() {
return ResultWrongDeviceState; return ResultWrongDeviceState;
} }
Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const { Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info) const {
if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) { if (device_state == DeviceState::TagRemoved) {
@ -242,41 +266,15 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const {
return ResultWrongDeviceState; return ResultWrongDeviceState;
} }
UniqueSerialNumber uuid{}; tag_info = real_tag_info;
u8 uuid_length{};
NfcProtocol protocol{NfcProtocol::TypeA};
TagType tag_type{TagType::Type2};
if (is_mifare) { // Generate random UUID to bypass amiibo load limits
tag_type = TagType::Mifare; if (real_tag_info.tag_type == TagType::Type2 && Settings::values.random_amiibo_id) {
uuid_length = sizeof(NFP::NtagTagUuid); Common::TinyMT rng{};
memcpy(uuid.data(), mifare_data.data(), uuid_length); rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
} else { rng.GenerateRandomBytes(tag_info.uuid.data(), tag_info.uuid_length);
tag_type = TagType::Type2;
uuid_length = sizeof(NFP::NtagTagUuid);
NFP::NtagTagUuid nUuid{
.part1 = encrypted_tag_data.uuid.part1,
.part2 = encrypted_tag_data.uuid.part2,
.nintendo_id = encrypted_tag_data.uuid.nintendo_id,
};
memcpy(uuid.data(), &nUuid, uuid_length);
// Generate random UUID to bypass amiibo load limits
if (Settings::values.random_amiibo_id) {
Common::TinyMT rng{};
rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
rng.GenerateRandomBytes(uuid.data(), uuid_length);
}
} }
// Protocol and tag type may change here
tag_info = {
.uuid = uuid,
.uuid_length = uuid_length,
.protocol = protocol,
.tag_type = tag_type,
};
return ResultSuccess; return ResultSuccess;
} }
@ -293,7 +291,7 @@ Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameter
Result result = ResultSuccess; Result result = ResultSuccess;
TagInfo tag_info{}; TagInfo tag_info{};
result = GetTagInfo(tag_info, true); result = GetTagInfo(tag_info);
if (result.IsError()) { if (result.IsError()) {
return result; return result;
@ -307,6 +305,8 @@ Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameter
return ResultInvalidArgument; return ResultInvalidArgument;
} }
Common::Input::MifareRequest request{};
Common::Input::MifareRequest out_data{};
const auto unknown = parameters[0].sector_key.unknown; const auto unknown = parameters[0].sector_key.unknown;
for (std::size_t i = 0; i < parameters.size(); i++) { for (std::size_t i = 0; i < parameters.size(); i++) {
if (unknown != parameters[i].sector_key.unknown) { if (unknown != parameters[i].sector_key.unknown) {
@ -315,25 +315,29 @@ Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameter
} }
for (std::size_t i = 0; i < parameters.size(); i++) { for (std::size_t i = 0; i < parameters.size(); i++) {
result = ReadMifare(parameters[i], read_block_data[i]); if (parameters[i].sector_key.command == MifareCmd::None) {
if (result.IsError()) { continue;
break;
} }
request.data[i].command = static_cast<u8>(parameters[i].sector_key.command);
request.data[i].sector = parameters[i].sector_number;
memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(),
sizeof(KeyData));
} }
return result; if (!npad_device->ReadMifareData(request, out_data)) {
}
Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter,
MifareReadBlockData& read_block_data) const {
const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock);
read_block_data.sector_number = parameter.sector_number;
if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
return ResultMifareError288; return ResultMifareError288;
} }
// TODO: Use parameter.sector_key to read encrypted data for (std::size_t i = 0; i < read_block_data.size(); i++) {
memcpy(read_block_data.data.data(), mifare_data.data() + sector_index, sizeof(DataBlock)); if (static_cast<MifareCmd>(out_data.data[i].command) == MifareCmd::None) {
continue;
}
read_block_data[i] = {
.data = out_data.data[i].data,
.sector_number = out_data.data[i].sector,
};
}
return ResultSuccess; return ResultSuccess;
} }
@ -342,7 +346,7 @@ Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> paramet
Result result = ResultSuccess; Result result = ResultSuccess;
TagInfo tag_info{}; TagInfo tag_info{};
result = GetTagInfo(tag_info, true); result = GetTagInfo(tag_info);
if (result.IsError()) { if (result.IsError()) {
return result; return result;
@ -363,42 +367,25 @@ Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> paramet
} }
} }
Common::Input::MifareRequest request{};
for (std::size_t i = 0; i < parameters.size(); i++) { for (std::size_t i = 0; i < parameters.size(); i++) {
result = WriteMifare(parameters[i]); if (parameters[i].sector_key.command == MifareCmd::None) {
if (result.IsError()) { continue;
break;
} }
request.data[i].command = static_cast<u8>(parameters[i].sector_key.command);
request.data[i].sector = parameters[i].sector_number;
memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(),
sizeof(KeyData));
memcpy(request.data[i].data.data(), parameters[i].data.data(), sizeof(KeyData));
} }
if (!npad_device->WriteNfc(mifare_data)) { if (!npad_device->WriteMifareData(request)) {
LOG_ERROR(Service_NFP, "Error writing to file");
return ResultMifareError288; return ResultMifareError288;
} }
return result; return result;
} }
Result NfcDevice::WriteMifare(const MifareWriteBlockParameter& parameter) {
const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock);
if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
return ResultTagRemoved;
}
return ResultWrongDeviceState;
}
if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
return ResultMifareError288;
}
// TODO: Use parameter.sector_key to encrypt the data
memcpy(mifare_data.data() + sector_index, parameter.data.data(), sizeof(DataBlock));
return ResultSuccess;
}
Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout, Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout,
std::span<const u8> command_data, std::span<const u8> command_data,
std::span<u8> out_data) { std::span<u8> out_data) {
@ -412,6 +399,11 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
return ResultWrongDeviceState; return ResultWrongDeviceState;
} }
if (!LoadAmiiboData()) {
LOG_ERROR(Service_NFP, "Not an amiibo");
return ResultInvalidTagType;
}
if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
LOG_ERROR(Service_NFP, "Not an amiibo"); LOG_ERROR(Service_NFP, "Not an amiibo");
return ResultInvalidTagType; return ResultInvalidTagType;
@ -562,7 +554,7 @@ Result NfcDevice::Restore() {
NFC::TagInfo tag_info{}; NFC::TagInfo tag_info{};
std::array<u8, sizeof(NFP::EncryptedNTAG215File)> data{}; std::array<u8, sizeof(NFP::EncryptedNTAG215File)> data{};
Result result = GetTagInfo(tag_info, false); Result result = GetTagInfo(tag_info);
if (result.IsError()) { if (result.IsError()) {
return result; return result;
@ -635,7 +627,7 @@ Result NfcDevice::GetCommonInfo(NFP::CommonInfo& common_info) const {
// TODO: Validate this data // TODO: Validate this data
common_info = { common_info = {
.last_write_date = settings.write_date.GetWriteDate(), .last_write_date = settings.write_date.GetWriteDate(),
.write_counter = tag_data.write_counter, .write_counter = tag_data.application_write_counter,
.version = tag_data.amiibo_version, .version = tag_data.amiibo_version,
.application_area_size = sizeof(NFP::ApplicationArea), .application_area_size = sizeof(NFP::ApplicationArea),
}; };

View file

@ -42,15 +42,12 @@ public:
Result StartDetection(NfcProtocol allowed_protocol); Result StartDetection(NfcProtocol allowed_protocol);
Result StopDetection(); Result StopDetection();
Result GetTagInfo(TagInfo& tag_info, bool is_mifare) const; Result GetTagInfo(TagInfo& tag_info) const;
Result ReadMifare(std::span<const MifareReadBlockParameter> parameters, Result ReadMifare(std::span<const MifareReadBlockParameter> parameters,
std::span<MifareReadBlockData> read_block_data) const; std::span<MifareReadBlockData> read_block_data) const;
Result ReadMifare(const MifareReadBlockParameter& parameter,
MifareReadBlockData& read_block_data) const;
Result WriteMifare(std::span<const MifareWriteBlockParameter> parameters); Result WriteMifare(std::span<const MifareWriteBlockParameter> parameters);
Result WriteMifare(const MifareWriteBlockParameter& parameter);
Result SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout, Result SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout,
std::span<const u8> command_data, std::span<u8> out_data); std::span<const u8> command_data, std::span<u8> out_data);
@ -105,7 +102,8 @@ public:
private: private:
void NpadUpdate(Core::HID::ControllerTriggerType type); void NpadUpdate(Core::HID::ControllerTriggerType type);
bool LoadNfcTag(std::span<const u8> data); bool LoadNfcTag(u8 protocol, u8 tag_type, u8 uuid_length, UniqueSerialNumber uuid);
bool LoadAmiiboData();
void CloseNfcTag(); void CloseNfcTag();
NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const; NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const;
@ -140,8 +138,8 @@ private:
bool is_write_protected{}; bool is_write_protected{};
NFP::MountTarget mount_target{NFP::MountTarget::None}; NFP::MountTarget mount_target{NFP::MountTarget::None};
TagInfo real_tag_info{};
NFP::NTAG215File tag_data{}; NFP::NTAG215File tag_data{};
std::vector<u8> mifare_data{};
NFP::EncryptedNTAG215File encrypted_tag_data{}; NFP::EncryptedNTAG215File encrypted_tag_data{};
}; };

View file

@ -29,6 +29,9 @@ DeviceManager::DeviceManager(Core::System& system_, KernelHelpers::ServiceContex
} }
DeviceManager ::~DeviceManager() { DeviceManager ::~DeviceManager() {
if (is_initialized) {
Finalize();
}
service_context.CloseEvent(availability_change_event); service_context.CloseEvent(availability_change_event);
} }
@ -125,14 +128,14 @@ Result DeviceManager::StopDetection(u64 device_handle) {
return result; return result;
} }
Result DeviceManager::GetTagInfo(u64 device_handle, TagInfo& tag_info, bool is_mifare) const { Result DeviceManager::GetTagInfo(u64 device_handle, TagInfo& tag_info) const {
std::scoped_lock lock{mutex}; std::scoped_lock lock{mutex};
std::shared_ptr<NfcDevice> device = nullptr; std::shared_ptr<NfcDevice> device = nullptr;
auto result = GetDeviceHandle(device_handle, device); auto result = GetDeviceHandle(device_handle, device);
if (result.IsSuccess()) { if (result.IsSuccess()) {
result = device->GetTagInfo(tag_info, is_mifare); result = device->GetTagInfo(tag_info);
result = VerifyDeviceResult(device, result); result = VerifyDeviceResult(device, result);
} }
@ -546,7 +549,7 @@ Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) cons
NFC::TagInfo tag_info{}; NFC::TagInfo tag_info{};
if (result.IsSuccess()) { if (result.IsSuccess()) {
result = device->GetTagInfo(tag_info, false); result = device->GetTagInfo(tag_info);
} }
if (result.IsSuccess()) { if (result.IsSuccess()) {
@ -565,7 +568,7 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> dat
NFC::TagInfo tag_info{}; NFC::TagInfo tag_info{};
if (result.IsSuccess()) { if (result.IsSuccess()) {
result = device->GetTagInfo(tag_info, false); result = device->GetTagInfo(tag_info);
} }
if (result.IsSuccess()) { if (result.IsSuccess()) {

View file

@ -33,7 +33,7 @@ public:
Kernel::KReadableEvent& AttachAvailabilityChangeEvent() const; Kernel::KReadableEvent& AttachAvailabilityChangeEvent() const;
Result StartDetection(u64 device_handle, NfcProtocol tag_protocol); Result StartDetection(u64 device_handle, NfcProtocol tag_protocol);
Result StopDetection(u64 device_handle); Result StopDetection(u64 device_handle);
Result GetTagInfo(u64 device_handle, NFP::TagInfo& tag_info, bool is_mifare) const; Result GetTagInfo(u64 device_handle, NFP::TagInfo& tag_info) const;
Kernel::KReadableEvent& AttachActivateEvent(u64 device_handle) const; Kernel::KReadableEvent& AttachActivateEvent(u64 device_handle) const;
Kernel::KReadableEvent& AttachDeactivateEvent(u64 device_handle) const; Kernel::KReadableEvent& AttachDeactivateEvent(u64 device_handle) const;
Result ReadMifare(u64 device_handle, Result ReadMifare(u64 device_handle,

View file

@ -11,9 +11,10 @@
namespace Service::NFC { namespace Service::NFC {
enum class MifareCmd : u8 { enum class MifareCmd : u8 {
None = 0x00,
Read = 0x30,
AuthA = 0x60, AuthA = 0x60,
AuthB = 0x61, AuthB = 0x61,
Read = 0x30,
Write = 0xA0, Write = 0xA0,
Transfer = 0xB0, Transfer = 0xB0,
Decrement = 0xC0, Decrement = 0xC0,
@ -35,17 +36,17 @@ static_assert(sizeof(SectorKey) == 0x10, "SectorKey is an invalid size");
// This is nn::nfc::MifareReadBlockParameter // This is nn::nfc::MifareReadBlockParameter
struct MifareReadBlockParameter { struct MifareReadBlockParameter {
u8 sector_number; u8 sector_number{};
INSERT_PADDING_BYTES(0x7); INSERT_PADDING_BYTES(0x7);
SectorKey sector_key; SectorKey sector_key{};
}; };
static_assert(sizeof(MifareReadBlockParameter) == 0x18, static_assert(sizeof(MifareReadBlockParameter) == 0x18,
"MifareReadBlockParameter is an invalid size"); "MifareReadBlockParameter is an invalid size");
// This is nn::nfc::MifareReadBlockData // This is nn::nfc::MifareReadBlockData
struct MifareReadBlockData { struct MifareReadBlockData {
DataBlock data; DataBlock data{};
u8 sector_number; u8 sector_number{};
INSERT_PADDING_BYTES(0x7); INSERT_PADDING_BYTES(0x7);
}; };
static_assert(sizeof(MifareReadBlockData) == 0x18, "MifareReadBlockData is an invalid size"); static_assert(sizeof(MifareReadBlockData) == 0x18, "MifareReadBlockData is an invalid size");

View file

@ -174,8 +174,7 @@ void NfcInterface::GetTagInfo(HLERequestContext& ctx) {
LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
TagInfo tag_info{}; TagInfo tag_info{};
auto result = auto result = GetManager()->GetTagInfo(device_handle, tag_info);
GetManager()->GetTagInfo(device_handle, tag_info, backend_type == BackendType::Mifare);
result = TranslateResultToServiceError(result); result = TranslateResultToServiceError(result);
if (result.IsSuccess()) { if (result.IsSuccess()) {
@ -216,8 +215,8 @@ void NfcInterface::ReadMifare(HLERequestContext& ctx) {
memcpy(read_commands.data(), buffer.data(), memcpy(read_commands.data(), buffer.data(),
number_of_commands * sizeof(MifareReadBlockParameter)); number_of_commands * sizeof(MifareReadBlockParameter));
LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, read_commands_size={}", LOG_INFO(Service_NFC, "called, device_handle={}, read_commands_size={}", device_handle,
device_handle, number_of_commands); number_of_commands);
std::vector<MifareReadBlockData> out_data(number_of_commands); std::vector<MifareReadBlockData> out_data(number_of_commands);
auto result = GetManager()->ReadMifare(device_handle, read_commands, out_data); auto result = GetManager()->ReadMifare(device_handle, read_commands, out_data);

View file

@ -195,8 +195,8 @@ void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
OnMotionUpdate(port, type, id, value); OnMotionUpdate(port, type, id, value);
}}, }},
.on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }}, .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }},
.on_amiibo_data = {[this, port, type](const std::vector<u8>& amiibo_data) { .on_amiibo_data = {[this, port, type](const Joycon::TagInfo& tag_info) {
OnAmiiboUpdate(port, type, amiibo_data); OnAmiiboUpdate(port, type, tag_info);
}}, }},
.on_camera_data = {[this, port](const std::vector<u8>& camera_data, .on_camera_data = {[this, port](const std::vector<u8>& camera_data,
Joycon::IrsResolution format) { Joycon::IrsResolution format) {
@ -291,13 +291,105 @@ Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) c
return Common::Input::NfcState::Success; return Common::Input::NfcState::Success;
}; };
Common::Input::NfcState Joycons::StartNfcPolling(const PadIdentifier& identifier) {
auto handle = GetHandle(identifier);
if (handle == nullptr) {
return Common::Input::NfcState::Unknown;
}
return TranslateDriverResult(handle->StartNfcPolling());
};
Common::Input::NfcState Joycons::StopNfcPolling(const PadIdentifier& identifier) {
auto handle = GetHandle(identifier);
if (handle == nullptr) {
return Common::Input::NfcState::Unknown;
}
return TranslateDriverResult(handle->StopNfcPolling());
};
Common::Input::NfcState Joycons::ReadAmiiboData(const PadIdentifier& identifier,
std::vector<u8>& out_data) {
auto handle = GetHandle(identifier);
if (handle == nullptr) {
return Common::Input::NfcState::Unknown;
}
return TranslateDriverResult(handle->ReadAmiiboData(out_data));
}
Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier, Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier,
const std::vector<u8>& data) { const std::vector<u8>& data) {
auto handle = GetHandle(identifier); auto handle = GetHandle(identifier);
if (handle->WriteNfcData(data) != Joycon::DriverResult::Success) { if (handle == nullptr) {
return Common::Input::NfcState::WriteFailed; return Common::Input::NfcState::Unknown;
} }
return Common::Input::NfcState::Success; return TranslateDriverResult(handle->WriteNfcData(data));
};
Common::Input::NfcState Joycons::ReadMifareData(const PadIdentifier& identifier,
const Common::Input::MifareRequest& request,
Common::Input::MifareRequest& data) {
auto handle = GetHandle(identifier);
if (handle == nullptr) {
return Common::Input::NfcState::Unknown;
}
const auto command = static_cast<Joycon::MifareCmd>(request.data[0].command);
std::vector<Joycon::MifareReadChunk> read_request{};
for (const auto& request_data : request.data) {
if (request_data.command == 0) {
continue;
}
Joycon::MifareReadChunk chunk = {
.command = command,
.sector_key = {},
.sector = request_data.sector,
};
memcpy(chunk.sector_key.data(), request_data.key.data(),
sizeof(Joycon::MifareReadChunk::sector_key));
read_request.emplace_back(chunk);
}
std::vector<Joycon::MifareReadData> read_data(read_request.size());
const auto result = handle->ReadMifareData(read_request, read_data);
if (result == Joycon::DriverResult::Success) {
for (std::size_t i = 0; i < read_request.size(); i++) {
data.data[i] = {
.command = static_cast<u8>(command),
.sector = read_data[i].sector,
.key = {},
.data = read_data[i].data,
};
}
}
return TranslateDriverResult(result);
};
Common::Input::NfcState Joycons::WriteMifareData(const PadIdentifier& identifier,
const Common::Input::MifareRequest& request) {
auto handle = GetHandle(identifier);
if (handle == nullptr) {
return Common::Input::NfcState::Unknown;
}
const auto command = static_cast<Joycon::MifareCmd>(request.data[0].command);
std::vector<Joycon::MifareWriteChunk> write_request{};
for (const auto& request_data : request.data) {
if (request_data.command == 0) {
continue;
}
Joycon::MifareWriteChunk chunk = {
.command = command,
.sector_key = {},
.sector = request_data.sector,
.data = {},
};
memcpy(chunk.sector_key.data(), request_data.key.data(),
sizeof(Joycon::MifareReadChunk::sector_key));
memcpy(chunk.data.data(), request_data.data.data(), sizeof(Joycon::MifareWriteChunk::data));
write_request.emplace_back(chunk);
}
return TranslateDriverResult(handle->WriteMifareData(write_request));
}; };
Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier, Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier,
@ -403,11 +495,20 @@ void Joycons::OnRingConUpdate(f32 ring_data) {
} }
void Joycons::OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type, void Joycons::OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type,
const std::vector<u8>& amiibo_data) { const Joycon::TagInfo& tag_info) {
const auto identifier = GetIdentifier(port, type); const auto identifier = GetIdentifier(port, type);
const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved const auto nfc_state = tag_info.uuid_length == 0 ? Common::Input::NfcState::AmiiboRemoved
: Common::Input::NfcState::NewAmiibo; : Common::Input::NfcState::NewAmiibo;
SetNfc(identifier, {nfc_state, amiibo_data});
const Common::Input::NfcStatus nfc_status{
.state = nfc_state,
.uuid_length = tag_info.uuid_length,
.protocol = tag_info.protocol,
.tag_type = tag_info.tag_type,
.uuid = tag_info.uuid,
};
SetNfc(identifier, nfc_status);
} }
void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
@ -726,4 +827,18 @@ std::string Joycons::JoyconName(Joycon::ControllerType type) const {
return "Unknown Switch Controller"; return "Unknown Switch Controller";
} }
} }
Common::Input::NfcState Joycons::TranslateDriverResult(Joycon::DriverResult result) const {
switch (result) {
case Joycon::DriverResult::Success:
return Common::Input::NfcState::Success;
case Joycon::DriverResult::Disabled:
return Common::Input::NfcState::WrongDeviceState;
case Joycon::DriverResult::NotSupported:
return Common::Input::NfcState::NotSupported;
default:
return Common::Input::NfcState::Unknown;
}
}
} // namespace InputCommon } // namespace InputCommon

View file

@ -15,6 +15,7 @@ using SerialNumber = std::array<u8, 15>;
struct Battery; struct Battery;
struct Color; struct Color;
struct MotionData; struct MotionData;
struct TagInfo;
enum class ControllerType : u8; enum class ControllerType : u8;
enum class DriverResult; enum class DriverResult;
enum class IrsResolution; enum class IrsResolution;
@ -39,9 +40,18 @@ public:
Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier, Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier,
Common::Input::CameraFormat camera_format) override; Common::Input::CameraFormat camera_format) override;
Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier) const override;
Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_, Common::Input::NfcState StartNfcPolling(const PadIdentifier& identifier) override;
Common::Input::NfcState StopNfcPolling(const PadIdentifier& identifier) override;
Common::Input::NfcState ReadAmiiboData(const PadIdentifier& identifier,
std::vector<u8>& out_data) override;
Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier,
const std::vector<u8>& data) override; const std::vector<u8>& data) override;
Common::Input::NfcState ReadMifareData(const PadIdentifier& identifier,
const Common::Input::MifareRequest& request,
Common::Input::MifareRequest& out_data) override;
Common::Input::NfcState WriteMifareData(const PadIdentifier& identifier,
const Common::Input::MifareRequest& request) override;
Common::Input::DriverResult SetPollingMode( Common::Input::DriverResult SetPollingMode(
const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override; const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override;
@ -82,7 +92,7 @@ private:
const Joycon::MotionData& value); const Joycon::MotionData& value);
void OnRingConUpdate(f32 ring_data); void OnRingConUpdate(f32 ring_data);
void OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type, void OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type,
const std::vector<u8>& amiibo_data); const Joycon::TagInfo& amiibo_data);
void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data, void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
Joycon::IrsResolution format); Joycon::IrsResolution format);
@ -102,6 +112,8 @@ private:
/// Returns the name of the device in text format /// Returns the name of the device in text format
std::string JoyconName(Joycon::ControllerType type) const; std::string JoyconName(Joycon::ControllerType type) const;
Common::Input::NfcState TranslateDriverResult(Joycon::DriverResult result) const;
std::jthread scan_thread; std::jthread scan_thread;
// Joycon types are split by type to ease supporting dualjoycon configurations // Joycon types are split by type to ease supporting dualjoycon configurations

View file

@ -29,14 +29,13 @@ Common::Input::DriverResult VirtualAmiibo::SetPollingMode(
switch (polling_mode) { switch (polling_mode) {
case Common::Input::PollingMode::NFC: case Common::Input::PollingMode::NFC:
if (state == State::Initialized) { state = State::Initialized;
state = State::WaitingForAmiibo;
}
return Common::Input::DriverResult::Success; return Common::Input::DriverResult::Success;
default: default:
if (state == State::AmiiboIsOpen) { if (state == State::TagNearby) {
CloseAmiibo(); CloseAmiibo();
} }
state = State::Disabled;
return Common::Input::DriverResult::NotSupported; return Common::Input::DriverResult::NotSupported;
} }
} }
@ -45,6 +44,39 @@ Common::Input::NfcState VirtualAmiibo::SupportsNfc(
[[maybe_unused]] const PadIdentifier& identifier_) const { [[maybe_unused]] const PadIdentifier& identifier_) const {
return Common::Input::NfcState::Success; return Common::Input::NfcState::Success;
} }
Common::Input::NfcState VirtualAmiibo::StartNfcPolling(const PadIdentifier& identifier_) {
if (state != State::Initialized) {
return Common::Input::NfcState::WrongDeviceState;
}
state = State::WaitingForAmiibo;
return Common::Input::NfcState::Success;
}
Common::Input::NfcState VirtualAmiibo::StopNfcPolling(const PadIdentifier& identifier_) {
if (state == State::Disabled) {
return Common::Input::NfcState::WrongDeviceState;
}
if (state == State::TagNearby) {
CloseAmiibo();
}
state = State::Initialized;
return Common::Input::NfcState::Success;
}
Common::Input::NfcState VirtualAmiibo::ReadAmiiboData(const PadIdentifier& identifier_,
std::vector<u8>& out_data) {
if (state != State::TagNearby) {
return Common::Input::NfcState::WrongDeviceState;
}
if (status.tag_type != 1U << 1) {
return Common::Input::NfcState::InvalidTagType;
}
out_data.resize(nfc_data.size());
memcpy(out_data.data(), nfc_data.data(), nfc_data.size());
return Common::Input::NfcState::Success;
}
Common::Input::NfcState VirtualAmiibo::WriteNfcData( Common::Input::NfcState VirtualAmiibo::WriteNfcData(
[[maybe_unused]] const PadIdentifier& identifier_, const std::vector<u8>& data) { [[maybe_unused]] const PadIdentifier& identifier_, const std::vector<u8>& data) {
@ -66,6 +98,69 @@ Common::Input::NfcState VirtualAmiibo::WriteNfcData(
return Common::Input::NfcState::Success; return Common::Input::NfcState::Success;
} }
Common::Input::NfcState VirtualAmiibo::ReadMifareData(const PadIdentifier& identifier_,
const Common::Input::MifareRequest& request,
Common::Input::MifareRequest& out_data) {
if (state != State::TagNearby) {
return Common::Input::NfcState::WrongDeviceState;
}
if (status.tag_type != 1U << 6) {
return Common::Input::NfcState::InvalidTagType;
}
for (std::size_t i = 0; i < request.data.size(); i++) {
if (request.data[i].command == 0) {
continue;
}
out_data.data[i].command = request.data[i].command;
out_data.data[i].sector = request.data[i].sector;
const std::size_t sector_index =
request.data[i].sector * sizeof(Common::Input::MifareData::data);
if (nfc_data.size() < sector_index + sizeof(Common::Input::MifareData::data)) {
return Common::Input::NfcState::WriteFailed;
}
// Ignore the sector key as we don't support it
memcpy(out_data.data[i].data.data(), nfc_data.data() + sector_index,
sizeof(Common::Input::MifareData::data));
}
return Common::Input::NfcState::Success;
}
Common::Input::NfcState VirtualAmiibo::WriteMifareData(
const PadIdentifier& identifier_, const Common::Input::MifareRequest& request) {
if (state != State::TagNearby) {
return Common::Input::NfcState::WrongDeviceState;
}
if (status.tag_type != 1U << 6) {
return Common::Input::NfcState::InvalidTagType;
}
for (std::size_t i = 0; i < request.data.size(); i++) {
if (request.data[i].command == 0) {
continue;
}
const std::size_t sector_index =
request.data[i].sector * sizeof(Common::Input::MifareData::data);
if (nfc_data.size() < sector_index + sizeof(Common::Input::MifareData::data)) {
return Common::Input::NfcState::WriteFailed;
}
// Ignore the sector key as we don't support it
memcpy(nfc_data.data() + sector_index, request.data[i].data.data(),
sizeof(Common::Input::MifareData::data));
}
return Common::Input::NfcState::Success;
}
VirtualAmiibo::State VirtualAmiibo::GetCurrentState() const { VirtualAmiibo::State VirtualAmiibo::GetCurrentState() const {
return state; return state;
} }
@ -112,23 +207,31 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(std::span<u8> data) {
case AmiiboSizeWithoutPassword: case AmiiboSizeWithoutPassword:
case AmiiboSizeWithSignature: case AmiiboSizeWithSignature:
nfc_data.resize(AmiiboSize); nfc_data.resize(AmiiboSize);
status.tag_type = 1U << 1;
status.uuid_length = 7;
break; break;
case MifareSize: case MifareSize:
nfc_data.resize(MifareSize); nfc_data.resize(MifareSize);
status.tag_type = 1U << 6;
status.uuid_length = 4;
break; break;
default: default:
return Info::NotAnAmiibo; return Info::NotAnAmiibo;
} }
state = State::AmiiboIsOpen; status.uuid = {};
status.protocol = 1;
state = State::TagNearby;
status.state = Common::Input::NfcState::NewAmiibo,
memcpy(nfc_data.data(), data.data(), data.size_bytes()); memcpy(nfc_data.data(), data.data(), data.size_bytes());
SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, nfc_data}); memcpy(status.uuid.data(), nfc_data.data(), status.uuid_length);
SetNfc(identifier, status);
return Info::Success; return Info::Success;
} }
VirtualAmiibo::Info VirtualAmiibo::ReloadAmiibo() { VirtualAmiibo::Info VirtualAmiibo::ReloadAmiibo() {
if (state == State::AmiiboIsOpen) { if (state == State::TagNearby) {
SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, nfc_data}); SetNfc(identifier, status);
return Info::Success; return Info::Success;
} }
@ -136,9 +239,14 @@ VirtualAmiibo::Info VirtualAmiibo::ReloadAmiibo() {
} }
VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() { VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() {
state = polling_mode == Common::Input::PollingMode::NFC ? State::WaitingForAmiibo if (state != State::TagNearby) {
: State::Initialized; return Info::Success;
SetNfc(identifier, {Common::Input::NfcState::AmiiboRemoved, {}}); }
state = State::WaitingForAmiibo;
status.state = Common::Input::NfcState::AmiiboRemoved;
SetNfc(identifier, status);
status.tag_type = 0;
return Info::Success; return Info::Success;
} }

View file

@ -20,9 +20,10 @@ namespace InputCommon {
class VirtualAmiibo final : public InputEngine { class VirtualAmiibo final : public InputEngine {
public: public:
enum class State { enum class State {
Disabled,
Initialized, Initialized,
WaitingForAmiibo, WaitingForAmiibo,
AmiiboIsOpen, TagNearby,
}; };
enum class Info { enum class Info {
@ -41,9 +42,17 @@ public:
const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override; const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;
Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
Common::Input::NfcState StartNfcPolling(const PadIdentifier& identifier_) override;
Common::Input::NfcState StopNfcPolling(const PadIdentifier& identifier_) override;
Common::Input::NfcState ReadAmiiboData(const PadIdentifier& identifier_,
std::vector<u8>& out_data) override;
Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_, Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
const std::vector<u8>& data) override; const std::vector<u8>& data) override;
Common::Input::NfcState ReadMifareData(const PadIdentifier& identifier_,
const Common::Input::MifareRequest& data,
Common::Input::MifareRequest& out_data) override;
Common::Input::NfcState WriteMifareData(const PadIdentifier& identifier_,
const Common::Input::MifareRequest& data) override;
State GetCurrentState() const; State GetCurrentState() const;
@ -61,8 +70,9 @@ private:
static constexpr std::size_t MifareSize = 0x400; static constexpr std::size_t MifareSize = 0x400;
std::string file_path{}; std::string file_path{};
State state{State::Initialized}; State state{State::Disabled};
std::vector<u8> nfc_data; std::vector<u8> nfc_data;
Common::Input::NfcStatus status;
Common::Input::PollingMode polling_mode{Common::Input::PollingMode::Passive}; Common::Input::PollingMode polling_mode{Common::Input::PollingMode::Passive};
}; };
} // namespace InputCommon } // namespace InputCommon

View file

@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/scope_exit.h"
#include "common/swap.h" #include "common/swap.h"
#include "common/thread.h" #include "common/thread.h"
#include "input_common/helpers/joycon_driver.h" #include "input_common/helpers/joycon_driver.h"
@ -112,7 +113,7 @@ DriverResult JoyconDriver::InitializeDevice() {
joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration, joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration,
right_stick_calibration, motion_calibration); right_stick_calibration, motion_calibration);
// Start pooling for data // Start polling for data
is_connected = true; is_connected = true;
if (!input_thread_running) { if (!input_thread_running) {
input_thread = input_thread =
@ -208,7 +209,7 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat()); joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat());
} }
if (nfc_protocol->IsEnabled()) { if (nfc_protocol->IsPolling()) {
if (amiibo_detected) { if (amiibo_detected) {
if (!nfc_protocol->HasAmiibo()) { if (!nfc_protocol->HasAmiibo()) {
joycon_poller->UpdateAmiibo({}); joycon_poller->UpdateAmiibo({});
@ -218,10 +219,10 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
} }
if (!amiibo_detected) { if (!amiibo_detected) {
std::vector<u8> data(0x21C); Joycon::TagInfo tag_info;
const auto result = nfc_protocol->ScanAmiibo(data); const auto result = nfc_protocol->GetTagInfo(tag_info);
if (result == DriverResult::Success) { if (result == DriverResult::Success) {
joycon_poller->UpdateAmiibo(data); joycon_poller->UpdateAmiibo(tag_info);
amiibo_detected = true; amiibo_detected = true;
} }
} }
@ -247,6 +248,7 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
} }
DriverResult JoyconDriver::SetPollingMode() { DriverResult JoyconDriver::SetPollingMode() {
SCOPE_EXIT({ disable_input_thread = false; });
disable_input_thread = true; disable_input_thread = true;
rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration); rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
@ -276,7 +278,6 @@ DriverResult JoyconDriver::SetPollingMode() {
if (irs_enabled && supported_features.irs) { if (irs_enabled && supported_features.irs) {
auto result = irs_protocol->EnableIrs(); auto result = irs_protocol->EnableIrs();
if (result == DriverResult::Success) { if (result == DriverResult::Success) {
disable_input_thread = false;
return result; return result;
} }
irs_protocol->DisableIrs(); irs_protocol->DisableIrs();
@ -286,10 +287,6 @@ DriverResult JoyconDriver::SetPollingMode() {
if (nfc_enabled && supported_features.nfc) { if (nfc_enabled && supported_features.nfc) {
auto result = nfc_protocol->EnableNfc(); auto result = nfc_protocol->EnableNfc();
if (result == DriverResult::Success) { if (result == DriverResult::Success) {
result = nfc_protocol->StartNFCPollingMode();
}
if (result == DriverResult::Success) {
disable_input_thread = false;
return result; return result;
} }
nfc_protocol->DisableNfc(); nfc_protocol->DisableNfc();
@ -303,7 +300,6 @@ DriverResult JoyconDriver::SetPollingMode() {
} }
if (result == DriverResult::Success) { if (result == DriverResult::Success) {
ring_connected = true; ring_connected = true;
disable_input_thread = false;
return result; return result;
} }
ring_connected = false; ring_connected = false;
@ -314,7 +310,6 @@ DriverResult JoyconDriver::SetPollingMode() {
if (passive_enabled && supported_features.passive) { if (passive_enabled && supported_features.passive) {
const auto result = generic_protocol->EnablePassiveMode(); const auto result = generic_protocol->EnablePassiveMode();
if (result == DriverResult::Success) { if (result == DriverResult::Success) {
disable_input_thread = false;
return result; return result;
} }
LOG_ERROR(Input, "Error enabling passive mode"); LOG_ERROR(Input, "Error enabling passive mode");
@ -328,7 +323,6 @@ DriverResult JoyconDriver::SetPollingMode() {
// Switch calls this function after enabling active mode // Switch calls this function after enabling active mode
generic_protocol->TriggersElapsed(); generic_protocol->TriggersElapsed();
disable_input_thread = false;
return result; return result;
} }
@ -492,9 +486,42 @@ DriverResult JoyconDriver::SetRingConMode() {
return result; return result;
} }
DriverResult JoyconDriver::WriteNfcData(std::span<const u8> data) { DriverResult JoyconDriver::StartNfcPolling() {
std::scoped_lock lock{mutex}; std::scoped_lock lock{mutex};
if (!supported_features.nfc) {
return DriverResult::NotSupported;
}
if (!nfc_protocol->IsEnabled()) {
return DriverResult::Disabled;
}
disable_input_thread = true; disable_input_thread = true;
const auto result = nfc_protocol->StartNFCPollingMode();
disable_input_thread = false;
return result;
}
DriverResult JoyconDriver::StopNfcPolling() {
std::scoped_lock lock{mutex};
if (!supported_features.nfc) {
return DriverResult::NotSupported;
}
if (!nfc_protocol->IsEnabled()) {
return DriverResult::Disabled;
}
disable_input_thread = true;
const auto result = nfc_protocol->StopNFCPollingMode();
disable_input_thread = false;
return result;
}
DriverResult JoyconDriver::ReadAmiiboData(std::vector<u8>& out_data) {
std::scoped_lock lock{mutex};
if (!supported_features.nfc) { if (!supported_features.nfc) {
return DriverResult::NotSupported; return DriverResult::NotSupported;
@ -506,9 +533,72 @@ DriverResult JoyconDriver::WriteNfcData(std::span<const u8> data) {
return DriverResult::ErrorWritingData; return DriverResult::ErrorWritingData;
} }
const auto result = nfc_protocol->WriteAmiibo(data); out_data.resize(0x21C);
disable_input_thread = true;
const auto result = nfc_protocol->ReadAmiibo(out_data);
disable_input_thread = false; disable_input_thread = false;
return result;
}
DriverResult JoyconDriver::WriteNfcData(std::span<const u8> data) {
std::scoped_lock lock{mutex};
if (!supported_features.nfc) {
return DriverResult::NotSupported;
}
if (!nfc_protocol->IsEnabled()) {
return DriverResult::Disabled;
}
if (!amiibo_detected) {
return DriverResult::ErrorWritingData;
}
disable_input_thread = true;
const auto result = nfc_protocol->WriteAmiibo(data);
disable_input_thread = false;
return result;
}
DriverResult JoyconDriver::ReadMifareData(std::span<const MifareReadChunk> data,
std::span<MifareReadData> out_data) {
std::scoped_lock lock{mutex};
if (!supported_features.nfc) {
return DriverResult::NotSupported;
}
if (!nfc_protocol->IsEnabled()) {
return DriverResult::Disabled;
}
if (!amiibo_detected) {
return DriverResult::ErrorWritingData;
}
disable_input_thread = true;
const auto result = nfc_protocol->ReadMifare(data, out_data);
disable_input_thread = false;
return result;
}
DriverResult JoyconDriver::WriteMifareData(std::span<const MifareWriteChunk> data) {
std::scoped_lock lock{mutex};
if (!supported_features.nfc) {
return DriverResult::NotSupported;
}
if (!nfc_protocol->IsEnabled()) {
return DriverResult::Disabled;
}
if (!amiibo_detected) {
return DriverResult::ErrorWritingData;
}
disable_input_thread = true;
const auto result = nfc_protocol->WriteMifare(data);
disable_input_thread = false;
return result; return result;
} }

View file

@ -49,7 +49,13 @@ public:
DriverResult SetIrMode(); DriverResult SetIrMode();
DriverResult SetNfcMode(); DriverResult SetNfcMode();
DriverResult SetRingConMode(); DriverResult SetRingConMode();
DriverResult StartNfcPolling();
DriverResult StopNfcPolling();
DriverResult ReadAmiiboData(std::vector<u8>& out_data);
DriverResult WriteNfcData(std::span<const u8> data); DriverResult WriteNfcData(std::span<const u8> data);
DriverResult ReadMifareData(std::span<const MifareReadChunk> request,
std::span<MifareReadData> out_data);
DriverResult WriteMifareData(std::span<const MifareWriteChunk> request);
void SetCallbacks(const JoyconCallbacks& callbacks); void SetCallbacks(const JoyconCallbacks& callbacks);

View file

@ -24,6 +24,7 @@ constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x
using MacAddress = std::array<u8, 6>; using MacAddress = std::array<u8, 6>;
using SerialNumber = std::array<u8, 15>; using SerialNumber = std::array<u8, 15>;
using TagUUID = std::array<u8, 7>; using TagUUID = std::array<u8, 7>;
using MifareUUID = std::array<u8, 4>;
enum class ControllerType : u8 { enum class ControllerType : u8 {
None = 0x00, None = 0x00,
@ -307,6 +308,19 @@ enum class NFCStatus : u8 {
WriteDone = 0x05, WriteDone = 0x05,
TagLost = 0x07, TagLost = 0x07,
WriteReady = 0x09, WriteReady = 0x09,
MifareDone = 0x10,
};
enum class MifareCmd : u8 {
None = 0x00,
Read = 0x30,
AuthA = 0x60,
AuthB = 0x61,
Write = 0xA0,
Transfer = 0xB0,
Decrement = 0xC0,
Increment = 0xC1,
Store = 0xC2
}; };
enum class IrsMode : u8 { enum class IrsMode : u8 {
@ -592,6 +606,14 @@ struct NFCWriteCommandData {
static_assert(sizeof(NFCWriteCommandData) == 0x15, "NFCWriteCommandData is an invalid size"); static_assert(sizeof(NFCWriteCommandData) == 0x15, "NFCWriteCommandData is an invalid size");
#pragma pack(pop) #pragma pack(pop)
struct MifareCommandData {
u8 unknown1;
u8 unknown2;
u8 number_of_short_bytes;
MifareUUID uid;
};
static_assert(sizeof(MifareCommandData) == 0x7, "MifareCommandData is an invalid size");
struct NFCPollingCommandData { struct NFCPollingCommandData {
u8 enable_mifare; u8 enable_mifare;
u8 unknown_1; u8 unknown_1;
@ -629,6 +651,41 @@ struct NFCWritePackage {
std::array<NFCDataChunk, 4> data_chunks; std::array<NFCDataChunk, 4> data_chunks;
}; };
struct MifareReadChunk {
MifareCmd command;
std::array<u8, 0x6> sector_key;
u8 sector;
};
struct MifareWriteChunk {
MifareCmd command;
std::array<u8, 0x6> sector_key;
u8 sector;
std::array<u8, 0x10> data;
};
struct MifareReadData {
u8 sector;
std::array<u8, 0x10> data;
};
struct MifareReadPackage {
MifareCommandData command_data;
std::array<MifareReadChunk, 0x10> data_chunks;
};
struct MifareWritePackage {
MifareCommandData command_data;
std::array<MifareWriteChunk, 0x10> data_chunks;
};
struct TagInfo {
u8 uuid_length;
u8 protocol;
u8 tag_type;
std::array<u8, 10> uuid;
};
struct IrsConfigure { struct IrsConfigure {
MCUCommand command; MCUCommand command;
MCUSubCommand sub_command; MCUSubCommand sub_command;
@ -744,7 +801,7 @@ struct JoyconCallbacks {
std::function<void(int, f32)> on_stick_data; std::function<void(int, f32)> on_stick_data;
std::function<void(int, const MotionData&)> on_motion_data; std::function<void(int, const MotionData&)> on_motion_data;
std::function<void(f32)> on_ring_data; std::function<void(f32)> on_ring_data;
std::function<void(const std::vector<u8>&)> on_amiibo_data; std::function<void(const Joycon::TagInfo&)> on_amiibo_data;
std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data; std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data;
}; };

View file

@ -40,6 +40,16 @@ DriverResult NfcProtocol::EnableNfc() {
if (result == DriverResult::Success) { if (result == DriverResult::Success) {
result = WaitUntilNfcIs(NFCStatus::Ready); result = WaitUntilNfcIs(NFCStatus::Ready);
} }
if (result == DriverResult::Success) {
MCUCommandResponse output{};
result = SendStopPollingRequest(output);
}
if (result == DriverResult::Success) {
result = WaitUntilNfcIs(NFCStatus::Ready);
}
if (result == DriverResult::Success) {
is_enabled = true;
}
return result; return result;
} }
@ -54,22 +64,16 @@ DriverResult NfcProtocol::DisableNfc() {
} }
is_enabled = false; is_enabled = false;
is_polling = false;
return result; return result;
} }
DriverResult NfcProtocol::StartNFCPollingMode() { DriverResult NfcProtocol::StartNFCPollingMode() {
LOG_DEBUG(Input, "Start NFC pooling Mode"); LOG_DEBUG(Input, "Start NFC polling Mode");
ScopedSetBlocking sb(this); ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success}; DriverResult result{DriverResult::Success};
if (result == DriverResult::Success) {
MCUCommandResponse output{};
result = SendStopPollingRequest(output);
}
if (result == DriverResult::Success) {
result = WaitUntilNfcIs(NFCStatus::Ready);
}
if (result == DriverResult::Success) { if (result == DriverResult::Success) {
MCUCommandResponse output{}; MCUCommandResponse output{};
result = SendStartPollingRequest(output); result = SendStartPollingRequest(output);
@ -78,13 +82,32 @@ DriverResult NfcProtocol::StartNFCPollingMode() {
result = WaitUntilNfcIs(NFCStatus::Polling); result = WaitUntilNfcIs(NFCStatus::Polling);
} }
if (result == DriverResult::Success) { if (result == DriverResult::Success) {
is_enabled = true; is_polling = true;
} }
return result; return result;
} }
DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) { DriverResult NfcProtocol::StopNFCPollingMode() {
LOG_DEBUG(Input, "Stop NFC polling Mode");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
if (result == DriverResult::Success) {
MCUCommandResponse output{};
result = SendStopPollingRequest(output);
}
if (result == DriverResult::Success) {
result = WaitUntilNfcIs(NFCStatus::WriteReady);
}
if (result == DriverResult::Success) {
is_polling = false;
}
return result;
}
DriverResult NfcProtocol::GetTagInfo(Joycon::TagInfo& tag_info) {
if (update_counter++ < AMIIBO_UPDATE_DELAY) { if (update_counter++ < AMIIBO_UPDATE_DELAY) {
return DriverResult::Delayed; return DriverResult::Delayed;
} }
@ -100,11 +123,41 @@ DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
} }
if (result == DriverResult::Success) { if (result == DriverResult::Success) {
tag_info = {
.uuid_length = tag_data.uuid_size,
.protocol = 1,
.tag_type = tag_data.type,
.uuid = {},
};
memcpy(tag_info.uuid.data(), tag_data.uuid.data(), tag_data.uuid_size);
// Investigate why mifare type is not correct
if (tag_info.tag_type == 144) {
tag_info.tag_type = 1U << 6;
}
std::string uuid_string; std::string uuid_string;
for (auto& content : tag_data.uuid) { for (auto& content : tag_data.uuid) {
uuid_string += fmt::format(" {:02x}", content); uuid_string += fmt::format(" {:02x}", content);
} }
LOG_INFO(Input, "Tag detected, type={}, uuid={}", tag_data.type, uuid_string); LOG_INFO(Input, "Tag detected, type={}, uuid={}", tag_data.type, uuid_string);
}
return result;
}
DriverResult NfcProtocol::ReadAmiibo(std::vector<u8>& data) {
LOG_DEBUG(Input, "Scan for amiibos");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
TagFoundData tag_data{};
if (result == DriverResult::Success) {
result = IsTagInRange(tag_data, 7);
}
if (result == DriverResult::Success) {
result = GetAmiiboData(data); result = GetAmiiboData(data);
} }
@ -154,6 +207,69 @@ DriverResult NfcProtocol::WriteAmiibo(std::span<const u8> data) {
return result; return result;
} }
DriverResult NfcProtocol::ReadMifare(std::span<const MifareReadChunk> read_request,
std::span<MifareReadData> out_data) {
LOG_DEBUG(Input, "Read mifare");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
TagFoundData tag_data{};
MifareUUID tag_uuid{};
if (result == DriverResult::Success) {
result = IsTagInRange(tag_data, 7);
}
if (result == DriverResult::Success) {
memcpy(tag_uuid.data(), tag_data.uuid.data(), sizeof(MifareUUID));
result = GetMifareData(tag_uuid, read_request, out_data);
}
if (result == DriverResult::Success) {
MCUCommandResponse output{};
result = SendStopPollingRequest(output);
}
if (result == DriverResult::Success) {
result = WaitUntilNfcIs(NFCStatus::Ready);
}
if (result == DriverResult::Success) {
MCUCommandResponse output{};
result = SendStartPollingRequest(output, true);
}
if (result == DriverResult::Success) {
result = WaitUntilNfcIs(NFCStatus::WriteReady);
}
return result;
}
DriverResult NfcProtocol::WriteMifare(std::span<const MifareWriteChunk> write_request) {
LOG_DEBUG(Input, "Write mifare");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
TagFoundData tag_data{};
MifareUUID tag_uuid{};
if (result == DriverResult::Success) {
result = IsTagInRange(tag_data, 7);
}
if (result == DriverResult::Success) {
memcpy(tag_uuid.data(), tag_data.uuid.data(), sizeof(MifareUUID));
result = WriteMifareData(tag_uuid, write_request);
}
if (result == DriverResult::Success) {
MCUCommandResponse output{};
result = SendStopPollingRequest(output);
}
if (result == DriverResult::Success) {
result = WaitUntilNfcIs(NFCStatus::Ready);
}
if (result == DriverResult::Success) {
MCUCommandResponse output{};
result = SendStartPollingRequest(output, true);
}
if (result == DriverResult::Success) {
result = WaitUntilNfcIs(NFCStatus::WriteReady);
}
return result;
}
bool NfcProtocol::HasAmiibo() { bool NfcProtocol::HasAmiibo() {
if (update_counter++ < AMIIBO_UPDATE_DELAY) { if (update_counter++ < AMIIBO_UPDATE_DELAY) {
return true; return true;
@ -341,6 +457,158 @@ DriverResult NfcProtocol::WriteAmiiboData(const TagUUID& tag_uuid, std::span<con
return result; return result;
} }
DriverResult NfcProtocol::GetMifareData(const MifareUUID& tag_uuid,
std::span<const MifareReadChunk> read_request,
std::span<MifareReadData> out_data) {
constexpr std::size_t timeout_limit = 60;
const auto nfc_data = MakeMifareReadPackage(tag_uuid, read_request);
const std::vector<u8> nfc_buffer_data = SerializeMifareReadPackage(nfc_data);
std::span<const u8> buffer(nfc_buffer_data);
DriverResult result = DriverResult::Success;
MCUCommandResponse output{};
u8 block_id = 1;
u8 package_index = 0;
std::size_t tries = 0;
std::size_t current_position = 0;
LOG_INFO(Input, "Reading Mifare data");
// Send data request. Nfc buffer size is 31, Send the data in smaller packages
while (current_position < buffer.size() && tries++ < timeout_limit) {
const std::size_t next_position =
std::min(current_position + sizeof(NFCRequestState::raw_data), buffer.size());
const std::size_t block_size = next_position - current_position;
const bool is_last_packet = block_size < sizeof(NFCRequestState::raw_data);
SendReadDataMifareRequest(output, block_id, is_last_packet,
buffer.subspan(current_position, block_size));
const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
return DriverResult::ErrorReadingData;
}
// Increase position when data is confirmed by the joycon
if (output.mcu_report == MCUReport::NFCState &&
(output.mcu_data[1] << 8) + output.mcu_data[0] == 0x0500 &&
output.mcu_data[3] == block_id) {
block_id++;
current_position = next_position;
}
}
if (result != DriverResult::Success) {
return result;
}
// Wait for reply and save the output data
while (tries++ < timeout_limit) {
result = SendNextPackageRequest(output, package_index);
const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
if (result != DriverResult::Success) {
return result;
}
if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
return DriverResult::ErrorReadingData;
}
if (output.mcu_report == MCUReport::NFCState && output.mcu_data[1] == 0x10) {
constexpr std::size_t DATA_LENGHT = 0x10 + 1;
constexpr std::size_t DATA_START = 11;
const u8 number_of_elements = output.mcu_data[10];
for (std::size_t i = 0; i < number_of_elements; i++) {
out_data[i].sector = output.mcu_data[DATA_START + (i * DATA_LENGHT)];
memcpy(out_data[i].data.data(),
output.mcu_data.data() + DATA_START + 1 + (i * DATA_LENGHT),
sizeof(MifareReadData::data));
}
package_index++;
continue;
}
if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::MifareDone) {
LOG_INFO(Input, "Finished reading mifare");
break;
}
}
return result;
}
DriverResult NfcProtocol::WriteMifareData(const MifareUUID& tag_uuid,
std::span<const MifareWriteChunk> write_request) {
constexpr std::size_t timeout_limit = 60;
const auto nfc_data = MakeMifareWritePackage(tag_uuid, write_request);
const std::vector<u8> nfc_buffer_data = SerializeMifareWritePackage(nfc_data);
std::span<const u8> buffer(nfc_buffer_data);
DriverResult result = DriverResult::Success;
MCUCommandResponse output{};
u8 block_id = 1;
u8 package_index = 0;
std::size_t tries = 0;
std::size_t current_position = 0;
LOG_INFO(Input, "Writing Mifare data");
// Send data request. Nfc buffer size is 31, Send the data in smaller packages
while (current_position < buffer.size() && tries++ < timeout_limit) {
const std::size_t next_position =
std::min(current_position + sizeof(NFCRequestState::raw_data), buffer.size());
const std::size_t block_size = next_position - current_position;
const bool is_last_packet = block_size < sizeof(NFCRequestState::raw_data);
SendReadDataMifareRequest(output, block_id, is_last_packet,
buffer.subspan(current_position, block_size));
const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
return DriverResult::ErrorReadingData;
}
// Increase position when data is confirmed by the joycon
if (output.mcu_report == MCUReport::NFCState &&
(output.mcu_data[1] << 8) + output.mcu_data[0] == 0x0500 &&
output.mcu_data[3] == block_id) {
block_id++;
current_position = next_position;
}
}
if (result != DriverResult::Success) {
return result;
}
// Wait for reply and ignore the output data
while (tries++ < timeout_limit) {
result = SendNextPackageRequest(output, package_index);
const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
if (result != DriverResult::Success) {
return result;
}
if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
return DriverResult::ErrorReadingData;
}
if (output.mcu_report == MCUReport::NFCState && output.mcu_data[1] == 0x10) {
package_index++;
continue;
}
if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::MifareDone) {
LOG_INFO(Input, "Finished writing mifare");
break;
}
}
return result;
}
DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output, DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output,
bool is_second_attempt) { bool is_second_attempt) {
NFCRequestState request{ NFCRequestState request{
@ -477,6 +745,28 @@ DriverResult NfcProtocol::SendWriteDataAmiiboRequest(MCUCommandResponse& output,
output); output);
} }
DriverResult NfcProtocol::SendReadDataMifareRequest(MCUCommandResponse& output, u8 block_id,
bool is_last_packet, std::span<const u8> data) {
const auto data_size = std::min(data.size(), sizeof(NFCRequestState::raw_data));
NFCRequestState request{
.command_argument = NFCCommand::Mifare,
.block_id = block_id,
.packet_id = {},
.packet_flag =
is_last_packet ? MCUPacketFlag::LastCommandPacket : MCUPacketFlag::MorePacketsRemaining,
.data_length = static_cast<u8>(data_size),
.raw_data = {},
.crc = {},
};
memcpy(request.raw_data.data(), data.data(), data_size);
std::array<u8, sizeof(NFCRequestState)> request_data{};
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
output);
}
std::vector<u8> NfcProtocol::SerializeWritePackage(const NFCWritePackage& package) const { std::vector<u8> NfcProtocol::SerializeWritePackage(const NFCWritePackage& package) const {
const std::size_t header_size = const std::size_t header_size =
sizeof(NFCWriteCommandData) + sizeof(NFCWritePackage::number_of_chunks); sizeof(NFCWriteCommandData) + sizeof(NFCWritePackage::number_of_chunks);
@ -498,6 +788,48 @@ std::vector<u8> NfcProtocol::SerializeWritePackage(const NFCWritePackage& packag
return serialized_data; return serialized_data;
} }
std::vector<u8> NfcProtocol::SerializeMifareReadPackage(const MifareReadPackage& package) const {
const std::size_t header_size = sizeof(MifareCommandData);
std::vector<u8> serialized_data(header_size);
std::size_t start_index = 0;
memcpy(serialized_data.data(), &package, header_size);
start_index += header_size;
for (const auto& data_chunk : package.data_chunks) {
const std::size_t chunk_size = sizeof(MifareReadChunk);
if (data_chunk.command == MifareCmd::None) {
continue;
}
serialized_data.resize(start_index + chunk_size);
memcpy(serialized_data.data() + start_index, &data_chunk, chunk_size);
start_index += chunk_size;
}
return serialized_data;
}
std::vector<u8> NfcProtocol::SerializeMifareWritePackage(const MifareWritePackage& package) const {
const std::size_t header_size = sizeof(MifareCommandData);
std::vector<u8> serialized_data(header_size);
std::size_t start_index = 0;
memcpy(serialized_data.data(), &package, header_size);
start_index += header_size;
for (const auto& data_chunk : package.data_chunks) {
const std::size_t chunk_size = sizeof(MifareWriteChunk);
if (data_chunk.command == MifareCmd::None) {
continue;
}
serialized_data.resize(start_index + chunk_size);
memcpy(serialized_data.data() + start_index, &data_chunk, chunk_size);
start_index += chunk_size;
}
return serialized_data;
}
NFCWritePackage NfcProtocol::MakeAmiiboWritePackage(const TagUUID& tag_uuid, NFCWritePackage NfcProtocol::MakeAmiiboWritePackage(const TagUUID& tag_uuid,
std::span<const u8> data) const { std::span<const u8> data) const {
return { return {
@ -527,6 +859,46 @@ NFCWritePackage NfcProtocol::MakeAmiiboWritePackage(const TagUUID& tag_uuid,
}; };
} }
MifareReadPackage NfcProtocol::MakeMifareReadPackage(
const MifareUUID& tag_uuid, std::span<const MifareReadChunk> read_request) const {
MifareReadPackage package{
.command_data{
.unknown1 = 0xd0,
.unknown2 = 0x07,
.number_of_short_bytes = static_cast<u8>(
((read_request.size() * sizeof(MifareReadChunk)) + sizeof(MifareUUID)) / 2),
.uid = tag_uuid,
},
.data_chunks = {},
};
for (std::size_t i = 0; i < read_request.size() && i < package.data_chunks.size(); ++i) {
package.data_chunks[i] = read_request[i];
}
return package;
}
MifareWritePackage NfcProtocol::MakeMifareWritePackage(
const MifareUUID& tag_uuid, std::span<const MifareWriteChunk> read_request) const {
MifareWritePackage package{
.command_data{
.unknown1 = 0xd0,
.unknown2 = 0x07,
.number_of_short_bytes = static_cast<u8>(
((read_request.size() * sizeof(MifareReadChunk)) + sizeof(MifareUUID) + 2) / 2),
.uid = tag_uuid,
},
.data_chunks = {},
};
for (std::size_t i = 0; i < read_request.size() && i < package.data_chunks.size(); ++i) {
package.data_chunks[i] = read_request[i];
}
return package;
}
NFCDataChunk NfcProtocol::MakeAmiiboChunk(u8 page, u8 size, std::span<const u8> data) const { NFCDataChunk NfcProtocol::MakeAmiiboChunk(u8 page, u8 size, std::span<const u8> data) const {
constexpr u8 NFC_PAGE_SIZE = 4; constexpr u8 NFC_PAGE_SIZE = 4;
@ -606,4 +978,8 @@ bool NfcProtocol::IsEnabled() const {
return is_enabled; return is_enabled;
} }
bool NfcProtocol::IsPolling() const {
return is_polling;
}
} // namespace InputCommon::Joycon } // namespace InputCommon::Joycon

View file

@ -25,14 +25,25 @@ public:
DriverResult StartNFCPollingMode(); DriverResult StartNFCPollingMode();
DriverResult ScanAmiibo(std::vector<u8>& data); DriverResult StopNFCPollingMode();
DriverResult GetTagInfo(Joycon::TagInfo& tag_info);
DriverResult ReadAmiibo(std::vector<u8>& data);
DriverResult WriteAmiibo(std::span<const u8> data); DriverResult WriteAmiibo(std::span<const u8> data);
DriverResult ReadMifare(std::span<const MifareReadChunk> read_request,
std::span<MifareReadData> out_data);
DriverResult WriteMifare(std::span<const MifareWriteChunk> write_request);
bool HasAmiibo(); bool HasAmiibo();
bool IsEnabled() const; bool IsEnabled() const;
bool IsPolling() const;
private: private:
// Number of times the function will be delayed until it outputs valid data // Number of times the function will be delayed until it outputs valid data
static constexpr std::size_t AMIIBO_UPDATE_DELAY = 15; static constexpr std::size_t AMIIBO_UPDATE_DELAY = 15;
@ -51,6 +62,13 @@ private:
DriverResult WriteAmiiboData(const TagUUID& tag_uuid, std::span<const u8> data); DriverResult WriteAmiiboData(const TagUUID& tag_uuid, std::span<const u8> data);
DriverResult GetMifareData(const MifareUUID& tag_uuid,
std::span<const MifareReadChunk> read_request,
std::span<MifareReadData> out_data);
DriverResult WriteMifareData(const MifareUUID& tag_uuid,
std::span<const MifareWriteChunk> write_request);
DriverResult SendStartPollingRequest(MCUCommandResponse& output, DriverResult SendStartPollingRequest(MCUCommandResponse& output,
bool is_second_attempt = false); bool is_second_attempt = false);
@ -65,17 +83,31 @@ private:
DriverResult SendWriteDataAmiiboRequest(MCUCommandResponse& output, u8 block_id, DriverResult SendWriteDataAmiiboRequest(MCUCommandResponse& output, u8 block_id,
bool is_last_packet, std::span<const u8> data); bool is_last_packet, std::span<const u8> data);
DriverResult SendReadDataMifareRequest(MCUCommandResponse& output, u8 block_id,
bool is_last_packet, std::span<const u8> data);
std::vector<u8> SerializeWritePackage(const NFCWritePackage& package) const; std::vector<u8> SerializeWritePackage(const NFCWritePackage& package) const;
std::vector<u8> SerializeMifareReadPackage(const MifareReadPackage& package) const;
std::vector<u8> SerializeMifareWritePackage(const MifareWritePackage& package) const;
NFCWritePackage MakeAmiiboWritePackage(const TagUUID& tag_uuid, std::span<const u8> data) const; NFCWritePackage MakeAmiiboWritePackage(const TagUUID& tag_uuid, std::span<const u8> data) const;
NFCDataChunk MakeAmiiboChunk(u8 page, u8 size, std::span<const u8> data) const; NFCDataChunk MakeAmiiboChunk(u8 page, u8 size, std::span<const u8> data) const;
MifareReadPackage MakeMifareReadPackage(const MifareUUID& tag_uuid,
std::span<const MifareReadChunk> read_request) const;
MifareWritePackage MakeMifareWritePackage(const MifareUUID& tag_uuid,
std::span<const MifareWriteChunk> read_request) const;
NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const; NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const;
TagUUID GetTagUUID(std::span<const u8> data) const; TagUUID GetTagUUID(std::span<const u8> data) const;
bool is_enabled{}; bool is_enabled{};
bool is_polling{};
std::size_t update_counter{}; std::size_t update_counter{};
}; };

View file

@ -70,8 +70,8 @@ void JoyconPoller::UpdateColor(const Color& color) {
callbacks.on_color_data(color); callbacks.on_color_data(color);
} }
void JoyconPoller::UpdateAmiibo(const std::vector<u8>& amiibo_data) { void JoyconPoller::UpdateAmiibo(const Joycon::TagInfo& tag_info) {
callbacks.on_amiibo_data(amiibo_data); callbacks.on_amiibo_data(tag_info);
} }
void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) { void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) {

View file

@ -36,8 +36,8 @@ public:
void UpdateColor(const Color& color); void UpdateColor(const Color& color);
void UpdateRing(s16 value, const RingStatus& ring_status); void UpdateRing(s16 value, const RingStatus& ring_status);
void UpdateAmiibo(const std::vector<u8>& amiibo_data); void UpdateAmiibo(const Joycon::TagInfo& tag_info);
void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format); void UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format);
private: private:
void UpdateActiveLeftPadInput(const InputReportActive& input, void UpdateActiveLeftPadInput(const InputReportActive& input,

View file

@ -143,12 +143,46 @@ public:
return Common::Input::NfcState::NotSupported; return Common::Input::NfcState::NotSupported;
} }
// Start scanning for nfc tags
virtual Common::Input::NfcState StartNfcPolling(
[[maybe_unused]] const PadIdentifier& identifier_) {
return Common::Input::NfcState::NotSupported;
}
// Start scanning for nfc tags
virtual Common::Input::NfcState StopNfcPolling(
[[maybe_unused]] const PadIdentifier& identifier_) {
return Common::Input::NfcState::NotSupported;
}
// Reads data from amiibo tag
virtual Common::Input::NfcState ReadAmiiboData(
[[maybe_unused]] const PadIdentifier& identifier_,
[[maybe_unused]] std::vector<u8>& out_data) {
return Common::Input::NfcState::NotSupported;
}
// Writes data to an nfc tag // Writes data to an nfc tag
virtual Common::Input::NfcState WriteNfcData([[maybe_unused]] const PadIdentifier& identifier, virtual Common::Input::NfcState WriteNfcData([[maybe_unused]] const PadIdentifier& identifier,
[[maybe_unused]] const std::vector<u8>& data) { [[maybe_unused]] const std::vector<u8>& data) {
return Common::Input::NfcState::NotSupported; return Common::Input::NfcState::NotSupported;
} }
// Reads data from mifare tag
virtual Common::Input::NfcState ReadMifareData(
[[maybe_unused]] const PadIdentifier& identifier_,
[[maybe_unused]] const Common::Input::MifareRequest& request,
[[maybe_unused]] Common::Input::MifareRequest& out_data) {
return Common::Input::NfcState::NotSupported;
}
// Write data to mifare tag
virtual Common::Input::NfcState WriteMifareData(
[[maybe_unused]] const PadIdentifier& identifier_,
[[maybe_unused]] const Common::Input::MifareRequest& request) {
return Common::Input::NfcState::NotSupported;
}
// Returns the engine name // Returns the engine name
[[nodiscard]] const std::string& GetEngineName() const; [[nodiscard]] const std::string& GetEngineName() const;

View file

@ -792,8 +792,7 @@ public:
const Common::Input::CallbackStatus status{ const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Nfc, .type = Common::Input::InputType::Nfc,
.nfc_status = nfc_status.state, .nfc_status = nfc_status,
.raw_data = nfc_status.data,
}; };
TriggerOnChange(status); TriggerOnChange(status);
@ -836,10 +835,31 @@ public:
return input_engine->SupportsNfc(identifier); return input_engine->SupportsNfc(identifier);
} }
Common::Input::NfcState StartNfcPolling() {
return input_engine->StartNfcPolling(identifier);
}
Common::Input::NfcState StopNfcPolling() {
return input_engine->StopNfcPolling(identifier);
}
Common::Input::NfcState ReadAmiiboData(std::vector<u8>& out_data) {
return input_engine->ReadAmiiboData(identifier, out_data);
}
Common::Input::NfcState WriteNfcData(const std::vector<u8>& data) override { Common::Input::NfcState WriteNfcData(const std::vector<u8>& data) override {
return input_engine->WriteNfcData(identifier, data); return input_engine->WriteNfcData(identifier, data);
} }
Common::Input::NfcState ReadMifareData(const Common::Input::MifareRequest& request,
Common::Input::MifareRequest& out_data) {
return input_engine->ReadMifareData(identifier, request, out_data);
}
Common::Input::NfcState WriteMifareData(const Common::Input::MifareRequest& request) {
return input_engine->WriteMifareData(identifier, request);
}
private: private:
const PadIdentifier identifier; const PadIdentifier identifier;
InputEngine* input_engine; InputEngine* input_engine;

View file

@ -3840,7 +3840,7 @@ void GMainWindow::OnLoadAmiibo() {
auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
// Remove amiibo if one is connected // Remove amiibo if one is connected
if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) { if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::TagNearby) {
virtual_amiibo->CloseAmiibo(); virtual_amiibo->CloseAmiibo();
QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed")); QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
return; return;
@ -3868,7 +3868,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) {
auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
const QString title = tr("Error loading Amiibo data"); const QString title = tr("Error loading Amiibo data");
// Remove amiibo if one is connected // Remove amiibo if one is connected
if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) { if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::TagNearby) {
virtual_amiibo->CloseAmiibo(); virtual_amiibo->CloseAmiibo();
QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed")); QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
return; return;