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.cpp575
-rw-r--r--src/input_common/helpers/joycon_driver.h150
-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.cpp316
-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.h697
-rw-r--r--src/input_common/helpers/joycon_protocol/nfc.cpp406
-rw-r--r--src/input_common/helpers/joycon_protocol/nfc.h61
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.cpp337
-rw-r--r--src/input_common/helpers/joycon_protocol/poller.h81
-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
20 files changed, 4239 insertions, 32 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..8f94c9f45
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -0,0 +1,575 @@
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/swap.h"
6#include "common/thread.h"
7#include "input_common/helpers/joycon_driver.h"
8#include "input_common/helpers/joycon_protocol/calibration.h"
9#include "input_common/helpers/joycon_protocol/generic_functions.h"
10#include "input_common/helpers/joycon_protocol/irs.h"
11#include "input_common/helpers/joycon_protocol/nfc.h"
12#include "input_common/helpers/joycon_protocol/poller.h"
13#include "input_common/helpers/joycon_protocol/ringcon.h"
14#include "input_common/helpers/joycon_protocol/rumble.h"
15
16namespace InputCommon::Joycon {
17JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} {
18 hidapi_handle = std::make_shared<JoyconHandle>();
19}
20
21JoyconDriver::~JoyconDriver() {
22 Stop();
23}
24
25void JoyconDriver::Stop() {
26 is_connected = false;
27 input_thread = {};
28}
29
30DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) {
31 std::scoped_lock lock{mutex};
32
33 handle_device_type = ControllerType::None;
34 GetDeviceType(device_info, handle_device_type);
35 if (handle_device_type == ControllerType::None) {
36 return DriverResult::UnsupportedControllerType;
37 }
38
39 hidapi_handle->handle =
40 SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
41 std::memcpy(&handle_serial_number, device_info->serial_number, 15);
42 if (!hidapi_handle->handle) {
43 LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
44 device_info->vendor_id, device_info->product_id);
45 return DriverResult::HandleInUse;
46 }
47 SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
48 return DriverResult::Success;
49}
50
51DriverResult JoyconDriver::InitializeDevice() {
52 if (!hidapi_handle->handle) {
53 return DriverResult::InvalidHandle;
54 }
55 std::scoped_lock lock{mutex};
56 disable_input_thread = true;
57
58 // Reset Counters
59 error_counter = 0;
60 hidapi_handle->packet_counter = 0;
61
62 // Reset external device status
63 starlink_connected = false;
64 ring_connected = false;
65 amiibo_detected = false;
66
67 // Set HW default configuration
68 vibration_enabled = true;
69 motion_enabled = true;
70 hidbus_enabled = false;
71 nfc_enabled = false;
72 passive_enabled = false;
73 irs_enabled = false;
74 gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
75 gyro_performance = Joycon::GyroPerformance::HZ833;
76 accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
77 accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
78
79 // Initialize HW Protocols
80 calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle);
81 generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle);
82 irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle);
83 nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle);
84 ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle);
85 rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle);
86
87 // Get fixed joycon info
88 generic_protocol->GetVersionNumber(version);
89 generic_protocol->SetLowPowerMode(false);
90 generic_protocol->GetColor(color);
91 if (handle_device_type == ControllerType::Pro) {
92 // Some 3rd party controllers aren't pro controllers
93 generic_protocol->GetControllerType(device_type);
94 } else {
95 device_type = handle_device_type;
96 }
97 generic_protocol->GetSerialNumber(serial_number);
98 supported_features = GetSupportedFeatures();
99
100 // Get Calibration data
101 calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration);
102 calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration);
103 calibration_protocol->GetImuCalibration(motion_calibration);
104
105 // Set led status
106 generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port));
107
108 // Apply HW configuration
109 SetPollingMode();
110
111 // Initialize joycon poller
112 joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration,
113 right_stick_calibration, motion_calibration);
114
115 // Start pooling for data
116 is_connected = true;
117 if (!input_thread_running) {
118 input_thread =
119 std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
120 }
121
122 disable_input_thread = false;
123 return DriverResult::Success;
124}
125
126void JoyconDriver::InputThread(std::stop_token stop_token) {
127 LOG_INFO(Input, "Joycon Adapter input thread started");
128 Common::SetCurrentThreadName("JoyconInput");
129 input_thread_running = true;
130
131 // Max update rate is 5ms, ensure we are always able to read a bit faster
132 constexpr int ThreadDelay = 2;
133 std::vector<u8> buffer(MaxBufferSize);
134
135 while (!stop_token.stop_requested()) {
136 int status = 0;
137
138 if (!IsInputThreadValid()) {
139 input_thread.request_stop();
140 continue;
141 }
142
143 // By disabling the input thread we can ensure custom commands will succeed as no package is
144 // skipped
145 if (!disable_input_thread) {
146 status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
147 ThreadDelay);
148 } else {
149 std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
150 }
151
152 if (IsPayloadCorrect(status, buffer)) {
153 OnNewData(buffer);
154 }
155
156 std::this_thread::yield();
157 }
158
159 is_connected = false;
160 input_thread_running = false;
161 LOG_INFO(Input, "Joycon Adapter input thread stopped");
162}
163
164void JoyconDriver::OnNewData(std::span<u8> buffer) {
165 const auto report_mode = static_cast<ReportMode>(buffer[0]);
166
167 // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
168 // experience
169 switch (report_mode) {
170 case ReportMode::STANDARD_FULL_60HZ:
171 case ReportMode::NFC_IR_MODE_60HZ:
172 case ReportMode::SIMPLE_HID_MODE: {
173 const auto now = std::chrono::steady_clock::now();
174 const auto new_delta_time = static_cast<u64>(
175 std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count());
176 delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10;
177 last_update = now;
178 joycon_poller->UpdateColor(color);
179 break;
180 }
181 default:
182 break;
183 }
184
185 const MotionStatus motion_status{
186 .is_enabled = motion_enabled,
187 .delta_time = delta_time,
188 .gyro_sensitivity = gyro_sensitivity,
189 .accelerometer_sensitivity = accelerometer_sensitivity,
190 };
191
192 // TODO: Remove this when calibration is properly loaded and not calculated
193 if (ring_connected && report_mode == ReportMode::STANDARD_FULL_60HZ) {
194 InputReportActive data{};
195 memcpy(&data, buffer.data(), sizeof(InputReportActive));
196 calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input);
197 }
198
199 const RingStatus ring_status{
200 .is_enabled = ring_connected,
201 .default_value = ring_calibration.default_value,
202 .max_value = ring_calibration.max_value,
203 .min_value = ring_calibration.min_value,
204 };
205
206 if (irs_protocol->IsEnabled()) {
207 irs_protocol->RequestImage(buffer);
208 joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat());
209 }
210
211 if (nfc_protocol->IsEnabled()) {
212 if (amiibo_detected) {
213 if (!nfc_protocol->HasAmiibo()) {
214 joycon_poller->UpdateAmiibo({});
215 amiibo_detected = false;
216 return;
217 }
218 }
219
220 if (!amiibo_detected) {
221 std::vector<u8> data(0x21C);
222 const auto result = nfc_protocol->ScanAmiibo(data);
223 if (result == DriverResult::Success) {
224 joycon_poller->UpdateAmiibo(data);
225 amiibo_detected = true;
226 }
227 }
228 }
229
230 switch (report_mode) {
231 case ReportMode::STANDARD_FULL_60HZ:
232 joycon_poller->ReadActiveMode(buffer, motion_status, ring_status);
233 break;
234 case ReportMode::NFC_IR_MODE_60HZ:
235 joycon_poller->ReadNfcIRMode(buffer, motion_status);
236 break;
237 case ReportMode::SIMPLE_HID_MODE:
238 joycon_poller->ReadPassiveMode(buffer);
239 break;
240 case ReportMode::SUBCMD_REPLY:
241 LOG_DEBUG(Input, "Unhandled command reply");
242 break;
243 default:
244 LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
245 break;
246 }
247}
248
249DriverResult JoyconDriver::SetPollingMode() {
250 disable_input_thread = true;
251
252 rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
253
254 if (motion_enabled && supported_features.motion) {
255 generic_protocol->EnableImu(true);
256 generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance,
257 accelerometer_sensitivity, accelerometer_performance);
258 } else {
259 generic_protocol->EnableImu(false);
260 }
261
262 if (irs_protocol->IsEnabled()) {
263 irs_protocol->DisableIrs();
264 }
265
266 if (nfc_protocol->IsEnabled()) {
267 amiibo_detected = false;
268 nfc_protocol->DisableNfc();
269 }
270
271 if (ring_protocol->IsEnabled()) {
272 ring_connected = false;
273 ring_protocol->DisableRingCon();
274 }
275
276 if (irs_enabled && supported_features.irs) {
277 auto result = irs_protocol->EnableIrs();
278 if (result == DriverResult::Success) {
279 disable_input_thread = false;
280 return result;
281 }
282 irs_protocol->DisableIrs();
283 LOG_ERROR(Input, "Error enabling IRS");
284 }
285
286 if (nfc_enabled && supported_features.nfc) {
287 auto result = nfc_protocol->EnableNfc();
288 if (result == DriverResult::Success) {
289 result = nfc_protocol->StartNFCPollingMode();
290 }
291 if (result == DriverResult::Success) {
292 disable_input_thread = false;
293 return result;
294 }
295 nfc_protocol->DisableNfc();
296 LOG_ERROR(Input, "Error enabling NFC");
297 }
298
299 if (hidbus_enabled && supported_features.hidbus) {
300 auto result = ring_protocol->EnableRingCon();
301 if (result == DriverResult::Success) {
302 result = ring_protocol->StartRingconPolling();
303 }
304 if (result == DriverResult::Success) {
305 ring_connected = true;
306 disable_input_thread = false;
307 return result;
308 }
309 ring_connected = false;
310 ring_protocol->DisableRingCon();
311 LOG_ERROR(Input, "Error enabling Ringcon");
312 }
313
314 if (passive_enabled && supported_features.passive) {
315 const auto result = generic_protocol->EnablePassiveMode();
316 if (result == DriverResult::Success) {
317 disable_input_thread = false;
318 return result;
319 }
320 LOG_ERROR(Input, "Error enabling passive mode");
321 }
322
323 // Default Mode
324 const auto result = generic_protocol->EnableActiveMode();
325 if (result != DriverResult::Success) {
326 LOG_ERROR(Input, "Error enabling active mode");
327 }
328 // Switch calls this function after enabling active mode
329 generic_protocol->TriggersElapsed();
330
331 disable_input_thread = false;
332 return result;
333}
334
335JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
336 SupportedFeatures features{
337 .passive = true,
338 .motion = true,
339 .vibration = true,
340 };
341
342 if (device_type == ControllerType::Right) {
343 features.nfc = true;
344 features.irs = true;
345 features.hidbus = true;
346 }
347
348 if (device_type == ControllerType::Pro) {
349 features.nfc = true;
350 }
351 return features;
352}
353
354bool JoyconDriver::IsInputThreadValid() const {
355 if (!is_connected.load()) {
356 return false;
357 }
358 if (hidapi_handle->handle == nullptr) {
359 return false;
360 }
361 // Controller is not responding. Terminate connection
362 if (error_counter > MaxErrorCount) {
363 return false;
364 }
365 return true;
366}
367
368bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
369 if (status <= -1) {
370 error_counter++;
371 return false;
372 }
373 // There's no new data
374 if (status == 0) {
375 return false;
376 }
377 // No reply ever starts with zero
378 if (buffer[0] == 0x00) {
379 error_counter++;
380 return false;
381 }
382 error_counter = 0;
383 return true;
384}
385
386DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
387 std::scoped_lock lock{mutex};
388 if (disable_input_thread) {
389 return DriverResult::HandleInUse;
390 }
391 return rumble_protocol->SendVibration(vibration);
392}
393
394DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
395 std::scoped_lock lock{mutex};
396 if (disable_input_thread) {
397 return DriverResult::HandleInUse;
398 }
399 return generic_protocol->SetLedPattern(led_pattern);
400}
401
402DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) {
403 std::scoped_lock lock{mutex};
404 if (disable_input_thread) {
405 return DriverResult::HandleInUse;
406 }
407 disable_input_thread = true;
408 const auto result = irs_protocol->SetIrsConfig(mode_, format_);
409 disable_input_thread = false;
410 return result;
411}
412
413DriverResult JoyconDriver::SetPasiveMode() {
414 std::scoped_lock lock{mutex};
415 motion_enabled = false;
416 hidbus_enabled = false;
417 nfc_enabled = false;
418 passive_enabled = true;
419 irs_enabled = false;
420 return SetPollingMode();
421}
422
423DriverResult JoyconDriver::SetActiveMode() {
424 if (is_ring_disabled_by_irs) {
425 is_ring_disabled_by_irs = false;
426 SetActiveMode();
427 return SetRingConMode();
428 }
429
430 std::scoped_lock lock{mutex};
431 motion_enabled = true;
432 hidbus_enabled = false;
433 nfc_enabled = false;
434 passive_enabled = false;
435 irs_enabled = false;
436 return SetPollingMode();
437}
438
439DriverResult JoyconDriver::SetIrMode() {
440 std::scoped_lock lock{mutex};
441
442 if (!supported_features.irs) {
443 return DriverResult::NotSupported;
444 }
445
446 if (ring_connected) {
447 is_ring_disabled_by_irs = true;
448 }
449
450 motion_enabled = false;
451 hidbus_enabled = false;
452 nfc_enabled = false;
453 passive_enabled = false;
454 irs_enabled = true;
455 return SetPollingMode();
456}
457
458DriverResult JoyconDriver::SetNfcMode() {
459 std::scoped_lock lock{mutex};
460
461 if (!supported_features.nfc) {
462 return DriverResult::NotSupported;
463 }
464
465 motion_enabled = true;
466 hidbus_enabled = false;
467 nfc_enabled = true;
468 passive_enabled = false;
469 irs_enabled = false;
470 return SetPollingMode();
471}
472
473DriverResult JoyconDriver::SetRingConMode() {
474 std::scoped_lock lock{mutex};
475
476 if (!supported_features.hidbus) {
477 return DriverResult::NotSupported;
478 }
479
480 motion_enabled = true;
481 hidbus_enabled = true;
482 nfc_enabled = false;
483 passive_enabled = false;
484 irs_enabled = false;
485
486 const auto result = SetPollingMode();
487
488 if (!ring_connected) {
489 return DriverResult::NoDeviceDetected;
490 }
491
492 return result;
493}
494
495bool JoyconDriver::IsConnected() const {
496 std::scoped_lock lock{mutex};
497 return is_connected.load();
498}
499
500bool JoyconDriver::IsVibrationEnabled() const {
501 std::scoped_lock lock{mutex};
502 return vibration_enabled;
503}
504
505FirmwareVersion JoyconDriver::GetDeviceVersion() const {
506 std::scoped_lock lock{mutex};
507 return version;
508}
509
510Color JoyconDriver::GetDeviceColor() const {
511 std::scoped_lock lock{mutex};
512 return color;
513}
514
515std::size_t JoyconDriver::GetDevicePort() const {
516 std::scoped_lock lock{mutex};
517 return port;
518}
519
520ControllerType JoyconDriver::GetDeviceType() const {
521 std::scoped_lock lock{mutex};
522 return device_type;
523}
524
525ControllerType JoyconDriver::GetHandleDeviceType() const {
526 std::scoped_lock lock{mutex};
527 return handle_device_type;
528}
529
530SerialNumber JoyconDriver::GetSerialNumber() const {
531 std::scoped_lock lock{mutex};
532 return serial_number;
533}
534
535SerialNumber JoyconDriver::GetHandleSerialNumber() const {
536 std::scoped_lock lock{mutex};
537 return handle_serial_number;
538}
539
540void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) {
541 joycon_poller->SetCallbacks(callbacks);
542}
543
544DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
545 ControllerType& controller_type) {
546 static constexpr std::array<std::pair<u32, ControllerType>, 2> supported_devices{
547 std::pair<u32, ControllerType>{0x2006, ControllerType::Left},
548 {0x2007, ControllerType::Right},
549 };
550 constexpr u16 nintendo_vendor_id = 0x057e;
551
552 controller_type = ControllerType::None;
553 if (device_info->vendor_id != nintendo_vendor_id) {
554 return DriverResult::UnsupportedControllerType;
555 }
556
557 for (const auto& [product_id, type] : supported_devices) {
558 if (device_info->product_id == static_cast<u16>(product_id)) {
559 controller_type = type;
560 return Joycon::DriverResult::Success;
561 }
562 }
563 return Joycon::DriverResult::UnsupportedControllerType;
564}
565
566DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
567 SerialNumber& serial_number) {
568 if (device_info->serial_number == nullptr) {
569 return DriverResult::Unknown;
570 }
571 std::memcpy(&serial_number, device_info->serial_number, 15);
572 return Joycon::DriverResult::Success;
573}
574
575} // 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..c1e189fa5
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.h
@@ -0,0 +1,150 @@
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 SetPasiveMode();
48 DriverResult SetActiveMode();
49 DriverResult SetIrMode();
50 DriverResult SetNfcMode();
51 DriverResult SetRingConMode();
52
53 void SetCallbacks(const JoyconCallbacks& callbacks);
54
55 // Returns device type from hidapi handle
56 static DriverResult GetDeviceType(SDL_hid_device_info* device_info,
57 ControllerType& controller_type);
58
59 // Returns serial number from hidapi handle
60 static DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
61 SerialNumber& serial_number);
62
63private:
64 struct SupportedFeatures {
65 bool passive{};
66 bool hidbus{};
67 bool irs{};
68 bool motion{};
69 bool nfc{};
70 bool vibration{};
71 };
72
73 /// Main thread, actively request new data from the handle
74 void InputThread(std::stop_token stop_token);
75
76 /// Called everytime a valid package arrives
77 void OnNewData(std::span<u8> buffer);
78
79 /// Updates device configuration to enable or disable features
80 DriverResult SetPollingMode();
81
82 /// Returns true if input thread is valid and doesn't need to be stopped
83 bool IsInputThreadValid() const;
84
85 /// Returns true if the data should be interpreted. Otherwise the error counter is incremented
86 bool IsPayloadCorrect(int status, std::span<const u8> buffer);
87
88 /// Returns a list of supported features that can be enabled on this device
89 SupportedFeatures GetSupportedFeatures();
90
91 // Protocol Features
92 std::unique_ptr<CalibrationProtocol> calibration_protocol;
93 std::unique_ptr<GenericProtocol> generic_protocol;
94 std::unique_ptr<IrsProtocol> irs_protocol;
95 std::unique_ptr<NfcProtocol> nfc_protocol;
96 std::unique_ptr<JoyconPoller> joycon_poller;
97 std::unique_ptr<RingConProtocol> ring_protocol;
98 std::unique_ptr<RumbleProtocol> rumble_protocol;
99
100 // Connection status
101 std::atomic<bool> is_connected{};
102 u64 delta_time;
103 std::size_t error_counter{};
104 std::shared_ptr<JoyconHandle> hidapi_handle;
105 std::chrono::time_point<std::chrono::steady_clock> last_update;
106
107 // External device status
108 bool starlink_connected{};
109 bool ring_connected{};
110 bool amiibo_detected{};
111 bool is_ring_disabled_by_irs{};
112
113 // Harware configuration
114 u8 leds{};
115 ReportMode mode{};
116 bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time
117 bool hidbus_enabled{}; // External device support
118 bool irs_enabled{}; // Infrared camera input
119 bool motion_enabled{}; // Enables motion input
120 bool nfc_enabled{}; // Enables Amiibo detection
121 bool vibration_enabled{}; // Allows vibrations
122
123 // Calibration data
124 GyroSensitivity gyro_sensitivity{};
125 GyroPerformance gyro_performance{};
126 AccelerometerSensitivity accelerometer_sensitivity{};
127 AccelerometerPerformance accelerometer_performance{};
128 JoyStickCalibration left_stick_calibration{};
129 JoyStickCalibration right_stick_calibration{};
130 MotionCalibration motion_calibration{};
131 RingCalibration ring_calibration{};
132
133 // Fixed joycon info
134 FirmwareVersion version{};
135 Color color{};
136 std::size_t port{};
137 ControllerType device_type{}; // Device type reported by controller
138 ControllerType handle_device_type{}; // Device type reported by hidapi
139 SerialNumber serial_number{}; // Serial number reported by controller
140 SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
141 SupportedFeatures supported_features{};
142
143 // Thread related
144 mutable std::mutex mutex;
145 std::jthread input_thread;
146 bool input_thread_running{};
147 bool disable_input_thread{};
148};
149
150} // 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..2b42a4555
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -0,0 +1,316 @@
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 = 15;
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 result = GetSubCommandResponse(sc, output);
117
118 return DriverResult::Success;
119}
120
121DriverResult JoyconCommonProtocol::SendSubCommand(SubCommand sc, std::span<const u8> buffer) {
122 SubCommandResponse output{};
123 return SendSubCommand(sc, buffer, output);
124}
125
126DriverResult JoyconCommonProtocol::SendMCUCommand(SubCommand sc, std::span<const u8> buffer) {
127 SubCommandPacket packet{
128 .output_report = OutputReport::MCU_DATA,
129 .packet_counter = GetCounter(),
130 .sub_command = sc,
131 .command_data = {},
132 };
133
134 if (buffer.size() > packet.command_data.size()) {
135 return DriverResult::InvalidParameters;
136 }
137
138 memcpy(packet.command_data.data(), buffer.data(), buffer.size());
139
140 return SendData(packet);
141}
142
143DriverResult JoyconCommonProtocol::SendVibrationReport(std::span<const u8> buffer) {
144 VibrationPacket packet{
145 .output_report = OutputReport::RUMBLE_ONLY,
146 .packet_counter = GetCounter(),
147 .vibration_data = {},
148 };
149
150 if (buffer.size() > packet.vibration_data.size()) {
151 return DriverResult::InvalidParameters;
152 }
153
154 memcpy(packet.vibration_data.data(), buffer.data(), buffer.size());
155
156 return SendData(packet);
157}
158
159DriverResult JoyconCommonProtocol::ReadRawSPI(SpiAddress addr, std::span<u8> output) {
160 constexpr std::size_t HeaderSize = 5;
161 constexpr std::size_t MaxTries = 10;
162 std::size_t tries = 0;
163 SubCommandResponse response{};
164 std::array<u8, sizeof(ReadSpiPacket)> buffer{};
165 const ReadSpiPacket packet_data{
166 .spi_address = addr,
167 .size = static_cast<u8>(output.size()),
168 };
169
170 memcpy(buffer.data(), &packet_data, sizeof(ReadSpiPacket));
171 do {
172 const auto result = SendSubCommand(SubCommand::SPI_FLASH_READ, buffer, response);
173 if (result != DriverResult::Success) {
174 return result;
175 }
176
177 if (tries++ > MaxTries) {
178 return DriverResult::Timeout;
179 }
180 } while (response.spi_address != addr);
181
182 if (response.command_data.size() < packet_data.size + HeaderSize) {
183 return DriverResult::WrongReply;
184 }
185
186 // Remove header from output
187 memcpy(output.data(), response.command_data.data() + HeaderSize, packet_data.size);
188 return DriverResult::Success;
189}
190
191DriverResult JoyconCommonProtocol::EnableMCU(bool enable) {
192 const std::array<u8, 1> mcu_state{static_cast<u8>(enable ? 1 : 0)};
193 const auto result = SendSubCommand(SubCommand::SET_MCU_STATE, mcu_state);
194
195 if (result != DriverResult::Success) {
196 LOG_ERROR(Input, "Failed with error {}", result);
197 }
198
199 return result;
200}
201
202DriverResult JoyconCommonProtocol::ConfigureMCU(const MCUConfig& config) {
203 LOG_DEBUG(Input, "ConfigureMCU");
204 std::array<u8, sizeof(MCUConfig)> config_buffer;
205 memcpy(config_buffer.data(), &config, sizeof(MCUConfig));
206 config_buffer[37] = CalculateMCU_CRC8(config_buffer.data() + 1, 36);
207
208 const auto result = SendSubCommand(SubCommand::SET_MCU_CONFIG, config_buffer);
209
210 if (result != DriverResult::Success) {
211 LOG_ERROR(Input, "Failed with error {}", result);
212 }
213
214 return result;
215}
216
217DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode,
218 MCUCommandResponse& output) {
219 constexpr int TimeoutMili = 200;
220 constexpr int MaxTries = 9;
221 int tries = 0;
222
223 do {
224 int result = SDL_hid_read_timeout(hidapi_handle->handle, reinterpret_cast<u8*>(&output),
225 sizeof(MCUCommandResponse), TimeoutMili);
226
227 if (result < 1) {
228 LOG_ERROR(Input, "No response from joycon attempt {}", tries);
229 }
230 if (tries++ > MaxTries) {
231 return DriverResult::Timeout;
232 }
233 } while (output.input_report.report_mode != report_mode ||
234 output.mcu_report == MCUReport::EmptyAwaitingCmd);
235
236 return DriverResult::Success;
237}
238
239DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc,
240 std::span<const u8> buffer,
241 MCUCommandResponse& output) {
242 SubCommandPacket packet{
243 .output_report = OutputReport::MCU_DATA,
244 .packet_counter = GetCounter(),
245 .sub_command = sc,
246 .command_data = {},
247 };
248
249 if (buffer.size() > packet.command_data.size()) {
250 return DriverResult::InvalidParameters;
251 }
252
253 memcpy(packet.command_data.data(), buffer.data(), buffer.size());
254
255 auto result = SendData(packet);
256
257 if (result != DriverResult::Success) {
258 return result;
259 }
260
261 result = GetMCUDataResponse(report_mode, output);
262
263 return DriverResult::Success;
264}
265
266DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMode mode) {
267 MCUCommandResponse output{};
268 constexpr std::size_t MaxTries{8};
269 std::size_t tries{};
270
271 do {
272 const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)};
273 const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output);
274
275 if (result != DriverResult::Success) {
276 return result;
277 }
278
279 if (tries++ > MaxTries) {
280 return DriverResult::WrongReply;
281 }
282 } while (output.mcu_report != MCUReport::StateReport ||
283 output.mcu_data[6] != static_cast<u8>(mode));
284
285 return DriverResult::Success;
286}
287
288// crc-8-ccitt / polynomial 0x07 look up table
289constexpr std::array<u8, 256> mcu_crc8_table = {
290 0x00, 0x07, 0x0E, 0x09, 0x1C, 0x1B, 0x12, 0x15, 0x38, 0x3F, 0x36, 0x31, 0x24, 0x23, 0x2A, 0x2D,
291 0x70, 0x77, 0x7E, 0x79, 0x6C, 0x6B, 0x62, 0x65, 0x48, 0x4F, 0x46, 0x41, 0x54, 0x53, 0x5A, 0x5D,
292 0xE0, 0xE7, 0xEE, 0xE9, 0xFC, 0xFB, 0xF2, 0xF5, 0xD8, 0xDF, 0xD6, 0xD1, 0xC4, 0xC3, 0xCA, 0xCD,
293 0x90, 0x97, 0x9E, 0x99, 0x8C, 0x8B, 0x82, 0x85, 0xA8, 0xAF, 0xA6, 0xA1, 0xB4, 0xB3, 0xBA, 0xBD,
294 0xC7, 0xC0, 0xC9, 0xCE, 0xDB, 0xDC, 0xD5, 0xD2, 0xFF, 0xF8, 0xF1, 0xF6, 0xE3, 0xE4, 0xED, 0xEA,
295 0xB7, 0xB0, 0xB9, 0xBE, 0xAB, 0xAC, 0xA5, 0xA2, 0x8F, 0x88, 0x81, 0x86, 0x93, 0x94, 0x9D, 0x9A,
296 0x27, 0x20, 0x29, 0x2E, 0x3B, 0x3C, 0x35, 0x32, 0x1F, 0x18, 0x11, 0x16, 0x03, 0x04, 0x0D, 0x0A,
297 0x57, 0x50, 0x59, 0x5E, 0x4B, 0x4C, 0x45, 0x42, 0x6F, 0x68, 0x61, 0x66, 0x73, 0x74, 0x7D, 0x7A,
298 0x89, 0x8E, 0x87, 0x80, 0x95, 0x92, 0x9B, 0x9C, 0xB1, 0xB6, 0xBF, 0xB8, 0xAD, 0xAA, 0xA3, 0xA4,
299 0xF9, 0xFE, 0xF7, 0xF0, 0xE5, 0xE2, 0xEB, 0xEC, 0xC1, 0xC6, 0xCF, 0xC8, 0xDD, 0xDA, 0xD3, 0xD4,
300 0x69, 0x6E, 0x67, 0x60, 0x75, 0x72, 0x7B, 0x7C, 0x51, 0x56, 0x5F, 0x58, 0x4D, 0x4A, 0x43, 0x44,
301 0x19, 0x1E, 0x17, 0x10, 0x05, 0x02, 0x0B, 0x0C, 0x21, 0x26, 0x2F, 0x28, 0x3D, 0x3A, 0x33, 0x34,
302 0x4E, 0x49, 0x40, 0x47, 0x52, 0x55, 0x5C, 0x5B, 0x76, 0x71, 0x78, 0x7F, 0x6A, 0x6D, 0x64, 0x63,
303 0x3E, 0x39, 0x30, 0x37, 0x22, 0x25, 0x2C, 0x2B, 0x06, 0x01, 0x08, 0x0F, 0x1A, 0x1D, 0x14, 0x13,
304 0xAE, 0xA9, 0xA0, 0xA7, 0xB2, 0xB5, 0xBC, 0xBB, 0x96, 0x91, 0x98, 0x9F, 0x8A, 0x8D, 0x84, 0x83,
305 0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4, 0xF3};
306
307u8 JoyconCommonProtocol::CalculateMCU_CRC8(u8* buffer, u8 size) const {
308 u8 crc8 = 0x0;
309
310 for (int i = 0; i < size; ++i) {
311 crc8 = mcu_crc8_table[static_cast<u8>(crc8 ^ buffer[i])];
312 }
313 return crc8;
314}
315
316} // 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..f44f73ba4
--- /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 matchs 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 correspoinding 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, SubCommand 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..b91934990
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -0,0 +1,697 @@
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>;
26
27enum class ControllerType : u8 {
28 None = 0x00,
29 Left = 0x01,
30 Right = 0x02,
31 Pro = 0x03,
32 Dual = 0x05, // TODO: Verify this id
33 LarkHvc1 = 0x07,
34 LarkHvc2 = 0x08,
35 LarkNesLeft = 0x09,
36 LarkNesRight = 0x0A,
37 Lucia = 0x0B,
38 Lagon = 0x0C,
39 Lager = 0x0D,
40};
41
42enum class PadAxes {
43 LeftStickX,
44 LeftStickY,
45 RightStickX,
46 RightStickY,
47 Undefined,
48};
49
50enum class PadMotion {
51 LeftMotion,
52 RightMotion,
53 Undefined,
54};
55
56enum class PadButton : u32 {
57 Down = 0x000001,
58 Up = 0x000002,
59 Right = 0x000004,
60 Left = 0x000008,
61 LeftSR = 0x000010,
62 LeftSL = 0x000020,
63 L = 0x000040,
64 ZL = 0x000080,
65 Y = 0x000100,
66 X = 0x000200,
67 B = 0x000400,
68 A = 0x000800,
69 RightSR = 0x001000,
70 RightSL = 0x002000,
71 R = 0x004000,
72 ZR = 0x008000,
73 Minus = 0x010000,
74 Plus = 0x020000,
75 StickR = 0x040000,
76 StickL = 0x080000,
77 Home = 0x100000,
78 Capture = 0x200000,
79};
80
81enum class PasivePadButton : u32 {
82 Down_A = 0x0001,
83 Right_X = 0x0002,
84 Left_B = 0x0004,
85 Up_Y = 0x0008,
86 SL = 0x0010,
87 SR = 0x0020,
88 Minus = 0x0100,
89 Plus = 0x0200,
90 StickL = 0x0400,
91 StickR = 0x0800,
92 Home = 0x1000,
93 Capture = 0x2000,
94 L_R = 0x4000,
95 ZL_ZR = 0x8000,
96};
97
98enum class OutputReport : u8 {
99 RUMBLE_AND_SUBCMD = 0x01,
100 FW_UPDATE_PKT = 0x03,
101 RUMBLE_ONLY = 0x10,
102 MCU_DATA = 0x11,
103 USB_CMD = 0x80,
104};
105
106enum class FeatureReport : u8 {
107 Last_SUBCMD = 0x02,
108 OTA_GW_UPGRADE = 0x70,
109 SETUP_MEM_READ = 0x71,
110 MEM_READ = 0x72,
111 ERASE_MEM_SECTOR = 0x73,
112 MEM_WRITE = 0x74,
113 LAUNCH = 0x75,
114};
115
116enum class SubCommand : u8 {
117 STATE = 0x00,
118 MANUAL_BT_PAIRING = 0x01,
119 REQ_DEV_INFO = 0x02,
120 SET_REPORT_MODE = 0x03,
121 TRIGGERS_ELAPSED = 0x04,
122 GET_PAGE_LIST_STATE = 0x05,
123 SET_HCI_STATE = 0x06,
124 RESET_PAIRING_INFO = 0x07,
125 LOW_POWER_MODE = 0x08,
126 SPI_FLASH_READ = 0x10,
127 SPI_FLASH_WRITE = 0x11,
128 SPI_SECTOR_ERASE = 0x12,
129 RESET_MCU = 0x20,
130 SET_MCU_CONFIG = 0x21,
131 SET_MCU_STATE = 0x22,
132 SET_PLAYER_LIGHTS = 0x30,
133 GET_PLAYER_LIGHTS = 0x31,
134 SET_HOME_LIGHT = 0x38,
135 ENABLE_IMU = 0x40,
136 SET_IMU_SENSITIVITY = 0x41,
137 WRITE_IMU_REG = 0x42,
138 READ_IMU_REG = 0x43,
139 ENABLE_VIBRATION = 0x48,
140 GET_REGULATED_VOLTAGE = 0x50,
141 SET_EXTERNAL_CONFIG = 0x58,
142 GET_EXTERNAL_DEVICE_INFO = 0x59,
143 ENABLE_EXTERNAL_POLLING = 0x5A,
144 DISABLE_EXTERNAL_POLLING = 0x5B,
145 SET_EXTERNAL_FORMAT_CONFIG = 0x5C,
146};
147
148enum class UsbSubCommand : u8 {
149 CONN_STATUS = 0x01,
150 HADSHAKE = 0x02,
151 BAUDRATE_3M = 0x03,
152 NO_TIMEOUT = 0x04,
153 EN_TIMEOUT = 0x05,
154 RESET = 0x06,
155 PRE_HANDSHAKE = 0x91,
156 SEND_UART = 0x92,
157};
158
159enum class CalibrationMagic : u8 {
160 USR_MAGIC_0 = 0xB2,
161 USR_MAGIC_1 = 0xA1,
162};
163
164enum class SpiAddress : u16 {
165 MAGIC = 0x0000,
166 MAC_ADDRESS = 0x0015,
167 PAIRING_INFO = 0x2000,
168 SHIPMENT = 0x5000,
169 SERIAL_NUMBER = 0x6000,
170 DEVICE_TYPE = 0x6012,
171 FORMAT_VERSION = 0x601B,
172 FACT_IMU_DATA = 0x6020,
173 FACT_LEFT_DATA = 0x603d,
174 FACT_RIGHT_DATA = 0x6046,
175 COLOR_DATA = 0x6050,
176 DESIGN_VARIATION = 0x605C,
177 SENSOR_DATA = 0x6080,
178 USER_LEFT_MAGIC = 0x8010,
179 USER_LEFT_DATA = 0x8012,
180 USER_RIGHT_MAGIC = 0x801B,
181 USER_RIGHT_DATA = 0x801D,
182 USER_IMU_MAGIC = 0x8026,
183 USER_IMU_DATA = 0x8028,
184};
185
186enum class ReportMode : u8 {
187 ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00,
188 ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01,
189 ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02,
190 ACTIVE_POLLING_IR_CAMERA_DATA = 0x03,
191 SUBCMD_REPLY = 0x21,
192 MCU_UPDATE_STATE = 0x23,
193 STANDARD_FULL_60HZ = 0x30,
194 NFC_IR_MODE_60HZ = 0x31,
195 SIMPLE_HID_MODE = 0x3F,
196 INPUT_USB_RESPONSE = 0x81,
197};
198
199enum class GyroSensitivity : u8 {
200 DPS250,
201 DPS500,
202 DPS1000,
203 DPS2000, // Default
204};
205
206enum class AccelerometerSensitivity : u8 {
207 G8, // Default
208 G4,
209 G2,
210 G16,
211};
212
213enum class GyroPerformance : u8 {
214 HZ833,
215 HZ208, // Default
216};
217
218enum class AccelerometerPerformance : u8 {
219 HZ200,
220 HZ100, // Default
221};
222
223enum class MCUCommand : u8 {
224 ConfigureMCU = 0x21,
225 ConfigureIR = 0x23,
226};
227
228enum class MCUSubCommand : u8 {
229 SetMCUMode = 0x0,
230 SetDeviceMode = 0x1,
231 ReadDeviceMode = 0x02,
232 WriteDeviceRegisters = 0x4,
233};
234
235enum class MCUMode : u8 {
236 Suspend = 0,
237 Standby = 1,
238 Ringcon = 3,
239 NFC = 4,
240 IR = 5,
241 MaybeFWUpdate = 6,
242};
243
244enum class MCURequest : u8 {
245 GetMCUStatus = 1,
246 GetNFCData = 2,
247 GetIRData = 3,
248};
249
250enum class MCUReport : u8 {
251 Empty = 0x00,
252 StateReport = 0x01,
253 IRData = 0x03,
254 BusyInitializing = 0x0b,
255 IRStatus = 0x13,
256 IRRegisters = 0x1b,
257 NFCState = 0x2a,
258 NFCReadData = 0x3a,
259 EmptyAwaitingCmd = 0xff,
260};
261
262enum class MCUPacketFlag : u8 {
263 MorePacketsRemaining = 0x00,
264 LastCommandPacket = 0x08,
265};
266
267enum class NFCReadCommand : u8 {
268 CancelAll = 0x00,
269 StartPolling = 0x01,
270 StopPolling = 0x02,
271 StartWaitingRecieve = 0x04,
272 Ntag = 0x06,
273 Mifare = 0x0F,
274};
275
276enum class NFCTagType : u8 {
277 AllTags = 0x00,
278 Ntag215 = 0x01,
279};
280
281enum class NFCPages {
282 Block0 = 0,
283 Block45 = 45,
284 Block135 = 135,
285 Block231 = 231,
286};
287
288enum class NFCStatus : u8 {
289 LastPackage = 0x04,
290 TagLost = 0x07,
291};
292
293enum class IrsMode : u8 {
294 None = 0x02,
295 Moment = 0x03,
296 Dpd = 0x04,
297 Clustering = 0x06,
298 ImageTransfer = 0x07,
299 Silhouette = 0x08,
300 TeraImage = 0x09,
301 SilhouetteTeraImage = 0x0A,
302};
303
304enum class IrsResolution {
305 Size320x240,
306 Size160x120,
307 Size80x60,
308 Size40x30,
309 Size20x15,
310 None,
311};
312
313enum class IrsResolutionCode : u8 {
314 Size320x240 = 0x00, // Full pixel array
315 Size160x120 = 0x50, // Sensor Binning [2 X 2]
316 Size80x60 = 0x64, // Sensor Binning [4 x 2] and Skipping [1 x 2]
317 Size40x30 = 0x69, // Sensor Binning [4 x 2] and Skipping [2 x 4]
318 Size20x15 = 0x6A, // Sensor Binning [4 x 2] and Skipping [4 x 4]
319};
320
321// Size of image divided by 300
322enum class IrsFragments : u8 {
323 Size20x15 = 0x00,
324 Size40x30 = 0x03,
325 Size80x60 = 0x0f,
326 Size160x120 = 0x3f,
327 Size320x240 = 0xFF,
328};
329
330enum class IrLeds : u8 {
331 BrightAndDim = 0x00,
332 Bright = 0x20,
333 Dim = 0x10,
334 None = 0x30,
335};
336
337enum class IrExLedFilter : u8 {
338 Disabled = 0x00,
339 Enabled = 0x03,
340};
341
342enum class IrImageFlip : u8 {
343 Normal = 0x00,
344 Inverted = 0x02,
345};
346
347enum class IrRegistersAddress : u16 {
348 UpdateTime = 0x0400,
349 FinalizeConfig = 0x0700,
350 LedFilter = 0x0e00,
351 Leds = 0x1000,
352 LedIntensitiyMSB = 0x1100,
353 LedIntensitiyLSB = 0x1200,
354 ImageFlip = 0x2d00,
355 Resolution = 0x2e00,
356 DigitalGainLSB = 0x2e01,
357 DigitalGainMSB = 0x2f01,
358 ExposureLSB = 0x3001,
359 ExposureMSB = 0x3101,
360 ExposureTime = 0x3201,
361 WhitePixelThreshold = 0x4301,
362 DenoiseSmoothing = 0x6701,
363 DenoiseEdge = 0x6801,
364 DenoiseColor = 0x6901,
365};
366
367enum class ExternalDeviceId : u16 {
368 RingController = 0x2000,
369 Starlink = 0x2800,
370};
371
372enum class DriverResult {
373 Success,
374 WrongReply,
375 Timeout,
376 InvalidParameters,
377 UnsupportedControllerType,
378 HandleInUse,
379 ErrorReadingData,
380 ErrorWritingData,
381 NoDeviceDetected,
382 InvalidHandle,
383 NotSupported,
384 Disabled,
385 Unknown,
386};
387
388struct MotionSensorCalibration {
389 s16 offset;
390 s16 scale;
391};
392
393struct MotionCalibration {
394 std::array<MotionSensorCalibration, 3> accelerometer;
395 std::array<MotionSensorCalibration, 3> gyro;
396};
397
398// Basic motion data containing data from the sensors and a timestamp in microseconds
399struct MotionData {
400 float gyro_x{};
401 float gyro_y{};
402 float gyro_z{};
403 float accel_x{};
404 float accel_y{};
405 float accel_z{};
406 u64 delta_timestamp{};
407};
408
409// Output from SPI read command containing user calibration magic
410struct MagicSpiCalibration {
411 CalibrationMagic first;
412 CalibrationMagic second;
413};
414static_assert(sizeof(MagicSpiCalibration) == 0x2, "MagicSpiCalibration is an invalid size");
415
416// Output from SPI read command containing left joystick calibration
417struct JoystickLeftSpiCalibration {
418 std::array<u8, 3> max;
419 std::array<u8, 3> center;
420 std::array<u8, 3> min;
421};
422static_assert(sizeof(JoystickLeftSpiCalibration) == 0x9,
423 "JoystickLeftSpiCalibration is an invalid size");
424
425// Output from SPI read command containing right joystick calibration
426struct JoystickRightSpiCalibration {
427 std::array<u8, 3> center;
428 std::array<u8, 3> min;
429 std::array<u8, 3> max;
430};
431static_assert(sizeof(JoystickRightSpiCalibration) == 0x9,
432 "JoystickRightSpiCalibration is an invalid size");
433
434struct JoyStickAxisCalibration {
435 u16 max;
436 u16 min;
437 u16 center;
438};
439
440struct JoyStickCalibration {
441 JoyStickAxisCalibration x;
442 JoyStickAxisCalibration y;
443};
444
445struct ImuSpiCalibration {
446 std::array<s16, 3> accelerometer_offset;
447 std::array<s16, 3> accelerometer_scale;
448 std::array<s16, 3> gyroscope_offset;
449 std::array<s16, 3> gyroscope_scale;
450};
451static_assert(sizeof(ImuSpiCalibration) == 0x18, "ImuSpiCalibration is an invalid size");
452
453struct RingCalibration {
454 s16 default_value;
455 s16 max_value;
456 s16 min_value;
457};
458
459struct Color {
460 u32 body;
461 u32 buttons;
462 u32 left_grip;
463 u32 right_grip;
464};
465
466struct Battery {
467 union {
468 u8 raw{};
469
470 BitField<0, 4, u8> unknown;
471 BitField<4, 1, u8> charging;
472 BitField<5, 3, u8> status;
473 };
474};
475
476struct VibrationValue {
477 f32 low_amplitude;
478 f32 low_frequency;
479 f32 high_amplitude;
480 f32 high_frequency;
481};
482
483struct JoyconHandle {
484 SDL_hid_device* handle = nullptr;
485 u8 packet_counter{};
486};
487
488struct MCUConfig {
489 MCUCommand command;
490 MCUSubCommand sub_command;
491 MCUMode mode;
492 INSERT_PADDING_BYTES(0x22);
493 u8 crc;
494};
495static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size");
496
497#pragma pack(push, 1)
498struct InputReportPassive {
499 ReportMode report_mode;
500 u16 button_input;
501 u8 stick_state;
502 std::array<u8, 10> unknown_data;
503};
504static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size");
505
506struct InputReportActive {
507 ReportMode report_mode;
508 u8 packet_id;
509 Battery battery_status;
510 std::array<u8, 3> button_input;
511 std::array<u8, 3> left_stick_state;
512 std::array<u8, 3> right_stick_state;
513 u8 vibration_code;
514 std::array<s16, 6 * 2> motion_input;
515 INSERT_PADDING_BYTES(0x2);
516 s16 ring_input;
517};
518static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size");
519
520struct InputReportNfcIr {
521 ReportMode report_mode;
522 u8 packet_id;
523 Battery battery_status;
524 std::array<u8, 3> button_input;
525 std::array<u8, 3> left_stick_state;
526 std::array<u8, 3> right_stick_state;
527 u8 vibration_code;
528 std::array<s16, 6 * 2> motion_input;
529 INSERT_PADDING_BYTES(0x4);
530};
531static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size");
532#pragma pack(pop)
533
534struct NFCReadBlock {
535 u8 start;
536 u8 end;
537};
538static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size");
539
540struct NFCReadBlockCommand {
541 u8 block_count{};
542 std::array<NFCReadBlock, 4> blocks{};
543};
544static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size");
545
546struct NFCReadCommandData {
547 u8 unknown;
548 u8 uuid_length;
549 u8 unknown_2;
550 std::array<u8, 6> uid;
551 NFCTagType tag_type;
552 NFCReadBlockCommand read_block;
553};
554static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size");
555
556struct NFCPollingCommandData {
557 u8 enable_mifare;
558 u8 unknown_1;
559 u8 unknown_2;
560 u8 unknown_3;
561 u8 unknown_4;
562};
563static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size");
564
565struct NFCRequestState {
566 MCUSubCommand sub_command;
567 NFCReadCommand command_argument;
568 u8 packet_id;
569 INSERT_PADDING_BYTES(0x1);
570 MCUPacketFlag packet_flag;
571 u8 data_length;
572 union {
573 std::array<u8, 0x1F> raw_data;
574 NFCReadCommandData nfc_read;
575 NFCPollingCommandData nfc_polling;
576 };
577 u8 crc;
578};
579static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
580
581struct IrsConfigure {
582 MCUCommand command;
583 MCUSubCommand sub_command;
584 IrsMode irs_mode;
585 IrsFragments number_of_fragments;
586 u16 mcu_major_version;
587 u16 mcu_minor_version;
588 INSERT_PADDING_BYTES(0x1D);
589 u8 crc;
590};
591static_assert(sizeof(IrsConfigure) == 0x26, "IrsConfigure is an invalid size");
592
593#pragma pack(push, 1)
594struct IrsRegister {
595 IrRegistersAddress address;
596 u8 value;
597};
598static_assert(sizeof(IrsRegister) == 0x3, "IrsRegister is an invalid size");
599
600struct IrsWriteRegisters {
601 MCUCommand command;
602 MCUSubCommand sub_command;
603 u8 number_of_registers;
604 std::array<IrsRegister, 9> registers;
605 INSERT_PADDING_BYTES(0x7);
606 u8 crc;
607};
608static_assert(sizeof(IrsWriteRegisters) == 0x26, "IrsWriteRegisters is an invalid size");
609#pragma pack(pop)
610
611struct FirmwareVersion {
612 u8 major;
613 u8 minor;
614};
615static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
616
617struct DeviceInfo {
618 FirmwareVersion firmware;
619 std::array<u8, 2> unknown_1;
620 MacAddress mac_address;
621 std::array<u8, 2> unknown_2;
622};
623static_assert(sizeof(DeviceInfo) == 0xC, "DeviceInfo is an invalid size");
624
625struct MotionStatus {
626 bool is_enabled;
627 u64 delta_time;
628 GyroSensitivity gyro_sensitivity;
629 AccelerometerSensitivity accelerometer_sensitivity;
630};
631
632struct RingStatus {
633 bool is_enabled;
634 s16 default_value;
635 s16 max_value;
636 s16 min_value;
637};
638
639struct VibrationPacket {
640 OutputReport output_report;
641 u8 packet_counter;
642 std::array<u8, 0x8> vibration_data;
643};
644static_assert(sizeof(VibrationPacket) == 0xA, "VibrationPacket is an invalid size");
645
646struct SubCommandPacket {
647 OutputReport output_report;
648 u8 packet_counter;
649 INSERT_PADDING_BYTES(0x8); // This contains vibration data
650 SubCommand sub_command;
651 std::array<u8, 0x26> command_data;
652};
653static_assert(sizeof(SubCommandPacket) == 0x31, "SubCommandPacket is an invalid size");
654
655#pragma pack(push, 1)
656struct ReadSpiPacket {
657 SpiAddress spi_address;
658 INSERT_PADDING_BYTES(0x2);
659 u8 size;
660};
661static_assert(sizeof(ReadSpiPacket) == 0x5, "ReadSpiPacket is an invalid size");
662
663struct SubCommandResponse {
664 InputReportPassive input_report;
665 SubCommand sub_command;
666 union {
667 std::array<u8, 0x30> command_data;
668 SpiAddress spi_address; // Reply from SPI_FLASH_READ subcommand
669 ExternalDeviceId external_device_id; // Reply from GET_EXTERNAL_DEVICE_INFO subcommand
670 DeviceInfo device_info; // Reply from REQ_DEV_INFO subcommand
671 };
672 u8 crc; // This is never used
673};
674static_assert(sizeof(SubCommandResponse) == 0x40, "SubCommandResponse is an invalid size");
675#pragma pack(pop)
676
677struct MCUCommandResponse {
678 InputReportNfcIr input_report;
679 INSERT_PADDING_BYTES(0x8);
680 MCUReport mcu_report;
681 std::array<u8, 0x13D> mcu_data;
682 u8 crc;
683};
684static_assert(sizeof(MCUCommandResponse) == 0x170, "MCUCommandResponse is an invalid size");
685
686struct JoyconCallbacks {
687 std::function<void(Battery)> on_battery_data;
688 std::function<void(Color)> on_color_data;
689 std::function<void(int, bool)> on_button_data;
690 std::function<void(int, f32)> on_stick_data;
691 std::function<void(int, const MotionData&)> on_motion_data;
692 std::function<void(f32)> on_ring_data;
693 std::function<void(const std::vector<u8>&)> on_amiibo_data;
694 std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data;
695};
696
697} // 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..eeba82986
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.cpp
@@ -0,0 +1,406 @@
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
38 return result;
39}
40
41DriverResult NfcProtocol::DisableNfc() {
42 LOG_DEBUG(Input, "Disable NFC");
43 ScopedSetBlocking sb(this);
44 DriverResult result{DriverResult::Success};
45
46 if (result == DriverResult::Success) {
47 result = EnableMCU(false);
48 }
49
50 is_enabled = false;
51
52 return result;
53}
54
55DriverResult NfcProtocol::StartNFCPollingMode() {
56 LOG_DEBUG(Input, "Start NFC pooling Mode");
57 ScopedSetBlocking sb(this);
58 DriverResult result{DriverResult::Success};
59 TagFoundData tag_data{};
60
61 if (result == DriverResult::Success) {
62 result = WaitSetMCUMode(ReportMode::NFC_IR_MODE_60HZ, MCUMode::NFC);
63 }
64 if (result == DriverResult::Success) {
65 result = WaitUntilNfcIsReady();
66 }
67 if (result == DriverResult::Success) {
68 is_enabled = true;
69 }
70
71 return result;
72}
73
74DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
75 LOG_DEBUG(Input, "Start NFC pooling Mode");
76 ScopedSetBlocking sb(this);
77 DriverResult result{DriverResult::Success};
78 TagFoundData tag_data{};
79
80 if (result == DriverResult::Success) {
81 result = StartPolling(tag_data);
82 }
83 if (result == DriverResult::Success) {
84 result = ReadTag(tag_data);
85 }
86 if (result == DriverResult::Success) {
87 result = WaitUntilNfcIsReady();
88 }
89 if (result == DriverResult::Success) {
90 result = StartPolling(tag_data);
91 }
92 if (result == DriverResult::Success) {
93 result = GetAmiiboData(data);
94 }
95
96 return result;
97}
98
99bool NfcProtocol::HasAmiibo() {
100 ScopedSetBlocking sb(this);
101 DriverResult result{DriverResult::Success};
102 TagFoundData tag_data{};
103
104 if (result == DriverResult::Success) {
105 result = StartPolling(tag_data);
106 }
107
108 return result == DriverResult::Success;
109}
110
111DriverResult NfcProtocol::WaitUntilNfcIsReady() {
112 constexpr std::size_t timeout_limit = 10;
113 MCUCommandResponse output{};
114 std::size_t tries = 0;
115
116 do {
117 auto result = SendStartWaitingRecieveRequest(output);
118
119 if (result != DriverResult::Success) {
120 return result;
121 }
122 if (tries++ > timeout_limit) {
123 return DriverResult::Timeout;
124 }
125 } while (output.mcu_report != MCUReport::NFCState ||
126 (output.mcu_data[1] << 8) + output.mcu_data[0] != 0x0500 ||
127 output.mcu_data[5] != 0x31 || output.mcu_data[6] != 0x00);
128
129 return DriverResult::Success;
130}
131
132DriverResult NfcProtocol::StartPolling(TagFoundData& data) {
133 LOG_DEBUG(Input, "Start Polling for tag");
134 constexpr std::size_t timeout_limit = 7;
135 MCUCommandResponse output{};
136 std::size_t tries = 0;
137
138 do {
139 const auto result = SendStartPollingRequest(output);
140 if (result != DriverResult::Success) {
141 return result;
142 }
143 if (tries++ > timeout_limit) {
144 return DriverResult::Timeout;
145 }
146 } while (output.mcu_report != MCUReport::NFCState ||
147 (output.mcu_data[1] << 8) + output.mcu_data[0] != 0x0500 ||
148 output.mcu_data[6] != 0x09);
149
150 data.type = output.mcu_data[12];
151 data.uuid.resize(output.mcu_data[14]);
152 memcpy(data.uuid.data(), output.mcu_data.data() + 15, data.uuid.size());
153
154 return DriverResult::Success;
155}
156
157DriverResult NfcProtocol::ReadTag(const TagFoundData& data) {
158 constexpr std::size_t timeout_limit = 10;
159 MCUCommandResponse output{};
160 std::size_t tries = 0;
161
162 std::string uuid_string;
163 for (auto& content : data.uuid) {
164 uuid_string += fmt::format(" {:02x}", content);
165 }
166
167 LOG_INFO(Input, "Tag detected, type={}, uuid={}", data.type, uuid_string);
168
169 tries = 0;
170 NFCPages ntag_pages = NFCPages::Block0;
171 // Read Tag data
172 while (true) {
173 auto result = SendReadAmiiboRequest(output, ntag_pages);
174 const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
175
176 if (result != DriverResult::Success) {
177 return result;
178 }
179
180 if ((output.mcu_report == MCUReport::NFCReadData ||
181 output.mcu_report == MCUReport::NFCState) &&
182 nfc_status == NFCStatus::TagLost) {
183 return DriverResult::ErrorReadingData;
184 }
185
186 if (output.mcu_report == MCUReport::NFCReadData && output.mcu_data[1] == 0x07 &&
187 output.mcu_data[2] == 0x01) {
188 if (data.type != 2) {
189 continue;
190 }
191 switch (output.mcu_data[24]) {
192 case 0:
193 ntag_pages = NFCPages::Block135;
194 break;
195 case 3:
196 ntag_pages = NFCPages::Block45;
197 break;
198 case 4:
199 ntag_pages = NFCPages::Block231;
200 break;
201 default:
202 return DriverResult::ErrorReadingData;
203 }
204 continue;
205 }
206
207 if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
208 // finished
209 SendStopPollingRequest(output);
210 return DriverResult::Success;
211 }
212
213 // Ignore other state reports
214 if (output.mcu_report == MCUReport::NFCState) {
215 continue;
216 }
217
218 if (tries++ > timeout_limit) {
219 return DriverResult::Timeout;
220 }
221 }
222
223 return DriverResult::Success;
224}
225
226DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) {
227 constexpr std::size_t timeout_limit = 10;
228 MCUCommandResponse output{};
229 std::size_t tries = 0;
230
231 NFCPages ntag_pages = NFCPages::Block135;
232 std::size_t ntag_buffer_pos = 0;
233 // Read Tag data
234 while (true) {
235 auto result = SendReadAmiiboRequest(output, ntag_pages);
236 const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
237
238 if (result != DriverResult::Success) {
239 return result;
240 }
241
242 if ((output.mcu_report == MCUReport::NFCReadData ||
243 output.mcu_report == MCUReport::NFCState) &&
244 nfc_status == NFCStatus::TagLost) {
245 return DriverResult::ErrorReadingData;
246 }
247
248 if (output.mcu_report == MCUReport::NFCReadData && output.mcu_data[1] == 0x07) {
249 std::size_t payload_size = (output.mcu_data[4] << 8 | output.mcu_data[5]) & 0x7FF;
250 if (output.mcu_data[2] == 0x01) {
251 memcpy(ntag_data.data() + ntag_buffer_pos, output.mcu_data.data() + 66,
252 payload_size - 60);
253 ntag_buffer_pos += payload_size - 60;
254 } else {
255 memcpy(ntag_data.data() + ntag_buffer_pos, output.mcu_data.data() + 6,
256 payload_size);
257 }
258 continue;
259 }
260
261 if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::LastPackage) {
262 LOG_INFO(Input, "Finished reading amiibo");
263 return DriverResult::Success;
264 }
265
266 // Ignore other state reports
267 if (output.mcu_report == MCUReport::NFCState) {
268 continue;
269 }
270
271 if (tries++ > timeout_limit) {
272 return DriverResult::Timeout;
273 }
274 }
275
276 return DriverResult::Success;
277}
278
279DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output) {
280 NFCRequestState request{
281 .sub_command = MCUSubCommand::ReadDeviceMode,
282 .command_argument = NFCReadCommand::StartPolling,
283 .packet_id = 0x0,
284 .packet_flag = MCUPacketFlag::LastCommandPacket,
285 .data_length = sizeof(NFCPollingCommandData),
286 .nfc_polling =
287 {
288 .enable_mifare = 0x01,
289 .unknown_1 = 0x00,
290 .unknown_2 = 0x00,
291 .unknown_3 = 0x2c,
292 .unknown_4 = 0x01,
293 },
294 .crc = {},
295 };
296
297 std::array<u8, sizeof(NFCRequestState)> request_data{};
298 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
299 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
300 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
301}
302
303DriverResult NfcProtocol::SendStopPollingRequest(MCUCommandResponse& output) {
304 NFCRequestState request{
305 .sub_command = MCUSubCommand::ReadDeviceMode,
306 .command_argument = NFCReadCommand::StopPolling,
307 .packet_id = 0x0,
308 .packet_flag = MCUPacketFlag::LastCommandPacket,
309 .data_length = 0,
310 .raw_data = {},
311 .crc = {},
312 };
313
314 std::array<u8, sizeof(NFCRequestState)> request_data{};
315 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
316 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
317 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
318}
319
320DriverResult NfcProtocol::SendStartWaitingRecieveRequest(MCUCommandResponse& output) {
321 NFCRequestState request{
322 .sub_command = MCUSubCommand::ReadDeviceMode,
323 .command_argument = NFCReadCommand::StartWaitingRecieve,
324 .packet_id = 0x0,
325 .packet_flag = MCUPacketFlag::LastCommandPacket,
326 .data_length = 0,
327 .raw_data = {},
328 .crc = {},
329 };
330
331 std::vector<u8> request_data(sizeof(NFCRequestState));
332 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
333 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
334 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
335}
336
337DriverResult NfcProtocol::SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages) {
338 NFCRequestState request{
339 .sub_command = MCUSubCommand::ReadDeviceMode,
340 .command_argument = NFCReadCommand::Ntag,
341 .packet_id = 0x0,
342 .packet_flag = MCUPacketFlag::LastCommandPacket,
343 .data_length = sizeof(NFCReadCommandData),
344 .nfc_read =
345 {
346 .unknown = 0xd0,
347 .uuid_length = 0x07,
348 .unknown_2 = 0x00,
349 .uid = {},
350 .tag_type = NFCTagType::AllTags,
351 .read_block = GetReadBlockCommand(ntag_pages),
352 },
353 .crc = {},
354 };
355
356 std::array<u8, sizeof(NFCRequestState)> request_data{};
357 memcpy(request_data.data(), &request, sizeof(NFCRequestState));
358 request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
359 return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
360}
361
362NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const {
363 switch (pages) {
364 case NFCPages::Block0:
365 return {
366 .block_count = 1,
367 };
368 case NFCPages::Block45:
369 return {
370 .block_count = 1,
371 .blocks =
372 {
373 NFCReadBlock{0x00, 0x2C},
374 },
375 };
376 case NFCPages::Block135:
377 return {
378 .block_count = 3,
379 .blocks =
380 {
381 NFCReadBlock{0x00, 0x3b},
382 {0x3c, 0x77},
383 {0x78, 0x86},
384 },
385 };
386 case NFCPages::Block231:
387 return {
388 .block_count = 4,
389 .blocks =
390 {
391 NFCReadBlock{0x00, 0x3b},
392 {0x3c, 0x77},
393 {0x78, 0x83},
394 {0xb4, 0xe6},
395 },
396 };
397 default:
398 return {};
399 };
400}
401
402bool NfcProtocol::IsEnabled() const {
403 return is_enabled;
404}
405
406} // 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..11e263e07
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/nfc.h
@@ -0,0 +1,61 @@
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 ScanAmiibo(std::vector<u8>& data);
29
30 bool HasAmiibo();
31
32 bool IsEnabled() const;
33
34private:
35 struct TagFoundData {
36 u8 type;
37 std::vector<u8> uuid;
38 };
39
40 DriverResult WaitUntilNfcIsReady();
41
42 DriverResult StartPolling(TagFoundData& data);
43
44 DriverResult ReadTag(const TagFoundData& data);
45
46 DriverResult GetAmiiboData(std::vector<u8>& data);
47
48 DriverResult SendStartPollingRequest(MCUCommandResponse& output);
49
50 DriverResult SendStopPollingRequest(MCUCommandResponse& output);
51
52 DriverResult SendStartWaitingRecieveRequest(MCUCommandResponse& output);
53
54 DriverResult SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages);
55
56 NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const;
57
58 bool is_enabled{};
59};
60
61} // 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..9bb15e935
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -0,0 +1,337 @@
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 Joycon::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 Joycon::ControllerType::Left:
26 UpdateActiveLeftPadInput(data, motion_status);
27 break;
28 case Joycon::ControllerType::Right:
29 UpdateActiveRightPadInput(data, motion_status);
30 break;
31 case Joycon::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 Joycon::ControllerType::Left:
51 UpdatePasiveLeftPadInput(data);
52 break;
53 case Joycon::ControllerType::Right:
54 UpdatePasiveRightPadInput(data);
55 break;
56 case Joycon::ControllerType::Pro:
57 UpdatePasiveProPadInput(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 std::vector<u8>& amiibo_data) {
74 callbacks.on_amiibo_data(amiibo_data);
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::UpdatePasiveLeftPadInput(const InputReportPassive& input) {
214 static constexpr std::array<Joycon::PasivePadButton, 11> left_buttons{
215 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
216 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
217 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
218 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
219 Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Capture,
220 Joycon::PasivePadButton::StickL,
221 };
222
223 for (auto left_button : left_buttons) {
224 const bool button_status = (input.button_input & static_cast<u32>(left_button)) != 0;
225 const int button = static_cast<int>(left_button);
226 callbacks.on_button_data(button, button_status);
227 }
228}
229
230void JoyconPoller::UpdatePasiveRightPadInput(const InputReportPassive& input) {
231 static constexpr std::array<Joycon::PasivePadButton, 11> right_buttons{
232 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
233 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
234 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
235 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
236 Joycon::PasivePadButton::Plus, Joycon::PasivePadButton::Home,
237 Joycon::PasivePadButton::StickR,
238 };
239
240 for (auto right_button : right_buttons) {
241 const bool button_status = (input.button_input & static_cast<u32>(right_button)) != 0;
242 const int button = static_cast<int>(right_button);
243 callbacks.on_button_data(button, button_status);
244 }
245}
246
247void JoyconPoller::UpdatePasiveProPadInput(const InputReportPassive& input) {
248 static constexpr std::array<Joycon::PasivePadButton, 14> pro_buttons{
249 Joycon::PasivePadButton::Down_A, Joycon::PasivePadButton::Right_X,
250 Joycon::PasivePadButton::Left_B, Joycon::PasivePadButton::Up_Y,
251 Joycon::PasivePadButton::SL, Joycon::PasivePadButton::SR,
252 Joycon::PasivePadButton::L_R, Joycon::PasivePadButton::ZL_ZR,
253 Joycon::PasivePadButton::Minus, Joycon::PasivePadButton::Plus,
254 Joycon::PasivePadButton::Capture, Joycon::PasivePadButton::Home,
255 Joycon::PasivePadButton::StickL, Joycon::PasivePadButton::StickR,
256 };
257
258 for (auto pro_button : pro_buttons) {
259 const bool button_status = (input.button_input & static_cast<u32>(pro_button)) != 0;
260 const int button = static_cast<int>(pro_button);
261 callbacks.on_button_data(button, button_status);
262 }
263}
264
265f32 JoyconPoller::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const {
266 const f32 value = static_cast<f32>(raw_value - calibration.center);
267 if (value > 0.0f) {
268 return value / calibration.max;
269 }
270 return value / calibration.min;
271}
272
273f32 JoyconPoller::GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
274 AccelerometerSensitivity sensitivity) const {
275 const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
276 switch (sensitivity) {
277 case Joycon::AccelerometerSensitivity::G2:
278 return value / 4.0f;
279 case Joycon::AccelerometerSensitivity::G4:
280 return value / 2.0f;
281 case Joycon::AccelerometerSensitivity::G8:
282 return value;
283 case Joycon::AccelerometerSensitivity::G16:
284 return value * 2.0f;
285 }
286 return value;
287}
288
289f32 JoyconPoller::GetGyroValue(s16 raw, const MotionSensorCalibration& cal,
290 GyroSensitivity sensitivity) const {
291 const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
292 switch (sensitivity) {
293 case Joycon::GyroSensitivity::DPS250:
294 return value / 8.0f;
295 case Joycon::GyroSensitivity::DPS500:
296 return value / 4.0f;
297 case Joycon::GyroSensitivity::DPS1000:
298 return value / 2.0f;
299 case Joycon::GyroSensitivity::DPS2000:
300 return value;
301 }
302 return value;
303}
304
305s16 JoyconPoller::GetRawIMUValues(std::size_t sensor, size_t axis,
306 const InputReportActive& input) const {
307 return input.motion_input[(sensor * 3) + axis];
308}
309
310MotionData JoyconPoller::GetMotionInput(const InputReportActive& input,
311 const MotionStatus& motion_status) const {
312 MotionData motion{};
313 const auto& accel_cal = motion_calibration.accelerometer;
314 const auto& gyro_cal = motion_calibration.gyro;
315 const s16 raw_accel_x = input.motion_input[1];
316 const s16 raw_accel_y = input.motion_input[0];
317 const s16 raw_accel_z = input.motion_input[2];
318 const s16 raw_gyro_x = input.motion_input[4];
319 const s16 raw_gyro_y = input.motion_input[3];
320 const s16 raw_gyro_z = input.motion_input[5];
321
322 motion.delta_timestamp = motion_status.delta_time;
323 motion.accel_x =
324 GetAccelerometerValue(raw_accel_x, accel_cal[1], motion_status.accelerometer_sensitivity);
325 motion.accel_y =
326 GetAccelerometerValue(raw_accel_y, accel_cal[0], motion_status.accelerometer_sensitivity);
327 motion.accel_z =
328 GetAccelerometerValue(raw_accel_z, accel_cal[2], motion_status.accelerometer_sensitivity);
329 motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], motion_status.gyro_sensitivity);
330 motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], motion_status.gyro_sensitivity);
331 motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], motion_status.gyro_sensitivity);
332
333 // TODO(German77): Return all three samples data
334 return motion;
335}
336
337} // 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..354d41dad
--- /dev/null
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -0,0 +1,81 @@
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 Joycon::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 std::vector<u8>& amiibo_data);
40 void UpdateCamera(const std::vector<u8>& amiibo_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 UpdatePasiveLeftPadInput(const InputReportPassive& buffer);
50 void UpdatePasiveRightPadInput(const InputReportPassive& buffer);
51 void UpdatePasiveProPadInput(const InputReportPassive& buffer);
52
53 /// Returns a calibrated joystick axis from raw axis data
54 f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const;
55
56 /// Returns a calibrated accelerometer axis from raw motion data
57 f32 GetAccelerometerValue(s16 raw, const MotionSensorCalibration& cal,
58 AccelerometerSensitivity sensitivity) const;
59
60 /// Returns a calibrated gyro axis from raw motion data
61 f32 GetGyroValue(s16 raw_value, const MotionSensorCalibration& cal,
62 GyroSensitivity sensitivity) const;
63
64 /// Returns a raw motion value from a buffer
65 s16 GetRawIMUValues(size_t sensor, size_t axis, const InputReportActive& input) const;
66
67 /// Returns motion data from a buffer
68 MotionData GetMotionInput(const InputReportActive& input,
69 const MotionStatus& motion_status) const;
70
71 ControllerType device_type{};
72
73 // Device calibration
74 JoyStickCalibration left_stick_calibration{};
75 JoyStickCalibration right_stick_calibration{};
76 MotionCalibration motion_calibration{};
77
78 Joycon::JoyconCallbacks callbacks{};
79};
80
81} // 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