summaryrefslogtreecommitdiff
path: root/src/input_common/helpers
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common/helpers')
-rw-r--r--src/input_common/helpers/joycon_driver.cpp710
-rw-r--r--src/input_common/helpers/joycon_driver.h158
-rw-r--r--src/input_common/helpers/joycon_protocol/calibration.cpp218
-rw-r--r--src/input_common/helpers/joycon_protocol/calibration.h82
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.cpp313
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.h201
-rw-r--r--src/input_common/helpers/joycon_protocol/generic_functions.cpp136
-rw-r--r--src/input_common/helpers/joycon_protocol/generic_functions.h114
-rw-r--r--src/input_common/helpers/joycon_protocol/irs.cpp299
-rw-r--r--src/input_common/helpers/joycon_protocol/irs.h63
-rw-r--r--src/input_common/helpers/joycon_protocol/joycon_types.h808
-rw-r--r--src/input_common/helpers/joycon_protocol/nfc.cpp985
-rw-r--r--src/input_common/helpers/joycon_protocol/nfc.h114
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.cpp374
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.h84
-rw-r--r--src/input_common/helpers/joycon_protocol/ringcon.cpp115
-rw-r--r--src/input_common/helpers/joycon_protocol/ringcon.h38
-rw-r--r--src/input_common/helpers/joycon_protocol/rumble.cpp299
-rw-r--r--src/input_common/helpers/joycon_protocol/rumble.h33
-rw-r--r--src/input_common/helpers/stick_from_buttons.cpp50
-rw-r--r--src/input_common/helpers/udp_protocol.cpp2
21 files changed, 5163 insertions, 33 deletions
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
new file mode 100644
index 000000000..ec984a647
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -0,0 +1,710 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "common/scope_exit.h"
6#include "common/swap.h"
7#include "common/thread.h"
8#include "input_common/helpers/joycon_driver.h"
9#include "input_common/helpers/joycon_protocol/calibration.h"
10#include "input_common/helpers/joycon_protocol/generic_functions.h"
11#include "input_common/helpers/joycon_protocol/irs.h"
12#include "input_common/helpers/joycon_protocol/nfc.h"
13#include "input_common/helpers/joycon_protocol/poller.h"
14#include "input_common/helpers/joycon_protocol/ringcon.h"
15#include "input_common/helpers/joycon_protocol/rumble.h"
16
17namespace InputCommon::Joycon {
18JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} {
19 hidapi_handle = std::make_shared<JoyconHandle>();
20}
21
22JoyconDriver::~JoyconDriver() {
23 Stop();
24}
25
26void JoyconDriver::Stop() {
27 is_connected = false;
28 input_thread = {};
29}
30
31DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) {
32 std::scoped_lock lock{mutex};
33
34 handle_device_type = ControllerType::None;
35 GetDeviceType(device_info, handle_device_type);
36 if (handle_device_type == ControllerType::None) {
37 return DriverResult::UnsupportedControllerType;
38 }
39
40 hidapi_handle->handle =
41 SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
42 std::memcpy(&handle_serial_number, device_info->serial_number, 15);
43 if (!hidapi_handle->handle) {
44 LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
45 device_info->vendor_id, device_info->product_id);
46 return DriverResult::HandleInUse;
47 }
48 SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
49 return DriverResult::Success;
50}
51
52DriverResult JoyconDriver::InitializeDevice() {
53 if (!hidapi_handle->handle) {
54 return DriverResult::InvalidHandle;
55 }
56 std::scoped_lock lock{mutex};
57 disable_input_thread = true;
58
59 // Reset Counters
60 error_counter = 0;
61 hidapi_handle->packet_counter = 0;
62
63 // Reset external device status
64 starlink_connected = false;
65 ring_connected = false;
66 amiibo_detected = false;
67
68 // Set HW default configuration
69 vibration_enabled = true;
70 motion_enabled = true;
71 hidbus_enabled = false;
72 nfc_enabled = false;
73 passive_enabled = false;
74 irs_enabled = false;
75 input_only_device = false;
76 gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
77 gyro_performance = Joycon::GyroPerformance::HZ833;
78 accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
79 accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
80
81 // Initialize HW Protocols
82 calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
83 generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
84 irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle);
85 nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle);
86 ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);
87 rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
88
89 // Get fixed joycon info
90 if (generic_protocol->GetVersionNumber(version) != DriverResult::Success) {
91 // If this command fails the device doesn't accept configuration commands
92 input_only_device = true;
93 }
94
95 if (!input_only_device) {
96 generic_protocol->SetLowPowerMode(false);
97 generic_protocol->GetColor(color);
98 if (handle_device_type == ControllerType::Pro) {
99 // Some 3rd party controllers aren't pro controllers
100 generic_protocol->GetControllerType(device_type);
101 } else {
102 device_type = handle_device_type;
103 }
104 generic_protocol->GetSerialNumber(serial_number);
105 }
106
107 supported_features = GetSupportedFeatures();
108
109 // Get Calibration data
110 calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration);
111 calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration);
112 calibration_protocol->GetImuCalibration(motion_calibration);
113
114 // Set led status
115 generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port));
116
117 // Apply HW configuration
118 SetPollingMode();
119
120 // Initialize joycon poller
121 joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration,
122 right_stick_calibration, motion_calibration);
123
124 // Start polling for data
125 is_connected = true;
126 if (!input_thread_running) {
127 input_thread =
128 std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
129 }
130
131 disable_input_thread = false;
132 return DriverResult::Success;
133}
134
135void JoyconDriver::InputThread(std::stop_token stop_token) {
136 LOG_INFO(Input, "Joycon Adapter input thread started");
137 Common::SetCurrentThreadName("JoyconInput");
138 input_thread_running = true;
139
140 // Max update rate is 5ms, ensure we are always able to read a bit faster
141 constexpr int ThreadDelay = 2;
142 std::vector<u8> buffer(MaxBufferSize);
143
144 while (!stop_token.stop_requested()) {
145 int status = 0;
146
147 if (!IsInputThreadValid()) {
148 input_thread.request_stop();
149 continue;
150 }
151
152 // By disabling the input thread we can ensure custom commands will succeed as no package is
153 // skipped
154 if (!disable_input_thread) {
155 status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
156 ThreadDelay);
157 } else {
158 std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
159 }
160
161 if (IsPayloadCorrect(status, buffer)) {
162 OnNewData(buffer);
163 }
164
165 std::this_thread::yield();
166 }
167
168 is_connected = false;
169 input_thread_running = false;
170 LOG_INFO(Input, "Joycon Adapter input thread stopped");
171}
172
173void JoyconDriver::OnNewData(std::span<u8> buffer) {
174 const auto report_mode = static_cast<ReportMode>(buffer[0]);
175
176 // Packages can be a little bit inconsistent. Average the delta time to provide a smoother
177 // motion experience
178 switch (report_mode) {
179 case ReportMode::STANDARD_FULL_60HZ:
180 case ReportMode::NFC_IR_MODE_60HZ:
181 case ReportMode::SIMPLE_HID_MODE: {
182 const auto now = std::chrono::steady_clock::now();
183 const auto new_delta_time = static_cast<u64>(
184 std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
185 delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10;
186 last_update = now;
187 joycon_poller->UpdateColor(color);
188 break;
189 }
190 default:
191 break;
192 }
193
194 const MotionStatus motion_status{
195 .is_enabled = motion_enabled,
196 .delta_time = delta_time,
197 .gyro_sensitivity = gyro_sensitivity,
198 .accelerometer_sensitivity = accelerometer_sensitivity,
199 };
200
201 // TODO: Remove this when calibration is properly loaded and not calculated
202 if (ring_connected && report_mode == ReportMode::STANDARD_FULL_60HZ) {
203 InputReportActive data{};
204 memcpy(&data, buffer.data(), sizeof(InputReportActive));
205 calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input);
206 }
207
208 const RingStatus ring_status{
209 .is_enabled = ring_connected,
210 .default_value = ring_calibration.default_value,
211 .max_value = ring_calibration.max_value,
212 .min_value = ring_calibration.min_value,
213 };
214
215 if (irs_protocol->IsEnabled()) {
216 irs_protocol->RequestImage(buffer);
217 joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat());
218 }
219
220 if (nfc_protocol->IsPolling()) {
221 if (amiibo_detected) {
222 if (!nfc_protocol->HasAmiibo()) {
223 joycon_poller->UpdateAmiibo({});
224 amiibo_detected = false;
225 return;
226 }
227 }
228
229 if (!amiibo_detected) {
230 Joycon::TagInfo tag_info;
231 const auto result = nfc_protocol->GetTagInfo(tag_info);
232 if (result == DriverResult::Success) {
233 joycon_poller->UpdateAmiibo(tag_info);
234 amiibo_detected = true;
235 }
236 }
237 }
238
239 switch (report_mode) {
240 case ReportMode::STANDARD_FULL_60HZ:
241 joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
242 break;
243 case ReportMode::NFC_IR_MODE_60HZ:
244 joycon_poller->ReadNfcIRMode(buffer, motion_status);
245 break;
246 case ReportMode::SIMPLE_HID_MODE:
247 joycon_poller->ReadPassiveMode(buffer);
248 break;
249 case ReportMode::SUBCMD_REPLY:
250 LOG_DEBUG(Input, "Unhandled command reply");
251 break;
252 default:
253 LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
254 break;
255 }
256}
257
258DriverResult JoyconDriver::SetPollingMode() {
259 SCOPE_EXIT({ disable_input_thread = false; });
260 disable_input_thread = true;
261
262 rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
263
264 if (motion_enabled && supported_features.motion) {
265 generic_protocol->EnableImu(true);
266 generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance,
267 accelerometer_sensitivity, accelerometer_performance);
268 } else {
269 generic_protocol->EnableImu(false);
270 }
271
272 if (input_only_device) {
273 return DriverResult::NotSupported;
274 }
275
276 if (irs_protocol->IsEnabled()) {
277 irs_protocol->DisableIrs();
278 }
279
280 if (nfc_protocol->IsEnabled()) {
281 amiibo_detected = false;
282 nfc_protocol->DisableNfc();
283 }
284
285 if (ring_protocol->IsEnabled()) {
286 ring_connected = false;
287 ring_protocol->DisableRingCon();
288 }
289
290 if (irs_enabled && supported_features.irs) {
291 auto result = irs_protocol->EnableIrs();
292 if (result == DriverResult::Success) {
293 return result;
294 }
295 irs_protocol->DisableIrs();
296 LOG_ERROR(Input, "Error enabling IRS");
297 return result;
298 }
299
300 if (nfc_enabled && supported_features.nfc) {
301 auto result = nfc_protocol->EnableNfc();
302 if (result == DriverResult::Success) {
303 return result;
304 }
305 nfc_protocol->DisableNfc();
306 LOG_ERROR(Input, "Error enabling NFC");
307 return result;
308 }
309
310 if (hidbus_enabled && supported_features.hidbus) {
311 auto result = ring_protocol->EnableRingCon();
312 if (result == DriverResult::Success) {
313 result = ring_protocol->StartRingconPolling();
314 }
315 if (result == DriverResult::Success) {
316 ring_connected = true;
317 return result;
318 }
319 ring_connected = false;
320 ring_protocol->DisableRingCon();
321 LOG_ERROR(Input, "Error enabling Ringcon");
322 return result;
323 }
324
325 if (passive_enabled && supported_features.passive) {
326 const auto result = generic_protocol->EnablePassiveMode();
327 if (result == DriverResult::Success) {
328 return result;
329 }
330 LOG_ERROR(Input, "Error enabling passive mode");
331 }
332
333 // Default Mode
334 const auto result = generic_protocol->EnableActiveMode();
335 if (result != DriverResult::Success) {
336 LOG_ERROR(Input, "Error enabling active mode");
337 }
338 // Switch calls this function after enabling active mode
339 generic_protocol->TriggersElapsed();
340
341 return result;
342}
343
344JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
345 SupportedFeatures features{
346 .passive = true,
347 .motion = true,
348 .vibration = true,
349 };
350
351 if (input_only_device) {
352 return features;
353 }
354
355 if (device_type == ControllerType::Right) {
356 features.nfc = true;
357 features.irs = true;
358 features.hidbus = true;
359 }
360
361 if (device_type == ControllerType::Pro) {
362 features.nfc = true;
363 }
364 return features;
365}
366
367bool JoyconDriver::IsInputThreadValid() const {
368 if (!is_connected.load()) {
369 return false;
370 }
371 if (hidapi_handle->handle == nullptr) {
372 return false;
373 }
374 // Controller is not responding. Terminate connection
375 if (error_counter > MaxErrorCount) {
376 return false;
377 }
378 return true;
379}
380
381bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
382 if (status <= -1) {
383 error_counter++;
384 return false;
385 }
386 // There's no new data
387 if (status == 0) {
388 return false;
389 }
390 // No reply ever starts with zero
391 if (buffer[0] == 0x00) {
392 error_counter++;
393 return false;
394 }
395 error_counter = 0;
396 return true;
397}
398
399DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
400 std::scoped_lock lock{mutex};
401 if (disable_input_thread) {
402 return DriverResult::HandleInUse;
403 }
404 return rumble_protocol->SendVibration(vibration);
405}
406
407DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
408 std::scoped_lock lock{mutex};
409 if (disable_input_thread) {
410 return DriverResult::HandleInUse;
411 }
412 return generic_protocol->SetLedPattern(led_pattern);
413}
414
415DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) {
416 std::scoped_lock lock{mutex};
417 if (disable_input_thread) {
418 return DriverResult::HandleInUse;
419 }
420 disable_input_thread = true;
421 const auto result = irs_protocol->SetIrsConfig(mode_, format_);
422 disable_input_thread = false;
423 return result;
424}
425
426DriverResult JoyconDriver::SetPassiveMode() {
427 std::scoped_lock lock{mutex};
428 motion_enabled = false;
429 hidbus_enabled = false;
430 nfc_enabled = false;
431 passive_enabled = true;
432 irs_enabled = false;
433 return SetPollingMode();
434}
435
436DriverResult JoyconDriver::SetActiveMode() {
437 if (is_ring_disabled_by_irs) {
438 is_ring_disabled_by_irs = false;
439 SetActiveMode();
440 return SetRingConMode();
441 }
442
443 std::scoped_lock lock{mutex};
444 motion_enabled = true;
445 hidbus_enabled = false;
446 nfc_enabled = false;
447 passive_enabled = false;
448 irs_enabled = false;
449 return SetPollingMode();
450}
451
452DriverResult JoyconDriver::SetIrMode() {
453 std::scoped_lock lock{mutex};
454
455 if (!supported_features.irs) {
456 return DriverResult::NotSupported;
457 }
458
459 if (ring_connected) {
460 is_ring_disabled_by_irs = true;
461 }
462
463 motion_enabled = false;
464 hidbus_enabled = false;
465 nfc_enabled = false;
466 passive_enabled = false;
467 irs_enabled = true;
468 return SetPollingMode();
469}
470
471DriverResult JoyconDriver::SetNfcMode() {
472 std::scoped_lock lock{mutex};
473
474 if (!supported_features.nfc) {
475 return DriverResult::NotSupported;
476 }
477
478 motion_enabled = true;
479 hidbus_enabled = false;
480 nfc_enabled = true;
481 passive_enabled = false;
482 irs_enabled = false;
483 return SetPollingMode();
484}
485
486DriverResult JoyconDriver::SetRingConMode() {
487 std::scoped_lock lock{mutex};
488
489 if (!supported_features.hidbus) {
490 return DriverResult::NotSupported;
491 }
492
493 motion_enabled = true;
494 hidbus_enabled = true;
495 nfc_enabled = false;
496 passive_enabled = false;
497 irs_enabled = false;
498
499 const auto result = SetPollingMode();
500
501 if (!ring_connected) {
502 return DriverResult::NoDeviceDetected;
503 }
504
505 return result;
506}
507
508DriverResult JoyconDriver::StartNfcPolling() {
509 std::scoped_lock lock{mutex};
510
511 if (!supported_features.nfc) {
512 return DriverResult::NotSupported;
513 }
514 if (!nfc_protocol->IsEnabled()) {
515 return DriverResult::Disabled;
516 }
517
518 disable_input_thread = true;
519 const auto result = nfc_protocol->StartNFCPollingMode();
520 disable_input_thread = false;
521
522 return result;
523}
524
525DriverResult JoyconDriver::StopNfcPolling() {
526 std::scoped_lock lock{mutex};
527
528 if (!supported_features.nfc) {
529 return DriverResult::NotSupported;
530 }
531 if (!nfc_protocol->IsEnabled()) {
532 return DriverResult::Disabled;
533 }
534
535 disable_input_thread = true;
536 const auto result = nfc_protocol->StopNFCPollingMode();
537 disable_input_thread = false;
538
539 if (amiibo_detected) {
540 amiibo_detected = false;
541 joycon_poller->UpdateAmiibo({});
542 }
543
544 return result;
545}
546
547DriverResult JoyconDriver::ReadAmiiboData(std::vector<u8>& out_data) {
548 std::scoped_lock lock{mutex};
549
550 if (!supported_features.nfc) {
551 return DriverResult::NotSupported;
552 }
553 if (!nfc_protocol->IsEnabled()) {
554 return DriverResult::Disabled;
555 }
556 if (!amiibo_detected) {
557 return DriverResult::ErrorWritingData;
558 }
559
560 out_data.resize(0x21C);
561 disable_input_thread = true;
562 const auto result = nfc_protocol->ReadAmiibo(out_data);
563 disable_input_thread = false;
564
565 return result;
566}
567
568DriverResult JoyconDriver::WriteNfcData(std::span<const u8> data) {
569 std::scoped_lock lock{mutex};
570
571 if (!supported_features.nfc) {
572 return DriverResult::NotSupported;
573 }
574 if (!nfc_protocol->IsEnabled()) {
575 return DriverResult::Disabled;
576 }
577 if (!amiibo_detected) {
578 return DriverResult::ErrorWritingData;
579 }
580
581 disable_input_thread = true;
582 const auto result = nfc_protocol->WriteAmiibo(data);
583 disable_input_thread = false;
584
585 return result;
586}
587
588DriverResult JoyconDriver::ReadMifareData(std::span<const MifareReadChunk> data,
589 std::span<MifareReadData> out_data) {
590 std::scoped_lock lock{mutex};
591
592 if (!supported_features.nfc) {
593 return DriverResult::NotSupported;
594 }
595 if (!nfc_protocol->IsEnabled()) {
596 return DriverResult::Disabled;
597 }
598 if (!amiibo_detected) {
599 return DriverResult::ErrorWritingData;
600 }
601
602 disable_input_thread = true;
603 const auto result = nfc_protocol->ReadMifare(data, out_data);
604 disable_input_thread = false;
605
606 return result;
607}
608
609DriverResult JoyconDriver::WriteMifareData(std::span<const MifareWriteChunk> data) {
610 std::scoped_lock lock{mutex};
611
612 if (!supported_features.nfc) {
613 return DriverResult::NotSupported;
614 }
615 if (!nfc_protocol->IsEnabled()) {
616 return DriverResult::Disabled;
617 }
618 if (!amiibo_detected) {
619 return DriverResult::ErrorWritingData;
620 }
621
622 disable_input_thread = true;
623 const auto result = nfc_protocol->WriteMifare(data);
624 disable_input_thread = false;
625
626 return result;
627}
628
629bool JoyconDriver::IsConnected() const {
630 std::scoped_lock lock{mutex};
631 return is_connected.load();
632}
633
634bool JoyconDriver::IsVibrationEnabled() const {
635 std::scoped_lock lock{mutex};
636 return vibration_enabled;
637}
638
639FirmwareVersion JoyconDriver::GetDeviceVersion() const {
640 std::scoped_lock lock{mutex};
641 return version;
642}
643
644Color JoyconDriver::GetDeviceColor() const {
645 std::scoped_lock lock{mutex};
646 return color;
647}
648
649std::size_t JoyconDriver::GetDevicePort() const {
650 std::scoped_lock lock{mutex};
651 return port;
652}
653
654ControllerType JoyconDriver::GetDeviceType() const {
655 std::scoped_lock lock{mutex};
656 return device_type;
657}
658
659ControllerType JoyconDriver::GetHandleDeviceType() const {
660 std::scoped_lock lock{mutex};
661 return handle_device_type;
662}
663
664SerialNumber JoyconDriver::GetSerialNumber() const {
665 std::scoped_lock lock{mutex};
666 return serial_number;
667}
668
669SerialNumber JoyconDriver::GetHandleSerialNumber() const {
670 std::scoped_lock lock{mutex};
671 return handle_serial_number;
672}
673
674void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) {
675 joycon_poller->SetCallbacks(callbacks);
676}
677
678DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
679 ControllerType& controller_type) {
680 static constexpr std::array<std::pair<u32, ControllerType>, 6> supported_devices{
681 std::pair<u32, ControllerType>{0x2006, ControllerType::Left},
682 {0x2007, ControllerType::Right},
683 {0x2009, ControllerType::Pro},
684 };
685 constexpr u16 nintendo_vendor_id = 0x057e;
686
687 controller_type = ControllerType::None;
688 if (device_info->vendor_id != nintendo_vendor_id) {
689 return DriverResult::UnsupportedControllerType;
690 }
691
692 for (const auto& [product_id, type] : supported_devices) {
693 if (device_info->product_id == static_cast<u16>(product_id)) {
694 controller_type = type;
695 return Joycon::DriverResult::Success;
696 }
697 }
698 return Joycon::DriverResult::UnsupportedControllerType;
699}
700
701DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
702 SerialNumber& serial_number) {
703 if (device_info->serial_number == nullptr) {
704 return DriverResult::Unknown;
705 }
706 std::memcpy(&serial_number, device_info->serial_number, 15);
707 return Joycon::DriverResult::Success;
708}
709
710} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
new file mode 100644
index 000000000..45b32d2f8
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.h
@@ -0,0 +1,158 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <atomic>
7#include <functional>
8#include <mutex>
9#include <span>
10#include <thread>
11
12#include "input_common/helpers/joycon_protocol/joycon_types.h"
13
14namespace InputCommon::Joycon {
15class CalibrationProtocol;
16class GenericProtocol;
17class IrsProtocol;
18class NfcProtocol;
19class JoyconPoller;
20class RingConProtocol;
21class RumbleProtocol;
22
23class JoyconDriver final {
24public:
25 explicit JoyconDriver(std::size_t port_);
26
27 ~JoyconDriver();
28
29 DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info);
30 DriverResult InitializeDevice();
31 void Stop();
32
33 bool IsConnected() const;
34 bool IsVibrationEnabled() const;
35
36 FirmwareVersion GetDeviceVersion() const;
37 Color GetDeviceColor() const;
38 std::size_t GetDevicePort() const;
39 ControllerType GetDeviceType() const;
40 ControllerType GetHandleDeviceType() const;
41 SerialNumber GetSerialNumber() const;
42 SerialNumber GetHandleSerialNumber() const;
43
44 DriverResult SetVibration(const VibrationValue& vibration);
45 DriverResult SetLedConfig(u8 led_pattern);
46 DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_);
47 DriverResult SetPassiveMode();
48 DriverResult SetActiveMode();
49 DriverResult SetIrMode();
50 DriverResult SetNfcMode();
51 DriverResult SetRingConMode();
52 DriverResult StartNfcPolling();
53 DriverResult StopNfcPolling();
54 DriverResult ReadAmiiboData(std::vector<u8>& out_data);
55 DriverResult WriteNfcData(std::span<const u8> data);
56 DriverResult ReadMifareData(std::span<const MifareReadChunk> request,
57 std::span<MifareReadData> out_data);
58 DriverResult WriteMifareData(std::span<const MifareWriteChunk> request);
59
60 void SetCallbacks(const JoyconCallbacks& callbacks);
61
62 // Returns device type from hidapi handle
63 static DriverResult GetDeviceType(SDL_hid_device_info* device_info,
64 ControllerType& controller_type);
65
66 // Returns serial number from hidapi handle
67 static DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
68 SerialNumber& serial_number);
69
70private:
71 struct SupportedFeatures {
72 bool passive{};
73 bool hidbus{};
74 bool irs{};
75 bool motion{};
76 bool nfc{};
77 bool vibration{};
78 };
79
80 /// Main thread, actively request new data from the handle
81 void InputThread(std::stop_token stop_token);
82
83 /// Called every time a valid package arrives
84 void OnNewData(std::span<u8> buffer);
85
86 /// Updates device configuration to enable or disable features
87 DriverResult SetPollingMode();
88
89 /// Returns true if input thread is valid and doesn't need to be stopped
90 bool IsInputThreadValid() const;
91
92 /// Returns true if the data should be interpreted. Otherwise the error counter is incremented
93 bool IsPayloadCorrect(int status, std::span<const u8> buffer);
94
95 /// Returns a list of supported features that can be enabled on this device
96 SupportedFeatures GetSupportedFeatures();
97
98 // Protocol Features
99 std::unique_ptr<CalibrationProtocol> calibration_protocol;
100 std::unique_ptr<GenericProtocol> generic_protocol;
101 std::unique_ptr<IrsProtocol> irs_protocol;
102 std::unique_ptr<NfcProtocol> nfc_protocol;
103 std::unique_ptr<JoyconPoller> joycon_poller;
104 std::unique_ptr<RingConProtocol> ring_protocol;
105 std::unique_ptr<RumbleProtocol> rumble_protocol;
106
107 // Connection status
108 std::atomic<bool> is_connected{};
109 u64 delta_time;
110 std::size_t error_counter{};
111 std::shared_ptr<JoyconHandle> hidapi_handle;
112 std::chrono::time_point<std::chrono::steady_clock> last_update;
113
114 // External device status
115 bool starlink_connected{};
116 bool ring_connected{};
117 bool amiibo_detected{};
118 bool is_ring_disabled_by_irs{};
119
120 // Hardware configuration
121 u8 leds{};
122 ReportMode mode{};
123 bool input_only_device{};
124 bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time
125 bool hidbus_enabled{}; // External device support
126 bool irs_enabled{}; // Infrared camera input
127 bool motion_enabled{}; // Enables motion input
128 bool nfc_enabled{}; // Enables Amiibo detection
129 bool vibration_enabled{}; // Allows vibrations
130
131 // Calibration data
132 GyroSensitivity gyro_sensitivity{};
133 GyroPerformance gyro_performance{};
134 AccelerometerSensitivity accelerometer_sensitivity{};
135 AccelerometerPerformance accelerometer_performance{};
136 JoyStickCalibration left_stick_calibration{};
137 JoyStickCalibration right_stick_calibration{};
138 MotionCalibration motion_calibration{};
139 RingCalibration ring_calibration{};
140
141 // Fixed joycon info
142 FirmwareVersion version{};
143 Color color{};
144 std::size_t port{};
145 ControllerType device_type{}; // Device type reported by controller
146 ControllerType handle_device_type{}; // Device type reported by hidapi
147 SerialNumber serial_number{}; // Serial number reported by controller
148 SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
149 SupportedFeatures supported_features{};
150
151 // Thread related
152 mutable std::mutex mutex;
153 std::jthread input_thread;
154 bool input_thread_running{};
155 bool disable_input_thread{};
156};
157
158} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/calibration.cpp b/src/input_common/helpers/joycon_protocol/calibration.cpp
new file mode 100644
index 000000000..d8f040f75
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/calibration.cpp
@@ -0,0 +1,218 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <cstring>
5
6#include "input_common/helpers/joycon_protocol/calibration.h"
7#include "input_common/helpers/joycon_protocol/joycon_types.h"
8
9namespace InputCommon::Joycon {
10
11CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle)
12 : JoyconCommonProtocol(std::move(handle)) {}
13
14DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(JoyStickCalibration& calibration) {
15 ScopedSetBlocking sb(this);
16 DriverResult result{DriverResult::Success};
17 JoystickLeftSpiCalibration spi_calibration{};
18 bool has_user_calibration = false;
19 calibration = {};
20
21 if (result == DriverResult::Success) {
22 result = HasUserCalibration(SpiAddress::USER_LEFT_MAGIC, has_user_calibration);
23 }
24
25 // Read User defined calibration
26 if (result == DriverResult::Success && has_user_calibration) {
27 result = ReadSPI(SpiAddress::USER_LEFT_DATA, spi_calibration);
28 }
29
30 // Read Factory calibration
31 if (result == DriverResult::Success && !has_user_calibration) {
32 result = ReadSPI(SpiAddress::FACT_LEFT_DATA, spi_calibration);
33 }
34
35 if (result == DriverResult::Success) {
36 calibration.x.center = GetXAxisCalibrationValue(spi_calibration.center);
37 calibration.y.center = GetYAxisCalibrationValue(spi_calibration.center);
38 calibration.x.min = GetXAxisCalibrationValue(spi_calibration.min);
39 calibration.y.min = GetYAxisCalibrationValue(spi_calibration.min);
40 calibration.x.max = GetXAxisCalibrationValue(spi_calibration.max);
41 calibration.y.max = GetYAxisCalibrationValue(spi_calibration.max);
42 }
43
44 // Set a valid default calibration if data is missing
45 ValidateCalibration(calibration);
46
47 return result;
48}
49
50DriverResult CalibrationProtocol::GetRightJoyStickCalibration(JoyStickCalibration& calibration) {
51 ScopedSetBlocking sb(this);
52 DriverResult result{DriverResult::Success};
53 JoystickRightSpiCalibration spi_calibration{};
54 bool has_user_calibration = false;
55 calibration = {};
56
57 if (result == DriverResult::Success) {
58 result = HasUserCalibration(SpiAddress::USER_RIGHT_MAGIC, has_user_calibration);
59 }
60
61 // Read User defined calibration
62 if (result == DriverResult::Success && has_user_calibration) {
63 result = ReadSPI(SpiAddress::USER_RIGHT_DATA, spi_calibration);
64 }
65
66 // Read Factory calibration
67 if (result == DriverResult::Success && !has_user_calibration) {
68 result = ReadSPI(SpiAddress::FACT_RIGHT_DATA, spi_calibration);
69 }
70
71 if (result == DriverResult::Success) {
72 calibration.x.center = GetXAxisCalibrationValue(spi_calibration.center);
73 calibration.y.center = GetYAxisCalibrationValue(spi_calibration.center);
74 calibration.x.min = GetXAxisCalibrationValue(spi_calibration.min);
75 calibration.y.min = GetYAxisCalibrationValue(spi_calibration.min);
76 calibration.x.max = GetXAxisCalibrationValue(spi_calibration.max);
77 calibration.y.max = GetYAxisCalibrationValue(spi_calibration.max);
78 }
79
80 // Set a valid default calibration if data is missing
81 ValidateCalibration(calibration);
82
83 return result;
84}
85
86DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) {
87 ScopedSetBlocking sb(this);
88 DriverResult result{DriverResult::Success};
89 ImuSpiCalibration spi_calibration{};
90 bool has_user_calibration = false;
91 calibration = {};
92
93 if (result == DriverResult::Success) {
94 result = HasUserCalibration(SpiAddress::USER_IMU_MAGIC, has_user_calibration);
95 }
96
97 // Read User defined calibration
98 if (result == DriverResult::Success && has_user_calibration) {
99 result = ReadSPI(SpiAddress::USER_IMU_DATA, spi_calibration);
100 }
101
102 // Read Factory calibration
103 if (result == DriverResult::Success && !has_user_calibration) {
104 result = ReadSPI(SpiAddress::FACT_IMU_DATA, spi_calibration);
105 }
106
107 if (result == DriverResult::Success) {
108 calibration.accelerometer[0].offset = spi_calibration.accelerometer_offset[0];
109 calibration.accelerometer[1].offset = spi_calibration.accelerometer_offset[1];
110 calibration.accelerometer[2].offset = spi_calibration.accelerometer_offset[2];
111
112 calibration.accelerometer[0].scale = spi_calibration.accelerometer_scale[0];
113 calibration.accelerometer[1].scale = spi_calibration.accelerometer_scale[1];
114 calibration.accelerometer[2].scale = spi_calibration.accelerometer_scale[2];
115
116 calibration.gyro[0].offset = spi_calibration.gyroscope_offset[0];
117 calibration.gyro[1].offset = spi_calibration.gyroscope_offset[1];
118 calibration.gyro[2].offset = spi_calibration.gyroscope_offset[2];
119
120 calibration.gyro[0].scale = spi_calibration.gyroscope_scale[0];
121 calibration.gyro[1].scale = spi_calibration.gyroscope_scale[1];
122 calibration.gyro[2].scale = spi_calibration.gyroscope_scale[2];
123 }
124
125 ValidateCalibration(calibration);
126
127 return result;
128}
129
130DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration,
131 s16 current_value) {
132 constexpr s16 DefaultRingRange{800};
133
134 // TODO: Get default calibration form ring itself
135 if (ring_data_max == 0 && ring_data_min == 0) {
136 ring_data_max = current_value + DefaultRingRange;
137 ring_data_min = current_value - DefaultRingRange;
138 ring_data_default = current_value;
139 }
140 ring_data_max = std::max(ring_data_max, current_value);
141 ring_data_min = std::min(ring_data_min, current_value);
142 calibration = {
143 .default_value = ring_data_default,
144 .max_value = ring_data_max,
145 .min_value = ring_data_min,
146 };
147 return DriverResult::Success;
148}
149
150DriverResult CalibrationProtocol::HasUserCalibration(SpiAddress address,
151 bool& has_user_calibration) {
152 MagicSpiCalibration spi_magic{};
153 const DriverResult result{ReadSPI(address, spi_magic)};
154 has_user_calibration = false;
155 if (result == DriverResult::Success) {
156 has_user_calibration = spi_magic.first == CalibrationMagic::USR_MAGIC_0 &&
157 spi_magic.second == CalibrationMagic::USR_MAGIC_1;
158 }
159 return result;
160}
161
162u16 CalibrationProtocol::GetXAxisCalibrationValue(std::span<u8> block) const {
163 return static_cast<u16>(((block[1] & 0x0F) << 8) | block[0]);
164}
165
166u16 CalibrationProtocol::GetYAxisCalibrationValue(std::span<u8> block) const {
167 return static_cast<u16>((block[2] << 4) | (block[1] >> 4));
168}
169
170void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) {
171 constexpr u16 DefaultStickCenter{0x800};
172 constexpr u16 DefaultStickRange{0x6cc};
173
174 calibration.x.center = ValidateValue(calibration.x.center, DefaultStickCenter);
175 calibration.x.max = ValidateValue(calibration.x.max, DefaultStickRange);
176 calibration.x.min = ValidateValue(calibration.x.min, DefaultStickRange);
177
178 calibration.y.center = ValidateValue(calibration.y.center, DefaultStickCenter);
179 calibration.y.max = ValidateValue(calibration.y.max, DefaultStickRange);
180 calibration.y.min = ValidateValue(calibration.y.min, DefaultStickRange);
181}
182
183void CalibrationProtocol::ValidateCalibration(MotionCalibration& calibration) {
184 constexpr s16 DefaultAccelerometerScale{0x4000};
185 constexpr s16 DefaultGyroScale{0x3be7};
186 constexpr s16 DefaultOffset{0};
187
188 for (auto& sensor : calibration.accelerometer) {
189 sensor.scale = ValidateValue(sensor.scale, DefaultAccelerometerScale);
190 sensor.offset = ValidateValue(sensor.offset, DefaultOffset);
191 }
192 for (auto& sensor : calibration.gyro) {
193 sensor.scale = ValidateValue(sensor.scale, DefaultGyroScale);
194 sensor.offset = ValidateValue(sensor.offset, DefaultOffset);
195 }
196}
197
198u16 CalibrationProtocol::ValidateValue(u16 value, u16 default_value) const {
199 if (value == 0) {
200 return default_value;
201 }
202 if (value == 0xFFF) {
203 return default_value;
204 }
205 return value;
206}
207
208s16 CalibrationProtocol::ValidateValue(s16 value, s16 default_value) const {
209 if (value == 0) {
210 return default_value;
211 }
212 if (value == 0xFFF) {
213 return default_value;
214 }
215 return value;
216}
217
218} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/calibration.h b/src/input_common/helpers/joycon_protocol/calibration.h
new file mode 100644
index 000000000..c6fd0f729
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/calibration.h
@@ -0,0 +1,82 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14
15namespace InputCommon::Joycon {
16enum class DriverResult;
17struct JoyStickCalibration;
18struct IMUCalibration;
19struct JoyconHandle;
20} // namespace InputCommon::Joycon
21
22namespace InputCommon::Joycon {
23
24/// Driver functions related to retrieving calibration data from the device
25class CalibrationProtocol final : private JoyconCommonProtocol {
26public:
27 explicit CalibrationProtocol(std::shared_ptr<JoyconHandle> handle);
28
29 /**
30 * Sends a request to obtain the left stick calibration from memory
31 * @param is_factory_calibration if true factory values will be returned
32 * @returns JoyStickCalibration of the left joystick
33 */
34 DriverResult GetLeftJoyStickCalibration(JoyStickCalibration& calibration);
35
36 /**
37 * Sends a request to obtain the right stick calibration from memory
38 * @param is_factory_calibration if true factory values will be returned
39 * @returns JoyStickCalibration of the right joystick
40 */
41 DriverResult GetRightJoyStickCalibration(JoyStickCalibration& calibration);
42
43 /**
44 * Sends a request to obtain the motion calibration from memory
45 * @returns ImuCalibration of the motion sensor
46 */
47 DriverResult GetImuCalibration(MotionCalibration& calibration);
48
49 /**
50 * Calculates on run time the proper calibration of the ring controller
51 * @returns RingCalibration of the ring sensor
52 */
53 DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value);
54
55private:
56 /// Returns true if the specified address corresponds to the magic value of user calibration
57 DriverResult HasUserCalibration(SpiAddress address, bool& has_user_calibration);
58
59 /// Converts a raw calibration block to an u16 value containing the x axis value
60 u16 GetXAxisCalibrationValue(std::span<u8> block) const;
61
62 /// Converts a raw calibration block to an u16 value containing the y axis value
63 u16 GetYAxisCalibrationValue(std::span<u8> block) const;
64
65 /// Ensures that all joystick calibration values are set
66 void ValidateCalibration(JoyStickCalibration& calibration);
67
68 /// Ensures that all motion calibration values are set
69 void ValidateCalibration(MotionCalibration& calibration);
70
71 /// Returns the default value if the value is either zero or 0xFFF
72 u16 ValidateValue(u16 value, u16 default_value) const;
73
74 /// Returns the default value if the value is either zero or 0xFFF
75 s16 ValidateValue(s16 value, s16 default_value) const;
76
77 s16 ring_data_max = 0;
78 s16 ring_data_default = 0;
79 s16 ring_data_min = 0;
80};
81
82} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
new file mode 100644
index 000000000..88f4cec1c
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -0,0 +1,313 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/common_protocol.h"
6
7namespace InputCommon::Joycon {
8JoyconCommonProtocol::JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_)
9 : hidapi_handle{std::move(hidapi_handle_)} {}
10
11u8 JoyconCommonProtocol::GetCounter() {
12 hidapi_handle->packet_counter = (hidapi_handle->packet_counter + 1) & 0x0F;
13 return hidapi_handle->packet_counter;
14}
15
16void JoyconCommonProtocol::SetBlocking() {
17 SDL_hid_set_nonblocking(hidapi_handle->handle, 0);
18}
19
20void JoyconCommonProtocol::SetNonBlocking() {
21 SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
22}
23
24DriverResult JoyconCommonProtocol::GetDeviceType(ControllerType& controller_type) {
25 const auto result = ReadSPI(SpiAddress::DEVICE_TYPE, controller_type);
26
27 if (result == DriverResult::Success) {
28 // Fallback to 3rd party pro controllers
29 if (controller_type == ControllerType::None) {
30 controller_type = ControllerType::Pro;
31 }
32 }
33
34 return result;
35}
36
37DriverResult JoyconCommonProtocol::CheckDeviceAccess(SDL_hid_device_info* device_info) {
38 ControllerType controller_type{ControllerType::None};
39 const auto result = GetDeviceType(controller_type);
40
41 if (result != DriverResult::Success || controller_type == ControllerType::None) {
42 return DriverResult::UnsupportedControllerType;
43 }
44
45 hidapi_handle->handle =
46 SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
47
48 if (!hidapi_handle->handle) {
49 LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
50 device_info->vendor_id, device_info->product_id);
51 return DriverResult::HandleInUse;
52 }
53
54 SetNonBlocking();
55 return DriverResult::Success;
56}
57
58DriverResult JoyconCommonProtocol::SetReportMode(ReportMode report_mode) {
59 const std::array<u8, 1> buffer{static_cast<u8>(report_mode)};
60 return SendSubCommand(SubCommand::SET_REPORT_MODE, buffer);
61}
62
63DriverResult JoyconCommonProtocol::SendRawData(std::span<const u8> buffer) {
64 const auto result = SDL_hid_write(hidapi_handle->handle, buffer.data(), buffer.size());
65
66 if (result == -1) {
67 return DriverResult::ErrorWritingData;
68 }
69
70 return DriverResult::Success;
71}
72
73DriverResult JoyconCommonProtocol::GetSubCommandResponse(SubCommand sc,
74 SubCommandResponse& output) {
75 constexpr int timeout_mili = 66;
76 constexpr int MaxTries = 3;
77 int tries = 0;
78
79 do {
80 int result = SDL_hid_read_timeout(hidapi_handle->handle, reinterpret_cast<u8*>(&output),
81 sizeof(SubCommandResponse), timeout_mili);
82
83 if (result < 1) {
84 LOG_ERROR(Input, "No response from joycon");
85 }
86 if (tries++ > MaxTries) {
87 return DriverResult::Timeout;
88 }
89 } while (output.input_report.report_mode != ReportMode::SUBCMD_REPLY &&
90 output.sub_command != sc);
91
92 return DriverResult::Success;
93}
94
95DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer,
96 SubCommandResponse& output) {
97 SubCommandPacket packet{
98 .output_report = OutputReport::RUMBLE_AND_SUBCMD,
99 .packet_counter = GetCounter(),
100 .sub_command = sc,
101 .command_data = {},
102 };
103
104 if (buffer.size() > packet.command_data.size()) {
105 return DriverResult::InvalidParameters;
106 }
107
108 memcpy(packet.command_data.data(), buffer.data(), buffer.size());
109
110 auto result = SendData(packet);
111
112 if (result != DriverResult::Success) {
113 return result;
114 }
115
116 return GetSubCommandResponse(sc, output);
117}
118
119DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) {
120 SubCommandResponse output{};
121 return SendSubCommand(sc, buffer, output);
122}
123
124DriverResult JoyconCommonProtocol::SendMCUCommand(SubCommand sc, std::span<const u8> buffer) {
125 SubCommandPacket packet{
126 .output_report = OutputReport::MCU_DATA,
127 .packet_counter = GetCounter(),
128 .sub_command = sc,
129 .command_data = {},
130 };
131
132 if (buffer.size() > packet.command_data.size()) {
133 return DriverResult::InvalidParameters;
134 }
135
136 memcpy(packet.command_data.data(), buffer.data(), buffer.size());
137
138 return SendData(packet);
139}
140
141DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) {
142 VibrationPacket packet{
143 .output_report = OutputReport::RUMBLE_ONLY,
144 .packet_counter = GetCounter(),
145 .vibration_data = {},
146 };
147
148 if (buffer.size() > packet.vibration_data.size()) {
149 return DriverResult::InvalidParameters;
150 }
151
152 memcpy(packet.vibration_data.data(), buffer.data(), buffer.size());
153
154 return SendData(packet);
155}
156
157DriverResult JoyconCommonProtocol::ReadRawSPI(SpiAddress addr, std::span<u8> output) {
158 constexpr std::size_t HeaderSize = 5;
159 constexpr std::size_t MaxTries = 5;
160 std::size_t tries = 0;
161 SubCommandResponse response{};
162 std::array<u8, sizeof(ReadSpiPacket)> buffer{};
163 const ReadSpiPacket packet_data{
164 .spi_address = addr,
165 .size = static_cast<u8>(output.size()),
166 };
167
168 memcpy(buffer.data(), &packet_data, sizeof(ReadSpiPacket));
169 do {
170 const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, response);
171 if (result != DriverResult::Success) {
172 return result;
173 }
174
175 if (tries++ > MaxTries) {
176 return DriverResult::Timeout;
177 }
178 } while (response.spi_address != addr);
179
180 if (response.command_data.size() < packet_data.size + HeaderSize) {
181 return DriverResult::WrongReply;
182 }
183
184 // Remove header from output
185 memcpy(output.data(), response.command_data.data() + HeaderSize, packet_data.size);
186 return DriverResult::Success;
187}
188
189DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
190 const std::array<u8, 1> mcu_state{static_cast<u8>(enable ? 1 : 0)};
191 const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state);
192
193 if (result != DriverResult::Success) {
194 LOG_ERROR(Input, "Failed with error {}", result);
195 }
196
197 return result;
198}
199
200DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) {
201 LOG_DEBUG(Input, "ConfigureMCU");
202 std::array<u8, sizeof(MCUConfig)> config_buffer;
203 memcpy(config_buffer.data(), &config, sizeof(MCUConfig));
204 config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36);
205
206 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer);
207
208 if (result != DriverResult::Success) {
209 LOG_ERROR(Input, "Failed with error {}", result);
210 }
211
212 return result;
213}
214
215DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode,
216 MCUCommandResponse& output) {
217 constexpr int TimeoutMili = 200;
218 constexpr int MaxTries = 9;
219 int tries = 0;
220
221 do {
222 int result = SDL_hid_read_timeout(hidapi_handle->handle, reinterpret_cast<u8*>(&output),
223 sizeof(MCUCommandResponse), TimeoutMili);
224
225 if (result < 1) {
226 LOG_ERROR(Input, "No response from joycon attempt {}", tries);
227 }
228 if (tries++ > MaxTries) {
229 return DriverResult::Timeout;
230 }
231 } while (output.input_report.report_mode != report_mode ||
232 output.mcu_report == MCUReport::EmptyAwaitingCmd);
233
234 return DriverResult::Success;
235}
236
237DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, MCUSubCommand sc,
238 std::span<const u8> buffer,
239 MCUCommandResponse& output) {
240 SubCommandPacket packet{
241 .output_report = OutputReport::MCU_DATA,
242 .packet_counter = GetCounter(),
243 .mcu_sub_command = sc,
244 .command_data = {},
245 };
246
247 if (buffer.size() > packet.command_data.size()) {
248 return DriverResult::InvalidParameters;
249 }
250
251 memcpy(packet.command_data.data(), buffer.data(), buffer.size());
252
253 auto result = SendData(packet);
254
255 if (result != DriverResult::Success) {
256 return result;
257 }
258
259 result = GetMCUDataResponse(report_mode, output);
260
261 return DriverResult::Success;
262}
263
264DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) {
265 MCUCommandResponse output{};
266 constexpr std::size_t MaxTries{16};
267 std::size_t tries{};
268
269 do {
270 const auto result = SendMCUData(report_mode, MCUSubCommand::SetDeviceMode, {}, output);
271
272 if (result != DriverResult::Success) {
273 return result;
274 }
275
276 if (tries++ > MaxTries) {
277 return DriverResult::WrongReply;
278 }
279 } while (output.mcu_report != MCUReport::StateReport ||
280 output.mcu_data[6] != static_cast<u8>(mode));
281
282 return DriverResult::Success;
283}
284
285// crc-8-ccitt / polynomial 0x07 look up table
286constexpr std::array<u8, 256> mcu_crc8_table = {
287 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
288 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
289 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
290 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
291 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
292 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
293 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
294 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
295 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
296 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
297 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
298 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
299 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
300 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
301 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
302 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3};
303
304u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const {
305 u8 crc8 = 0x0;
306
307 for (int i = 0; i < size; ++i) {
308 crc8 = mcu_crc8_table[static_cast<u8>(crc8 ^ buffer[i])];
309 }
310 return crc8;
311}
312
313} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h
new file mode 100644
index 000000000..411ec018a
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.h
@@ -0,0 +1,201 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <memory>
12#include <span>
13#include <vector>
14
15#include "common/common_types.h"
16#include "input_common/helpers/joycon_protocol/joycon_types.h"
17
18namespace InputCommon::Joycon {
19
20/// Joycon driver functions that handle low level communication
21class JoyconCommonProtocol {
22public:
23 explicit JoyconCommonProtocol(std::shared_ptr<JoyconHandle> hidapi_handle_);
24
25 /**
26 * Sets handle to blocking. In blocking mode, SDL_hid_read() will wait (block) until there is
27 * data to read before returning.
28 */
29 void SetBlocking();
30
31 /**
32 * Sets handle to non blocking. In non-blocking mode calls to SDL_hid_read() will return
33 * immediately with a value of 0 if there is no data to be read
34 */
35 void SetNonBlocking();
36
37 /**
38 * Sends a request to obtain the joycon type from device
39 * @returns controller type of the joycon
40 */
41 DriverResult GetDeviceType(ControllerType& controller_type);
42
43 /**
44 * Verifies and sets the joycon_handle if device is valid
45 * @param device info from the driver
46 * @returns success if the device is valid
47 */
48 DriverResult CheckDeviceAccess(SDL_hid_device_info* device);
49
50 /**
51 * Sends a request to set the polling mode of the joycon
52 * @param report_mode polling mode to be set
53 */
54 DriverResult SetReportMode(Joycon::ReportMode report_mode);
55
56 /**
57 * Sends data to the joycon device
58 * @param buffer data to be send
59 */
60 DriverResult SendRawData(std::span<const u8> buffer);
61
62 template <typename Output>
63 requires std::is_trivially_copyable_v<Output>
64 DriverResult SendData(const Output& output) {
65 std::array<u8, sizeof(Output)> buffer;
66 std::memcpy(buffer.data(), &output, sizeof(Output));
67 return SendRawData(buffer);
68 }
69
70 /**
71 * Waits for incoming data of the joycon device that matches the subcommand
72 * @param sub_command type of data to be returned
73 * @returns a buffer containing the response
74 */
75 DriverResult GetSubCommandResponse(SubCommand sub_command, SubCommandResponse& output);
76
77 /**
78 * Sends a sub command to the device and waits for it's reply
79 * @param sc sub command to be send
80 * @param buffer data to be send
81 * @returns output buffer containing the response
82 */
83 DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer,
84 SubCommandResponse& output);
85
86 /**
87 * Sends a sub command to the device and waits for it's reply and ignores the output
88 * @param sc sub command to be send
89 * @param buffer data to be send
90 */
91 DriverResult SendSubCommand(SubCommand sc, std::span<const u8> buffer);
92
93 /**
94 * Sends a mcu command to the device
95 * @param sc sub command to be send
96 * @param buffer data to be send
97 */
98 DriverResult SendMCUCommand(SubCommand sc, std::span<const u8> buffer);
99
100 /**
101 * Sends vibration data to the joycon
102 * @param buffer data to be send
103 */
104 DriverResult SendVibrationReport(std::span<const u8> buffer);
105
106 /**
107 * Reads the SPI memory stored on the joycon
108 * @param Initial address location
109 * @returns output buffer containing the response
110 */
111 DriverResult ReadRawSPI(SpiAddress addr, std::span<u8> output);
112
113 /**
114 * Reads the SPI memory stored on the joycon
115 * @param Initial address location
116 * @returns output object containing the response
117 */
118 template <typename Output>
119 requires std::is_trivially_copyable_v<Output>
120 DriverResult ReadSPI(SpiAddress addr, Output& output) {
121 std::array<u8, sizeof(Output)> buffer;
122 output = {};
123
124 const auto result = ReadRawSPI(addr, buffer);
125 if (result != DriverResult::Success) {
126 return result;
127 }
128
129 std::memcpy(&output, buffer.data(), sizeof(Output));
130 return DriverResult::Success;
131 }
132
133 /**
134 * Enables MCU chip on the joycon
135 * @param enable if true the chip will be enabled
136 */
137 DriverResult EnableMCU(bool enable);
138
139 /**
140 * Configures the MCU to the corresponding mode
141 * @param MCUConfig configuration
142 */
143 DriverResult ConfigureMCU(const MCUConfig& config);
144
145 /**
146 * Waits until there's MCU data available. On timeout returns error
147 * @param report mode of the expected reply
148 * @returns a buffer containing the response
149 */
150 DriverResult GetMCUDataResponse(ReportMode report_mode_, MCUCommandResponse& output);
151
152 /**
153 * Sends data to the MCU chip and waits for it's reply
154 * @param report mode of the expected reply
155 * @param sub command to be send
156 * @param buffer data to be send
157 * @returns output buffer containing the response
158 */
159 DriverResult SendMCUData(ReportMode report_mode, MCUSubCommand sc, std::span<const u8> buffer,
160 MCUCommandResponse& output);
161
162 /**
163 * Wait's until the MCU chip is on the specified mode
164 * @param report mode of the expected reply
165 * @param MCUMode configuration
166 */
167 DriverResult WaitSetMCUMode(ReportMode report_mode, MCUMode mode);
168
169 /**
170 * Calculates the checksum from the MCU data
171 * @param buffer containing the data to be send
172 * @param size of the buffer in bytes
173 * @returns byte with the correct checksum
174 */
175 u8 CalculateMCU_CRC8(u8* buffer, u8 size) const;
176
177private:
178 /**
179 * Increments and returns the packet counter of the handle
180 * @param joycon_handle device to send the data
181 * @returns packet counter value
182 */
183 u8 GetCounter();
184
185 std::shared_ptr<JoyconHandle> hidapi_handle;
186};
187
188class ScopedSetBlocking {
189public:
190 explicit ScopedSetBlocking(JoyconCommonProtocol* self) : m_self{self} {
191 m_self->SetBlocking();
192 }
193
194 ~ScopedSetBlocking() {
195 m_self->SetNonBlocking();
196 }
197
198private:
199 JoyconCommonProtocol* m_self{};
200};
201} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.cpp b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
new file mode 100644
index 000000000..548a4b9e3
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.cpp
@@ -0,0 +1,136 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/generic_functions.h"
6
7namespace InputCommon::Joycon {
8
9GenericProtocol::GenericProtocol(std::shared_ptr<JoyconHandle> handle)
10 : JoyconCommonProtocol(std::move(handle)) {}
11
12DriverResult GenericProtocol::EnablePassiveMode() {
13 ScopedSetBlocking sb(this);
14 return SetReportMode(ReportMode::SIMPLE_HID_MODE);
15}
16
17DriverResult GenericProtocol::EnableActiveMode() {
18 ScopedSetBlocking sb(this);
19 return SetReportMode(ReportMode::STANDARD_FULL_60HZ);
20}
21
22DriverResult GenericProtocol::SetLowPowerMode(bool enable) {
23 ScopedSetBlocking sb(this);
24 const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)};
25 return SendSubCommand(SubCommand::LOW_POWER_MODE, buffer);
26}
27
28DriverResult GenericProtocol::TriggersElapsed() {
29 ScopedSetBlocking sb(this);
30 return SendSubCommand(SubCommand::TRIGGERS_ELAPSED, {});
31}
32
33DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) {
34 ScopedSetBlocking sb(this);
35 SubCommandResponse output{};
36
37 const auto result = SendSubCommand(SubCommand::REQ_DEV_INFO, {}, output);
38
39 device_info = {};
40 if (result == DriverResult::Success) {
41 device_info = output.device_info;
42 }
43
44 return result;
45}
46
47DriverResult GenericProtocol::GetControllerType(ControllerType& controller_type) {
48 return GetDeviceType(controller_type);
49}
50
51DriverResult GenericProtocol::EnableImu(bool enable) {
52 ScopedSetBlocking sb(this);
53 const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)};
54 return SendSubCommand(SubCommand::ENABLE_IMU, buffer);
55}
56
57DriverResult GenericProtocol::SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
58 AccelerometerSensitivity asen,
59 AccelerometerPerformance afrec) {
60 ScopedSetBlocking sb(this);
61 const std::array<u8, 4> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
62 static_cast<u8>(gfrec), static_cast<u8>(afrec)};
63 return SendSubCommand(SubCommand::SET_IMU_SENSITIVITY, buffer);
64}
65
66DriverResult GenericProtocol::GetBattery(u32& battery_level) {
67 // This function is meant to request the high resolution battery status
68 battery_level = 0;
69 return DriverResult::NotSupported;
70}
71
72DriverResult GenericProtocol::GetColor(Color& color) {
73 ScopedSetBlocking sb(this);
74 std::array<u8, 12> buffer{};
75 const auto result = ReadRawSPI(SpiAddress::COLOR_DATA, buffer);
76
77 color = {};
78 if (result == DriverResult::Success) {
79 color.body = static_cast<u32>((buffer[0] << 16) | (buffer[1] << 8) | buffer[2]);
80 color.buttons = static_cast<u32>((buffer[3] << 16) | (buffer[4] << 8) | buffer[5]);
81 color.left_grip = static_cast<u32>((buffer[6] << 16) | (buffer[7] << 8) | buffer[8]);
82 color.right_grip = static_cast<u32>((buffer[9] << 16) | (buffer[10] << 8) | buffer[11]);
83 }
84
85 return result;
86}
87
88DriverResult GenericProtocol::GetSerialNumber(SerialNumber& serial_number) {
89 ScopedSetBlocking sb(this);
90 std::array<u8, 16> buffer{};
91 const auto result = ReadRawSPI(SpiAddress::SERIAL_NUMBER, buffer);
92
93 serial_number = {};
94 if (result == DriverResult::Success) {
95 memcpy(serial_number.data(), buffer.data() + 1, sizeof(SerialNumber));
96 }
97
98 return result;
99}
100
101DriverResult GenericProtocol::GetTemperature(u32& temperature) {
102 // Not all devices have temperature sensor
103 temperature = 25;
104 return DriverResult::NotSupported;
105}
106
107DriverResult GenericProtocol::GetVersionNumber(FirmwareVersion& version) {
108 DeviceInfo device_info{};
109
110 const auto result = GetDeviceInfo(device_info);
111 version = device_info.firmware;
112
113 return result;
114}
115
116DriverResult GenericProtocol::SetHomeLight() {
117 ScopedSetBlocking sb(this);
118 static constexpr std::array<u8, 3> buffer{0x0f, 0xf0, 0x00};
119 return SendSubCommand(SubCommand::SET_HOME_LIGHT, buffer);
120}
121
122DriverResult GenericProtocol::SetLedBusy() {
123 return DriverResult::NotSupported;
124}
125
126DriverResult GenericProtocol::SetLedPattern(u8 leds) {
127 ScopedSetBlocking sb(this);
128 const std::array<u8, 1> buffer{leds};
129 return SendSubCommand(SubCommand::SET_PLAYER_LIGHTS, buffer);
130}
131
132DriverResult GenericProtocol::SetLedBlinkPattern(u8 leds) {
133 return SetLedPattern(static_cast<u8>(leds << 4));
134}
135
136} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.h b/src/input_common/helpers/joycon_protocol/generic_functions.h
new file mode 100644
index 000000000..424831e81
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/generic_functions.h
@@ -0,0 +1,114 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include "input_common/helpers/joycon_protocol/common_protocol.h"
12#include "input_common/helpers/joycon_protocol/joycon_types.h"
13
14namespace InputCommon::Joycon {
15
16/// Joycon driver functions that easily implemented
17class GenericProtocol final : private JoyconCommonProtocol {
18public:
19 explicit GenericProtocol(std::shared_ptr<JoyconHandle> handle);
20
21 /// Enables passive mode. This mode only sends button data on change. Sticks will return digital
22 /// data instead of analog. Motion will be disabled
23 DriverResult EnablePassiveMode();
24
25 /// Enables active mode. This mode will return the current status every 5-15ms
26 DriverResult EnableActiveMode();
27
28 /// Enables or disables the low power mode
29 DriverResult SetLowPowerMode(bool enable);
30
31 /// Unknown function used by the switch
32 DriverResult TriggersElapsed();
33
34 /**
35 * Sends a request to obtain the joycon firmware and mac from handle
36 * @returns controller device info
37 */
38 DriverResult GetDeviceInfo(DeviceInfo& controller_type);
39
40 /**
41 * Sends a request to obtain the joycon type from handle
42 * @returns controller type of the joycon
43 */
44 DriverResult GetControllerType(ControllerType& controller_type);
45
46 /**
47 * Enables motion input
48 * @param enable if true motion data will be enabled
49 */
50 DriverResult EnableImu(bool enable);
51
52 /**
53 * Configures the motion sensor with the specified parameters
54 * @param gsen gyroscope sensor sensitvity in degrees per second
55 * @param gfrec gyroscope sensor frequency in hertz
56 * @param asen accelerometer sensitivity in G force
57 * @param afrec accelerometer frequency in hertz
58 */
59 DriverResult SetImuConfig(GyroSensitivity gsen, GyroPerformance gfrec,
60 AccelerometerSensitivity asen, AccelerometerPerformance afrec);
61
62 /**
63 * Request battery level from the device
64 * @returns battery level
65 */
66 DriverResult GetBattery(u32& battery_level);
67
68 /**
69 * Request joycon colors from the device
70 * @returns colors of the body and buttons
71 */
72 DriverResult GetColor(Color& color);
73
74 /**
75 * Request joycon serial number from the device
76 * @returns 16 byte serial number
77 */
78 DriverResult GetSerialNumber(SerialNumber& serial_number);
79
80 /**
81 * Request joycon serial number from the device
82 * @returns 16 byte serial number
83 */
84 DriverResult GetTemperature(u32& temperature);
85
86 /**
87 * Request joycon serial number from the device
88 * @returns 16 byte serial number
89 */
90 DriverResult GetVersionNumber(FirmwareVersion& version);
91
92 /**
93 * Sets home led behaviour
94 */
95 DriverResult SetHomeLight();
96
97 /**
98 * Sets home led into a slow breathing state
99 */
100 DriverResult SetLedBusy();
101
102 /**
103 * Sets the 4 player leds on the joycon on a solid state
104 * @params bit flag containing the led state
105 */
106 DriverResult SetLedPattern(u8 leds);
107
108 /**
109 * Sets the 4 player leds on the joycon on a blinking state
110 * @returns bit flag containing the led state
111 */
112 DriverResult SetLedBlinkPattern(u8 leds);
113};
114} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/irs.cpp b/src/input_common/helpers/joycon_protocol/irs.cpp
new file mode 100644
index 000000000..731fd5981
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/irs.cpp
@@ -0,0 +1,299 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <thread>
5#include "common/logging/log.h"
6#include "input_common/helpers/joycon_protocol/irs.h"
7
8namespace InputCommon::Joycon {
9
10IrsProtocol::IrsProtocol(std::shared_ptr<JoyconHandle> handle)
11 : JoyconCommonProtocol(std::move(handle)) {}
12
13DriverResult IrsProtocol::EnableIrs() {
14 LOG_INFO(Input, "Enable IRS");
15 ScopedSetBlocking sb(this);
16 DriverResult result{DriverResult::Success};
17
18 if (result == DriverResult::Success) {
19 result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
20 }
21 if (result == DriverResult::Success) {
22 result = EnableMCU(true);
23 }
24 if (result == DriverResult::Success) {
25 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
26 }
27 if (result == DriverResult::Success) {
28 const MCUConfig config{
29 .command = MCUCommand::ConfigureMCU,
30 .sub_command = MCUSubCommand::SetMCUMode,
31 .mode = MCUMode::IR,
32 .crc = {},
33 };
34
35 result = ConfigureMCU(config);
36 }
37 if (result == DriverResult::Success) {
38 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::IR);
39 }
40 if (result == DriverResult::Success) {
41 result = ConfigureIrs();
42 }
43 if (result == DriverResult::Success) {
44 result = WriteRegistersStep1();
45 }
46 if (result == DriverResult::Success) {
47 result = WriteRegistersStep2();
48 }
49
50 is_enabled = true;
51
52 return result;
53}
54
55DriverResult IrsProtocol::DisableIrs() {
56 LOG_DEBUG(Input, "Disable IRS");
57 ScopedSetBlocking sb(this);
58 DriverResult result{DriverResult::Success};
59
60 if (result == DriverResult::Success) {
61 result = EnableMCU(false);
62 }
63
64 is_enabled = false;
65
66 return result;
67}
68
69DriverResult IrsProtocol::SetIrsConfig(IrsMode mode, IrsResolution format) {
70 irs_mode = mode;
71 switch (format) {
72 case IrsResolution::Size320x240:
73 resolution_code = IrsResolutionCode::Size320x240;
74 fragments = IrsFragments::Size320x240;
75 resolution = IrsResolution::Size320x240;
76 break;
77 case IrsResolution::Size160x120:
78 resolution_code = IrsResolutionCode::Size160x120;
79 fragments = IrsFragments::Size160x120;
80 resolution = IrsResolution::Size160x120;
81 break;
82 case IrsResolution::Size80x60:
83 resolution_code = IrsResolutionCode::Size80x60;
84 fragments = IrsFragments::Size80x60;
85 resolution = IrsResolution::Size80x60;
86 break;
87 case IrsResolution::Size20x15:
88 resolution_code = IrsResolutionCode::Size20x15;
89 fragments = IrsFragments::Size20x15;
90 resolution = IrsResolution::Size20x15;
91 break;
92 case IrsResolution::Size40x30:
93 default:
94 resolution_code = IrsResolutionCode::Size40x30;
95 fragments = IrsFragments::Size40x30;
96 resolution = IrsResolution::Size40x30;
97 break;
98 }
99
100 // Restart feature
101 if (is_enabled) {
102 DisableIrs();
103 return EnableIrs();
104 }
105
106 return DriverResult::Success;
107}
108
109DriverResult IrsProtocol::RequestImage(std::span<u8> buffer) {
110 const u8 next_packet_fragment =
111 static_cast<u8>((packet_fragment + 1) % (static_cast<u8>(fragments) + 1));
112
113 if (buffer[0] == 0x31 && buffer[49] == 0x03) {
114 u8 new_packet_fragment = buffer[52];
115 if (new_packet_fragment == next_packet_fragment) {
116 packet_fragment = next_packet_fragment;
117 memcpy(buf_image.data() + (300 * packet_fragment), buffer.data() + 59, 300);
118
119 return RequestFrame(packet_fragment);
120 }
121
122 if (new_packet_fragment == packet_fragment) {
123 return RequestFrame(packet_fragment);
124 }
125
126 return ResendFrame(next_packet_fragment);
127 }
128
129 return RequestFrame(packet_fragment);
130}
131
132DriverResult IrsProtocol::ConfigureIrs() {
133 LOG_DEBUG(Input, "Configure IRS");
134 constexpr std::size_t max_tries = 28;
135 SubCommandResponse output{};
136 std::size_t tries = 0;
137
138 const IrsConfigure irs_configuration{
139 .command = MCUCommand::ConfigureIR,
140 .sub_command = MCUSubCommand::SetDeviceMode,
141 .irs_mode = IrsMode::ImageTransfer,
142 .number_of_fragments = fragments,
143 .mcu_major_version = 0x0500,
144 .mcu_minor_version = 0x1800,
145 .crc = {},
146 };
147 buf_image.resize((static_cast<u8>(fragments) + 1) * 300);
148
149 std::array<u8, sizeof(IrsConfigure)> request_data{};
150 memcpy(request_data.data(), &irs_configuration, sizeof(IrsConfigure));
151 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
152 do {
153 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
154
155 if (result != DriverResult::Success) {
156 return result;
157 }
158 if (tries++ >= max_tries) {
159 return DriverResult::WrongReply;
160 }
161 } while (output.command_data[0] != 0x0b);
162
163 return DriverResult::Success;
164}
165
166DriverResult IrsProtocol::WriteRegistersStep1() {
167 LOG_DEBUG(Input, "WriteRegistersStep1");
168 DriverResult result{DriverResult::Success};
169 constexpr std::size_t max_tries = 28;
170 SubCommandResponse output{};
171 std::size_t tries = 0;
172
173 const IrsWriteRegisters irs_registers{
174 .command = MCUCommand::ConfigureIR,
175 .sub_command = MCUSubCommand::WriteDeviceRegisters,
176 .number_of_registers = 0x9,
177 .registers =
178 {
179 IrsRegister{IrRegistersAddress::Resolution, static_cast<u8>(resolution_code)},
180 {IrRegistersAddress::ExposureLSB, static_cast<u8>(exposure & 0xff)},
181 {IrRegistersAddress::ExposureMSB, static_cast<u8>(exposure >> 8)},
182 {IrRegistersAddress::ExposureTime, 0x00},
183 {IrRegistersAddress::Leds, static_cast<u8>(leds)},
184 {IrRegistersAddress::DigitalGainLSB, static_cast<u8>((digital_gain & 0x0f) << 4)},
185 {IrRegistersAddress::DigitalGainMSB, static_cast<u8>((digital_gain & 0xf0) >> 4)},
186 {IrRegistersAddress::LedFilter, static_cast<u8>(led_filter)},
187 {IrRegistersAddress::WhitePixelThreshold, 0xc8},
188 },
189 .crc = {},
190 };
191
192 std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
193 memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
194 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
195
196 std::array<u8, 38> mcu_request{0x02};
197 mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
198 mcu_request[37] = 0xFF;
199
200 if (result != DriverResult::Success) {
201 return result;
202 }
203
204 do {
205 result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
206
207 // First time we need to set the report mode
208 if (result == DriverResult::Success && tries == 0) {
209 result = SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
210 }
211 if (result == DriverResult::Success && tries == 0) {
212 GetSubCommandResponse(SubCommand::SET_MCU_CONFIG, output);
213 }
214
215 if (result != DriverResult::Success) {
216 return result;
217 }
218 if (tries++ >= max_tries) {
219 return DriverResult::WrongReply;
220 }
221 } while (!(output.command_data[0] == 0x13 && output.command_data[2] == 0x07) &&
222 output.command_data[0] != 0x23);
223
224 return DriverResult::Success;
225}
226
227DriverResult IrsProtocol::WriteRegistersStep2() {
228 LOG_DEBUG(Input, "WriteRegistersStep2");
229 constexpr std::size_t max_tries = 28;
230 SubCommandResponse output{};
231 std::size_t tries = 0;
232
233 const IrsWriteRegisters irs_registers{
234 .command = MCUCommand::ConfigureIR,
235 .sub_command = MCUSubCommand::WriteDeviceRegisters,
236 .number_of_registers = 0x8,
237 .registers =
238 {
239 IrsRegister{IrRegistersAddress::LedIntensitiyMSB,
240 static_cast<u8>(led_intensity >> 8)},
241 {IrRegistersAddress::LedIntensitiyLSB, static_cast<u8>(led_intensity & 0xff)},
242 {IrRegistersAddress::ImageFlip, static_cast<u8>(image_flip)},
243 {IrRegistersAddress::DenoiseSmoothing, static_cast<u8>((denoise >> 16) & 0xff)},
244 {IrRegistersAddress::DenoiseEdge, static_cast<u8>((denoise >> 8) & 0xff)},
245 {IrRegistersAddress::DenoiseColor, static_cast<u8>(denoise & 0xff)},
246 {IrRegistersAddress::UpdateTime, 0x2d},
247 {IrRegistersAddress::FinalizeConfig, 0x01},
248 },
249 .crc = {},
250 };
251
252 std::array<u8, sizeof(IrsWriteRegisters)> request_data{};
253 memcpy(request_data.data(), &irs_registers, sizeof(IrsWriteRegisters));
254 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
255 do {
256 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, request_data, output);
257
258 if (result != DriverResult::Success) {
259 return result;
260 }
261 if (tries++ >= max_tries) {
262 return DriverResult::WrongReply;
263 }
264 } while (output.command_data[0] != 0x13 && output.command_data[0] != 0x23);
265
266 return DriverResult::Success;
267}
268
269DriverResult IrsProtocol::RequestFrame(u8 frame) {
270 std::array<u8, 38> mcu_request{};
271 mcu_request[3] = frame;
272 mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
273 mcu_request[37] = 0xFF;
274 return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
275}
276
277DriverResult IrsProtocol::ResendFrame(u8 frame) {
278 std::array<u8, 38> mcu_request{};
279 mcu_request[1] = 0x1;
280 mcu_request[2] = frame;
281 mcu_request[3] = 0x0;
282 mcu_request[36] = CalculateMCU_CRC8(mcu_request.data(), 36);
283 mcu_request[37] = 0xFF;
284 return SendMCUCommand(SubCommand::SET_REPORT_MODE, mcu_request);
285}
286
287std::vector<u8> IrsProtocol::GetImage() const {
288 return buf_image;
289}
290
291IrsResolution IrsProtocol::GetIrsFormat() const {
292 return resolution;
293}
294
295bool IrsProtocol::IsEnabled() const {
296 return is_enabled;
297}
298
299} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/irs.h b/src/input_common/helpers/joycon_protocol/irs.h
new file mode 100644
index 000000000..76dfa02ea
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/irs.h
@@ -0,0 +1,63 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class IrsProtocol final : private JoyconCommonProtocol {
19public:
20 explicit IrsProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableIrs();
23
24 DriverResult DisableIrs();
25
26 DriverResult SetIrsConfig(IrsMode mode, IrsResolution format);
27
28 DriverResult RequestImage(std::span<u8> buffer);
29
30 std::vector<u8> GetImage() const;
31
32 IrsResolution GetIrsFormat() const;
33
34 bool IsEnabled() const;
35
36private:
37 DriverResult ConfigureIrs();
38
39 DriverResult WriteRegistersStep1();
40 DriverResult WriteRegistersStep2();
41
42 DriverResult RequestFrame(u8 frame);
43 DriverResult ResendFrame(u8 frame);
44
45 IrsMode irs_mode{IrsMode::ImageTransfer};
46 IrsResolution resolution{IrsResolution::Size40x30};
47 IrsResolutionCode resolution_code{IrsResolutionCode::Size40x30};
48 IrsFragments fragments{IrsFragments::Size40x30};
49 IrLeds leds{IrLeds::BrightAndDim};
50 IrExLedFilter led_filter{IrExLedFilter::Enabled};
51 IrImageFlip image_flip{IrImageFlip::Normal};
52 u8 digital_gain{0x01};
53 u16 exposure{0x2490};
54 u16 led_intensity{0x0f10};
55 u32 denoise{0x012344};
56
57 u8 packet_fragment{};
58 std::vector<u8> buf_image; // 8bpp greyscale image.
59
60 bool is_enabled{};
61};
62
63} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h
new file mode 100644
index 000000000..e0e431156
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -0,0 +1,808 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <array>
12#include <functional>
13#include <SDL_hidapi.h>
14
15#include "common/bit_field.h"
16#include "common/common_funcs.h"
17#include "common/common_types.h"
18
19namespace InputCommon::Joycon {
20constexpr u32 MaxErrorCount = 50;
21constexpr u32 MaxBufferSize = 368;
22constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
23
24using MacAddress = std::array<u8, 6>;
25using SerialNumber = std::array<u8, 15>;
26using TagUUID = std::array<u8, 7>;
27using MifareUUID = std::array<u8, 4>;
28
29enum class ControllerType : u8 {
30 None = 0x00,
31 Left = 0x01,
32 Right = 0x02,
33 Pro = 0x03,
34 Dual = 0x05, // TODO: Verify this id
35 LarkHvc1 = 0x07,
36 LarkHvc2 = 0x08,
37 LarkNesLeft = 0x09,
38 LarkNesRight = 0x0A,
39 Lucia = 0x0B,
40 Lagon = 0x0C,
41 Lager = 0x0D,
42};
43
44enum class PadAxes {
45 LeftStickX,
46 LeftStickY,
47 RightStickX,
48 RightStickY,
49 Undefined,
50};
51
52enum class PadMotion {
53 LeftMotion,
54 RightMotion,
55 Undefined,
56};
57
58enum class PadButton : u32 {
59 Down = 0x000001,
60 Up = 0x000002,
61 Right = 0x000004,
62 Left = 0x000008,
63 LeftSR = 0x000010,
64 LeftSL = 0x000020,
65 L = 0x000040,
66 ZL = 0x000080,
67 Y = 0x000100,
68 X = 0x000200,
69 B = 0x000400,
70 A = 0x000800,
71 RightSR = 0x001000,
72 RightSL = 0x002000,
73 R = 0x004000,
74 ZR = 0x008000,
75 Minus = 0x010000,
76 Plus = 0x020000,
77 StickR = 0x040000,
78 StickL = 0x080000,
79 Home = 0x100000,
80 Capture = 0x200000,
81};
82
83enum class PassivePadButton : u32 {
84 Down_A = 0x0001,
85 Right_X = 0x0002,
86 Left_B = 0x0004,
87 Up_Y = 0x0008,
88 SL = 0x0010,
89 SR = 0x0020,
90 Minus = 0x0100,
91 Plus = 0x0200,
92 StickL = 0x0400,
93 StickR = 0x0800,
94 Home = 0x1000,
95 Capture = 0x2000,
96 L_R = 0x4000,
97 ZL_ZR = 0x8000,
98};
99
100enum class PassivePadStick : u8 {
101 Right = 0x00,
102 RightDown = 0x01,
103 Down = 0x02,
104 DownLeft = 0x03,
105 Left = 0x04,
106 LeftUp = 0x05,
107 Up = 0x06,
108 UpRight = 0x07,
109 Neutral = 0x08,
110};
111
112enum class OutputReport : u8 {
113 RUMBLE_AND_SUBCMD = 0x01,
114 FW_UPDATE_PKT = 0x03,
115 RUMBLE_ONLY = 0x10,
116 MCU_DATA = 0x11,
117 USB_CMD = 0x80,
118};
119
120enum class FeatureReport : u8 {
121 Last_SUBCMD = 0x02,
122 OTA_GW_UPGRADE = 0x70,
123 SETUP_MEM_READ = 0x71,
124 MEM_READ = 0x72,
125 ERASE_MEM_SECTOR = 0x73,
126 MEM_WRITE = 0x74,
127 LAUNCH = 0x75,
128};
129
130enum class SubCommand : u8 {
131 STATE = 0x00,
132 MANUAL_BT_PAIRING = 0x01,
133 REQ_DEV_INFO = 0x02,
134 SET_REPORT_MODE = 0x03,
135 TRIGGERS_ELAPSED = 0x04,
136 GET_PAGE_LIST_STATE = 0x05,
137 SET_HCI_STATE = 0x06,
138 RESET_PAIRING_INFO = 0x07,
139 LOW_POWER_MODE = 0x08,
140 SPI_FLASH_READ = 0x10,
141 SPI_FLASH_WRITE = 0x11,
142 SPI_SECTOR_ERASE = 0x12,
143 RESET_MCU = 0x20,
144 SET_MCU_CONFIG = 0x21,
145 SET_MCU_STATE = 0x22,
146 SET_PLAYER_LIGHTS = 0x30,
147 GET_PLAYER_LIGHTS = 0x31,
148 SET_HOME_LIGHT = 0x38,
149 ENABLE_IMU = 0x40,
150 SET_IMU_SENSITIVITY = 0x41,
151 WRITE_IMU_REG = 0x42,
152 READ_IMU_REG = 0x43,
153 ENABLE_VIBRATION = 0x48,
154 GET_REGULATED_VOLTAGE = 0x50,
155 SET_EXTERNAL_CONFIG = 0x58,
156 GET_EXTERNAL_DEVICE_INFO = 0x59,
157 ENABLE_EXTERNAL_POLLING = 0x5A,
158 DISABLE_EXTERNAL_POLLING = 0x5B,
159 SET_EXTERNAL_FORMAT_CONFIG = 0x5C,
160};
161
162enum class UsbSubCommand : u8 {
163 CONN_STATUS = 0x01,
164 HADSHAKE = 0x02,
165 BAUDRATE_3M = 0x03,
166 NO_TIMEOUT = 0x04,
167 EN_TIMEOUT = 0x05,
168 RESET = 0x06,
169 PRE_HANDSHAKE = 0x91,
170 SEND_UART = 0x92,
171};
172
173enum class CalibrationMagic : u8 {
174 USR_MAGIC_0 = 0xB2,
175 USR_MAGIC_1 = 0xA1,
176};
177
178enum class SpiAddress : u16 {
179 MAGIC = 0x0000,
180 MAC_ADDRESS = 0x0015,
181 PAIRING_INFO = 0x2000,
182 SHIPMENT = 0x5000,
183 SERIAL_NUMBER = 0x6000,
184 DEVICE_TYPE = 0x6012,
185 FORMAT_VERSION = 0x601B,
186 FACT_IMU_DATA = 0x6020,
187 FACT_LEFT_DATA = 0x603d,
188 FACT_RIGHT_DATA = 0x6046,
189 COLOR_DATA = 0x6050,
190 DESIGN_VARIATION = 0x605C,
191 SENSOR_DATA = 0x6080,
192 USER_LEFT_MAGIC = 0x8010,
193 USER_LEFT_DATA = 0x8012,
194 USER_RIGHT_MAGIC = 0x801B,
195 USER_RIGHT_DATA = 0x801D,
196 USER_IMU_MAGIC = 0x8026,
197 USER_IMU_DATA = 0x8028,
198};
199
200enum class ReportMode : u8 {
201 ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00,
202 ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01,
203 ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02,
204 ACTIVE_POLLING_IR_CAMERA_DATA = 0x03,
205 SUBCMD_REPLY = 0x21,
206 MCU_UPDATE_STATE = 0x23,
207 STANDARD_FULL_60HZ = 0x30,
208 NFC_IR_MODE_60HZ = 0x31,
209 SIMPLE_HID_MODE = 0x3F,
210 INPUT_USB_RESPONSE = 0x81,
211};
212
213enum class GyroSensitivity : u8 {
214 DPS250,
215 DPS500,
216 DPS1000,
217 DPS2000, // Default
218};
219
220enum class AccelerometerSensitivity : u8 {
221 G8, // Default
222 G4,
223 G2,
224 G16,
225};
226
227enum class GyroPerformance : u8 {
228 HZ833,
229 HZ208, // Default
230};
231
232enum class AccelerometerPerformance : u8 {
233 HZ200,
234 HZ100, // Default
235};
236
237enum class MCUCommand : u8 {
238 ConfigureMCU = 0x21,
239 ConfigureIR = 0x23,
240};
241
242enum class MCUSubCommand : u8 {
243 SetMCUMode = 0x0,
244 SetDeviceMode = 0x1,
245 ReadDeviceMode = 0x02,
246 WriteDeviceRegisters = 0x4,
247};
248
249enum class MCUMode : u8 {
250 Suspend = 0,
251 Standby = 1,
252 Ringcon = 3,
253 NFC = 4,
254 IR = 5,
255 MaybeFWUpdate = 6,
256};
257
258enum class MCURequest : u8 {
259 GetMCUStatus = 1,
260 GetNFCData = 2,
261 GetIRData = 3,
262};
263
264enum class MCUReport : u8 {
265 Empty = 0x00,
266 StateReport = 0x01,
267 IRData = 0x03,
268 BusyInitializing = 0x0b,
269 IRStatus = 0x13,
270 IRRegisters = 0x1b,
271 NFCState = 0x2a,
272 NFCReadData = 0x3a,
273 EmptyAwaitingCmd = 0xff,
274};
275
276enum class MCUPacketFlag : u8 {
277 MorePacketsRemaining = 0x00,
278 LastCommandPacket = 0x08,
279};
280
281enum class NFCCommand : u8 {
282 CancelAll = 0x00,
283 StartPolling = 0x01,
284 StopPolling = 0x02,
285 StartWaitingRecieve = 0x04,
286 ReadNtag = 0x06,
287 WriteNtag = 0x08,
288 Mifare = 0x0F,
289};
290
291enum class NFCTagType : u8 {
292 AllTags = 0x00,
293 Ntag215 = 0x01,
294};
295
296enum class NFCPages {
297 Block0 = 0,
298 Block3 = 3,
299 Block45 = 45,
300 Block135 = 135,
301 Block231 = 231,
302};
303
304enum class NFCStatus : u8 {
305 Ready = 0x00,
306 Polling = 0x01,
307 LastPackage = 0x04,
308 WriteDone = 0x05,
309 TagLost = 0x07,
310 WriteReady = 0x09,
311 MifareDone = 0x10,
312};
313
314enum class MifareCmd : u8 {
315 None = 0x00,
316 Read = 0x30,
317 AuthA = 0x60,
318 AuthB = 0x61,
319 Write = 0xA0,
320 Transfer = 0xB0,
321 Decrement = 0xC0,
322 Increment = 0xC1,
323 Store = 0xC2
324};
325
326enum class IrsMode : u8 {
327 None = 0x02,
328 Moment = 0x03,
329 Dpd = 0x04,
330 Clustering = 0x06,
331 ImageTransfer = 0x07,
332 Silhouette = 0x08,
333 TeraImage = 0x09,
334 SilhouetteTeraImage = 0x0A,
335};
336
337enum class IrsResolution {
338 Size320x240,
339 Size160x120,
340 Size80x60,
341 Size40x30,
342 Size20x15,
343 None,
344};
345
346enum class IrsResolutionCode : u8 {
347 Size320x240 = 0x00, // Full pixel array
348 Size160x120 = 0x50, // Sensor Binning [2 X 2]
349 Size80x60 = 0x64, // Sensor Binning [4 x 2] and Skipping [1 x 2]
350 Size40x30 = 0x69, // Sensor Binning [4 x 2] and Skipping [2 x 4]
351 Size20x15 = 0x6A, // Sensor Binning [4 x 2] and Skipping [4 x 4]
352};
353
354// Size of image divided by 300
355enum class IrsFragments : u8 {
356 Size20x15 = 0x00,
357 Size40x30 = 0x03,
358 Size80x60 = 0x0f,
359 Size160x120 = 0x3f,
360 Size320x240 = 0xFF,
361};
362
363enum class IrLeds : u8 {
364 BrightAndDim = 0x00,
365 Bright = 0x20,
366 Dim = 0x10,
367 None = 0x30,
368};
369
370enum class IrExLedFilter : u8 {
371 Disabled = 0x00,
372 Enabled = 0x03,
373};
374
375enum class IrImageFlip : u8 {
376 Normal = 0x00,
377 Inverted = 0x02,
378};
379
380enum class IrRegistersAddress : u16 {
381 UpdateTime = 0x0400,
382 FinalizeConfig = 0x0700,
383 LedFilter = 0x0e00,
384 Leds = 0x1000,
385 LedIntensitiyMSB = 0x1100,
386 LedIntensitiyLSB = 0x1200,
387 ImageFlip = 0x2d00,
388 Resolution = 0x2e00,
389 DigitalGainLSB = 0x2e01,
390 DigitalGainMSB = 0x2f01,
391 ExposureLSB = 0x3001,
392 ExposureMSB = 0x3101,
393 ExposureTime = 0x3201,
394 WhitePixelThreshold = 0x4301,
395 DenoiseSmoothing = 0x6701,
396 DenoiseEdge = 0x6801,
397 DenoiseColor = 0x6901,
398};
399
400enum class ExternalDeviceId : u16 {
401 RingController = 0x2000,
402 Starlink = 0x2800,
403};
404
405enum class DriverResult {
406 Success,
407 WrongReply,
408 Timeout,
409 InvalidParameters,
410 UnsupportedControllerType,
411 HandleInUse,
412 ErrorReadingData,
413 ErrorWritingData,
414 NoDeviceDetected,
415 InvalidHandle,
416 NotSupported,
417 Disabled,
418 Delayed,
419 Unknown,
420};
421
422struct MotionSensorCalibration {
423 s16 offset;
424 s16 scale;
425};
426
427struct MotionCalibration {
428 std::array<MotionSensorCalibration, 3> accelerometer;
429 std::array<MotionSensorCalibration, 3> gyro;
430};
431
432// Basic motion data containing data from the sensors and a timestamp in microseconds
433struct MotionData {
434 float gyro_x{};
435 float gyro_y{};
436 float gyro_z{};
437 float accel_x{};
438 float accel_y{};
439 float accel_z{};
440 u64 delta_timestamp{};
441};
442
443// Output from SPI read command containing user calibration magic
444struct MagicSpiCalibration {
445 CalibrationMagic first;
446 CalibrationMagic second;
447};
448static_assert(sizeof(MagicSpiCalibration) == 0x2, "MagicSpiCalibration is an invalid size");
449
450// Output from SPI read command containing left joystick calibration
451struct JoystickLeftSpiCalibration {
452 std::array<u8, 3> max;
453 std::array<u8, 3> center;
454 std::array<u8, 3> min;
455};
456static_assert(sizeof(JoystickLeftSpiCalibration) == 0x9,
457 "JoystickLeftSpiCalibration is an invalid size");
458
459// Output from SPI read command containing right joystick calibration
460struct JoystickRightSpiCalibration {
461 std::array<u8, 3> center;
462 std::array<u8, 3> min;
463 std::array<u8, 3> max;
464};
465static_assert(sizeof(JoystickRightSpiCalibration) == 0x9,
466 "JoystickRightSpiCalibration is an invalid size");
467
468struct JoyStickAxisCalibration {
469 u16 max;
470 u16 min;
471 u16 center;
472};
473
474struct JoyStickCalibration {
475 JoyStickAxisCalibration x;
476 JoyStickAxisCalibration y;
477};
478
479struct ImuSpiCalibration {
480 std::array<s16, 3> accelerometer_offset;
481 std::array<s16, 3> accelerometer_scale;
482 std::array<s16, 3> gyroscope_offset;
483 std::array<s16, 3> gyroscope_scale;
484};
485static_assert(sizeof(ImuSpiCalibration) == 0x18, "ImuSpiCalibration is an invalid size");
486
487struct RingCalibration {
488 s16 default_value;
489 s16 max_value;
490 s16 min_value;
491};
492
493struct Color {
494 u32 body;
495 u32 buttons;
496 u32 left_grip;
497 u32 right_grip;
498};
499
500struct Battery {
501 union {
502 u8 raw{};
503
504 BitField<0, 4, u8> unknown;
505 BitField<4, 1, u8> charging;
506 BitField<5, 3, u8> status;
507 };
508};
509
510struct VibrationValue {
511 f32 low_amplitude;
512 f32 low_frequency;
513 f32 high_amplitude;
514 f32 high_frequency;
515};
516
517struct JoyconHandle {
518 SDL_hid_device* handle = nullptr;
519 u8 packet_counter{};
520};
521
522struct MCUConfig {
523 MCUCommand command;
524 MCUSubCommand sub_command;
525 MCUMode mode;
526 INSERT_PADDING_BYTES(0x22);
527 u8 crc;
528};
529static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size");
530
531#pragma pack(push, 1)
532struct InputReportPassive {
533 ReportMode report_mode;
534 u16 button_input;
535 u8 stick_state;
536 std::array<u8, 10> unknown_data;
537};
538static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size");
539
540struct InputReportActive {
541 ReportMode report_mode;
542 u8 packet_id;
543 Battery battery_status;
544 std::array<u8, 3> button_input;
545 std::array<u8, 3> left_stick_state;
546 std::array<u8, 3> right_stick_state;
547 u8 vibration_code;
548 std::array<s16, 6 * 2> motion_input;
549 INSERT_PADDING_BYTES(0x2);
550 s16 ring_input;
551};
552static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size");
553
554struct InputReportNfcIr {
555 ReportMode report_mode;
556 u8 packet_id;
557 Battery battery_status;
558 std::array<u8, 3> button_input;
559 std::array<u8, 3> left_stick_state;
560 std::array<u8, 3> right_stick_state;
561 u8 vibration_code;
562 std::array<s16, 6 * 2> motion_input;
563 INSERT_PADDING_BYTES(0x4);
564};
565static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size");
566#pragma pack(pop)
567
568struct NFCReadBlock {
569 u8 start;
570 u8 end;
571};
572static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size");
573
574struct NFCReadBlockCommand {
575 u8 block_count{};
576 std::array<NFCReadBlock, 4> blocks{};
577};
578static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size");
579
580struct NFCReadCommandData {
581 u8 unknown;
582 u8 uuid_length;
583 TagUUID uid;
584 NFCTagType tag_type;
585 NFCReadBlockCommand read_block;
586};
587static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size");
588
589#pragma pack(push, 1)
590struct NFCWriteCommandData {
591 u8 unknown;
592 u8 uuid_length;
593 TagUUID uid;
594 NFCTagType tag_type;
595 u8 unknown2;
596 u8 unknown3;
597 u8 unknown4;
598 u8 unknown5;
599 u8 unknown6;
600 u8 unknown7;
601 u8 unknown8;
602 u8 magic;
603 u16_be write_count;
604 u8 amiibo_version;
605};
606static_assert(sizeof(NFCWriteCommandData) == 0x15, "NFCWriteCommandData is an invalid size");
607#pragma pack(pop)
608
609struct MifareCommandData {
610 u8 unknown1;
611 u8 unknown2;
612 u8 number_of_short_bytes;
613 MifareUUID uid;
614};
615static_assert(sizeof(MifareCommandData) == 0x7, "MifareCommandData is an invalid size");
616
617struct NFCPollingCommandData {
618 u8 enable_mifare;
619 u8 unknown_1;
620 u8 unknown_2;
621 u8 unknown_3;
622 u8 unknown_4;
623};
624static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size");
625
626struct NFCRequestState {
627 NFCCommand command_argument;
628 u8 block_id;
629 u8 packet_id;
630 MCUPacketFlag packet_flag;
631 u8 data_length;
632 union {
633 std::array<u8, 0x1F> raw_data;
634 NFCReadCommandData nfc_read;
635 NFCPollingCommandData nfc_polling;
636 };
637 u8 crc;
638 INSERT_PADDING_BYTES(0x1);
639};
640static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
641
642struct NFCDataChunk {
643 u8 nfc_page;
644 u8 data_size;
645 std::array<u8, 0xFF> data;
646};
647
648struct NFCWritePackage {
649 NFCWriteCommandData command_data;
650 u8 number_of_chunks;
651 std::array<NFCDataChunk, 4> data_chunks;
652};
653
654struct MifareReadChunk {
655 MifareCmd command;
656 std::array<u8, 0x6> sector_key;
657 u8 sector;
658};
659
660struct MifareWriteChunk {
661 MifareCmd command;
662 std::array<u8, 0x6> sector_key;
663 u8 sector;
664 std::array<u8, 0x10> data;
665};
666
667struct MifareReadData {
668 u8 sector;
669 std::array<u8, 0x10> data;
670};
671
672struct MifareReadPackage {
673 MifareCommandData command_data;
674 std::array<MifareReadChunk, 0x10> data_chunks;
675};
676
677struct MifareWritePackage {
678 MifareCommandData command_data;
679 std::array<MifareWriteChunk, 0x10> data_chunks;
680};
681
682struct TagInfo {
683 u8 uuid_length;
684 u8 protocol;
685 u8 tag_type;
686 std::array<u8, 10> uuid;
687};
688
689struct IrsConfigure {
690 MCUCommand command;
691 MCUSubCommand sub_command;
692 IrsMode irs_mode;
693 IrsFragments number_of_fragments;
694 u16 mcu_major_version;
695 u16 mcu_minor_version;
696 INSERT_PADDING_BYTES(0x1D);
697 u8 crc;
698};
699static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size");
700
701#pragma pack(push, 1)
702struct IrsRegister {
703 IrRegistersAddress address;
704 u8 value;
705};
706static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size");
707
708struct IrsWriteRegisters {
709 MCUCommand command;
710 MCUSubCommand sub_command;
711 u8 number_of_registers;
712 std::array<IrsRegister, 9> registers;
713 INSERT_PADDING_BYTES(0x7);
714 u8 crc;
715};
716static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size");
717#pragma pack(pop)
718
719struct FirmwareVersion {
720 u8 major;
721 u8 minor;
722};
723static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
724
725struct DeviceInfo {
726 FirmwareVersion firmware;
727 std::array<u8, 2> unknown_1;
728 MacAddress mac_address;
729 std::array<u8, 2> unknown_2;
730};
731static_assert(sizeof(DeviceInfo) == 0xC, "DeviceInfo is an invalid size");
732
733struct MotionStatus {
734 bool is_enabled;
735 u64 delta_time;
736 GyroSensitivity gyro_sensitivity;
737 AccelerometerSensitivity accelerometer_sensitivity;
738};
739
740struct RingStatus {
741 bool is_enabled;
742 s16 default_value;
743 s16 max_value;
744 s16 min_value;
745};
746
747struct VibrationPacket {
748 OutputReport output_report;
749 u8 packet_counter;
750 std::array<u8, 0x8> vibration_data;
751};
752static_assert(sizeof(VibrationPacket) == 0xA, "VibrationPacket is an invalid size");
753
754struct SubCommandPacket {
755 OutputReport output_report;
756 u8 packet_counter;
757 INSERT_PADDING_BYTES(0x8); // This contains vibration data
758 union {
759 SubCommand sub_command;
760 MCUSubCommand mcu_sub_command;
761 };
762 std::array<u8, 0x26> command_data;
763};
764static_assert(sizeof(SubCommandPacket) == 0x31, "SubCommandPacket is an invalid size");
765
766#pragma pack(push, 1)
767struct ReadSpiPacket {
768 SpiAddress spi_address;
769 INSERT_PADDING_BYTES(0x2);
770 u8 size;
771};
772static_assert(sizeof(ReadSpiPacket) == 0x5, "ReadSpiPacket is an invalid size");
773
774struct SubCommandResponse {
775 InputReportPassive input_report;
776 SubCommand sub_command;
777 union {
778 std::array<u8, 0x30> command_data;
779 SpiAddress spi_address; // Reply from SPI_FLASH_READ subcommand
780 ExternalDeviceId external_device_id; // Reply from GET_EXTERNAL_DEVICE_INFO subcommand
781 DeviceInfo device_info; // Reply from REQ_DEV_INFO subcommand
782 };
783 u8 crc; // This is never used
784};
785static_assert(sizeof(SubCommandResponse) == 0x40, "SubCommandResponse is an invalid size");
786#pragma pack(pop)
787
788struct MCUCommandResponse {
789 InputReportNfcIr input_report;
790 INSERT_PADDING_BYTES(0x8);
791 MCUReport mcu_report;
792 std::array<u8, 0x13D> mcu_data;
793 u8 crc;
794};
795static_assert(sizeof(MCUCommandResponse) == 0x170, "MCUCommandResponse is an invalid size");
796
797struct JoyconCallbacks {
798 std::function<void(Battery)> on_battery_data;
799 std::function<void(Color)> on_color_data;
800 std::function<void(int, bool)> on_button_data;
801 std::function<void(int, f32)> on_stick_data;
802 std::function<void(int, const MotionData&)> on_motion_data;
803 std::function<void(f32)> on_ring_data;
804 std::function<void(const Joycon::TagInfo&)> on_amiibo_data;
805 std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data;
806};
807
808} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp
new file mode 100644
index 000000000..261f46255
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.cpp
@@ -0,0 +1,985 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <thread>
5#include "common/logging/log.h"
6#include "input_common/helpers/joycon_protocol/nfc.h"
7
8namespace InputCommon::Joycon {
9
10NfcProtocol::NfcProtocol(std::shared_ptr<JoyconHandle> handle)
11 : JoyconCommonProtocol(std::move(handle)) {}
12
13DriverResult NfcProtocol::EnableNfc() {
14 LOG_INFO(Input, "Enable NFC");
15 ScopedSetBlocking sb(this);
16 DriverResult result{DriverResult::Success};
17
18 if (result == DriverResult::Success) {
19 result = SetReportMode(ReportMode::NFC_IR_MODE_60HZ);
20 }
21 if (result == DriverResult::Success) {
22 result = EnableMCU(true);
23 }
24 if (result == DriverResult::Success) {
25 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::Standby);
26 }
27 if (result == DriverResult::Success) {
28 const MCUConfig config{
29 .command = MCUCommand::ConfigureMCU,
30 .sub_command = MCUSubCommand::SetMCUMode,
31 .mode = MCUMode::NFC,
32 .crc = {},
33 };
34
35 result = ConfigureMCU(config);
36 }
37 if (result == DriverResult::Success) {
38 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC);
39 }
40 if (result == DriverResult::Success) {
41 result = WaitUntilNfcIs(NFCStatus::Ready);
42 }
43 if (result == DriverResult::Success) {
44 MCUCommandResponse output{};
45 result = SendStopPollingRequest(output);
46 }
47 if (result == DriverResult::Success) {
48 result = WaitUntilNfcIs(NFCStatus::Ready);
49 }
50 if (result == DriverResult::Success) {
51 is_enabled = true;
52 }
53
54 return result;
55}
56
57DriverResult NfcProtocol::DisableNfc() {
58 LOG_DEBUG(Input, "Disable NFC");
59 ScopedSetBlocking sb(this);
60 DriverResult result{DriverResult::Success};
61
62 if (result == DriverResult::Success) {
63 result = EnableMCU(false);
64 }
65
66 is_enabled = false;
67 is_polling = false;
68
69 return result;
70}
71
72DriverResult NfcProtocol::StartNFCPollingMode() {
73 LOG_DEBUG(Input, "Start NFC polling Mode");
74 ScopedSetBlocking sb(this);
75 DriverResult result{DriverResult::Success};
76
77 if (result == DriverResult::Success) {
78 MCUCommandResponse output{};
79 result = SendStartPollingRequest(output);
80 }
81 if (result == DriverResult::Success) {
82 result = WaitUntilNfcIs(NFCStatus::Polling);
83 }
84 if (result == DriverResult::Success) {
85 is_polling = true;
86 }
87
88 return result;
89}
90
91DriverResult NfcProtocol::StopNFCPollingMode() {
92 LOG_DEBUG(Input, "Stop NFC polling Mode");
93 ScopedSetBlocking sb(this);
94 DriverResult result{DriverResult::Success};
95
96 if (result == DriverResult::Success) {
97 MCUCommandResponse output{};
98 result = SendStopPollingRequest(output);
99 }
100 if (result == DriverResult::Success) {
101 result = WaitUntilNfcIs(NFCStatus::WriteReady);
102 }
103 if (result == DriverResult::Success) {
104 is_polling = false;
105 }
106
107 return result;
108}
109
110DriverResult NfcProtocol::GetTagInfo(Joycon::TagInfo& tag_info) {
111 if (update_counter++ < AMIIBO_UPDATE_DELAY) {
112 return DriverResult::Delayed;
113 }
114 update_counter = 0;
115
116 LOG_DEBUG(Input, "Scan for amiibos");
117 ScopedSetBlocking sb(this);
118 DriverResult result{DriverResult::Success};
119 TagFoundData tag_data{};
120
121 if (result == DriverResult::Success) {
122 result = IsTagInRange(tag_data);
123 }
124
125 if (result == DriverResult::Success) {
126 tag_info = {
127 .uuid_length = tag_data.uuid_size,
128 .protocol = 1,
129 .tag_type = tag_data.type,
130 .uuid = {},
131 };
132
133 memcpy(tag_info.uuid.data(), tag_data.uuid.data(), tag_data.uuid_size);
134
135 // Investigate why mifare type is not correct
136 if (tag_info.tag_type == 144) {
137 tag_info.tag_type = 1U << 6;
138 }
139
140 std::string uuid_string;
141 for (auto& content : tag_data.uuid) {
142 uuid_string += fmt::format(" {:02x}", content);
143 }
144 LOG_INFO(Input, "Tag detected, type={}, uuid={}", tag_data.type, uuid_string);
145 }
146
147 return result;
148}
149
150DriverResult NfcProtocol::ReadAmiibo(std::vector<u8>& data) {
151 LOG_DEBUG(Input, "Scan for amiibos");
152 ScopedSetBlocking sb(this);
153 DriverResult result{DriverResult::Success};
154 TagFoundData tag_data{};
155
156 if (result == DriverResult::Success) {
157 result = IsTagInRange(tag_data, 7);
158 }
159
160 if (result == DriverResult::Success) {
161 result = GetAmiiboData(data);
162 }
163
164 return result;
165}
166
167DriverResult NfcProtocol::WriteAmiibo(std::span<const u8> data) {
168 LOG_DEBUG(Input, "Write amiibo");
169 ScopedSetBlocking sb(this);
170 DriverResult result{DriverResult::Success};
171 TagUUID tag_uuid = GetTagUUID(data);
172 TagFoundData tag_data{};
173
174 if (result == DriverResult::Success) {
175 result = IsTagInRange(tag_data, 7);
176 }
177 if (result == DriverResult::Success) {
178 if (tag_data.uuid != tag_uuid) {
179 result = DriverResult::InvalidParameters;
180 }
181 }
182 if (result == DriverResult::Success) {
183 MCUCommandResponse output{};
184 result = SendStopPollingRequest(output);
185 }
186 if (result == DriverResult::Success) {
187 result = WaitUntilNfcIs(NFCStatus::Ready);
188 }
189 if (result == DriverResult::Success) {
190 MCUCommandResponse output{};
191 result = SendStartPollingRequest(output, true);
192 }
193 if (result == DriverResult::Success) {
194 result = WaitUntilNfcIs(NFCStatus::WriteReady);
195 }
196 if (result == DriverResult::Success) {
197 result = WriteAmiiboData(tag_uuid, data);
198 }
199 if (result == DriverResult::Success) {
200 result = WaitUntilNfcIs(NFCStatus::WriteDone);
201 }
202 if (result == DriverResult::Success) {
203 MCUCommandResponse output{};
204 result = SendStopPollingRequest(output);
205 }
206
207 return result;
208}
209
210DriverResult NfcProtocol::ReadMifare(std::span<const MifareReadChunk> read_request,
211 std::span<MifareReadData> out_data) {
212 LOG_DEBUG(Input, "Read mifare");
213 ScopedSetBlocking sb(this);
214 DriverResult result{DriverResult::Success};
215 TagFoundData tag_data{};
216 MifareUUID tag_uuid{};
217
218 if (result == DriverResult::Success) {
219 result = IsTagInRange(tag_data, 7);
220 }
221 if (result == DriverResult::Success) {
222 memcpy(tag_uuid.data(), tag_data.uuid.data(), sizeof(MifareUUID));
223 result = GetMifareData(tag_uuid, read_request, out_data);
224 }
225 if (result == DriverResult::Success) {
226 MCUCommandResponse output{};
227 result = SendStopPollingRequest(output);
228 }
229 if (result == DriverResult::Success) {
230 result = WaitUntilNfcIs(NFCStatus::Ready);
231 }
232 if (result == DriverResult::Success) {
233 MCUCommandResponse output{};
234 result = SendStartPollingRequest(output, true);
235 }
236 if (result == DriverResult::Success) {
237 result = WaitUntilNfcIs(NFCStatus::WriteReady);
238 }
239 return result;
240}
241
242DriverResult NfcProtocol::WriteMifare(std::span<const MifareWriteChunk> write_request) {
243 LOG_DEBUG(Input, "Write mifare");
244 ScopedSetBlocking sb(this);
245 DriverResult result{DriverResult::Success};
246 TagFoundData tag_data{};
247 MifareUUID tag_uuid{};
248
249 if (result == DriverResult::Success) {
250 result = IsTagInRange(tag_data, 7);
251 }
252 if (result == DriverResult::Success) {
253 memcpy(tag_uuid.data(), tag_data.uuid.data(), sizeof(MifareUUID));
254 result = WriteMifareData(tag_uuid, write_request);
255 }
256 if (result == DriverResult::Success) {
257 MCUCommandResponse output{};
258 result = SendStopPollingRequest(output);
259 }
260 if (result == DriverResult::Success) {
261 result = WaitUntilNfcIs(NFCStatus::Ready);
262 }
263 if (result == DriverResult::Success) {
264 MCUCommandResponse output{};
265 result = SendStartPollingRequest(output, true);
266 }
267 if (result == DriverResult::Success) {
268 result = WaitUntilNfcIs(NFCStatus::WriteReady);
269 }
270 return result;
271}
272
273bool NfcProtocol::HasAmiibo() {
274 if (update_counter++ < AMIIBO_UPDATE_DELAY) {
275 return true;
276 }
277 update_counter = 0;
278
279 ScopedSetBlocking sb(this);
280 DriverResult result{DriverResult::Success};
281 TagFoundData tag_data{};
282
283 if (result == DriverResult::Success) {
284 result = IsTagInRange(tag_data, 7);
285 }
286
287 return result == DriverResult::Success;
288}
289
290DriverResult NfcProtocol::WaitUntilNfcIs(NFCStatus status) {
291 constexpr std::size_t timeout_limit = 10;
292 MCUCommandResponse output{};
293 std::size_t tries = 0;
294
295 do {
296 auto result = SendNextPackageRequest(output, {});
297
298 if (result != DriverResult::Success) {
299 return result;
300 }
301 if (tries++ > timeout_limit) {
302 return DriverResult::Timeout;
303 }
304 } while (output.mcu_report != MCUReport::NFCState ||
305 (output.mcu_data[1] << 8) + output.mcu_data[0] != 0x0500 ||
306 output.mcu_data[5] != 0x31 || output.mcu_data[6] != static_cast<u8>(status));
307
308 return DriverResult::Success;
309}
310
311DriverResult NfcProtocol::IsTagInRange(TagFoundData& data, std::size_t timeout_limit) {
312 MCUCommandResponse output{};
313 std::size_t tries = 0;
314
315 do {
316 const auto result = SendNextPackageRequest(output, {});
317 if (result != DriverResult::Success) {
318 return result;
319 }
320 if (tries++ > timeout_limit) {
321 return DriverResult::Timeout;
322 }
323 } while (output.mcu_report != MCUReport::NFCState ||
324 (output.mcu_data[1] << 8) + output.mcu_data[0] != 0x0500 ||
325 (output.mcu_data[6] != 0x09 && output.mcu_data[6] != 0x04));
326
327 data.type = output.mcu_data[12];
328 data.uuid_size = std::min(output.mcu_data[14], static_cast<u8>(sizeof(TagUUID)));
329 memcpy(data.uuid.data(), output.mcu_data.data() + 15, data.uuid.size());
330
331 return DriverResult::Success;
332}
333
334DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) {
335 constexpr std::size_t timeout_limit = 60;
336 MCUCommandResponse output{};
337 std::size_t tries = 0;
338
339 u8 package_index = 0;
340 std::size_t ntag_buffer_pos = 0;
341 auto result = SendReadAmiiboRequest(output, NFCPages::Block135);
342
343 if (result != DriverResult::Success) {
344 return result;
345 }
346
347 // Read Tag data
348 while (tries++ < timeout_limit) {
349 result = SendNextPackageRequest(output, package_index);
350 const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
351
352 if (result != DriverResult::Success) {
353 return result;
354 }
355
356 if ((output.mcu_report == MCUReport::NFCReadData ||
357 output.mcu_report == MCUReport::NFCState) &&
358 nfc_status == NFCStatus::TagLost) {
359 return DriverResult::ErrorReadingData;
360 }
361
362 if (output.mcu_report == MCUReport::NFCReadData && output.mcu_data[1] == 0x07) {
363 std::size_t payload_size = (output.mcu_data[4] << 8 | output.mcu_data[5]) & 0x7FF;
364 if (output.mcu_data[2] == 0x01) {
365 memcpy(ntag_data.data() + ntag_buffer_pos, output.mcu_data.data() + 66,
366 payload_size - 60);
367 ntag_buffer_pos += payload_size - 60;
368 } else {
369 memcpy(ntag_data.data() + ntag_buffer_pos, output.mcu_data.data() + 6,
370 payload_size);
371 }
372 package_index++;
373 continue;
374 }
375
376 if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
377 LOG_INFO(Input, "Finished reading amiibo");
378 return DriverResult::Success;
379 }
380 }
381
382 return DriverResult::Timeout;
383}
384
385DriverResult NfcProtocol::WriteAmiiboData(const TagUUID& tag_uuid, std::span<const u8> data) {
386 constexpr std::size_t timeout_limit = 60;
387 const auto nfc_data = MakeAmiiboWritePackage(tag_uuid, data);
388 const std::vector<u8> nfc_buffer_data = SerializeWritePackage(nfc_data);
389 std::span<const u8> buffer(nfc_buffer_data);
390 MCUCommandResponse output{};
391 u8 block_id = 1;
392 u8 package_index = 0;
393 std::size_t tries = 0;
394 std::size_t current_position = 0;
395
396 LOG_INFO(Input, "Writing amiibo data");
397
398 auto result = SendWriteAmiiboRequest(output, tag_uuid);
399
400 if (result != DriverResult::Success) {
401 return result;
402 }
403
404 // Read Tag data but ignore the actual sent data
405 while (tries++ < timeout_limit) {
406 result = SendNextPackageRequest(output, package_index);
407 const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
408
409 if (result != DriverResult::Success) {
410 return result;
411 }
412
413 if ((output.mcu_report == MCUReport::NFCReadData ||
414 output.mcu_report == MCUReport::NFCState) &&
415 nfc_status == NFCStatus::TagLost) {
416 return DriverResult::ErrorReadingData;
417 }
418
419 if (output.mcu_report == MCUReport::NFCReadData && output.mcu_data[1] == 0x07) {
420 package_index++;
421 continue;
422 }
423
424 if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
425 LOG_INFO(Input, "Finished reading amiibo");
426 break;
427 }
428 }
429
430 // Send Data. Nfc buffer size is 31, Send the data in smaller packages
431 while (current_position < buffer.size() && tries++ < timeout_limit) {
432 const std::size_t next_position =
433 std::min(current_position + sizeof(NFCRequestState::raw_data), buffer.size());
434 const std::size_t block_size = next_position - current_position;
435 const bool is_last_packet = block_size < sizeof(NFCRequestState::raw_data);
436
437 SendWriteDataAmiiboRequest(output, block_id, is_last_packet,
438 buffer.subspan(current_position, block_size));
439
440 const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
441
442 if ((output.mcu_report == MCUReport::NFCReadData ||
443 output.mcu_report == MCUReport::NFCState) &&
444 nfc_status == NFCStatus::TagLost) {
445 return DriverResult::ErrorReadingData;
446 }
447
448 // Increase position when data is confirmed by the joycon
449 if (output.mcu_report == MCUReport::NFCState &&
450 (output.mcu_data[1] << 8) + output.mcu_data[0] == 0x0500 &&
451 output.mcu_data[3] == block_id) {
452 block_id++;
453 current_position = next_position;
454 }
455 }
456
457 return result;
458}
459
460DriverResult NfcProtocol::GetMifareData(const MifareUUID& tag_uuid,
461 std::span<const MifareReadChunk> read_request,
462 std::span<MifareReadData> out_data) {
463 constexpr std::size_t timeout_limit = 60;
464 const auto nfc_data = MakeMifareReadPackage(tag_uuid, read_request);
465 const std::vector<u8> nfc_buffer_data = SerializeMifareReadPackage(nfc_data);
466 std::span<const u8> buffer(nfc_buffer_data);
467 DriverResult result = DriverResult::Success;
468 MCUCommandResponse output{};
469 u8 block_id = 1;
470 u8 package_index = 0;
471 std::size_t tries = 0;
472 std::size_t current_position = 0;
473
474 LOG_INFO(Input, "Reading Mifare data");
475
476 // Send data request. Nfc buffer size is 31, Send the data in smaller packages
477 while (current_position < buffer.size() && tries++ < timeout_limit) {
478 const std::size_t next_position =
479 std::min(current_position + sizeof(NFCRequestState::raw_data), buffer.size());
480 const std::size_t block_size = next_position - current_position;
481 const bool is_last_packet = block_size < sizeof(NFCRequestState::raw_data);
482
483 SendReadDataMifareRequest(output, block_id, is_last_packet,
484 buffer.subspan(current_position, block_size));
485
486 const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
487
488 if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
489 return DriverResult::ErrorReadingData;
490 }
491
492 // Increase position when data is confirmed by the joycon
493 if (output.mcu_report == MCUReport::NFCState &&
494 (output.mcu_data[1] << 8) + output.mcu_data[0] == 0x0500 &&
495 output.mcu_data[3] == block_id) {
496 block_id++;
497 current_position = next_position;
498 }
499 }
500
501 if (result != DriverResult::Success) {
502 return result;
503 }
504
505 // Wait for reply and save the output data
506 while (tries++ < timeout_limit) {
507 result = SendNextPackageRequest(output, package_index);
508 const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
509
510 if (result != DriverResult::Success) {
511 return result;
512 }
513
514 if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
515 return DriverResult::ErrorReadingData;
516 }
517
518 if (output.mcu_report == MCUReport::NFCState && output.mcu_data[1] == 0x10) {
519 constexpr std::size_t DATA_LENGHT = 0x10 + 1;
520 constexpr std::size_t DATA_START = 11;
521 const u8 number_of_elements = output.mcu_data[10];
522 for (std::size_t i = 0; i < number_of_elements; i++) {
523 out_data[i].sector = output.mcu_data[DATA_START + (i * DATA_LENGHT)];
524 memcpy(out_data[i].data.data(),
525 output.mcu_data.data() + DATA_START + 1 + (i * DATA_LENGHT),
526 sizeof(MifareReadData::data));
527 }
528 package_index++;
529 continue;
530 }
531
532 if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::MifareDone) {
533 LOG_INFO(Input, "Finished reading mifare");
534 break;
535 }
536 }
537
538 return result;
539}
540
541DriverResult NfcProtocol::WriteMifareData(const MifareUUID& tag_uuid,
542 std::span<const MifareWriteChunk> write_request) {
543 constexpr std::size_t timeout_limit = 60;
544 const auto nfc_data = MakeMifareWritePackage(tag_uuid, write_request);
545 const std::vector<u8> nfc_buffer_data = SerializeMifareWritePackage(nfc_data);
546 std::span<const u8> buffer(nfc_buffer_data);
547 DriverResult result = DriverResult::Success;
548 MCUCommandResponse output{};
549 u8 block_id = 1;
550 u8 package_index = 0;
551 std::size_t tries = 0;
552 std::size_t current_position = 0;
553
554 LOG_INFO(Input, "Writing Mifare data");
555
556 // Send data request. Nfc buffer size is 31, Send the data in smaller packages
557 while (current_position < buffer.size() && tries++ < timeout_limit) {
558 const std::size_t next_position =
559 std::min(current_position + sizeof(NFCRequestState::raw_data), buffer.size());
560 const std::size_t block_size = next_position - current_position;
561 const bool is_last_packet = block_size < sizeof(NFCRequestState::raw_data);
562
563 SendReadDataMifareRequest(output, block_id, is_last_packet,
564 buffer.subspan(current_position, block_size));
565
566 const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
567
568 if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
569 return DriverResult::ErrorReadingData;
570 }
571
572 // Increase position when data is confirmed by the joycon
573 if (output.mcu_report == MCUReport::NFCState &&
574 (output.mcu_data[1] << 8) + output.mcu_data[0] == 0x0500 &&
575 output.mcu_data[3] == block_id) {
576 block_id++;
577 current_position = next_position;
578 }
579 }
580
581 if (result != DriverResult::Success) {
582 return result;
583 }
584
585 // Wait for reply and ignore the output data
586 while (tries++ < timeout_limit) {
587 result = SendNextPackageRequest(output, package_index);
588 const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
589
590 if (result != DriverResult::Success) {
591 return result;
592 }
593
594 if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
595 return DriverResult::ErrorReadingData;
596 }
597
598 if (output.mcu_report == MCUReport::NFCState && output.mcu_data[1] == 0x10) {
599 package_index++;
600 continue;
601 }
602
603 if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::MifareDone) {
604 LOG_INFO(Input, "Finished writing mifare");
605 break;
606 }
607 }
608
609 return result;
610}
611
612DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output,
613 bool is_second_attempt) {
614 NFCRequestState request{
615 .command_argument = NFCCommand::StartPolling,
616 .block_id = {},
617 .packet_id = {},
618 .packet_flag = MCUPacketFlag::LastCommandPacket,
619 .data_length = sizeof(NFCPollingCommandData),
620 .nfc_polling =
621 {
622 .enable_mifare = 0x00,
623 .unknown_1 = static_cast<u8>(is_second_attempt ? 0xe8 : 0x00),
624 .unknown_2 = static_cast<u8>(is_second_attempt ? 0x03 : 0x00),
625 .unknown_3 = 0x2c,
626 .unknown_4 = 0x01,
627 },
628 .crc = {},
629 };
630
631 std::array<u8, sizeof(NFCRequestState)> request_data{};
632 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
633 request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
634 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
635 output);
636}
637
638DriverResult NfcProtocol::SendStopPollingRequest(MCUCommandResponse& output) {
639 NFCRequestState request{
640 .command_argument = NFCCommand::StopPolling,
641 .block_id = {},
642 .packet_id = {},
643 .packet_flag = MCUPacketFlag::LastCommandPacket,
644 .data_length = {},
645 .raw_data = {},
646 .crc = {},
647 };
648
649 std::array<u8, sizeof(NFCRequestState)> request_data{};
650 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
651 request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
652 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
653 output);
654}
655
656DriverResult NfcProtocol::SendNextPackageRequest(MCUCommandResponse& output, u8 packet_id) {
657 NFCRequestState request{
658 .command_argument = NFCCommand::StartWaitingRecieve,
659 .block_id = {},
660 .packet_id = packet_id,
661 .packet_flag = MCUPacketFlag::LastCommandPacket,
662 .data_length = {},
663 .raw_data = {},
664 .crc = {},
665 };
666
667 std::vector<u8> request_data(sizeof(NFCRequestState));
668 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
669 request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
670 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
671 output);
672}
673
674DriverResult NfcProtocol::SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages) {
675 NFCRequestState request{
676 .command_argument = NFCCommand::ReadNtag,
677 .block_id = {},
678 .packet_id = {},
679 .packet_flag = MCUPacketFlag::LastCommandPacket,
680 .data_length = sizeof(NFCReadCommandData),
681 .nfc_read =
682 {
683 .unknown = 0xd0,
684 .uuid_length = sizeof(NFCReadCommandData::uid),
685 .uid = {},
686 .tag_type = NFCTagType::Ntag215,
687 .read_block = GetReadBlockCommand(ntag_pages),
688 },
689 .crc = {},
690 };
691
692 std::array<u8, sizeof(NFCRequestState)> request_data{};
693 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
694 request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
695 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
696 output);
697}
698
699DriverResult NfcProtocol::SendWriteAmiiboRequest(MCUCommandResponse& output,
700 const TagUUID& tag_uuid) {
701 NFCRequestState request{
702 .command_argument = NFCCommand::ReadNtag,
703 .block_id = {},
704 .packet_id = {},
705 .packet_flag = MCUPacketFlag::LastCommandPacket,
706 .data_length = sizeof(NFCReadCommandData),
707 .nfc_read =
708 {
709 .unknown = 0xd0,
710 .uuid_length = sizeof(NFCReadCommandData::uid),
711 .uid = tag_uuid,
712 .tag_type = NFCTagType::Ntag215,
713 .read_block = GetReadBlockCommand(NFCPages::Block3),
714 },
715 .crc = {},
716 };
717
718 std::array<u8, sizeof(NFCRequestState)> request_data{};
719 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
720 request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
721 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
722 output);
723}
724
725DriverResult NfcProtocol::SendWriteDataAmiiboRequest(MCUCommandResponse& output, u8 block_id,
726 bool is_last_packet,
727 std::span<const u8> data) {
728 const auto data_size = std::min(data.size(), sizeof(NFCRequestState::raw_data));
729 NFCRequestState request{
730 .command_argument = NFCCommand::WriteNtag,
731 .block_id = block_id,
732 .packet_id = {},
733 .packet_flag =
734 is_last_packet ? MCUPacketFlag::LastCommandPacket : MCUPacketFlag::MorePacketsRemaining,
735 .data_length = static_cast<u8>(data_size),
736 .raw_data = {},
737 .crc = {},
738 };
739 memcpy(request.raw_data.data(), data.data(), data_size);
740
741 std::array<u8, sizeof(NFCRequestState)> request_data{};
742 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
743 request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
744 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
745 output);
746}
747
748DriverResult NfcProtocol::SendReadDataMifareRequest(MCUCommandResponse& output, u8 block_id,
749 bool is_last_packet, std::span<const u8> data) {
750 const auto data_size = std::min(data.size(), sizeof(NFCRequestState::raw_data));
751 NFCRequestState request{
752 .command_argument = NFCCommand::Mifare,
753 .block_id = block_id,
754 .packet_id = {},
755 .packet_flag =
756 is_last_packet ? MCUPacketFlag::LastCommandPacket : MCUPacketFlag::MorePacketsRemaining,
757 .data_length = static_cast<u8>(data_size),
758 .raw_data = {},
759 .crc = {},
760 };
761 memcpy(request.raw_data.data(), data.data(), data_size);
762
763 std::array<u8, sizeof(NFCRequestState)> request_data{};
764 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
765 request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
766 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
767 output);
768}
769
770std::vector<u8> NfcProtocol::SerializeWritePackage(const NFCWritePackage& package) const {
771 const std::size_t header_size =
772 sizeof(NFCWriteCommandData) + sizeof(NFCWritePackage::number_of_chunks);
773 std::vector<u8> serialized_data(header_size);
774 std::size_t start_index = 0;
775
776 memcpy(serialized_data.data(), &package, header_size);
777 start_index += header_size;
778
779 for (const auto& data_chunk : package.data_chunks) {
780 const std::size_t chunk_size =
781 sizeof(NFCDataChunk::nfc_page) + sizeof(NFCDataChunk::data_size) + data_chunk.data_size;
782
783 serialized_data.resize(start_index + chunk_size);
784 memcpy(serialized_data.data() + start_index, &data_chunk, chunk_size);
785 start_index += chunk_size;
786 }
787
788 return serialized_data;
789}
790
791std::vector<u8> NfcProtocol::SerializeMifareReadPackage(const MifareReadPackage& package) const {
792 const std::size_t header_size = sizeof(MifareCommandData);
793 std::vector<u8> serialized_data(header_size);
794 std::size_t start_index = 0;
795
796 memcpy(serialized_data.data(), &package, header_size);
797 start_index += header_size;
798
799 for (const auto& data_chunk : package.data_chunks) {
800 const std::size_t chunk_size = sizeof(MifareReadChunk);
801 if (data_chunk.command == MifareCmd::None) {
802 continue;
803 }
804 serialized_data.resize(start_index + chunk_size);
805 memcpy(serialized_data.data() + start_index, &data_chunk, chunk_size);
806 start_index += chunk_size;
807 }
808
809 return serialized_data;
810}
811
812std::vector<u8> NfcProtocol::SerializeMifareWritePackage(const MifareWritePackage& package) const {
813 const std::size_t header_size = sizeof(MifareCommandData);
814 std::vector<u8> serialized_data(header_size);
815 std::size_t start_index = 0;
816
817 memcpy(serialized_data.data(), &package, header_size);
818 start_index += header_size;
819
820 for (const auto& data_chunk : package.data_chunks) {
821 const std::size_t chunk_size = sizeof(MifareWriteChunk);
822 if (data_chunk.command == MifareCmd::None) {
823 continue;
824 }
825 serialized_data.resize(start_index + chunk_size);
826 memcpy(serialized_data.data() + start_index, &data_chunk, chunk_size);
827 start_index += chunk_size;
828 }
829
830 return serialized_data;
831}
832
833NFCWritePackage NfcProtocol::MakeAmiiboWritePackage(const TagUUID& tag_uuid,
834 std::span<const u8> data) const {
835 return {
836 .command_data{
837 .unknown = 0xd0,
838 .uuid_length = sizeof(NFCReadCommandData::uid),
839 .uid = tag_uuid,
840 .tag_type = NFCTagType::Ntag215,
841 .unknown2 = 0x00,
842 .unknown3 = 0x01,
843 .unknown4 = 0x04,
844 .unknown5 = 0xff,
845 .unknown6 = 0xff,
846 .unknown7 = 0xff,
847 .unknown8 = 0xff,
848 .magic = data[16],
849 .write_count = static_cast<u16>((data[17] << 8) + data[18]),
850 .amiibo_version = data[19],
851 },
852 .number_of_chunks = 3,
853 .data_chunks =
854 {
855 MakeAmiiboChunk(0x05, 0x20, data),
856 MakeAmiiboChunk(0x20, 0xf0, data),
857 MakeAmiiboChunk(0x5c, 0x98, data),
858 },
859 };
860}
861
862MifareReadPackage NfcProtocol::MakeMifareReadPackage(
863 const MifareUUID& tag_uuid, std::span<const MifareReadChunk> read_request) const {
864 MifareReadPackage package{
865 .command_data{
866 .unknown1 = 0xd0,
867 .unknown2 = 0x07,
868 .number_of_short_bytes = static_cast<u8>(
869 ((read_request.size() * sizeof(MifareReadChunk)) + sizeof(MifareUUID)) / 2),
870 .uid = tag_uuid,
871 },
872 .data_chunks = {},
873 };
874
875 for (std::size_t i = 0; i < read_request.size() && i < package.data_chunks.size(); ++i) {
876 package.data_chunks[i] = read_request[i];
877 }
878
879 return package;
880}
881
882MifareWritePackage NfcProtocol::MakeMifareWritePackage(
883 const MifareUUID& tag_uuid, std::span<const MifareWriteChunk> read_request) const {
884 MifareWritePackage package{
885 .command_data{
886 .unknown1 = 0xd0,
887 .unknown2 = 0x07,
888 .number_of_short_bytes = static_cast<u8>(
889 ((read_request.size() * sizeof(MifareReadChunk)) + sizeof(MifareUUID) + 2) / 2),
890 .uid = tag_uuid,
891 },
892 .data_chunks = {},
893 };
894
895 for (std::size_t i = 0; i < read_request.size() && i < package.data_chunks.size(); ++i) {
896 package.data_chunks[i] = read_request[i];
897 }
898
899 return package;
900}
901
902NFCDataChunk NfcProtocol::MakeAmiiboChunk(u8 page, u8 size, std::span<const u8> data) const {
903 constexpr u8 NFC_PAGE_SIZE = 4;
904
905 if (static_cast<std::size_t>(page * NFC_PAGE_SIZE) + size >= data.size()) {
906 return {};
907 }
908
909 NFCDataChunk chunk{
910 .nfc_page = page,
911 .data_size = size,
912 .data = {},
913 };
914 std::memcpy(chunk.data.data(), data.data() + (page * NFC_PAGE_SIZE), size);
915 return chunk;
916}
917
918NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const {
919 switch (pages) {
920 case NFCPages::Block0:
921 return {
922 .block_count = 1,
923 };
924 case NFCPages::Block3:
925 return {
926 .block_count = 1,
927 .blocks =
928 {
929 NFCReadBlock{0x03, 0x03},
930 },
931 };
932 case NFCPages::Block45:
933 return {
934 .block_count = 1,
935 .blocks =
936 {
937 NFCReadBlock{0x00, 0x2C},
938 },
939 };
940 case NFCPages::Block135:
941 return {
942 .block_count = 3,
943 .blocks =
944 {
945 NFCReadBlock{0x00, 0x3b},
946 {0x3c, 0x77},
947 {0x78, 0x86},
948 },
949 };
950 case NFCPages::Block231:
951 return {
952 .block_count = 4,
953 .blocks =
954 {
955 NFCReadBlock{0x00, 0x3b},
956 {0x3c, 0x77},
957 {0x78, 0x83},
958 {0xb4, 0xe6},
959 },
960 };
961 default:
962 return {};
963 };
964}
965
966TagUUID NfcProtocol::GetTagUUID(std::span<const u8> data) const {
967 if (data.size() < 10) {
968 return {};
969 }
970
971 // crc byte 3 is omitted in this operation
972 return {
973 data[0], data[1], data[2], data[4], data[5], data[6], data[7],
974 };
975}
976
977bool NfcProtocol::IsEnabled() const {
978 return is_enabled;
979}
980
981bool NfcProtocol::IsPolling() const {
982 return is_polling;
983}
984
985} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h
new file mode 100644
index 000000000..0be95e40e
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.h
@@ -0,0 +1,114 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class NfcProtocol final : private JoyconCommonProtocol {
19public:
20 explicit NfcProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableNfc();
23
24 DriverResult DisableNfc();
25
26 DriverResult StartNFCPollingMode();
27
28 DriverResult StopNFCPollingMode();
29
30 DriverResult GetTagInfo(Joycon::TagInfo& tag_info);
31
32 DriverResult ReadAmiibo(std::vector<u8>& data);
33
34 DriverResult WriteAmiibo(std::span<const u8> data);
35
36 DriverResult ReadMifare(std::span<const MifareReadChunk> read_request,
37 std::span<MifareReadData> out_data);
38
39 DriverResult WriteMifare(std::span<const MifareWriteChunk> write_request);
40
41 bool HasAmiibo();
42
43 bool IsEnabled() const;
44
45 bool IsPolling() const;
46
47private:
48 // Number of times the function will be delayed until it outputs valid data
49 static constexpr std::size_t AMIIBO_UPDATE_DELAY = 15;
50
51 struct TagFoundData {
52 u8 type;
53 u8 uuid_size;
54 TagUUID uuid;
55 };
56
57 DriverResult WaitUntilNfcIs(NFCStatus status);
58
59 DriverResult IsTagInRange(TagFoundData& data, std::size_t timeout_limit = 1);
60
61 DriverResult GetAmiiboData(std::vector<u8>& data);
62
63 DriverResult WriteAmiiboData(const TagUUID& tag_uuid, std::span<const u8> data);
64
65 DriverResult GetMifareData(const MifareUUID& tag_uuid,
66 std::span<const MifareReadChunk> read_request,
67 std::span<MifareReadData> out_data);
68
69 DriverResult WriteMifareData(const MifareUUID& tag_uuid,
70 std::span<const MifareWriteChunk> write_request);
71
72 DriverResult SendStartPollingRequest(MCUCommandResponse& output,
73 bool is_second_attempt = false);
74
75 DriverResult SendStopPollingRequest(MCUCommandResponse& output);
76
77 DriverResult SendNextPackageRequest(MCUCommandResponse& output, u8 packet_id);
78
79 DriverResult SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages);
80
81 DriverResult SendWriteAmiiboRequest(MCUCommandResponse& output, const TagUUID& tag_uuid);
82
83 DriverResult SendWriteDataAmiiboRequest(MCUCommandResponse& output, u8 block_id,
84 bool is_last_packet, std::span<const u8> data);
85
86 DriverResult SendReadDataMifareRequest(MCUCommandResponse& output, u8 block_id,
87 bool is_last_packet, std::span<const u8> data);
88
89 std::vector<u8> SerializeWritePackage(const NFCWritePackage& package) const;
90
91 std::vector<u8> SerializeMifareReadPackage(const MifareReadPackage& package) const;
92
93 std::vector<u8> SerializeMifareWritePackage(const MifareWritePackage& package) const;
94
95 NFCWritePackage MakeAmiiboWritePackage(const TagUUID& tag_uuid, std::span<const u8> data) const;
96
97 NFCDataChunk MakeAmiiboChunk(u8 page, u8 size, std::span<const u8> data) const;
98
99 MifareReadPackage MakeMifareReadPackage(const MifareUUID& tag_uuid,
100 std::span<const MifareReadChunk> read_request) const;
101
102 MifareWritePackage MakeMifareWritePackage(const MifareUUID& tag_uuid,
103 std::span<const MifareWriteChunk> read_request) const;
104
105 NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const;
106
107 TagUUID GetTagUUID(std::span<const u8> data) const;
108
109 bool is_enabled{};
110 bool is_polling{};
111 std::size_t update_counter{};
112};
113
114} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
new file mode 100644
index 000000000..1aab9e12a
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -0,0 +1,374 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/poller.h"
6
7namespace InputCommon::Joycon {
8
9JoyconPoller::JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
10 JoyStickCalibration right_stick_calibration_,
11 MotionCalibration motion_calibration_)
12 : device_type{device_type_}, left_stick_calibration{left_stick_calibration_},
13 right_stick_calibration{right_stick_calibration_}, motion_calibration{motion_calibration_} {}
14
15void JoyconPoller::SetCallbacks(const JoyconCallbacks& callbacks_) {
16 callbacks = std::move(callbacks_);
17}
18
19void JoyconPoller::ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
20 const RingStatus& ring_status) {
21 InputReportActive data{};
22 memcpy(&data, buffer.data(), sizeof(InputReportActive));
23
24 switch (device_type) {
25 case ControllerType::Left:
26 UpdateActiveLeftPadInput(data, motion_status);
27 break;
28 case ControllerType::Right:
29 UpdateActiveRightPadInput(data, motion_status);
30 break;
31 case ControllerType::Pro:
32 UpdateActiveProPadInput(data, motion_status);
33 break;
34 default:
35 break;
36 }
37
38 if (ring_status.is_enabled) {
39 UpdateRing(data.ring_input, ring_status);
40 }
41
42 callbacks.on_battery_data(data.battery_status);
43}
44
45void JoyconPoller::ReadPassiveMode(std::span<u8> buffer) {
46 InputReportPassive data{};
47 memcpy(&data, buffer.data(), sizeof(InputReportPassive));
48
49 switch (device_type) {
50 case ControllerType::Left:
51 UpdatePassiveLeftPadInput(data);
52 break;
53 case ControllerType::Right:
54 UpdatePassiveRightPadInput(data);
55 break;
56 case ControllerType::Pro:
57 UpdatePassiveProPadInput(data);
58 break;
59 default:
60 break;
61 }
62}
63
64void JoyconPoller::ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status) {
65 // This mode is compatible with the active mode
66 ReadActiveMode(buffer, motion_status, {});
67}
68
69void JoyconPoller::UpdateColor(const Color& color) {
70 callbacks.on_color_data(color);
71}
72
73void JoyconPoller::UpdateAmiibo(const Joycon::TagInfo& tag_info) {
74 callbacks.on_amiibo_data(tag_info);
75}
76
77void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) {
78 callbacks.on_camera_data(camera_data, format);
79}
80
81void JoyconPoller::UpdateRing(s16 value, const RingStatus& ring_status) {
82 float normalized_value = static_cast<float>(value - ring_status.default_value);
83 if (normalized_value > 0) {
84 normalized_value = normalized_value /
85 static_cast<float>(ring_status.max_value - ring_status.default_value);
86 }
87 if (normalized_value < 0) {
88 normalized_value = normalized_value /
89 static_cast<float>(ring_status.default_value - ring_status.min_value);
90 }
91 callbacks.on_ring_data(normalized_value);
92}
93
94void JoyconPoller::UpdateActiveLeftPadInput(const InputReportActive& input,
95 const MotionStatus& motion_status) {
96 static constexpr std::array<Joycon::PadButton, 11> left_buttons{
97 Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
98 Joycon::PadButton::Left, Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR,
99 Joycon::PadButton::L, Joycon::PadButton::ZL, Joycon::PadButton::Minus,
100 Joycon::PadButton::Capture, Joycon::PadButton::StickL,
101 };
102
103 const u32 raw_button =
104 static_cast<u32>(input.button_input[2] | ((input.button_input[1] & 0b00101001) << 16));
105 for (std::size_t i = 0; i < left_buttons.size(); ++i) {
106 const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0;
107 const int button = static_cast<int>(left_buttons[i]);
108 callbacks.on_button_data(button, button_status);
109 }
110
111 const u16 raw_left_axis_x =
112 static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
113 const u16 raw_left_axis_y =
114 static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
115 const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
116 const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
117 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
118 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
119
120 if (motion_status.is_enabled) {
121 auto left_motion = GetMotionInput(input, motion_status);
122 // Rotate motion axis to the correct direction
123 left_motion.accel_y = -left_motion.accel_y;
124 left_motion.accel_z = -left_motion.accel_z;
125 left_motion.gyro_x = -left_motion.gyro_x;
126 callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), left_motion);
127 }
128}
129
130void JoyconPoller::UpdateActiveRightPadInput(const InputReportActive& input,
131 const MotionStatus& motion_status) {
132 static constexpr std::array<Joycon::PadButton, 11> right_buttons{
133 Joycon::PadButton::Y, Joycon::PadButton::X, Joycon::PadButton::B,
134 Joycon::PadButton::A, Joycon::PadButton::RightSL, Joycon::PadButton::RightSR,
135 Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
136 Joycon::PadButton::Home, Joycon::PadButton::StickR,
137 };
138
139 const u32 raw_button =
140 static_cast<u32>((input.button_input[0] << 8) | (input.button_input[1] << 16));
141 for (std::size_t i = 0; i < right_buttons.size(); ++i) {
142 const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0;
143 const int button = static_cast<int>(right_buttons[i]);
144 callbacks.on_button_data(button, button_status);
145 }
146
147 const u16 raw_right_axis_x =
148 static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
149 const u16 raw_right_axis_y =
150 static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
151 const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
152 const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
153 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
154 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
155
156 if (motion_status.is_enabled) {
157 auto right_motion = GetMotionInput(input, motion_status);
158 // Rotate motion axis to the correct direction
159 right_motion.accel_x = -right_motion.accel_x;
160 right_motion.accel_y = -right_motion.accel_y;
161 right_motion.gyro_z = -right_motion.gyro_z;
162 callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), right_motion);
163 }
164}
165
166void JoyconPoller::UpdateActiveProPadInput(const InputReportActive& input,
167 const MotionStatus& motion_status) {
168 static constexpr std::array<Joycon::PadButton, 18> pro_buttons{
169 Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
170 Joycon::PadButton::Left, Joycon::PadButton::L, Joycon::PadButton::ZL,
171 Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y,
172 Joycon::PadButton::X, Joycon::PadButton::B, Joycon::PadButton::A,
173 Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
174 Joycon::PadButton::Home, Joycon::PadButton::StickL, Joycon::PadButton::StickR,
175 };
176
177 const u32 raw_button = static_cast<u32>(input.button_input[2] | (input.button_input[0] << 8) |
178 (input.button_input[1] << 16));
179 for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
180 const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0;
181 const int button = static_cast<int>(pro_buttons[i]);
182 callbacks.on_button_data(button, button_status);
183 }
184
185 const u16 raw_left_axis_x =
186 static_cast<u16>(input.left_stick_state[0] | ((input.left_stick_state[1] & 0xf) << 8));
187 const u16 raw_left_axis_y =
188 static_cast<u16>((input.left_stick_state[1] >> 4) | (input.left_stick_state[2] << 4));
189 const u16 raw_right_axis_x =
190 static_cast<u16>(input.right_stick_state[0] | ((input.right_stick_state[1] & 0xf) << 8));
191 const u16 raw_right_axis_y =
192 static_cast<u16>((input.right_stick_state[1] >> 4) | (input.right_stick_state[2] << 4));
193
194 const f32 left_axis_x = GetAxisValue(raw_left_axis_x, left_stick_calibration.x);
195 const f32 left_axis_y = GetAxisValue(raw_left_axis_y, left_stick_calibration.y);
196 const f32 right_axis_x = GetAxisValue(raw_right_axis_x, right_stick_calibration.x);
197 const f32 right_axis_y = GetAxisValue(raw_right_axis_y, right_stick_calibration.y);
198 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
199 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
200 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
201 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
202
203 if (motion_status.is_enabled) {
204 auto pro_motion = GetMotionInput(input, motion_status);
205 pro_motion.gyro_x = -pro_motion.gyro_x;
206 pro_motion.accel_y = -pro_motion.accel_y;
207 pro_motion.accel_z = -pro_motion.accel_z;
208 callbacks.on_motion_data(static_cast<int>(PadMotion::LeftMotion), pro_motion);
209 callbacks.on_motion_data(static_cast<int>(PadMotion::RightMotion), pro_motion);
210 }
211}
212
213void JoyconPoller::UpdatePassiveLeftPadInput(const InputReportPassive& input) {
214 static constexpr std::array<PassivePadButton, 11> left_buttons{
215 PassivePadButton::Down_A, PassivePadButton::Right_X, PassivePadButton::Left_B,
216 PassivePadButton::Up_Y, PassivePadButton::SL, PassivePadButton::SR,
217 PassivePadButton::L_R, PassivePadButton::ZL_ZR, PassivePadButton::Minus,
218 PassivePadButton::Capture, PassivePadButton::StickL,
219 };
220
221 for (auto left_button : left_buttons) {
222 const bool button_status = (input.button_input & static_cast<u32>(left_button)) != 0;
223 const int button = static_cast<int>(left_button);
224 callbacks.on_button_data(button, button_status);
225 }
226
227 const auto [left_axis_x, left_axis_y] =
228 GetPassiveAxisValue(static_cast<PassivePadStick>(input.stick_state));
229 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
230 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
231}
232
233void JoyconPoller::UpdatePassiveRightPadInput(const InputReportPassive& input) {
234 static constexpr std::array<PassivePadButton, 11> right_buttons{
235 PassivePadButton::Down_A, PassivePadButton::Right_X, PassivePadButton::Left_B,
236 PassivePadButton::Up_Y, PassivePadButton::SL, PassivePadButton::SR,
237 PassivePadButton::L_R, PassivePadButton::ZL_ZR, PassivePadButton::Plus,
238 PassivePadButton::Home, PassivePadButton::StickR,
239 };
240
241 for (auto right_button : right_buttons) {
242 const bool button_status = (input.button_input & static_cast<u32>(right_button)) != 0;
243 const int button = static_cast<int>(right_button);
244 callbacks.on_button_data(button, button_status);
245 }
246
247 const auto [right_axis_x, right_axis_y] =
248 GetPassiveAxisValue(static_cast<PassivePadStick>(input.stick_state));
249 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
250 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
251}
252
253void JoyconPoller::UpdatePassiveProPadInput(const InputReportPassive& input) {
254 static constexpr std::array<PassivePadButton, 14> pro_buttons{
255 PassivePadButton::Down_A, PassivePadButton::Right_X, PassivePadButton::Left_B,
256 PassivePadButton::Up_Y, PassivePadButton::SL, PassivePadButton::SR,
257 PassivePadButton::L_R, PassivePadButton::ZL_ZR, PassivePadButton::Minus,
258 PassivePadButton::Plus, PassivePadButton::Capture, PassivePadButton::Home,
259 PassivePadButton::StickL, PassivePadButton::StickR,
260 };
261
262 for (auto pro_button : pro_buttons) {
263 const bool button_status = (input.button_input & static_cast<u32>(pro_button)) != 0;
264 const int button = static_cast<int>(pro_button);
265 callbacks.on_button_data(button, button_status);
266 }
267
268 const auto [left_axis_x, left_axis_y] =
269 GetPassiveAxisValue(static_cast<PassivePadStick>(input.stick_state & 0xf));
270 const auto [right_axis_x, right_axis_y] =
271 GetPassiveAxisValue(static_cast<PassivePadStick>(input.stick_state >> 4));
272 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickX), left_axis_x);
273 callbacks.on_stick_data(static_cast<int>(PadAxes::LeftStickY), left_axis_y);
274 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickX), right_axis_x);
275 callbacks.on_stick_data(static_cast<int>(PadAxes::RightStickY), right_axis_y);
276}
277
278f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const {
279 const f32 value = static_cast<f32>(raw_value - calibration.center);
280 if (value > 0.0f) {
281 return value / calibration.max;
282 }
283 return value / calibration.min;
284}
285
286std::pair<f32, f32> JoyconPoller::GetPassiveAxisValue(PassivePadStick raw_value) const {
287 switch (raw_value) {
288 case PassivePadStick::Right:
289 return {1.0f, 0.0f};
290 case PassivePadStick::RightDown:
291 return {1.0f, -1.0f};
292 case PassivePadStick::Down:
293 return {0.0f, -1.0f};
294 case PassivePadStick::DownLeft:
295 return {-1.0f, -1.0f};
296 case PassivePadStick::Left:
297 return {-1.0f, 0.0f};
298 case PassivePadStick::LeftUp:
299 return {-1.0f, 1.0f};
300 case PassivePadStick::Up:
301 return {0.0f, 1.0f};
302 case PassivePadStick::UpRight:
303 return {1.0f, 1.0f};
304 case PassivePadStick::Neutral:
305 default:
306 return {0.0f, 0.0f};
307 }
308}
309
310f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
311 AccelerometerSensitivity sensitivity) const {
312 const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
313 switch (sensitivity) {
314 case Joycon::AccelerometerSensitivity::G2:
315 return value / 4.0f;
316 case Joycon::AccelerometerSensitivity::G4:
317 return value / 2.0f;
318 case Joycon::AccelerometerSensitivity::G8:
319 return value;
320 case Joycon::AccelerometerSensitivity::G16:
321 return value * 2.0f;
322 }
323 return value;
324}
325
326f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal,
327 GyroSensitivity sensitivity) const {
328 const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
329 switch (sensitivity) {
330 case Joycon::GyroSensitivity::DPS250:
331 return value / 8.0f;
332 case Joycon::GyroSensitivity::DPS500:
333 return value / 4.0f;
334 case Joycon::GyroSensitivity::DPS1000:
335 return value / 2.0f;
336 case Joycon::GyroSensitivity::DPS2000:
337 return value;
338 }
339 return value;
340}
341
342s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis,
343 const InputReportActive& input) const {
344 return input.motion_input[(sensor * 3) + axis];
345}
346
347MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
348 const MotionStatus& motion_status) const {
349 MotionData motion{};
350 const auto& accel_cal = motion_calibration.accelerometer;
351 const auto& gyro_cal = motion_calibration.gyro;
352 const s16 raw_accel_x = input.motion_input[1];
353 const s16 raw_accel_y = input.motion_input[0];
354 const s16 raw_accel_z = input.motion_input[2];
355 const s16 raw_gyro_x = input.motion_input[4];
356 const s16 raw_gyro_y = input.motion_input[3];
357 const s16 raw_gyro_z = input.motion_input[5];
358
359 motion.delta_timestamp = motion_status.delta_time;
360 motion.accel_x =
361 GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity);
362 motion.accel_y =
363 GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity);
364 motion.accel_z =
365 GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity);
366 motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity);
367 motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity);
368 motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity);
369
370 // TODO(German77): Return all three samples data
371 return motion;
372}
373
374} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h
new file mode 100644
index 000000000..3746abe5d
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -0,0 +1,84 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <functional>
12#include <span>
13
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18// Handles input packages and triggers the corresponding input events
19class JoyconPoller {
20public:
21 JoyconPoller(ControllerType device_type_, JoyStickCalibration left_stick_calibration_,
22 JoyStickCalibration right_stick_calibration_,
23 MotionCalibration motion_calibration_);
24
25 void SetCallbacks(const JoyconCallbacks& callbacks_);
26
27 /// Handles data from passive packages
28 void ReadPassiveMode(std::span<u8> buffer);
29
30 /// Handles data from active packages
31 void ReadActiveMode(std::span<u8> buffer, const MotionStatus& motion_status,
32 const RingStatus& ring_status);
33
34 /// Handles data from nfc or ir packages
35 void ReadNfcIRMode(std::span<u8> buffer, const MotionStatus& motion_status);
36
37 void UpdateColor(const Color& color);
38 void UpdateRing(s16 value, const RingStatus& ring_status);
39 void UpdateAmiibo(const Joycon::TagInfo& tag_info);
40 void UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format);
41
42private:
43 void UpdateActiveLeftPadInput(const InputReportActive& input,
44 const MotionStatus& motion_status);
45 void UpdateActiveRightPadInput(const InputReportActive& input,
46 const MotionStatus& motion_status);
47 void UpdateActiveProPadInput(const InputReportActive& input, const MotionStatus& motion_status);
48
49 void UpdatePassiveLeftPadInput(const InputReportPassive& buffer);
50 void UpdatePassiveRightPadInput(const InputReportPassive& buffer);
51 void UpdatePassiveProPadInput(const InputReportPassive& buffer);
52
53 /// Returns a calibrated joystick axis from raw axis data
54 f32 GetAxisValue(u16 raw_value, JoyStickAxisCalibration calibration) const;
55
56 /// Returns a digital joystick axis from passive axis data
57 std::pair<f32, f32> GetPassiveAxisValue(PassivePadStick raw_value) const;
58
59 /// Returns a calibrated accelerometer axis from raw motion data
60 f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
61 AccelerometerSensitivity sensitivity) const;
62
63 /// Returns a calibrated gyro axis from raw motion data
64 f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal,
65 GyroSensitivity sensitivity) const;
66
67 /// Returns a raw motion value from a buffer
68 s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const;
69
70 /// Returns motion data from a buffer
71 MotionData GetMotionInput(const InputReportActive& input,
72 const MotionStatus& motion_status) const;
73
74 ControllerType device_type{};
75
76 // Device calibration
77 JoyStickCalibration left_stick_calibration{};
78 JoyStickCalibration right_stick_calibration{};
79 MotionCalibration motion_calibration{};
80
81 JoyconCallbacks callbacks{};
82};
83
84} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.cpp b/src/input_common/helpers/joycon_protocol/ringcon.cpp
new file mode 100644
index 000000000..190cef812
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/ringcon.cpp
@@ -0,0 +1,115 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "common/logging/log.h"
5#include "input_common/helpers/joycon_protocol/ringcon.h"
6
7namespace InputCommon::Joycon {
8
9RingConProtocol::RingConProtocol(std::shared_ptr<JoyconHandle> handle)
10 : JoyconCommonProtocol(std::move(handle)) {}
11
12DriverResult RingConProtocol::EnableRingCon() {
13 LOG_DEBUG(Input, "Enable Ringcon");
14 ScopedSetBlocking sb(this);
15 DriverResult result{DriverResult::Success};
16
17 if (result == DriverResult::Success) {
18 result = SetReportMode(ReportMode::STANDARD_FULL_60HZ);
19 }
20 if (result == DriverResult::Success) {
21 result = EnableMCU(true);
22 }
23 if (result == DriverResult::Success) {
24 const MCUConfig config{
25 .command = MCUCommand::ConfigureMCU,
26 .sub_command = MCUSubCommand::SetDeviceMode,
27 .mode = MCUMode::Standby,
28 .crc = {},
29 };
30 result = ConfigureMCU(config);
31 }
32
33 return result;
34}
35
36DriverResult RingConProtocol::DisableRingCon() {
37 LOG_DEBUG(Input, "Disable RingCon");
38 ScopedSetBlocking sb(this);
39 DriverResult result{DriverResult::Success};
40
41 if (result == DriverResult::Success) {
42 result = EnableMCU(false);
43 }
44
45 is_enabled = false;
46
47 return result;
48}
49
50DriverResult RingConProtocol::StartRingconPolling() {
51 LOG_DEBUG(Input, "Enable Ringcon");
52 ScopedSetBlocking sb(this);
53 DriverResult result{DriverResult::Success};
54 bool is_connected = false;
55
56 if (result == DriverResult::Success) {
57 result = IsRingConnected(is_connected);
58 }
59 if (result == DriverResult::Success && is_connected) {
60 LOG_INFO(Input, "Ringcon detected");
61 result = ConfigureRing();
62 }
63 if (result == DriverResult::Success) {
64 is_enabled = true;
65 }
66
67 return result;
68}
69
70DriverResult RingConProtocol::IsRingConnected(bool& is_connected) {
71 LOG_DEBUG(Input, "IsRingConnected");
72 constexpr std::size_t max_tries = 28;
73 SubCommandResponse output{};
74 std::size_t tries = 0;
75 is_connected = false;
76
77 do {
78 const auto result = SendSubCommand(SubCommand::GET_EXTERNAL_DEVICE_INFO, {}, output);
79
80 if (result != DriverResult::Success) {
81 return result;
82 }
83
84 if (tries++ >= max_tries) {
85 return DriverResult::NoDeviceDetected;
86 }
87 } while (output.external_device_id != ExternalDeviceId::RingController);
88
89 is_connected = true;
90 return DriverResult::Success;
91}
92
93DriverResult RingConProtocol::ConfigureRing() {
94 LOG_DEBUG(Input, "ConfigureRing");
95
96 static constexpr std::array<u8, 37> ring_config{
97 0x06, 0x03, 0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x1C, 0x16, 0xED, 0x34, 0x36,
98 0x00, 0x00, 0x00, 0x0A, 0x64, 0x0B, 0xE6, 0xA9, 0x22, 0x00, 0x00, 0x04, 0x00,
99 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0xA8, 0xE1, 0x34, 0x36};
100
101 const DriverResult result = SendSubCommand(SubCommand::SET_EXTERNAL_FORMAT_CONFIG, ring_config);
102
103 if (result != DriverResult::Success) {
104 return result;
105 }
106
107 static constexpr std::array<u8, 4> ringcon_data{0x04, 0x01, 0x01, 0x02};
108 return SendSubCommand(SubCommand::ENABLE_EXTERNAL_POLLING, ringcon_data);
109}
110
111bool RingConProtocol::IsEnabled() const {
112 return is_enabled;
113}
114
115} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/ringcon.h b/src/input_common/helpers/joycon_protocol/ringcon.h
new file mode 100644
index 000000000..6e858f3fc
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/ringcon.h
@@ -0,0 +1,38 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class RingConProtocol final : private JoyconCommonProtocol {
19public:
20 explicit RingConProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableRingCon();
23
24 DriverResult DisableRingCon();
25
26 DriverResult StartRingconPolling();
27
28 bool IsEnabled() const;
29
30private:
31 DriverResult IsRingConnected(bool& is_connected);
32
33 DriverResult ConfigureRing();
34
35 bool is_enabled{};
36};
37
38} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/rumble.cpp b/src/input_common/helpers/joycon_protocol/rumble.cpp
new file mode 100644
index 000000000..63b60c946
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/rumble.cpp
@@ -0,0 +1,299 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <algorithm>
5#include <cmath>
6
7#include "common/logging/log.h"
8#include "input_common/helpers/joycon_protocol/rumble.h"
9
10namespace InputCommon::Joycon {
11
12RumbleProtocol::RumbleProtocol(std::shared_ptr<JoyconHandle> handle)
13 : JoyconCommonProtocol(std::move(handle)) {}
14
15DriverResult RumbleProtocol::EnableRumble(bool is_enabled) {
16 LOG_DEBUG(Input, "Enable Rumble");
17 ScopedSetBlocking sb(this);
18 const std::array<u8, 1> buffer{static_cast<u8>(is_enabled ? 1 : 0)};
19 return SendSubCommand(SubCommand::ENABLE_VIBRATION, buffer);
20}
21
22DriverResult RumbleProtocol::SendVibration(const VibrationValue& vibration) {
23 std::array<u8, sizeof(DefaultVibrationBuffer)> buffer{};
24
25 if (vibration.high_amplitude <= 0.0f && vibration.low_amplitude <= 0.0f) {
26 return SendVibrationReport(DefaultVibrationBuffer);
27 }
28
29 // Protect joycons from damage from strong vibrations
30 const f32 clamp_amplitude =
31 1.0f / std::max(1.0f, vibration.high_amplitude + vibration.low_amplitude);
32
33 const u16 encoded_high_frequency = EncodeHighFrequency(vibration.high_frequency);
34 const u8 encoded_high_amplitude =
35 EncodeHighAmplitude(vibration.high_amplitude * clamp_amplitude);
36 const u8 encoded_low_frequency = EncodeLowFrequency(vibration.low_frequency);
37 const u16 encoded_low_amplitude = EncodeLowAmplitude(vibration.low_amplitude * clamp_amplitude);
38
39 buffer[0] = static_cast<u8>(encoded_high_frequency & 0xFF);
40 buffer[1] = static_cast<u8>(encoded_high_amplitude | ((encoded_high_frequency >> 8) & 0x01));
41 buffer[2] = static_cast<u8>(encoded_low_frequency | ((encoded_low_amplitude >> 8) & 0x80));
42 buffer[3] = static_cast<u8>(encoded_low_amplitude & 0xFF);
43
44 // Duplicate rumble for now
45 buffer[4] = buffer[0];
46 buffer[5] = buffer[1];
47 buffer[6] = buffer[2];
48 buffer[7] = buffer[3];
49
50 return SendVibrationReport(buffer);
51}
52
53u16 RumbleProtocol::EncodeHighFrequency(f32 frequency) const {
54 const u8 new_frequency =
55 static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
56 return static_cast<u16>((new_frequency - 0x60) * 4);
57}
58
59u8 RumbleProtocol::EncodeLowFrequency(f32 frequency) const {
60 const u8 new_frequency =
61 static_cast<u8>(std::clamp(std::log2(frequency / 10.0f) * 32.0f, 0.0f, 255.0f));
62 return static_cast<u8>(new_frequency - 0x40);
63}
64
65u8 RumbleProtocol::EncodeHighAmplitude(f32 amplitude) const {
66 // More information about these values can be found here:
67 // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
68
69 static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
70 std::pair<f32, int>{0.0f, 0x0},
71 {0.01f, 0x2},
72 {0.012f, 0x4},
73 {0.014f, 0x6},
74 {0.017f, 0x8},
75 {0.02f, 0x0a},
76 {0.024f, 0x0c},
77 {0.028f, 0x0e},
78 {0.033f, 0x10},
79 {0.04f, 0x12},
80 {0.047f, 0x14},
81 {0.056f, 0x16},
82 {0.067f, 0x18},
83 {0.08f, 0x1a},
84 {0.095f, 0x1c},
85 {0.112f, 0x1e},
86 {0.117f, 0x20},
87 {0.123f, 0x22},
88 {0.128f, 0x24},
89 {0.134f, 0x26},
90 {0.14f, 0x28},
91 {0.146f, 0x2a},
92 {0.152f, 0x2c},
93 {0.159f, 0x2e},
94 {0.166f, 0x30},
95 {0.173f, 0x32},
96 {0.181f, 0x34},
97 {0.189f, 0x36},
98 {0.198f, 0x38},
99 {0.206f, 0x3a},
100 {0.215f, 0x3c},
101 {0.225f, 0x3e},
102 {0.23f, 0x40},
103 {0.235f, 0x42},
104 {0.24f, 0x44},
105 {0.245f, 0x46},
106 {0.251f, 0x48},
107 {0.256f, 0x4a},
108 {0.262f, 0x4c},
109 {0.268f, 0x4e},
110 {0.273f, 0x50},
111 {0.279f, 0x52},
112 {0.286f, 0x54},
113 {0.292f, 0x56},
114 {0.298f, 0x58},
115 {0.305f, 0x5a},
116 {0.311f, 0x5c},
117 {0.318f, 0x5e},
118 {0.325f, 0x60},
119 {0.332f, 0x62},
120 {0.34f, 0x64},
121 {0.347f, 0x66},
122 {0.355f, 0x68},
123 {0.362f, 0x6a},
124 {0.37f, 0x6c},
125 {0.378f, 0x6e},
126 {0.387f, 0x70},
127 {0.395f, 0x72},
128 {0.404f, 0x74},
129 {0.413f, 0x76},
130 {0.422f, 0x78},
131 {0.431f, 0x7a},
132 {0.44f, 0x7c},
133 {0.45f, 0x7e},
134 {0.46f, 0x80},
135 {0.47f, 0x82},
136 {0.48f, 0x84},
137 {0.491f, 0x86},
138 {0.501f, 0x88},
139 {0.512f, 0x8a},
140 {0.524f, 0x8c},
141 {0.535f, 0x8e},
142 {0.547f, 0x90},
143 {0.559f, 0x92},
144 {0.571f, 0x94},
145 {0.584f, 0x96},
146 {0.596f, 0x98},
147 {0.609f, 0x9a},
148 {0.623f, 0x9c},
149 {0.636f, 0x9e},
150 {0.65f, 0xa0},
151 {0.665f, 0xa2},
152 {0.679f, 0xa4},
153 {0.694f, 0xa6},
154 {0.709f, 0xa8},
155 {0.725f, 0xaa},
156 {0.741f, 0xac},
157 {0.757f, 0xae},
158 {0.773f, 0xb0},
159 {0.79f, 0xb2},
160 {0.808f, 0xb4},
161 {0.825f, 0xb6},
162 {0.843f, 0xb8},
163 {0.862f, 0xba},
164 {0.881f, 0xbc},
165 {0.9f, 0xbe},
166 {0.92f, 0xc0},
167 {0.94f, 0xc2},
168 {0.96f, 0xc4},
169 {0.981f, 0xc6},
170 {1.003f, 0xc8},
171 };
172
173 for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
174 if (amplitude <= amplitude_value) {
175 return static_cast<u8>(code);
176 }
177 }
178
179 return static_cast<u8>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
180}
181
182u16 RumbleProtocol::EncodeLowAmplitude(f32 amplitude) const {
183 // More information about these values can be found here:
184 // https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
185
186 static constexpr std::array<std::pair<f32, int>, 101> high_fequency_amplitude{
187 std::pair<f32, int>{0.0f, 0x0040},
188 {0.01f, 0x8040},
189 {0.012f, 0x0041},
190 {0.014f, 0x8041},
191 {0.017f, 0x0042},
192 {0.02f, 0x8042},
193 {0.024f, 0x0043},
194 {0.028f, 0x8043},
195 {0.033f, 0x0044},
196 {0.04f, 0x8044},
197 {0.047f, 0x0045},
198 {0.056f, 0x8045},
199 {0.067f, 0x0046},
200 {0.08f, 0x8046},
201 {0.095f, 0x0047},
202 {0.112f, 0x8047},
203 {0.117f, 0x0048},
204 {0.123f, 0x8048},
205 {0.128f, 0x0049},
206 {0.134f, 0x8049},
207 {0.14f, 0x004a},
208 {0.146f, 0x804a},
209 {0.152f, 0x004b},
210 {0.159f, 0x804b},
211 {0.166f, 0x004c},
212 {0.173f, 0x804c},
213 {0.181f, 0x004d},
214 {0.189f, 0x804d},
215 {0.198f, 0x004e},
216 {0.206f, 0x804e},
217 {0.215f, 0x004f},
218 {0.225f, 0x804f},
219 {0.23f, 0x0050},
220 {0.235f, 0x8050},
221 {0.24f, 0x0051},
222 {0.245f, 0x8051},
223 {0.251f, 0x0052},
224 {0.256f, 0x8052},
225 {0.262f, 0x0053},
226 {0.268f, 0x8053},
227 {0.273f, 0x0054},
228 {0.279f, 0x8054},
229 {0.286f, 0x0055},
230 {0.292f, 0x8055},
231 {0.298f, 0x0056},
232 {0.305f, 0x8056},
233 {0.311f, 0x0057},
234 {0.318f, 0x8057},
235 {0.325f, 0x0058},
236 {0.332f, 0x8058},
237 {0.34f, 0x0059},
238 {0.347f, 0x8059},
239 {0.355f, 0x005a},
240 {0.362f, 0x805a},
241 {0.37f, 0x005b},
242 {0.378f, 0x805b},
243 {0.387f, 0x005c},
244 {0.395f, 0x805c},
245 {0.404f, 0x005d},
246 {0.413f, 0x805d},
247 {0.422f, 0x005e},
248 {0.431f, 0x805e},
249 {0.44f, 0x005f},
250 {0.45f, 0x805f},
251 {0.46f, 0x0060},
252 {0.47f, 0x8060},
253 {0.48f, 0x0061},
254 {0.491f, 0x8061},
255 {0.501f, 0x0062},
256 {0.512f, 0x8062},
257 {0.524f, 0x0063},
258 {0.535f, 0x8063},
259 {0.547f, 0x0064},
260 {0.559f, 0x8064},
261 {0.571f, 0x0065},
262 {0.584f, 0x8065},
263 {0.596f, 0x0066},
264 {0.609f, 0x8066},
265 {0.623f, 0x0067},
266 {0.636f, 0x8067},
267 {0.65f, 0x0068},
268 {0.665f, 0x8068},
269 {0.679f, 0x0069},
270 {0.694f, 0x8069},
271 {0.709f, 0x006a},
272 {0.725f, 0x806a},
273 {0.741f, 0x006b},
274 {0.757f, 0x806b},
275 {0.773f, 0x006c},
276 {0.79f, 0x806c},
277 {0.808f, 0x006d},
278 {0.825f, 0x806d},
279 {0.843f, 0x006e},
280 {0.862f, 0x806e},
281 {0.881f, 0x006f},
282 {0.9f, 0x806f},
283 {0.92f, 0x0070},
284 {0.94f, 0x8070},
285 {0.96f, 0x0071},
286 {0.981f, 0x8071},
287 {1.003f, 0x0072},
288 };
289
290 for (const auto& [amplitude_value, code] : high_fequency_amplitude) {
291 if (amplitude <= amplitude_value) {
292 return static_cast<u16>(code);
293 }
294 }
295
296 return static_cast<u16>(high_fequency_amplitude[high_fequency_amplitude.size() - 1].second);
297}
298
299} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/rumble.h b/src/input_common/helpers/joycon_protocol/rumble.h
new file mode 100644
index 000000000..6c12b7925
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/rumble.h
@@ -0,0 +1,33 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
5// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
6// https://github.com/CTCaer/jc_toolkit
7// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
8
9#pragma once
10
11#include <vector>
12
13#include "input_common/helpers/joycon_protocol/common_protocol.h"
14#include "input_common/helpers/joycon_protocol/joycon_types.h"
15
16namespace InputCommon::Joycon {
17
18class RumbleProtocol final : private JoyconCommonProtocol {
19public:
20 explicit RumbleProtocol(std::shared_ptr<JoyconHandle> handle);
21
22 DriverResult EnableRumble(bool is_enabled);
23
24 DriverResult SendVibration(const VibrationValue& vibration);
25
26private:
27 u16 EncodeHighFrequency(f32 frequency) const;
28 u8 EncodeLowFrequency(f32 frequency) const;
29 u8 EncodeHighAmplitude(f32 amplitude) const;
30 u16 EncodeLowAmplitude(f32 amplitude) const;
31};
32
33} // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp
index f3a0b3419..a6be6dac1 100644
--- a/src/input_common/helpers/stick_from_buttons.cpp
+++ b/src/input_common/helpers/stick_from_buttons.cpp
@@ -11,6 +11,14 @@ namespace InputCommon {
11 11
12class Stick final : public Common::Input::InputDevice { 12class Stick final : public Common::Input::InputDevice {
13public: 13public:
14 // Some games such as EARTH DEFENSE FORCE: WORLD BROTHERS
15 // do not play nicely with the theoretical maximum range.
16 // Using a value one lower from the maximum emulates real stick behavior.
17 static constexpr float MAX_RANGE = 32766.0f / 32767.0f;
18 static constexpr float TAU = Common::PI * 2.0f;
19 // Use wider angle to ease the transition.
20 static constexpr float APERTURE = TAU * 0.15f;
21
14 using Button = std::unique_ptr<Common::Input::InputDevice>; 22 using Button = std::unique_ptr<Common::Input::InputDevice>;
15 23
16 Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, Button updater_, 24 Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, Button updater_,
@@ -56,30 +64,23 @@ public:
56 } 64 }
57 65
58 bool IsAngleGreater(float old_angle, float new_angle) const { 66 bool IsAngleGreater(float old_angle, float new_angle) const {
59 constexpr float TAU = Common::PI * 2.0f; 67 const float top_limit = new_angle + APERTURE;
60 // Use wider angle to ease the transition.
61 constexpr float aperture = TAU * 0.15f;
62 const float top_limit = new_angle + aperture;
63 return (old_angle > new_angle && old_angle <= top_limit) || 68 return (old_angle > new_angle && old_angle <= top_limit) ||
64 (old_angle + TAU > new_angle && old_angle + TAU <= top_limit); 69 (old_angle + TAU > new_angle && old_angle + TAU <= top_limit);
65 } 70 }
66 71
67 bool IsAngleSmaller(float old_angle, float new_angle) const { 72 bool IsAngleSmaller(float old_angle, float new_angle) const {
68 constexpr float TAU = Common::PI * 2.0f; 73 const float bottom_limit = new_angle - APERTURE;
69 // Use wider angle to ease the transition.
70 constexpr float aperture = TAU * 0.15f;
71 const float bottom_limit = new_angle - aperture;
72 return (old_angle >= bottom_limit && old_angle < new_angle) || 74 return (old_angle >= bottom_limit && old_angle < new_angle) ||
73 (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle); 75 (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle);
74 } 76 }
75 77
76 float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const { 78 float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const {
77 constexpr float TAU = Common::PI * 2.0f;
78 float new_angle = angle; 79 float new_angle = angle;
79 80
80 auto time_difference = static_cast<float>( 81 auto time_difference = static_cast<float>(
81 std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count()); 82 std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count());
82 time_difference /= 1000.0f * 1000.0f; 83 time_difference /= 1000.0f;
83 if (time_difference > 0.5f) { 84 if (time_difference > 0.5f) {
84 time_difference = 0.5f; 85 time_difference = 0.5f;
85 } 86 }
@@ -196,8 +197,6 @@ public:
196 } 197 }
197 198
198 void UpdateStatus() { 199 void UpdateStatus() {
199 const float coef = modifier_status.value ? modifier_scale : 1.0f;
200
201 bool r = right_status; 200 bool r = right_status;
202 bool l = left_status; 201 bool l = left_status;
203 bool u = up_status; 202 bool u = up_status;
@@ -215,7 +214,7 @@ public:
215 214
216 // Move if a key is pressed 215 // Move if a key is pressed
217 if (r || l || u || d) { 216 if (r || l || u || d) {
218 amplitude = coef; 217 amplitude = modifier_status.value ? modifier_scale : MAX_RANGE;
219 } else { 218 } else {
220 amplitude = 0; 219 amplitude = 0;
221 } 220 }
@@ -269,30 +268,17 @@ public:
269 Common::Input::StickStatus status{}; 268 Common::Input::StickStatus status{};
270 status.x.properties = properties; 269 status.x.properties = properties;
271 status.y.properties = properties; 270 status.y.properties = properties;
271
272 if (Settings::values.emulate_analog_keyboard) { 272 if (Settings::values.emulate_analog_keyboard) {
273 const auto now = std::chrono::steady_clock::now(); 273 const auto now = std::chrono::steady_clock::now();
274 float angle_ = GetAngle(now); 274 const float angle_ = GetAngle(now);
275 status.x.raw_value = std::cos(angle_) * amplitude; 275 status.x.raw_value = std::cos(angle_) * amplitude;
276 status.y.raw_value = std::sin(angle_) * amplitude; 276 status.y.raw_value = std::sin(angle_) * amplitude;
277 return status; 277 return status;
278 } 278 }
279 constexpr float SQRT_HALF = 0.707106781f; 279
280 int x = 0, y = 0; 280 status.x.raw_value = std::cos(goal_angle) * amplitude;
281 if (right_status) { 281 status.y.raw_value = std::sin(goal_angle) * amplitude;
282 ++x;
283 }
284 if (left_status) {
285 --x;
286 }
287 if (up_status) {
288 ++y;
289 }
290 if (down_status) {
291 --y;
292 }
293 const float coef = modifier_status.value ? modifier_scale : 1.0f;
294 status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF);
295 status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF);
296 return status; 282 return status;
297 } 283 }
298 284
diff --git a/src/input_common/helpers/udp_protocol.cpp b/src/input_common/helpers/udp_protocol.cpp
index 994380d21..e54a8fce1 100644
--- a/src/input_common/helpers/udp_protocol.cpp
+++ b/src/input_common/helpers/udp_protocol.cpp
@@ -25,7 +25,7 @@ namespace Response {
25/** 25/**
26 * Returns Type if the packet is valid, else none 26 * Returns Type if the packet is valid, else none
27 * 27 *
28 * Note: Modifies the buffer to zero out the crc (since thats the easiest way to check without 28 * Note: Modifies the buffer to zero out the crc (since that's the easiest way to check without
29 * copying the buffer) 29 * copying the buffer)
30 */ 30 */
31std::optional<Type> Validate(u8* data, std::size_t size) { 31std::optional<Type> Validate(u8* data, std::size_t size) {