summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/input.h27
-rw-r--r--src/core/CMakeLists.txt9
-rw-r--r--src/core/hid/emulated_controller.cpp72
-rw-r--r--src/core/hid/emulated_controller.h34
-rw-r--r--src/core/hid/input_converter.cpp14
-rw-r--r--src/core/hid/input_converter.h8
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp6
-rw-r--r--src/core/hle/service/hid/controllers/palma.cpp229
-rw-r--r--src/core/hle/service/hid/controllers/palma.h163
-rw-r--r--src/core/hle/service/hid/errors.h2
-rw-r--r--src/core/hle/service/hid/hid.cpp447
-rw-r--r--src/core/hle/service/hid/hid.h29
-rw-r--r--src/core/hle/service/hid/irs.cpp3
-rw-r--r--src/core/hle/service/ldn/lan_discovery.cpp633
-rw-r--r--src/core/hle/service/ldn/lan_discovery.h134
-rw-r--r--src/core/hle/service/ldn/ldn.cpp227
-rw-r--r--src/core/hle/service/ldn/ldn_types.h48
-rw-r--r--src/core/hle/service/mii/mii_manager.cpp161
-rw-r--r--src/core/hle/service/mii/mii_manager.h4
-rw-r--r--src/core/hle/service/nfc/nfc.cpp8
-rw-r--r--src/core/hle/service/nfp/amiibo_crypto.cpp77
-rw-r--r--src/core/hle/service/nfp/amiibo_crypto.h10
-rw-r--r--src/core/hle/service/nfp/nfp.cpp1093
-rw-r--r--src/core/hle/service/nfp/nfp.h161
-rw-r--r--src/core/hle/service/nfp/nfp_device.cpp676
-rw-r--r--src/core/hle/service/nfp/nfp_device.h101
-rw-r--r--src/core/hle/service/nfp/nfp_result.h22
-rw-r--r--src/core/hle/service/nfp/nfp_types.h (renamed from src/core/hle/service/nfp/amiibo_types.h)116
-rw-r--r--src/core/hle/service/nfp/nfp_user.cpp634
-rw-r--r--src/core/hle/service/nfp/nfp_user.h44
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.cpp7
-rw-r--r--src/core/hle/service/nvflinger/nvflinger.h6
-rw-r--r--src/core/hle/service/vi/display/vi_display.cpp15
-rw-r--r--src/core/hle/service/vi/display/vi_display.h14
-rw-r--r--src/core/hle/service/vi/vi.cpp40
-rw-r--r--src/core/hle/service/vi/vi_results.h13
-rw-r--r--src/core/internal_network/network_interface.cpp12
-rw-r--r--src/core/internal_network/network_interface.h1
-rw-r--r--src/core/internal_network/socket_proxy.cpp8
-rw-r--r--src/core/loader/loader.cpp4
-rw-r--r--src/dedicated_room/yuzu_room.cpp13
-rw-r--r--src/input_common/CMakeLists.txt2
-rw-r--r--src/input_common/drivers/virtual_amiibo.cpp101
-rw-r--r--src/input_common/drivers/virtual_amiibo.h61
-rw-r--r--src/input_common/input_engine.cpp37
-rw-r--r--src/input_common/input_engine.h17
-rw-r--r--src/input_common/input_poller.cpp64
-rw-r--r--src/input_common/input_poller.h10
-rw-r--r--src/input_common/main.cpp21
-rw-r--r--src/input_common/main.h7
-rw-r--r--src/network/room.cpp63
-rw-r--r--src/network/room.h1
-rw-r--r--src/network/room_member.cpp57
-rw-r--r--src/network/room_member.h35
-rw-r--r--src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp25
-rw-r--r--src/video_core/host_shaders/astc_decoder.comp2
-rw-r--r--src/video_core/macro/macro_hle.cpp63
-rw-r--r--src/video_core/macro/macro_jit_x64.cpp62
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp2
-rw-r--r--src/video_core/textures/astc.cpp2
-rw-r--r--src/yuzu/applets/qt_controller.cpp2
-rw-r--r--src/yuzu/configuration/configure_input.cpp7
-rw-r--r--src/yuzu/main.cpp67
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu/main.ui24
-rw-r--r--src/yuzu/multiplayer/chat_room.cpp12
-rw-r--r--src/yuzu/multiplayer/client_room.cpp3
-rw-r--r--src/yuzu/multiplayer/direct_connect.cpp2
-rw-r--r--src/yuzu/multiplayer/direct_connect.h1
-rw-r--r--src/yuzu/multiplayer/host_room.cpp1
-rw-r--r--src/yuzu/multiplayer/host_room.h3
-rw-r--r--src/yuzu/multiplayer/lobby.cpp67
-rw-r--r--src/yuzu/multiplayer/lobby.h8
-rw-r--r--src/yuzu/multiplayer/lobby_p.h18
-rw-r--r--src/yuzu/multiplayer/message.cpp6
-rw-r--r--src/yuzu/multiplayer/state.cpp80
-rw-r--r--src/yuzu/multiplayer/state.h14
-rw-r--r--src/yuzu/uisettings.h2
78 files changed, 4631 insertions, 1644 deletions
diff --git a/src/common/input.h b/src/common/input.h
index 825b0d650..bfa0639f5 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -76,6 +76,19 @@ enum class PollingError {
76 Unknown, 76 Unknown,
77}; 77};
78 78
79// Nfc reply from the controller
80enum class NfcState {
81 Success,
82 NewAmiibo,
83 WaitingForAmiibo,
84 AmiiboRemoved,
85 NotAnAmiibo,
86 NotSupported,
87 WrongDeviceState,
88 WriteFailed,
89 Unknown,
90};
91
79// Ir camera reply from the controller 92// Ir camera reply from the controller
80enum class CameraError { 93enum class CameraError {
81 None, 94 None,
@@ -202,6 +215,11 @@ struct CameraStatus {
202 std::vector<u8> data{}; 215 std::vector<u8> data{};
203}; 216};
204 217
218struct NfcStatus {
219 NfcState state{};
220 std::vector<u8> data{};
221};
222
205// List of buttons to be passed to Qt that can be translated 223// List of buttons to be passed to Qt that can be translated
206enum class ButtonNames { 224enum class ButtonNames {
207 Undefined, 225 Undefined,
@@ -260,6 +278,7 @@ struct CallbackStatus {
260 BatteryStatus battery_status{}; 278 BatteryStatus battery_status{};
261 VibrationStatus vibration_status{}; 279 VibrationStatus vibration_status{};
262 CameraStatus camera_status{}; 280 CameraStatus camera_status{};
281 NfcStatus nfc_status{};
263}; 282};
264 283
265// Triggered once every input change 284// Triggered once every input change
@@ -312,6 +331,14 @@ public:
312 virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) { 331 virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
313 return CameraError::NotSupported; 332 return CameraError::NotSupported;
314 } 333 }
334
335 virtual NfcState SupportsNfc() const {
336 return NfcState::NotSupported;
337 }
338
339 virtual NfcState WriteNfcData([[maybe_unused]] const std::vector<u8>& data) {
340 return NfcState::NotSupported;
341 }
315}; 342};
316 343
317/// An abstract class template for a factory that can create input devices. 344/// An abstract class template for a factory that can create input devices.
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 33cf470d5..8e3fd4505 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -460,6 +460,8 @@ add_library(core STATIC
460 hle/service/hid/controllers/mouse.h 460 hle/service/hid/controllers/mouse.h
461 hle/service/hid/controllers/npad.cpp 461 hle/service/hid/controllers/npad.cpp
462 hle/service/hid/controllers/npad.h 462 hle/service/hid/controllers/npad.h
463 hle/service/hid/controllers/palma.cpp
464 hle/service/hid/controllers/palma.h
463 hle/service/hid/controllers/stubbed.cpp 465 hle/service/hid/controllers/stubbed.cpp
464 hle/service/hid/controllers/stubbed.h 466 hle/service/hid/controllers/stubbed.h
465 hle/service/hid/controllers/touchscreen.cpp 467 hle/service/hid/controllers/touchscreen.cpp
@@ -494,6 +496,8 @@ add_library(core STATIC
494 hle/service/jit/jit.h 496 hle/service/jit/jit.h
495 hle/service/lbl/lbl.cpp 497 hle/service/lbl/lbl.cpp
496 hle/service/lbl/lbl.h 498 hle/service/lbl/lbl.h
499 hle/service/ldn/lan_discovery.cpp
500 hle/service/ldn/lan_discovery.h
497 hle/service/ldn/ldn_results.h 501 hle/service/ldn/ldn_results.h
498 hle/service/ldn/ldn.cpp 502 hle/service/ldn/ldn.cpp
499 hle/service/ldn/ldn.h 503 hle/service/ldn/ldn.h
@@ -521,9 +525,12 @@ add_library(core STATIC
521 hle/service/nfc/nfc.h 525 hle/service/nfc/nfc.h
522 hle/service/nfp/amiibo_crypto.cpp 526 hle/service/nfp/amiibo_crypto.cpp
523 hle/service/nfp/amiibo_crypto.h 527 hle/service/nfp/amiibo_crypto.h
524 hle/service/nfp/amiibo_types.h
525 hle/service/nfp/nfp.cpp 528 hle/service/nfp/nfp.cpp
526 hle/service/nfp/nfp.h 529 hle/service/nfp/nfp.h
530 hle/service/nfp/nfp_device.cpp
531 hle/service/nfp/nfp_device.h
532 hle/service/nfp/nfp_result.h
533 hle/service/nfp/nfp_types.h
527 hle/service/nfp/nfp_user.cpp 534 hle/service/nfp/nfp_user.cpp
528 hle/service/nfp/nfp_user.h 535 hle/service/nfp/nfp_user.h
529 hle/service/ngct/ngct.cpp 536 hle/service/ngct/ngct.cpp
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 01c43be93..e27d84734 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -93,7 +93,7 @@ void EmulatedController::ReloadFromSettings() {
93 .body = GetNpadColor(player.body_color_left), 93 .body = GetNpadColor(player.body_color_left),
94 .button = GetNpadColor(player.button_color_left), 94 .button = GetNpadColor(player.button_color_left),
95 }; 95 };
96 controller.colors_state.left = { 96 controller.colors_state.right = {
97 .body = GetNpadColor(player.body_color_right), 97 .body = GetNpadColor(player.body_color_right),
98 .button = GetNpadColor(player.button_color_right), 98 .button = GetNpadColor(player.button_color_right),
99 }; 99 };
@@ -131,13 +131,16 @@ void EmulatedController::LoadDevices() {
131 battery_params[RightIndex].Set("battery", true); 131 battery_params[RightIndex].Set("battery", true);
132 132
133 camera_params = Common::ParamPackage{"engine:camera,camera:1"}; 133 camera_params = Common::ParamPackage{"engine:camera,camera:1"};
134 nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
134 135
135 output_params[LeftIndex] = left_joycon; 136 output_params[LeftIndex] = left_joycon;
136 output_params[RightIndex] = right_joycon; 137 output_params[RightIndex] = right_joycon;
137 output_params[2] = camera_params; 138 output_params[2] = camera_params;
139 output_params[3] = nfc_params;
138 output_params[LeftIndex].Set("output", true); 140 output_params[LeftIndex].Set("output", true);
139 output_params[RightIndex].Set("output", true); 141 output_params[RightIndex].Set("output", true);
140 output_params[2].Set("output", true); 142 output_params[2].Set("output", true);
143 output_params[3].Set("output", true);
141 144
142 LoadTASParams(); 145 LoadTASParams();
143 146
@@ -155,6 +158,7 @@ void EmulatedController::LoadDevices() {
155 std::transform(battery_params.begin(), battery_params.end(), battery_devices.begin(), 158 std::transform(battery_params.begin(), battery_params.end(), battery_devices.begin(),
156 Common::Input::CreateDevice<Common::Input::InputDevice>); 159 Common::Input::CreateDevice<Common::Input::InputDevice>);
157 camera_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(camera_params); 160 camera_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(camera_params);
161 nfc_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(nfc_params);
158 std::transform(output_params.begin(), output_params.end(), output_devices.begin(), 162 std::transform(output_params.begin(), output_params.end(), output_devices.begin(),
159 Common::Input::CreateDevice<Common::Input::OutputDevice>); 163 Common::Input::CreateDevice<Common::Input::OutputDevice>);
160 164
@@ -284,6 +288,16 @@ void EmulatedController::ReloadInput() {
284 camera_devices->ForceUpdate(); 288 camera_devices->ForceUpdate();
285 } 289 }
286 290
291 if (nfc_devices) {
292 if (npad_id_type == NpadIdType::Handheld || npad_id_type == NpadIdType::Player1) {
293 nfc_devices->SetCallback({
294 .on_change =
295 [this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); },
296 });
297 nfc_devices->ForceUpdate();
298 }
299 }
300
287 // Use a common UUID for TAS 301 // Use a common UUID for TAS
288 static constexpr Common::UUID TAS_UUID = Common::UUID{ 302 static constexpr Common::UUID TAS_UUID = Common::UUID{
289 {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; 303 {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
@@ -339,6 +353,8 @@ void EmulatedController::UnloadInput() {
339 for (auto& stick : tas_stick_devices) { 353 for (auto& stick : tas_stick_devices) {
340 stick.reset(); 354 stick.reset();
341 } 355 }
356 camera_devices.reset();
357 nfc_devices.reset();
342} 358}
343 359
344void EmulatedController::EnableConfiguration() { 360void EmulatedController::EnableConfiguration() {
@@ -903,6 +919,25 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback
903 TriggerOnChange(ControllerTriggerType::IrSensor, true); 919 TriggerOnChange(ControllerTriggerType::IrSensor, true);
904} 920}
905 921
922void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
923 std::unique_lock lock{mutex};
924 controller.nfc_values = TransformToNfc(callback);
925
926 if (is_configuring) {
927 lock.unlock();
928 TriggerOnChange(ControllerTriggerType::Nfc, false);
929 return;
930 }
931
932 controller.nfc_state = {
933 controller.nfc_values.state,
934 controller.nfc_values.data,
935 };
936
937 lock.unlock();
938 TriggerOnChange(ControllerTriggerType::Nfc, true);
939}
940
906bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { 941bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
907 if (device_index >= output_devices.size()) { 942 if (device_index >= output_devices.size()) {
908 return false; 943 return false;
@@ -980,6 +1015,10 @@ bool EmulatedController::TestVibration(std::size_t device_index) {
980bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) { 1015bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) {
981 LOG_INFO(Service_HID, "Set polling mode {}", polling_mode); 1016 LOG_INFO(Service_HID, "Set polling mode {}", polling_mode);
982 auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; 1017 auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
1018 auto& nfc_output_device = output_devices[3];
1019
1020 nfc_output_device->SetPollingMode(polling_mode);
1021
983 return output_device->SetPollingMode(polling_mode) == Common::Input::PollingError::None; 1022 return output_device->SetPollingMode(polling_mode) == Common::Input::PollingError::None;
984} 1023}
985 1024
@@ -1000,6 +1039,32 @@ bool EmulatedController::SetCameraFormat(
1000 camera_format)) == Common::Input::CameraError::None; 1039 camera_format)) == Common::Input::CameraError::None;
1001} 1040}
1002 1041
1042bool EmulatedController::HasNfc() const {
1043 const auto& nfc_output_device = output_devices[3];
1044
1045 switch (npad_type) {
1046 case NpadStyleIndex::JoyconRight:
1047 case NpadStyleIndex::JoyconDual:
1048 case NpadStyleIndex::ProController:
1049 break;
1050 default:
1051 return false;
1052 }
1053
1054 const bool has_virtual_nfc =
1055 npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld;
1056 const bool is_virtual_nfc_supported =
1057 nfc_output_device->SupportsNfc() != Common::Input::NfcState::NotSupported;
1058
1059 return is_connected && (has_virtual_nfc && is_virtual_nfc_supported);
1060}
1061
1062bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
1063 auto& nfc_output_device = output_devices[3];
1064
1065 return nfc_output_device->WriteNfcData(data) == Common::Input::NfcState::Success;
1066}
1067
1003void EmulatedController::SetLedPattern() { 1068void EmulatedController::SetLedPattern() {
1004 for (auto& device : output_devices) { 1069 for (auto& device : output_devices) {
1005 if (!device) { 1070 if (!device) {
@@ -1363,6 +1428,11 @@ const CameraState& EmulatedController::GetCamera() const {
1363 return controller.camera_state; 1428 return controller.camera_state;
1364} 1429}
1365 1430
1431const NfcState& EmulatedController::GetNfc() const {
1432 std::scoped_lock lock{mutex};
1433 return controller.nfc_state;
1434}
1435
1366NpadColor EmulatedController::GetNpadColor(u32 color) { 1436NpadColor EmulatedController::GetNpadColor(u32 color) {
1367 return { 1437 return {
1368 .r = static_cast<u8>((color >> 16) & 0xFF), 1438 .r = static_cast<u8>((color >> 16) & 0xFF),
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index c3aa8f9d3..319226bf8 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -20,7 +20,7 @@
20 20
21namespace Core::HID { 21namespace Core::HID {
22const std::size_t max_emulated_controllers = 2; 22const std::size_t max_emulated_controllers = 2;
23const std::size_t output_devices = 3; 23const std::size_t output_devices_size = 4;
24struct ControllerMotionInfo { 24struct ControllerMotionInfo {
25 Common::Input::MotionStatus raw_status{}; 25 Common::Input::MotionStatus raw_status{};
26 MotionInput emulated{}; 26 MotionInput emulated{};
@@ -37,7 +37,8 @@ using TriggerDevices =
37using BatteryDevices = 37using BatteryDevices =
38 std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; 38 std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
39using CameraDevices = std::unique_ptr<Common::Input::InputDevice>; 39using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
40using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices>; 40using NfcDevices = std::unique_ptr<Common::Input::InputDevice>;
41using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>;
41 42
42using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>; 43using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
43using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>; 44using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
@@ -45,7 +46,8 @@ using ControllerMotionParams = std::array<Common::ParamPackage, Settings::Native
45using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>; 46using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
46using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>; 47using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
47using CameraParams = Common::ParamPackage; 48using CameraParams = Common::ParamPackage;
48using OutputParams = std::array<Common::ParamPackage, output_devices>; 49using NfcParams = Common::ParamPackage;
50using OutputParams = std::array<Common::ParamPackage, output_devices_size>;
49 51
50using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>; 52using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
51using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>; 53using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
@@ -55,6 +57,7 @@ using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::Native
55using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>; 57using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
56using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>; 58using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
57using CameraValues = Common::Input::CameraStatus; 59using CameraValues = Common::Input::CameraStatus;
60using NfcValues = Common::Input::NfcStatus;
58using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>; 61using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
59 62
60struct AnalogSticks { 63struct AnalogSticks {
@@ -80,6 +83,11 @@ struct CameraState {
80 std::size_t sample{}; 83 std::size_t sample{};
81}; 84};
82 85
86struct NfcState {
87 Common::Input::NfcState state{};
88 std::vector<u8> data{};
89};
90
83struct ControllerMotion { 91struct ControllerMotion {
84 Common::Vec3f accel{}; 92 Common::Vec3f accel{};
85 Common::Vec3f gyro{}; 93 Common::Vec3f gyro{};
@@ -107,6 +115,7 @@ struct ControllerStatus {
107 BatteryValues battery_values{}; 115 BatteryValues battery_values{};
108 VibrationValues vibration_values{}; 116 VibrationValues vibration_values{};
109 CameraValues camera_values{}; 117 CameraValues camera_values{};
118 NfcValues nfc_values{};
110 119
111 // Data for HID serices 120 // Data for HID serices
112 HomeButtonState home_button_state{}; 121 HomeButtonState home_button_state{};
@@ -119,6 +128,7 @@ struct ControllerStatus {
119 ControllerColors colors_state{}; 128 ControllerColors colors_state{};
120 BatteryLevelState battery_state{}; 129 BatteryLevelState battery_state{};
121 CameraState camera_state{}; 130 CameraState camera_state{};
131 NfcState nfc_state{};
122}; 132};
123 133
124enum class ControllerTriggerType { 134enum class ControllerTriggerType {
@@ -130,6 +140,7 @@ enum class ControllerTriggerType {
130 Battery, 140 Battery,
131 Vibration, 141 Vibration,
132 IrSensor, 142 IrSensor,
143 Nfc,
133 Connected, 144 Connected,
134 Disconnected, 145 Disconnected,
135 Type, 146 Type,
@@ -315,6 +326,9 @@ public:
315 /// Returns the latest camera status from the controller 326 /// Returns the latest camera status from the controller
316 const CameraState& GetCamera() const; 327 const CameraState& GetCamera() const;
317 328
329 /// Returns the latest ntag status from the controller
330 const NfcState& GetNfc() const;
331
318 /** 332 /**
319 * Sends a specific vibration to the output device 333 * Sends a specific vibration to the output device
320 * @return true if vibration had no errors 334 * @return true if vibration had no errors
@@ -341,6 +355,12 @@ public:
341 */ 355 */
342 bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format); 356 bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format);
343 357
358 /// Returns true if the device has nfc support
359 bool HasNfc() const;
360
361 /// Returns true if the nfc tag was written
362 bool WriteNfc(const std::vector<u8>& data);
363
344 /// Returns the led pattern corresponding to this emulated controller 364 /// Returns the led pattern corresponding to this emulated controller
345 LedPattern GetLedPattern() const; 365 LedPattern GetLedPattern() const;
346 366
@@ -425,6 +445,12 @@ private:
425 void SetCamera(const Common::Input::CallbackStatus& callback); 445 void SetCamera(const Common::Input::CallbackStatus& callback);
426 446
427 /** 447 /**
448 * Updates the nfc status of the controller
449 * @param callback A CallbackStatus containing the nfc status
450 */
451 void SetNfc(const Common::Input::CallbackStatus& callback);
452
453 /**
428 * Converts a color format from bgra to rgba 454 * Converts a color format from bgra to rgba
429 * @param color in bgra format 455 * @param color in bgra format
430 * @return NpadColor in rgba format 456 * @return NpadColor in rgba format
@@ -458,6 +484,7 @@ private:
458 TriggerParams trigger_params; 484 TriggerParams trigger_params;
459 BatteryParams battery_params; 485 BatteryParams battery_params;
460 CameraParams camera_params; 486 CameraParams camera_params;
487 NfcParams nfc_params;
461 OutputParams output_params; 488 OutputParams output_params;
462 489
463 ButtonDevices button_devices; 490 ButtonDevices button_devices;
@@ -466,6 +493,7 @@ private:
466 TriggerDevices trigger_devices; 493 TriggerDevices trigger_devices;
467 BatteryDevices battery_devices; 494 BatteryDevices battery_devices;
468 CameraDevices camera_devices; 495 CameraDevices camera_devices;
496 NfcDevices nfc_devices;
469 OutputDevices output_devices; 497 OutputDevices output_devices;
470 498
471 // TAS related variables 499 // TAS related variables
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index 52fb69e9c..fe9915abe 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -287,6 +287,20 @@ Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatu
287 return camera; 287 return camera;
288} 288}
289 289
290Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback) {
291 Common::Input::NfcStatus nfc{};
292 switch (callback.type) {
293 case Common::Input::InputType::Nfc:
294 return callback.nfc_status;
295 break;
296 default:
297 LOG_ERROR(Input, "Conversion from type {} to NFC not implemented", callback.type);
298 break;
299 }
300
301 return nfc;
302}
303
290void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) { 304void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
291 const auto& properties = analog.properties; 305 const auto& properties = analog.properties;
292 float& raw_value = analog.raw_value; 306 float& raw_value = analog.raw_value;
diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h
index 143c50cc0..b7eb6e660 100644
--- a/src/core/hid/input_converter.h
+++ b/src/core/hid/input_converter.h
@@ -85,6 +85,14 @@ Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatu
85Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback); 85Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback);
86 86
87/** 87/**
88 * Converts raw input data into a valid nfc status.
89 *
90 * @param callback Supported callbacks: Nfc.
91 * @return A valid CameraObject object.
92 */
93Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback);
94
95/**
88 * Converts raw analog data into a valid analog value 96 * Converts raw analog data into a valid analog value
89 * @param analog An analog object containing raw data and properties 97 * @param analog An analog object containing raw data and properties
90 * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f. 98 * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f.
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index cb29004e8..f8972ec7a 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -660,7 +660,6 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
660 ASSERT(false); 660 ASSERT(false);
661 break; 661 break;
662 case Core::HID::NpadStyleIndex::ProController: 662 case Core::HID::NpadStyleIndex::ProController:
663 case Core::HID::NpadStyleIndex::Pokeball:
664 set_motion_state(sixaxis_fullkey_state, motion_state[0]); 663 set_motion_state(sixaxis_fullkey_state, motion_state[0]);
665 break; 664 break;
666 case Core::HID::NpadStyleIndex::Handheld: 665 case Core::HID::NpadStyleIndex::Handheld:
@@ -676,6 +675,11 @@ void Controller_NPad::OnMotionUpdate(const Core::Timing::CoreTiming& core_timing
676 case Core::HID::NpadStyleIndex::JoyconRight: 675 case Core::HID::NpadStyleIndex::JoyconRight:
677 set_motion_state(sixaxis_right_lifo_state, motion_state[1]); 676 set_motion_state(sixaxis_right_lifo_state, motion_state[1]);
678 break; 677 break;
678 case Core::HID::NpadStyleIndex::Pokeball:
679 using namespace std::literals::chrono_literals;
680 set_motion_state(sixaxis_fullkey_state, motion_state[0]);
681 sixaxis_fullkey_state.delta_time = std::chrono::nanoseconds(15ms).count();
682 break;
679 default: 683 default:
680 break; 684 break;
681 } 685 }
diff --git a/src/core/hle/service/hid/controllers/palma.cpp b/src/core/hle/service/hid/controllers/palma.cpp
new file mode 100644
index 000000000..575d4e626
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/palma.cpp
@@ -0,0 +1,229 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/core_timing.h"
5#include "core/hid/emulated_controller.h"
6#include "core/hid/hid_core.h"
7#include "core/hid/hid_types.h"
8#include "core/hle/kernel/k_event.h"
9#include "core/hle/kernel/k_readable_event.h"
10#include "core/hle/service/hid/controllers/palma.h"
11#include "core/hle/service/kernel_helpers.h"
12
13namespace Service::HID {
14
15Controller_Palma::Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
16 KernelHelpers::ServiceContext& service_context_)
17 : ControllerBase{hid_core_}, service_context{service_context_} {
18 controller = hid_core.GetEmulatedController(Core::HID::NpadIdType::Other);
19 operation_complete_event = service_context.CreateEvent("hid:PalmaOperationCompleteEvent");
20}
21
22Controller_Palma::~Controller_Palma() = default;
23
24void Controller_Palma::OnInit() {}
25
26void Controller_Palma::OnRelease() {}
27
28void Controller_Palma::OnUpdate(const Core::Timing::CoreTiming& core_timing) {
29 if (!IsControllerActivated()) {
30 return;
31 }
32}
33
34Result Controller_Palma::GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id,
35 PalmaConnectionHandle& handle) {
36 active_handle.npad_id = npad_id;
37 handle = active_handle;
38 return ResultSuccess;
39}
40
41Result Controller_Palma::InitializePalma(const PalmaConnectionHandle& handle) {
42 if (handle.npad_id != active_handle.npad_id) {
43 return InvalidPalmaHandle;
44 }
45 ActivateController();
46 return ResultSuccess;
47}
48
49Kernel::KReadableEvent& Controller_Palma::AcquirePalmaOperationCompleteEvent(
50 const PalmaConnectionHandle& handle) const {
51 if (handle.npad_id != active_handle.npad_id) {
52 LOG_ERROR(Service_HID, "Invalid npad id {}", handle.npad_id);
53 }
54 return operation_complete_event->GetReadableEvent();
55}
56
57Result Controller_Palma::GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
58 PalmaOperationType& operation_type,
59 PalmaOperationData& data) const {
60 if (handle.npad_id != active_handle.npad_id) {
61 return InvalidPalmaHandle;
62 }
63 operation_type = operation.operation;
64 data = operation.data;
65 return ResultSuccess;
66}
67
68Result Controller_Palma::PlayPalmaActivity(const PalmaConnectionHandle& handle,
69 u64 palma_activity) {
70 if (handle.npad_id != active_handle.npad_id) {
71 return InvalidPalmaHandle;
72 }
73 operation.operation = PalmaOperationType::PlayActivity;
74 operation.result = PalmaResultSuccess;
75 operation.data = {};
76 operation_complete_event->GetWritableEvent().Signal();
77 return ResultSuccess;
78}
79
80Result Controller_Palma::SetPalmaFrModeType(const PalmaConnectionHandle& handle,
81 PalmaFrModeType fr_mode_) {
82 if (handle.npad_id != active_handle.npad_id) {
83 return InvalidPalmaHandle;
84 }
85 fr_mode = fr_mode_;
86 return ResultSuccess;
87}
88
89Result Controller_Palma::ReadPalmaStep(const PalmaConnectionHandle& handle) {
90 if (handle.npad_id != active_handle.npad_id) {
91 return InvalidPalmaHandle;
92 }
93 operation.operation = PalmaOperationType::ReadStep;
94 operation.result = PalmaResultSuccess;
95 operation.data = {};
96 operation_complete_event->GetWritableEvent().Signal();
97 return ResultSuccess;
98}
99
100Result Controller_Palma::EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled) {
101 if (handle.npad_id != active_handle.npad_id) {
102 return InvalidPalmaHandle;
103 }
104 return ResultSuccess;
105}
106
107Result Controller_Palma::ResetPalmaStep(const PalmaConnectionHandle& handle) {
108 if (handle.npad_id != active_handle.npad_id) {
109 return InvalidPalmaHandle;
110 }
111 return ResultSuccess;
112}
113
114void Controller_Palma::ReadPalmaApplicationSection() {}
115
116void Controller_Palma::WritePalmaApplicationSection() {}
117
118Result Controller_Palma::ReadPalmaUniqueCode(const PalmaConnectionHandle& handle) {
119 if (handle.npad_id != active_handle.npad_id) {
120 return InvalidPalmaHandle;
121 }
122 operation.operation = PalmaOperationType::ReadUniqueCode;
123 operation.result = PalmaResultSuccess;
124 operation.data = {};
125 operation_complete_event->GetWritableEvent().Signal();
126 return ResultSuccess;
127}
128
129Result Controller_Palma::SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle) {
130 if (handle.npad_id != active_handle.npad_id) {
131 return InvalidPalmaHandle;
132 }
133 operation.operation = PalmaOperationType::SetUniqueCodeInvalid;
134 operation.result = PalmaResultSuccess;
135 operation.data = {};
136 operation_complete_event->GetWritableEvent().Signal();
137 return ResultSuccess;
138}
139
140void Controller_Palma::WritePalmaActivityEntry() {}
141
142Result Controller_Palma::WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle,
143 u64 unknown) {
144 if (handle.npad_id != active_handle.npad_id) {
145 return InvalidPalmaHandle;
146 }
147 operation.operation = PalmaOperationType::WriteRgbLedPatternEntry;
148 operation.result = PalmaResultSuccess;
149 operation.data = {};
150 operation_complete_event->GetWritableEvent().Signal();
151 return ResultSuccess;
152}
153
154Result Controller_Palma::WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave,
155 u8* t_mem, u64 size) {
156 if (handle.npad_id != active_handle.npad_id) {
157 return InvalidPalmaHandle;
158 }
159 operation.operation = PalmaOperationType::WriteWaveEntry;
160 operation.result = PalmaResultSuccess;
161 operation.data = {};
162 operation_complete_event->GetWritableEvent().Signal();
163 return ResultSuccess;
164}
165
166Result Controller_Palma::SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
167 s32 database_id_version_) {
168 if (handle.npad_id != active_handle.npad_id) {
169 return InvalidPalmaHandle;
170 }
171 database_id_version = database_id_version_;
172 operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
173 operation.result = PalmaResultSuccess;
174 operation.data[0] = {};
175 operation_complete_event->GetWritableEvent().Signal();
176 return ResultSuccess;
177}
178
179Result Controller_Palma::GetPalmaDataBaseIdentificationVersion(
180 const PalmaConnectionHandle& handle) {
181 if (handle.npad_id != active_handle.npad_id) {
182 return InvalidPalmaHandle;
183 }
184 operation.operation = PalmaOperationType::ReadDataBaseIdentificationVersion;
185 operation.result = PalmaResultSuccess;
186 operation.data = {};
187 operation.data[0] = static_cast<u8>(database_id_version);
188 operation_complete_event->GetWritableEvent().Signal();
189 return ResultSuccess;
190}
191
192void Controller_Palma::SuspendPalmaFeature() {}
193
194Result Controller_Palma::GetPalmaOperationResult(const PalmaConnectionHandle& handle) const {
195 if (handle.npad_id != active_handle.npad_id) {
196 return InvalidPalmaHandle;
197 }
198 return operation.result;
199}
200void Controller_Palma::ReadPalmaPlayLog() {}
201
202void Controller_Palma::ResetPalmaPlayLog() {}
203
204void Controller_Palma::SetIsPalmaAllConnectable(bool is_all_connectable) {
205 // If true controllers are able to be paired
206 is_connectable = is_all_connectable;
207}
208
209void Controller_Palma::SetIsPalmaPairedConnectable() {}
210
211Result Controller_Palma::PairPalma(const PalmaConnectionHandle& handle) {
212 if (handle.npad_id != active_handle.npad_id) {
213 return InvalidPalmaHandle;
214 }
215 // TODO: Do something
216 return ResultSuccess;
217}
218
219void Controller_Palma::SetPalmaBoostMode(bool boost_mode) {}
220
221void Controller_Palma::CancelWritePalmaWaveEntry() {}
222
223void Controller_Palma::EnablePalmaBoostMode() {}
224
225void Controller_Palma::GetPalmaBluetoothAddress() {}
226
227void Controller_Palma::SetDisallowedPalmaConnection() {}
228
229} // namespace Service::HID
diff --git a/src/core/hle/service/hid/controllers/palma.h b/src/core/hle/service/hid/controllers/palma.h
new file mode 100644
index 000000000..1d7fc94e1
--- /dev/null
+++ b/src/core/hle/service/hid/controllers/palma.h
@@ -0,0 +1,163 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include "common/common_funcs.h"
8#include "common/common_types.h"
9#include "core/hle/service/hid/controllers/controller_base.h"
10#include "core/hle/service/hid/errors.h"
11
12namespace Kernel {
13class KEvent;
14class KReadableEvent;
15} // namespace Kernel
16
17namespace Service::KernelHelpers {
18class ServiceContext;
19}
20
21namespace Core::HID {
22class EmulatedController;
23} // namespace Core::HID
24
25namespace Service::HID {
26class Controller_Palma final : public ControllerBase {
27public:
28 using PalmaOperationData = std::array<u8, 0x140>;
29
30 // This is nn::hid::PalmaOperationType
31 enum class PalmaOperationType {
32 PlayActivity,
33 SetFrModeType,
34 ReadStep,
35 EnableStep,
36 ResetStep,
37 ReadApplicationSection,
38 WriteApplicationSection,
39 ReadUniqueCode,
40 SetUniqueCodeInvalid,
41 WriteActivityEntry,
42 WriteRgbLedPatternEntry,
43 WriteWaveEntry,
44 ReadDataBaseIdentificationVersion,
45 WriteDataBaseIdentificationVersion,
46 SuspendFeature,
47 ReadPlayLog,
48 ResetPlayLog,
49 };
50
51 // This is nn::hid::PalmaWaveSet
52 enum class PalmaWaveSet : u64 {
53 Small,
54 Medium,
55 Large,
56 };
57
58 // This is nn::hid::PalmaFrModeType
59 enum class PalmaFrModeType : u64 {
60 Off,
61 B01,
62 B02,
63 B03,
64 Downloaded,
65 };
66
67 // This is nn::hid::PalmaFeature
68 enum class PalmaFeature : u64 {
69 FrMode,
70 RumbleFeedback,
71 Step,
72 MuteSwitch,
73 };
74
75 // This is nn::hid::PalmaOperationInfo
76 struct PalmaOperationInfo {
77 PalmaOperationType operation{};
78 Result result{PalmaResultSuccess};
79 PalmaOperationData data{};
80 };
81 static_assert(sizeof(PalmaOperationInfo) == 0x148, "PalmaOperationInfo is an invalid size");
82
83 // This is nn::hid::PalmaActivityEntry
84 struct PalmaActivityEntry {
85 u32 rgb_led_pattern_index;
86 INSERT_PADDING_BYTES(2);
87 PalmaWaveSet wave_set;
88 u32 wave_index;
89 INSERT_PADDING_BYTES(12);
90 };
91 static_assert(sizeof(PalmaActivityEntry) == 0x20, "PalmaActivityEntry is an invalid size");
92
93 struct PalmaConnectionHandle {
94 Core::HID::NpadIdType npad_id;
95 INSERT_PADDING_BYTES(4); // Unknown
96 };
97 static_assert(sizeof(PalmaConnectionHandle) == 0x8,
98 "PalmaConnectionHandle has incorrect size.");
99
100 explicit Controller_Palma(Core::HID::HIDCore& hid_core_, u8* raw_shared_memory_,
101 KernelHelpers::ServiceContext& service_context_);
102 ~Controller_Palma() override;
103
104 // Called when the controller is initialized
105 void OnInit() override;
106
107 // When the controller is released
108 void OnRelease() override;
109
110 // When the controller is requesting an update for the shared memory
111 void OnUpdate(const Core::Timing::CoreTiming& core_timing) override;
112
113 Result GetPalmaConnectionHandle(Core::HID::NpadIdType npad_id, PalmaConnectionHandle& handle);
114 Result InitializePalma(const PalmaConnectionHandle& handle);
115 Kernel::KReadableEvent& AcquirePalmaOperationCompleteEvent(
116 const PalmaConnectionHandle& handle) const;
117 Result GetPalmaOperationInfo(const PalmaConnectionHandle& handle,
118 PalmaOperationType& operation_type,
119 PalmaOperationData& data) const;
120 Result PlayPalmaActivity(const PalmaConnectionHandle& handle, u64 palma_activity);
121 Result SetPalmaFrModeType(const PalmaConnectionHandle& handle, PalmaFrModeType fr_mode_);
122 Result ReadPalmaStep(const PalmaConnectionHandle& handle);
123 Result EnablePalmaStep(const PalmaConnectionHandle& handle, bool is_enabled);
124 Result ResetPalmaStep(const PalmaConnectionHandle& handle);
125 Result ReadPalmaUniqueCode(const PalmaConnectionHandle& handle);
126 Result SetPalmaUniqueCodeInvalid(const PalmaConnectionHandle& handle);
127 Result WritePalmaRgbLedPatternEntry(const PalmaConnectionHandle& handle, u64 unknown);
128 Result WritePalmaWaveEntry(const PalmaConnectionHandle& handle, PalmaWaveSet wave, u8* t_mem,
129 u64 size);
130 Result SetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle,
131 s32 database_id_version_);
132 Result GetPalmaDataBaseIdentificationVersion(const PalmaConnectionHandle& handle);
133 Result GetPalmaOperationResult(const PalmaConnectionHandle& handle) const;
134 void SetIsPalmaAllConnectable(bool is_all_connectable);
135 Result PairPalma(const PalmaConnectionHandle& handle);
136 void SetPalmaBoostMode(bool boost_mode);
137
138private:
139 void ReadPalmaApplicationSection();
140 void WritePalmaApplicationSection();
141 void WritePalmaActivityEntry();
142 void SuspendPalmaFeature();
143 void ReadPalmaPlayLog();
144 void ResetPalmaPlayLog();
145 void SetIsPalmaPairedConnectable();
146 void CancelWritePalmaWaveEntry();
147 void EnablePalmaBoostMode();
148 void GetPalmaBluetoothAddress();
149 void SetDisallowedPalmaConnection();
150
151 bool is_connectable{};
152 s32 database_id_version{};
153 PalmaOperationInfo operation{};
154 PalmaFrModeType fr_mode{};
155 PalmaConnectionHandle active_handle{};
156
157 Core::HID::EmulatedController* controller;
158
159 Kernel::KEvent* operation_complete_event;
160 KernelHelpers::ServiceContext& service_context;
161};
162
163} // namespace Service::HID
diff --git a/src/core/hle/service/hid/errors.h b/src/core/hle/service/hid/errors.h
index 4613a4e60..76208e9a4 100644
--- a/src/core/hle/service/hid/errors.h
+++ b/src/core/hle/service/hid/errors.h
@@ -7,6 +7,7 @@
7 7
8namespace Service::HID { 8namespace Service::HID {
9 9
10constexpr Result PalmaResultSuccess{ErrorModule::HID, 0};
10constexpr Result NpadInvalidHandle{ErrorModule::HID, 100}; 11constexpr Result NpadInvalidHandle{ErrorModule::HID, 100};
11constexpr Result NpadDeviceIndexOutOfRange{ErrorModule::HID, 107}; 12constexpr Result NpadDeviceIndexOutOfRange{ErrorModule::HID, 107};
12constexpr Result VibrationInvalidStyleIndex{ErrorModule::HID, 122}; 13constexpr Result VibrationInvalidStyleIndex{ErrorModule::HID, 122};
@@ -17,6 +18,7 @@ constexpr Result NpadIsDualJoycon{ErrorModule::HID, 601};
17constexpr Result NpadIsSameType{ErrorModule::HID, 602}; 18constexpr Result NpadIsSameType{ErrorModule::HID, 602};
18constexpr Result InvalidNpadId{ErrorModule::HID, 709}; 19constexpr Result InvalidNpadId{ErrorModule::HID, 709};
19constexpr Result NpadNotConnected{ErrorModule::HID, 710}; 20constexpr Result NpadNotConnected{ErrorModule::HID, 710};
21constexpr Result InvalidPalmaHandle{ErrorModule::HID, 3302};
20 22
21} // namespace Service::HID 23} // namespace Service::HID
22 24
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 3d3457160..46bad7871 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -27,6 +27,7 @@
27#include "core/hle/service/hid/controllers/keyboard.h" 27#include "core/hle/service/hid/controllers/keyboard.h"
28#include "core/hle/service/hid/controllers/mouse.h" 28#include "core/hle/service/hid/controllers/mouse.h"
29#include "core/hle/service/hid/controllers/npad.h" 29#include "core/hle/service/hid/controllers/npad.h"
30#include "core/hle/service/hid/controllers/palma.h"
30#include "core/hle/service/hid/controllers/stubbed.h" 31#include "core/hle/service/hid/controllers/stubbed.h"
31#include "core/hle/service/hid/controllers/touchscreen.h" 32#include "core/hle/service/hid/controllers/touchscreen.h"
32#include "core/hle/service/hid/controllers/xpad.h" 33#include "core/hle/service/hid/controllers/xpad.h"
@@ -35,7 +36,8 @@ namespace Service::HID {
35 36
36// Updating period for each HID device. 37// Updating period for each HID device.
37// Period time is obtained by measuring the number of samples in a second on HW using a homebrew 38// Period time is obtained by measuring the number of samples in a second on HW using a homebrew
38constexpr auto pad_update_ns = std::chrono::nanoseconds{4 * 1000 * 1000}; // (4ms, 250Hz) 39// Correct pad_update_ns is 4ms this is overclocked to lower input lag
40constexpr auto pad_update_ns = std::chrono::nanoseconds{1 * 1000 * 1000}; // (1ms, 1000Hz)
39constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz) 41constexpr auto mouse_keyboard_update_ns = std::chrono::nanoseconds{8 * 1000 * 1000}; // (8ms, 125Hz)
40constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz) 42constexpr auto motion_update_ns = std::chrono::nanoseconds{5 * 1000 * 1000}; // (5ms, 200Hz)
41 43
@@ -60,6 +62,7 @@ IAppletResource::IAppletResource(Core::System& system_,
60 MakeControllerWithServiceContext<Controller_NPad>(HidController::NPad, shared_memory); 62 MakeControllerWithServiceContext<Controller_NPad>(HidController::NPad, shared_memory);
61 MakeController<Controller_Gesture>(HidController::Gesture, shared_memory); 63 MakeController<Controller_Gesture>(HidController::Gesture, shared_memory);
62 MakeController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor, shared_memory); 64 MakeController<Controller_ConsoleSixAxis>(HidController::ConsoleSixAxisSensor, shared_memory);
65 MakeControllerWithServiceContext<Controller_Palma>(HidController::Palma, shared_memory);
63 66
64 // Homebrew doesn't try to activate some controllers, so we activate them by default 67 // Homebrew doesn't try to activate some controllers, so we activate them by default
65 GetController<Controller_NPad>(HidController::NPad).ActivateController(); 68 GetController<Controller_NPad>(HidController::NPad).ActivateController();
@@ -310,36 +313,36 @@ Hid::Hid(Core::System& system_)
310 {406, nullptr, "GetNpadLeftRightInterfaceType"}, 313 {406, nullptr, "GetNpadLeftRightInterfaceType"},
311 {407, nullptr, "GetNpadOfHighestBatteryLevel"}, 314 {407, nullptr, "GetNpadOfHighestBatteryLevel"},
312 {408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"}, 315 {408, nullptr, "GetNpadOfHighestBatteryLevelForJoyRight"},
313 {500, nullptr, "GetPalmaConnectionHandle"}, 316 {500, &Hid::GetPalmaConnectionHandle, "GetPalmaConnectionHandle"},
314 {501, nullptr, "InitializePalma"}, 317 {501, &Hid::InitializePalma, "InitializePalma"},
315 {502, nullptr, "AcquirePalmaOperationCompleteEvent"}, 318 {502, &Hid::AcquirePalmaOperationCompleteEvent, "AcquirePalmaOperationCompleteEvent"},
316 {503, nullptr, "GetPalmaOperationInfo"}, 319 {503, &Hid::GetPalmaOperationInfo, "GetPalmaOperationInfo"},
317 {504, nullptr, "PlayPalmaActivity"}, 320 {504, &Hid::PlayPalmaActivity, "PlayPalmaActivity"},
318 {505, nullptr, "SetPalmaFrModeType"}, 321 {505, &Hid::SetPalmaFrModeType, "SetPalmaFrModeType"},
319 {506, nullptr, "ReadPalmaStep"}, 322 {506, &Hid::ReadPalmaStep, "ReadPalmaStep"},
320 {507, nullptr, "EnablePalmaStep"}, 323 {507, &Hid::EnablePalmaStep, "EnablePalmaStep"},
321 {508, nullptr, "ResetPalmaStep"}, 324 {508, &Hid::ResetPalmaStep, "ResetPalmaStep"},
322 {509, nullptr, "ReadPalmaApplicationSection"}, 325 {509, &Hid::ReadPalmaApplicationSection, "ReadPalmaApplicationSection"},
323 {510, nullptr, "WritePalmaApplicationSection"}, 326 {510, &Hid::WritePalmaApplicationSection, "WritePalmaApplicationSection"},
324 {511, nullptr, "ReadPalmaUniqueCode"}, 327 {511, &Hid::ReadPalmaUniqueCode, "ReadPalmaUniqueCode"},
325 {512, nullptr, "SetPalmaUniqueCodeInvalid"}, 328 {512, &Hid::SetPalmaUniqueCodeInvalid, "SetPalmaUniqueCodeInvalid"},
326 {513, nullptr, "WritePalmaActivityEntry"}, 329 {513, &Hid::WritePalmaActivityEntry, "WritePalmaActivityEntry"},
327 {514, nullptr, "WritePalmaRgbLedPatternEntry"}, 330 {514, &Hid::WritePalmaRgbLedPatternEntry, "WritePalmaRgbLedPatternEntry"},
328 {515, nullptr, "WritePalmaWaveEntry"}, 331 {515, &Hid::WritePalmaWaveEntry, "WritePalmaWaveEntry"},
329 {516, nullptr, "SetPalmaDataBaseIdentificationVersion"}, 332 {516, &Hid::SetPalmaDataBaseIdentificationVersion, "SetPalmaDataBaseIdentificationVersion"},
330 {517, nullptr, "GetPalmaDataBaseIdentificationVersion"}, 333 {517, &Hid::GetPalmaDataBaseIdentificationVersion, "GetPalmaDataBaseIdentificationVersion"},
331 {518, nullptr, "SuspendPalmaFeature"}, 334 {518, &Hid::SuspendPalmaFeature, "SuspendPalmaFeature"},
332 {519, nullptr, "GetPalmaOperationResult"}, 335 {519, &Hid::GetPalmaOperationResult, "GetPalmaOperationResult"},
333 {520, nullptr, "ReadPalmaPlayLog"}, 336 {520, &Hid::ReadPalmaPlayLog, "ReadPalmaPlayLog"},
334 {521, nullptr, "ResetPalmaPlayLog"}, 337 {521, &Hid::ResetPalmaPlayLog, "ResetPalmaPlayLog"},
335 {522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"}, 338 {522, &Hid::SetIsPalmaAllConnectable, "SetIsPalmaAllConnectable"},
336 {523, nullptr, "SetIsPalmaPairedConnectable"}, 339 {523, &Hid::SetIsPalmaPairedConnectable, "SetIsPalmaPairedConnectable"},
337 {524, nullptr, "PairPalma"}, 340 {524, &Hid::PairPalma, "PairPalma"},
338 {525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"}, 341 {525, &Hid::SetPalmaBoostMode, "SetPalmaBoostMode"},
339 {526, nullptr, "CancelWritePalmaWaveEntry"}, 342 {526, &Hid::CancelWritePalmaWaveEntry, "CancelWritePalmaWaveEntry"},
340 {527, nullptr, "EnablePalmaBoostMode"}, 343 {527, &Hid::EnablePalmaBoostMode, "EnablePalmaBoostMode"},
341 {528, nullptr, "GetPalmaBluetoothAddress"}, 344 {528, &Hid::GetPalmaBluetoothAddress, "GetPalmaBluetoothAddress"},
342 {529, nullptr, "SetDisallowedPalmaConnection"}, 345 {529, &Hid::SetDisallowedPalmaConnection, "SetDisallowedPalmaConnection"},
343 {1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"}, 346 {1000, &Hid::SetNpadCommunicationMode, "SetNpadCommunicationMode"},
344 {1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"}, 347 {1001, &Hid::GetNpadCommunicationMode, "GetNpadCommunicationMode"},
345 {1002, &Hid::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"}, 348 {1002, &Hid::SetTouchScreenConfiguration, "SetTouchScreenConfiguration"},
@@ -1878,14 +1881,361 @@ void Hid::IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx) {
1878 rb.Push(false); 1881 rb.Push(false);
1879} 1882}
1880 1883
1884void Hid::GetPalmaConnectionHandle(Kernel::HLERequestContext& ctx) {
1885 IPC::RequestParser rp{ctx};
1886 struct Parameters {
1887 Core::HID::NpadIdType npad_id;
1888 INSERT_PADDING_WORDS_NOINIT(1);
1889 u64 applet_resource_user_id;
1890 };
1891 static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
1892
1893 const auto parameters{rp.PopRaw<Parameters>()};
1894
1895 LOG_WARNING(Service_HID, "(STUBBED) called, npad_id={}, applet_resource_user_id={}",
1896 parameters.npad_id, parameters.applet_resource_user_id);
1897
1898 Controller_Palma::PalmaConnectionHandle handle;
1899 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1900 const auto result = controller.GetPalmaConnectionHandle(parameters.npad_id, handle);
1901
1902 IPC::ResponseBuilder rb{ctx, 4};
1903 rb.Push(result);
1904 rb.PushRaw(handle);
1905}
1906
1907void Hid::InitializePalma(Kernel::HLERequestContext& ctx) {
1908 IPC::RequestParser rp{ctx};
1909 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
1910
1911 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
1912
1913 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1914 const auto result = controller.InitializePalma(connection_handle);
1915
1916 IPC::ResponseBuilder rb{ctx, 2};
1917 rb.Push(result);
1918}
1919
1920void Hid::AcquirePalmaOperationCompleteEvent(Kernel::HLERequestContext& ctx) {
1921 IPC::RequestParser rp{ctx};
1922 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
1923
1924 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
1925
1926 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1927
1928 IPC::ResponseBuilder rb{ctx, 2, 1};
1929 rb.Push(ResultSuccess);
1930 rb.PushCopyObjects(controller.AcquirePalmaOperationCompleteEvent(connection_handle));
1931}
1932
1933void Hid::GetPalmaOperationInfo(Kernel::HLERequestContext& ctx) {
1934 IPC::RequestParser rp{ctx};
1935 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
1936
1937 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
1938
1939 Controller_Palma::PalmaOperationType operation_type;
1940 Controller_Palma::PalmaOperationData data;
1941 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1942 const auto result = controller.GetPalmaOperationInfo(connection_handle, operation_type, data);
1943
1944 if (result.IsError()) {
1945 IPC::ResponseBuilder rb{ctx, 2};
1946 rb.Push(result);
1947 }
1948
1949 ctx.WriteBuffer(data);
1950 IPC::ResponseBuilder rb{ctx, 4};
1951 rb.Push(result);
1952 rb.Push(static_cast<u64>(operation_type));
1953}
1954
1955void Hid::PlayPalmaActivity(Kernel::HLERequestContext& ctx) {
1956 IPC::RequestParser rp{ctx};
1957 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
1958 const auto palma_activity{rp.Pop<u64>()};
1959
1960 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, palma_activity={}",
1961 connection_handle.npad_id, palma_activity);
1962
1963 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1964 const auto result = controller.PlayPalmaActivity(connection_handle, palma_activity);
1965
1966 IPC::ResponseBuilder rb{ctx, 2};
1967 rb.Push(result);
1968}
1969
1970void Hid::SetPalmaFrModeType(Kernel::HLERequestContext& ctx) {
1971 IPC::RequestParser rp{ctx};
1972 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
1973 const auto fr_mode{rp.PopEnum<Controller_Palma::PalmaFrModeType>()};
1974
1975 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, fr_mode={}",
1976 connection_handle.npad_id, fr_mode);
1977
1978 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1979 const auto result = controller.SetPalmaFrModeType(connection_handle, fr_mode);
1980
1981 IPC::ResponseBuilder rb{ctx, 2};
1982 rb.Push(result);
1983}
1984
1985void Hid::ReadPalmaStep(Kernel::HLERequestContext& ctx) {
1986 IPC::RequestParser rp{ctx};
1987 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
1988
1989 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
1990
1991 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
1992 const auto result = controller.ReadPalmaStep(connection_handle);
1993
1994 IPC::ResponseBuilder rb{ctx, 2};
1995 rb.Push(result);
1996}
1997
1998void Hid::EnablePalmaStep(Kernel::HLERequestContext& ctx) {
1999 IPC::RequestParser rp{ctx};
2000 struct Parameters {
2001 bool is_enabled;
2002 INSERT_PADDING_WORDS_NOINIT(1);
2003 Controller_Palma::PalmaConnectionHandle connection_handle;
2004 };
2005 static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
2006
2007 const auto parameters{rp.PopRaw<Parameters>()};
2008
2009 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, is_enabled={}",
2010 parameters.connection_handle.npad_id, parameters.is_enabled);
2011
2012 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
2013 const auto result =
2014 controller.EnablePalmaStep(parameters.connection_handle, parameters.is_enabled);
2015
2016 IPC::ResponseBuilder rb{ctx, 2};
2017 rb.Push(result);
2018}
2019
2020void Hid::ResetPalmaStep(Kernel::HLERequestContext& ctx) {
2021 IPC::RequestParser rp{ctx};
2022 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2023
2024 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
2025
2026 auto& controller = GetAppletResource()->GetController<Controller_Palma>(HidController::Palma);
2027 const auto result = controller.ResetPalmaStep(connection_handle);
2028
2029 IPC::ResponseBuilder rb{ctx, 2};
2030 rb.Push(result);
2031}
2032
2033void Hid::ReadPalmaApplicationSection(Kernel::HLERequestContext& ctx) {
2034 LOG_WARNING(Service_HID, "(STUBBED) called");
2035
2036 IPC::ResponseBuilder rb{ctx, 2};
2037 rb.Push(ResultSuccess);
2038}
2039
2040void Hid::WritePalmaApplicationSection(Kernel::HLERequestContext& ctx) {
2041 LOG_WARNING(Service_HID, "(STUBBED) called");
2042
2043 IPC::ResponseBuilder rb{ctx, 2};
2044 rb.Push(ResultSuccess);
2045}
2046
2047void Hid::ReadPalmaUniqueCode(Kernel::HLERequestContext& ctx) {
2048 IPC::RequestParser rp{ctx};
2049 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2050
2051 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
2052
2053 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2054 .ReadPalmaUniqueCode(connection_handle);
2055
2056 IPC::ResponseBuilder rb{ctx, 2};
2057 rb.Push(ResultSuccess);
2058}
2059
2060void Hid::SetPalmaUniqueCodeInvalid(Kernel::HLERequestContext& ctx) {
2061 IPC::RequestParser rp{ctx};
2062 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2063
2064 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
2065
2066 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2067 .SetPalmaUniqueCodeInvalid(connection_handle);
2068
2069 IPC::ResponseBuilder rb{ctx, 2};
2070 rb.Push(ResultSuccess);
2071}
2072
2073void Hid::WritePalmaActivityEntry(Kernel::HLERequestContext& ctx) {
2074 LOG_CRITICAL(Service_HID, "(STUBBED) called");
2075
2076 IPC::ResponseBuilder rb{ctx, 2};
2077 rb.Push(ResultSuccess);
2078}
2079
2080void Hid::WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx) {
2081 IPC::RequestParser rp{ctx};
2082 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2083 const auto unknown{rp.Pop<u64>()};
2084
2085 const auto buffer = ctx.ReadBuffer();
2086
2087 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, unknown={}",
2088 connection_handle.npad_id, unknown);
2089
2090 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2091 .WritePalmaRgbLedPatternEntry(connection_handle, unknown);
2092
2093 IPC::ResponseBuilder rb{ctx, 2};
2094 rb.Push(ResultSuccess);
2095}
2096
2097void Hid::WritePalmaWaveEntry(Kernel::HLERequestContext& ctx) {
2098 IPC::RequestParser rp{ctx};
2099 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2100 const auto wave_set{rp.PopEnum<Controller_Palma::PalmaWaveSet>()};
2101 const auto unknown{rp.Pop<u64>()};
2102 const auto t_mem_size{rp.Pop<u64>()};
2103 const auto t_mem_handle{ctx.GetCopyHandle(0)};
2104 const auto size{rp.Pop<u64>()};
2105
2106 ASSERT_MSG(t_mem_size == 0x3000, "t_mem_size is not 0x3000 bytes");
2107
2108 auto t_mem =
2109 system.CurrentProcess()->GetHandleTable().GetObject<Kernel::KTransferMemory>(t_mem_handle);
2110
2111 if (t_mem.IsNull()) {
2112 LOG_ERROR(Service_HID, "t_mem is a nullptr for handle=0x{:08X}", t_mem_handle);
2113 IPC::ResponseBuilder rb{ctx, 2};
2114 rb.Push(ResultUnknown);
2115 return;
2116 }
2117
2118 ASSERT_MSG(t_mem->GetSize() == 0x3000, "t_mem has incorrect size");
2119
2120 LOG_WARNING(Service_HID,
2121 "(STUBBED) called, connection_handle={}, wave_set={}, unkown={}, "
2122 "t_mem_handle=0x{:08X}, t_mem_size={}, size={}",
2123 connection_handle.npad_id, wave_set, unknown, t_mem_handle, t_mem_size, size);
2124
2125 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2126 .WritePalmaWaveEntry(connection_handle, wave_set,
2127 system.Memory().GetPointer(t_mem->GetSourceAddress()), t_mem_size);
2128
2129 IPC::ResponseBuilder rb{ctx, 2};
2130 rb.Push(ResultSuccess);
2131}
2132
2133void Hid::SetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) {
2134 IPC::RequestParser rp{ctx};
2135 struct Parameters {
2136 s32 database_id_version;
2137 INSERT_PADDING_WORDS_NOINIT(1);
2138 Controller_Palma::PalmaConnectionHandle connection_handle;
2139 };
2140 static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
2141
2142 const auto parameters{rp.PopRaw<Parameters>()};
2143
2144 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}, database_id_version={}",
2145 parameters.connection_handle.npad_id, parameters.database_id_version);
2146
2147 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2148 .SetPalmaDataBaseIdentificationVersion(parameters.connection_handle,
2149 parameters.database_id_version);
2150
2151 IPC::ResponseBuilder rb{ctx, 2};
2152 rb.Push(ResultSuccess);
2153}
2154
2155void Hid::GetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx) {
2156 IPC::RequestParser rp{ctx};
2157 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2158
2159 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
2160
2161 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2162 .GetPalmaDataBaseIdentificationVersion(connection_handle);
2163
2164 IPC::ResponseBuilder rb{ctx, 2};
2165 rb.Push(ResultSuccess);
2166}
2167
2168void Hid::SuspendPalmaFeature(Kernel::HLERequestContext& ctx) {
2169 LOG_WARNING(Service_HID, "(STUBBED) called");
2170
2171 IPC::ResponseBuilder rb{ctx, 2};
2172 rb.Push(ResultSuccess);
2173}
2174
2175void Hid::GetPalmaOperationResult(Kernel::HLERequestContext& ctx) {
2176 IPC::RequestParser rp{ctx};
2177 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2178
2179 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
2180
2181 const auto result = applet_resource->GetController<Controller_Palma>(HidController::Palma)
2182 .GetPalmaOperationResult(connection_handle);
2183
2184 IPC::ResponseBuilder rb{ctx, 2};
2185 rb.Push(result);
2186}
2187
2188void Hid::ReadPalmaPlayLog(Kernel::HLERequestContext& ctx) {
2189 LOG_WARNING(Service_HID, "(STUBBED) called");
2190
2191 IPC::ResponseBuilder rb{ctx, 2};
2192 rb.Push(ResultSuccess);
2193}
2194
2195void Hid::ResetPalmaPlayLog(Kernel::HLERequestContext& ctx) {
2196 LOG_WARNING(Service_HID, "(STUBBED) called");
2197
2198 IPC::ResponseBuilder rb{ctx, 2};
2199 rb.Push(ResultSuccess);
2200}
2201
1881void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) { 2202void Hid::SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx) {
1882 IPC::RequestParser rp{ctx}; 2203 IPC::RequestParser rp{ctx};
1883 const auto applet_resource_user_id{rp.Pop<u64>()}; 2204 struct Parameters {
1884 const auto is_palma_all_connectable{rp.Pop<bool>()}; 2205 bool is_palma_all_connectable;
2206 INSERT_PADDING_BYTES_NOINIT(7);
2207 u64 applet_resource_user_id;
2208 };
2209 static_assert(sizeof(Parameters) == 0x10, "Parameters has incorrect size.");
2210
2211 const auto parameters{rp.PopRaw<Parameters>()};
1885 2212
1886 LOG_WARNING(Service_HID, 2213 LOG_WARNING(Service_HID,
1887 "(STUBBED) called, applet_resource_user_id={}, is_palma_all_connectable={}", 2214 "(STUBBED) called, is_palma_all_connectable={},applet_resource_user_id={}",
1888 applet_resource_user_id, is_palma_all_connectable); 2215 parameters.is_palma_all_connectable, parameters.applet_resource_user_id);
2216
2217 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2218 .SetIsPalmaAllConnectable(parameters.is_palma_all_connectable);
2219
2220 IPC::ResponseBuilder rb{ctx, 2};
2221 rb.Push(ResultSuccess);
2222}
2223
2224void Hid::SetIsPalmaPairedConnectable(Kernel::HLERequestContext& ctx) {
2225 LOG_WARNING(Service_HID, "(STUBBED) called");
2226
2227 IPC::ResponseBuilder rb{ctx, 2};
2228 rb.Push(ResultSuccess);
2229}
2230
2231void Hid::PairPalma(Kernel::HLERequestContext& ctx) {
2232 IPC::RequestParser rp{ctx};
2233 const auto connection_handle{rp.PopRaw<Controller_Palma::PalmaConnectionHandle>()};
2234
2235 LOG_WARNING(Service_HID, "(STUBBED) called, connection_handle={}", connection_handle.npad_id);
2236
2237 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2238 .PairPalma(connection_handle);
1889 2239
1890 IPC::ResponseBuilder rb{ctx, 2}; 2240 IPC::ResponseBuilder rb{ctx, 2};
1891 rb.Push(ResultSuccess); 2241 rb.Push(ResultSuccess);
@@ -1897,6 +2247,37 @@ void Hid::SetPalmaBoostMode(Kernel::HLERequestContext& ctx) {
1897 2247
1898 LOG_WARNING(Service_HID, "(STUBBED) called, palma_boost_mode={}", palma_boost_mode); 2248 LOG_WARNING(Service_HID, "(STUBBED) called, palma_boost_mode={}", palma_boost_mode);
1899 2249
2250 applet_resource->GetController<Controller_Palma>(HidController::Palma)
2251 .SetPalmaBoostMode(palma_boost_mode);
2252
2253 IPC::ResponseBuilder rb{ctx, 2};
2254 rb.Push(ResultSuccess);
2255}
2256
2257void Hid::CancelWritePalmaWaveEntry(Kernel::HLERequestContext& ctx) {
2258 LOG_WARNING(Service_HID, "(STUBBED) called");
2259
2260 IPC::ResponseBuilder rb{ctx, 2};
2261 rb.Push(ResultSuccess);
2262}
2263
2264void Hid::EnablePalmaBoostMode(Kernel::HLERequestContext& ctx) {
2265 LOG_WARNING(Service_HID, "(STUBBED) called");
2266
2267 IPC::ResponseBuilder rb{ctx, 2};
2268 rb.Push(ResultSuccess);
2269}
2270
2271void Hid::GetPalmaBluetoothAddress(Kernel::HLERequestContext& ctx) {
2272 LOG_WARNING(Service_HID, "(STUBBED) called");
2273
2274 IPC::ResponseBuilder rb{ctx, 2};
2275 rb.Push(ResultSuccess);
2276}
2277
2278void Hid::SetDisallowedPalmaConnection(Kernel::HLERequestContext& ctx) {
2279 LOG_WARNING(Service_HID, "(STUBBED) called");
2280
1900 IPC::ResponseBuilder rb{ctx, 2}; 2281 IPC::ResponseBuilder rb{ctx, 2};
1901 rb.Push(ResultSuccess); 2282 rb.Push(ResultSuccess);
1902} 2283}
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index ac4333022..340d26fdc 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -33,6 +33,7 @@ enum class HidController : std::size_t {
33 NPad, 33 NPad,
34 Gesture, 34 Gesture,
35 ConsoleSixAxisSensor, 35 ConsoleSixAxisSensor,
36 Palma,
36 37
37 MaxControllers, 38 MaxControllers,
38}; 39};
@@ -166,8 +167,36 @@ private:
166 void FinalizeSevenSixAxisSensor(Kernel::HLERequestContext& ctx); 167 void FinalizeSevenSixAxisSensor(Kernel::HLERequestContext& ctx);
167 void ResetSevenSixAxisSensorTimestamp(Kernel::HLERequestContext& ctx); 168 void ResetSevenSixAxisSensorTimestamp(Kernel::HLERequestContext& ctx);
168 void IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx); 169 void IsUsbFullKeyControllerEnabled(Kernel::HLERequestContext& ctx);
170 void GetPalmaConnectionHandle(Kernel::HLERequestContext& ctx);
171 void InitializePalma(Kernel::HLERequestContext& ctx);
172 void AcquirePalmaOperationCompleteEvent(Kernel::HLERequestContext& ctx);
173 void GetPalmaOperationInfo(Kernel::HLERequestContext& ctx);
174 void PlayPalmaActivity(Kernel::HLERequestContext& ctx);
175 void SetPalmaFrModeType(Kernel::HLERequestContext& ctx);
176 void ReadPalmaStep(Kernel::HLERequestContext& ctx);
177 void EnablePalmaStep(Kernel::HLERequestContext& ctx);
178 void ResetPalmaStep(Kernel::HLERequestContext& ctx);
179 void ReadPalmaApplicationSection(Kernel::HLERequestContext& ctx);
180 void WritePalmaApplicationSection(Kernel::HLERequestContext& ctx);
181 void ReadPalmaUniqueCode(Kernel::HLERequestContext& ctx);
182 void SetPalmaUniqueCodeInvalid(Kernel::HLERequestContext& ctx);
183 void WritePalmaActivityEntry(Kernel::HLERequestContext& ctx);
184 void WritePalmaRgbLedPatternEntry(Kernel::HLERequestContext& ctx);
185 void WritePalmaWaveEntry(Kernel::HLERequestContext& ctx);
186 void SetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx);
187 void GetPalmaDataBaseIdentificationVersion(Kernel::HLERequestContext& ctx);
188 void SuspendPalmaFeature(Kernel::HLERequestContext& ctx);
189 void GetPalmaOperationResult(Kernel::HLERequestContext& ctx);
190 void ReadPalmaPlayLog(Kernel::HLERequestContext& ctx);
191 void ResetPalmaPlayLog(Kernel::HLERequestContext& ctx);
169 void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx); 192 void SetIsPalmaAllConnectable(Kernel::HLERequestContext& ctx);
193 void SetIsPalmaPairedConnectable(Kernel::HLERequestContext& ctx);
194 void PairPalma(Kernel::HLERequestContext& ctx);
170 void SetPalmaBoostMode(Kernel::HLERequestContext& ctx); 195 void SetPalmaBoostMode(Kernel::HLERequestContext& ctx);
196 void CancelWritePalmaWaveEntry(Kernel::HLERequestContext& ctx);
197 void EnablePalmaBoostMode(Kernel::HLERequestContext& ctx);
198 void GetPalmaBluetoothAddress(Kernel::HLERequestContext& ctx);
199 void SetDisallowedPalmaConnection(Kernel::HLERequestContext& ctx);
171 void SetNpadCommunicationMode(Kernel::HLERequestContext& ctx); 200 void SetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
172 void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx); 201 void GetNpadCommunicationMode(Kernel::HLERequestContext& ctx);
173 void SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx); 202 void SetTouchScreenConfiguration(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/hid/irs.cpp b/src/core/hle/service/hid/irs.cpp
index c4b44cbf9..6a3453457 100644
--- a/src/core/hle/service/hid/irs.cpp
+++ b/src/core/hle/service/hid/irs.cpp
@@ -542,7 +542,8 @@ Result IRS::IsIrCameraHandleValid(const Core::IrSensor::IrCameraHandle& camera_h
542 542
543Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry( 543Core::IrSensor::DeviceFormat& IRS::GetIrCameraSharedMemoryDeviceEntry(
544 const Core::IrSensor::IrCameraHandle& camera_handle) { 544 const Core::IrSensor::IrCameraHandle& camera_handle) {
545 ASSERT_MSG(sizeof(StatusManager::device) > camera_handle.npad_id, "invalid npad_id"); 545 const auto npad_id_max_index = static_cast<u8>(sizeof(StatusManager::device));
546 ASSERT_MSG(camera_handle.npad_id < npad_id_max_index, "invalid npad_id");
546 return shared_memory->device[camera_handle.npad_id]; 547 return shared_memory->device[camera_handle.npad_id];
547} 548}
548 549
diff --git a/src/core/hle/service/ldn/lan_discovery.cpp b/src/core/hle/service/ldn/lan_discovery.cpp
new file mode 100644
index 000000000..8f3c04550
--- /dev/null
+++ b/src/core/hle/service/ldn/lan_discovery.cpp
@@ -0,0 +1,633 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/hle/service/ldn/lan_discovery.h"
5#include "core/internal_network/network.h"
6#include "core/internal_network/network_interface.h"
7
8namespace Service::LDN {
9
10LanStation::LanStation(s8 node_id_, LANDiscovery* discovery_)
11 : node_info(nullptr), status(NodeStatus::Disconnected), node_id(node_id_),
12 discovery(discovery_) {}
13
14LanStation::~LanStation() = default;
15
16NodeStatus LanStation::GetStatus() const {
17 return status;
18}
19
20void LanStation::OnClose() {
21 LOG_INFO(Service_LDN, "OnClose {}", node_id);
22 Reset();
23 discovery->UpdateNodes();
24}
25
26void LanStation::Reset() {
27 status = NodeStatus::Disconnected;
28};
29
30void LanStation::OverrideInfo() {
31 bool connected = GetStatus() == NodeStatus::Connected;
32 node_info->node_id = node_id;
33 node_info->is_connected = connected ? 1 : 0;
34}
35
36LANDiscovery::LANDiscovery(Network::RoomNetwork& room_network_)
37 : stations({{{1, this}, {2, this}, {3, this}, {4, this}, {5, this}, {6, this}, {7, this}}}),
38 room_network{room_network_} {}
39
40LANDiscovery::~LANDiscovery() {
41 if (inited) {
42 Result rc = Finalize();
43 LOG_INFO(Service_LDN, "Finalize: {}", rc.raw);
44 }
45}
46
47void LANDiscovery::InitNetworkInfo() {
48 network_info.common.bssid = GetFakeMac();
49 network_info.common.channel = WifiChannel::Wifi24_6;
50 network_info.common.link_level = LinkLevel::Good;
51 network_info.common.network_type = PackedNetworkType::Ldn;
52 network_info.common.ssid = fake_ssid;
53
54 auto& nodes = network_info.ldn.nodes;
55 for (std::size_t i = 0; i < NodeCountMax; i++) {
56 nodes[i].node_id = static_cast<s8>(i);
57 nodes[i].is_connected = 0;
58 }
59}
60
61void LANDiscovery::InitNodeStateChange() {
62 for (auto& node_update : node_changes) {
63 node_update.state_change = NodeStateChange::None;
64 }
65 for (auto& node_state : node_last_states) {
66 node_state = 0;
67 }
68}
69
70State LANDiscovery::GetState() const {
71 return state;
72}
73
74void LANDiscovery::SetState(State new_state) {
75 state = new_state;
76}
77
78Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network) const {
79 if (state == State::AccessPointCreated || state == State::StationConnected) {
80 std::memcpy(&out_network, &network_info, sizeof(network_info));
81 return ResultSuccess;
82 }
83
84 return ResultBadState;
85}
86
87Result LANDiscovery::GetNetworkInfo(NetworkInfo& out_network,
88 std::vector<NodeLatestUpdate>& out_updates,
89 std::size_t buffer_count) {
90 if (buffer_count > NodeCountMax) {
91 return ResultInvalidBufferCount;
92 }
93
94 if (state == State::AccessPointCreated || state == State::StationConnected) {
95 std::memcpy(&out_network, &network_info, sizeof(network_info));
96 for (std::size_t i = 0; i < buffer_count; i++) {
97 out_updates[i].state_change = node_changes[i].state_change;
98 node_changes[i].state_change = NodeStateChange::None;
99 }
100 return ResultSuccess;
101 }
102
103 return ResultBadState;
104}
105
106DisconnectReason LANDiscovery::GetDisconnectReason() const {
107 return disconnect_reason;
108}
109
110Result LANDiscovery::Scan(std::vector<NetworkInfo>& networks, u16& count,
111 const ScanFilter& filter) {
112 if (!IsFlagSet(filter.flag, ScanFilterFlag::NetworkType) ||
113 filter.network_type <= NetworkType::All) {
114 if (!IsFlagSet(filter.flag, ScanFilterFlag::Ssid) && filter.ssid.length >= SsidLengthMax) {
115 return ResultBadInput;
116 }
117 }
118
119 {
120 std::scoped_lock lock{packet_mutex};
121 scan_results.clear();
122
123 SendBroadcast(Network::LDNPacketType::Scan);
124 }
125
126 LOG_INFO(Service_LDN, "Waiting for scan replies");
127 std::this_thread::sleep_for(std::chrono::seconds(1));
128
129 std::scoped_lock lock{packet_mutex};
130 for (const auto& [key, info] : scan_results) {
131 if (count >= networks.size()) {
132 break;
133 }
134
135 if (IsFlagSet(filter.flag, ScanFilterFlag::LocalCommunicationId)) {
136 if (filter.network_id.intent_id.local_communication_id !=
137 info.network_id.intent_id.local_communication_id) {
138 continue;
139 }
140 }
141 if (IsFlagSet(filter.flag, ScanFilterFlag::SessionId)) {
142 if (filter.network_id.session_id != info.network_id.session_id) {
143 continue;
144 }
145 }
146 if (IsFlagSet(filter.flag, ScanFilterFlag::NetworkType)) {
147 if (filter.network_type != static_cast<NetworkType>(info.common.network_type)) {
148 continue;
149 }
150 }
151 if (IsFlagSet(filter.flag, ScanFilterFlag::Ssid)) {
152 if (filter.ssid != info.common.ssid) {
153 continue;
154 }
155 }
156 if (IsFlagSet(filter.flag, ScanFilterFlag::SceneId)) {
157 if (filter.network_id.intent_id.scene_id != info.network_id.intent_id.scene_id) {
158 continue;
159 }
160 }
161
162 networks[count++] = info;
163 }
164
165 return ResultSuccess;
166}
167
168Result LANDiscovery::SetAdvertiseData(std::span<const u8> data) {
169 std::scoped_lock lock{packet_mutex};
170 const std::size_t size = data.size();
171 if (size > AdvertiseDataSizeMax) {
172 return ResultAdvertiseDataTooLarge;
173 }
174
175 std::memcpy(network_info.ldn.advertise_data.data(), data.data(), size);
176 network_info.ldn.advertise_data_size = static_cast<u16>(size);
177
178 UpdateNodes();
179
180 return ResultSuccess;
181}
182
183Result LANDiscovery::OpenAccessPoint() {
184 std::scoped_lock lock{packet_mutex};
185 disconnect_reason = DisconnectReason::None;
186 if (state == State::None) {
187 return ResultBadState;
188 }
189
190 ResetStations();
191 SetState(State::AccessPointOpened);
192
193 return ResultSuccess;
194}
195
196Result LANDiscovery::CloseAccessPoint() {
197 std::scoped_lock lock{packet_mutex};
198 if (state == State::None) {
199 return ResultBadState;
200 }
201
202 if (state == State::AccessPointCreated) {
203 DestroyNetwork();
204 }
205
206 ResetStations();
207 SetState(State::Initialized);
208
209 return ResultSuccess;
210}
211
212Result LANDiscovery::OpenStation() {
213 std::scoped_lock lock{packet_mutex};
214 disconnect_reason = DisconnectReason::None;
215 if (state == State::None) {
216 return ResultBadState;
217 }
218
219 ResetStations();
220 SetState(State::StationOpened);
221
222 return ResultSuccess;
223}
224
225Result LANDiscovery::CloseStation() {
226 std::scoped_lock lock{packet_mutex};
227 if (state == State::None) {
228 return ResultBadState;
229 }
230
231 if (state == State::StationConnected) {
232 Disconnect();
233 }
234
235 ResetStations();
236 SetState(State::Initialized);
237
238 return ResultSuccess;
239}
240
241Result LANDiscovery::CreateNetwork(const SecurityConfig& security_config,
242 const UserConfig& user_config,
243 const NetworkConfig& network_config) {
244 std::scoped_lock lock{packet_mutex};
245
246 if (state != State::AccessPointOpened) {
247 return ResultBadState;
248 }
249
250 InitNetworkInfo();
251 network_info.ldn.node_count_max = network_config.node_count_max;
252 network_info.ldn.security_mode = security_config.security_mode;
253
254 if (network_config.channel == WifiChannel::Default) {
255 network_info.common.channel = WifiChannel::Wifi24_6;
256 } else {
257 network_info.common.channel = network_config.channel;
258 }
259
260 std::independent_bits_engine<std::mt19937, 64, u64> bits_engine;
261 network_info.network_id.session_id.high = bits_engine();
262 network_info.network_id.session_id.low = bits_engine();
263 network_info.network_id.intent_id = network_config.intent_id;
264
265 NodeInfo& node0 = network_info.ldn.nodes[0];
266 const Result rc2 = GetNodeInfo(node0, user_config, network_config.local_communication_version);
267 if (rc2.IsError()) {
268 return ResultAccessPointConnectionFailed;
269 }
270
271 SetState(State::AccessPointCreated);
272
273 InitNodeStateChange();
274 node0.is_connected = 1;
275 UpdateNodes();
276
277 return rc2;
278}
279
280Result LANDiscovery::DestroyNetwork() {
281 for (auto local_ip : connected_clients) {
282 SendPacket(Network::LDNPacketType::DestroyNetwork, local_ip);
283 }
284
285 ResetStations();
286
287 SetState(State::AccessPointOpened);
288 lan_event();
289
290 return ResultSuccess;
291}
292
293Result LANDiscovery::Connect(const NetworkInfo& network_info_, const UserConfig& user_config,
294 u16 local_communication_version) {
295 std::scoped_lock lock{packet_mutex};
296 if (network_info_.ldn.node_count == 0) {
297 return ResultInvalidNodeCount;
298 }
299
300 Result rc = GetNodeInfo(node_info, user_config, local_communication_version);
301 if (rc.IsError()) {
302 return ResultConnectionFailed;
303 }
304
305 Ipv4Address node_host = network_info_.ldn.nodes[0].ipv4_address;
306 std::reverse(std::begin(node_host), std::end(node_host)); // htonl
307 host_ip = node_host;
308 SendPacket(Network::LDNPacketType::Connect, node_info, *host_ip);
309
310 InitNodeStateChange();
311
312 std::this_thread::sleep_for(std::chrono::seconds(1));
313
314 return ResultSuccess;
315}
316
317Result LANDiscovery::Disconnect() {
318 if (host_ip) {
319 SendPacket(Network::LDNPacketType::Disconnect, node_info, *host_ip);
320 }
321
322 SetState(State::StationOpened);
323 lan_event();
324
325 return ResultSuccess;
326}
327
328Result LANDiscovery::Initialize(LanEventFunc lan_event_, bool listening) {
329 std::scoped_lock lock{packet_mutex};
330 if (inited) {
331 return ResultSuccess;
332 }
333
334 for (auto& station : stations) {
335 station.discovery = this;
336 station.node_info = &network_info.ldn.nodes[station.node_id];
337 station.Reset();
338 }
339
340 connected_clients.clear();
341 lan_event = lan_event_;
342
343 SetState(State::Initialized);
344
345 inited = true;
346 return ResultSuccess;
347}
348
349Result LANDiscovery::Finalize() {
350 std::scoped_lock lock{packet_mutex};
351 Result rc = ResultSuccess;
352
353 if (inited) {
354 if (state == State::AccessPointCreated) {
355 DestroyNetwork();
356 }
357 if (state == State::StationConnected) {
358 Disconnect();
359 }
360
361 ResetStations();
362 inited = false;
363 }
364
365 SetState(State::None);
366
367 return rc;
368}
369
370void LANDiscovery::ResetStations() {
371 for (auto& station : stations) {
372 station.Reset();
373 }
374 connected_clients.clear();
375}
376
377void LANDiscovery::UpdateNodes() {
378 u8 count = 0;
379 for (auto& station : stations) {
380 bool connected = station.GetStatus() == NodeStatus::Connected;
381 if (connected) {
382 count++;
383 }
384 station.OverrideInfo();
385 }
386 network_info.ldn.node_count = count + 1;
387
388 for (auto local_ip : connected_clients) {
389 SendPacket(Network::LDNPacketType::SyncNetwork, network_info, local_ip);
390 }
391
392 OnNetworkInfoChanged();
393}
394
395void LANDiscovery::OnSyncNetwork(const NetworkInfo& info) {
396 network_info = info;
397 if (state == State::StationOpened) {
398 SetState(State::StationConnected);
399 }
400 OnNetworkInfoChanged();
401}
402
403void LANDiscovery::OnDisconnectFromHost() {
404 LOG_INFO(Service_LDN, "OnDisconnectFromHost state: {}", static_cast<int>(state));
405 host_ip = std::nullopt;
406 if (state == State::StationConnected) {
407 SetState(State::StationOpened);
408 lan_event();
409 }
410}
411
412void LANDiscovery::OnNetworkInfoChanged() {
413 if (IsNodeStateChanged()) {
414 lan_event();
415 }
416 return;
417}
418
419Network::IPv4Address LANDiscovery::GetLocalIp() const {
420 Network::IPv4Address local_ip{0xFF, 0xFF, 0xFF, 0xFF};
421 if (auto room_member = room_network.GetRoomMember().lock()) {
422 if (room_member->IsConnected()) {
423 local_ip = room_member->GetFakeIpAddress();
424 }
425 }
426 return local_ip;
427}
428
429template <typename Data>
430void LANDiscovery::SendPacket(Network::LDNPacketType type, const Data& data,
431 Ipv4Address remote_ip) {
432 Network::LDNPacket packet;
433 packet.type = type;
434
435 packet.broadcast = false;
436 packet.local_ip = GetLocalIp();
437 packet.remote_ip = remote_ip;
438
439 packet.data.resize(sizeof(data));
440 std::memcpy(packet.data.data(), &data, sizeof(data));
441 SendPacket(packet);
442}
443
444void LANDiscovery::SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip) {
445 Network::LDNPacket packet;
446 packet.type = type;
447
448 packet.broadcast = false;
449 packet.local_ip = GetLocalIp();
450 packet.remote_ip = remote_ip;
451
452 SendPacket(packet);
453}
454
455template <typename Data>
456void LANDiscovery::SendBroadcast(Network::LDNPacketType type, const Data& data) {
457 Network::LDNPacket packet;
458 packet.type = type;
459
460 packet.broadcast = true;
461 packet.local_ip = GetLocalIp();
462
463 packet.data.resize(sizeof(data));
464 std::memcpy(packet.data.data(), &data, sizeof(data));
465 SendPacket(packet);
466}
467
468void LANDiscovery::SendBroadcast(Network::LDNPacketType type) {
469 Network::LDNPacket packet;
470 packet.type = type;
471
472 packet.broadcast = true;
473 packet.local_ip = GetLocalIp();
474
475 SendPacket(packet);
476}
477
478void LANDiscovery::SendPacket(const Network::LDNPacket& packet) {
479 if (auto room_member = room_network.GetRoomMember().lock()) {
480 if (room_member->IsConnected()) {
481 room_member->SendLdnPacket(packet);
482 }
483 }
484}
485
486void LANDiscovery::ReceivePacket(const Network::LDNPacket& packet) {
487 std::scoped_lock lock{packet_mutex};
488 switch (packet.type) {
489 case Network::LDNPacketType::Scan: {
490 LOG_INFO(Frontend, "Scan packet received!");
491 if (state == State::AccessPointCreated) {
492 // Reply to the sender
493 SendPacket(Network::LDNPacketType::ScanResp, network_info, packet.local_ip);
494 }
495 break;
496 }
497 case Network::LDNPacketType::ScanResp: {
498 LOG_INFO(Frontend, "ScanResp packet received!");
499
500 NetworkInfo info{};
501 std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo));
502 scan_results.insert({info.common.bssid, info});
503
504 break;
505 }
506 case Network::LDNPacketType::Connect: {
507 LOG_INFO(Frontend, "Connect packet received!");
508
509 NodeInfo info{};
510 std::memcpy(&info, packet.data.data(), sizeof(NodeInfo));
511
512 connected_clients.push_back(packet.local_ip);
513
514 for (LanStation& station : stations) {
515 if (station.status != NodeStatus::Connected) {
516 *station.node_info = info;
517 station.status = NodeStatus::Connected;
518 break;
519 }
520 }
521
522 UpdateNodes();
523
524 break;
525 }
526 case Network::LDNPacketType::Disconnect: {
527 LOG_INFO(Frontend, "Disconnect packet received!");
528
529 connected_clients.erase(
530 std::remove(connected_clients.begin(), connected_clients.end(), packet.local_ip),
531 connected_clients.end());
532
533 NodeInfo info{};
534 std::memcpy(&info, packet.data.data(), sizeof(NodeInfo));
535
536 for (LanStation& station : stations) {
537 if (station.status == NodeStatus::Connected &&
538 station.node_info->mac_address == info.mac_address) {
539 station.OnClose();
540 break;
541 }
542 }
543
544 break;
545 }
546 case Network::LDNPacketType::DestroyNetwork: {
547 ResetStations();
548 OnDisconnectFromHost();
549 break;
550 }
551 case Network::LDNPacketType::SyncNetwork: {
552 if (state == State::StationOpened || state == State::StationConnected) {
553 LOG_INFO(Frontend, "SyncNetwork packet received!");
554 NetworkInfo info{};
555 std::memcpy(&info, packet.data.data(), sizeof(NetworkInfo));
556
557 OnSyncNetwork(info);
558 } else {
559 LOG_INFO(Frontend, "SyncNetwork packet received but in wrong State!");
560 }
561
562 break;
563 }
564 default: {
565 LOG_INFO(Frontend, "ReceivePacket unhandled type {}", static_cast<int>(packet.type));
566 break;
567 }
568 }
569}
570
571bool LANDiscovery::IsNodeStateChanged() {
572 bool changed = false;
573 const auto& nodes = network_info.ldn.nodes;
574 for (int i = 0; i < NodeCountMax; i++) {
575 if (nodes[i].is_connected != node_last_states[i]) {
576 if (nodes[i].is_connected) {
577 node_changes[i].state_change |= NodeStateChange::Connect;
578 } else {
579 node_changes[i].state_change |= NodeStateChange::Disconnect;
580 }
581 node_last_states[i] = nodes[i].is_connected;
582 changed = true;
583 }
584 }
585 return changed;
586}
587
588bool LANDiscovery::IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const {
589 const auto flag_value = static_cast<u32>(flag);
590 const auto search_flag_value = static_cast<u32>(search_flag);
591 return (flag_value & search_flag_value) == search_flag_value;
592}
593
594int LANDiscovery::GetStationCount() const {
595 return static_cast<int>(
596 std::count_if(stations.begin(), stations.end(), [](const auto& station) {
597 return station.GetStatus() != NodeStatus::Disconnected;
598 }));
599}
600
601MacAddress LANDiscovery::GetFakeMac() const {
602 MacAddress mac{};
603 mac.raw[0] = 0x02;
604 mac.raw[1] = 0x00;
605
606 const auto ip = GetLocalIp();
607 memcpy(mac.raw.data() + 2, &ip, sizeof(ip));
608
609 return mac;
610}
611
612Result LANDiscovery::GetNodeInfo(NodeInfo& node, const UserConfig& userConfig,
613 u16 localCommunicationVersion) {
614 const auto network_interface = Network::GetSelectedNetworkInterface();
615
616 if (!network_interface) {
617 LOG_ERROR(Service_LDN, "No network interface available");
618 return ResultNoIpAddress;
619 }
620
621 node.mac_address = GetFakeMac();
622 node.is_connected = 1;
623 std::memcpy(node.user_name.data(), userConfig.user_name.data(), UserNameBytesMax + 1);
624 node.local_communication_version = localCommunicationVersion;
625
626 Ipv4Address current_address = GetLocalIp();
627 std::reverse(std::begin(current_address), std::end(current_address)); // ntohl
628 node.ipv4_address = current_address;
629
630 return ResultSuccess;
631}
632
633} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/lan_discovery.h b/src/core/hle/service/ldn/lan_discovery.h
new file mode 100644
index 000000000..3833cd764
--- /dev/null
+++ b/src/core/hle/service/ldn/lan_discovery.h
@@ -0,0 +1,134 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <cstring>
8#include <functional>
9#include <memory>
10#include <mutex>
11#include <optional>
12#include <random>
13#include <span>
14#include <thread>
15#include <unordered_map>
16
17#include "common/logging/log.h"
18#include "common/socket_types.h"
19#include "core/hle/result.h"
20#include "core/hle/service/ldn/ldn_results.h"
21#include "core/hle/service/ldn/ldn_types.h"
22#include "network/network.h"
23
24namespace Service::LDN {
25
26class LANDiscovery;
27
28class LanStation {
29public:
30 LanStation(s8 node_id_, LANDiscovery* discovery_);
31 ~LanStation();
32
33 void OnClose();
34 NodeStatus GetStatus() const;
35 void Reset();
36 void OverrideInfo();
37
38protected:
39 friend class LANDiscovery;
40 NodeInfo* node_info;
41 NodeStatus status;
42 s8 node_id;
43 LANDiscovery* discovery;
44};
45
46class LANDiscovery {
47public:
48 using LanEventFunc = std::function<void()>;
49
50 LANDiscovery(Network::RoomNetwork& room_network_);
51 ~LANDiscovery();
52
53 State GetState() const;
54 void SetState(State new_state);
55
56 Result GetNetworkInfo(NetworkInfo& out_network) const;
57 Result GetNetworkInfo(NetworkInfo& out_network, std::vector<NodeLatestUpdate>& out_updates,
58 std::size_t buffer_count);
59
60 DisconnectReason GetDisconnectReason() const;
61 Result Scan(std::vector<NetworkInfo>& networks, u16& count, const ScanFilter& filter);
62 Result SetAdvertiseData(std::span<const u8> data);
63
64 Result OpenAccessPoint();
65 Result CloseAccessPoint();
66
67 Result OpenStation();
68 Result CloseStation();
69
70 Result CreateNetwork(const SecurityConfig& security_config, const UserConfig& user_config,
71 const NetworkConfig& network_config);
72 Result DestroyNetwork();
73
74 Result Connect(const NetworkInfo& network_info_, const UserConfig& user_config,
75 u16 local_communication_version);
76 Result Disconnect();
77
78 Result Initialize(LanEventFunc lan_event_ = empty_func, bool listening = true);
79 Result Finalize();
80
81 void ReceivePacket(const Network::LDNPacket& packet);
82
83protected:
84 friend class LanStation;
85
86 void InitNetworkInfo();
87 void InitNodeStateChange();
88
89 void ResetStations();
90 void UpdateNodes();
91
92 void OnSyncNetwork(const NetworkInfo& info);
93 void OnDisconnectFromHost();
94 void OnNetworkInfoChanged();
95
96 bool IsNodeStateChanged();
97 bool IsFlagSet(ScanFilterFlag flag, ScanFilterFlag search_flag) const;
98 int GetStationCount() const;
99 MacAddress GetFakeMac() const;
100 Result GetNodeInfo(NodeInfo& node, const UserConfig& user_config,
101 u16 local_communication_version);
102
103 Network::IPv4Address GetLocalIp() const;
104 template <typename Data>
105 void SendPacket(Network::LDNPacketType type, const Data& data, Ipv4Address remote_ip);
106 void SendPacket(Network::LDNPacketType type, Ipv4Address remote_ip);
107 template <typename Data>
108 void SendBroadcast(Network::LDNPacketType type, const Data& data);
109 void SendBroadcast(Network::LDNPacketType type);
110 void SendPacket(const Network::LDNPacket& packet);
111
112 static const LanEventFunc empty_func;
113 static constexpr Ssid fake_ssid{"YuzuFakeSsidForLdn"};
114
115 bool inited{};
116 std::mutex packet_mutex;
117 std::array<LanStation, StationCountMax> stations;
118 std::array<NodeLatestUpdate, NodeCountMax> node_changes{};
119 std::array<u8, NodeCountMax> node_last_states{};
120 std::unordered_map<MacAddress, NetworkInfo, MACAddressHash> scan_results{};
121 NodeInfo node_info{};
122 NetworkInfo network_info{};
123 State state{State::None};
124 DisconnectReason disconnect_reason{DisconnectReason::None};
125
126 // TODO (flTobi): Should this be an std::set?
127 std::vector<Ipv4Address> connected_clients;
128 std::optional<Ipv4Address> host_ip;
129
130 LanEventFunc lan_event;
131
132 Network::RoomNetwork& room_network;
133};
134} // namespace Service::LDN
diff --git a/src/core/hle/service/ldn/ldn.cpp b/src/core/hle/service/ldn/ldn.cpp
index c11daff54..ea3e7e55a 100644
--- a/src/core/hle/service/ldn/ldn.cpp
+++ b/src/core/hle/service/ldn/ldn.cpp
@@ -4,11 +4,13 @@
4#include <memory> 4#include <memory>
5 5
6#include "core/core.h" 6#include "core/core.h"
7#include "core/hle/service/ldn/lan_discovery.h"
7#include "core/hle/service/ldn/ldn.h" 8#include "core/hle/service/ldn/ldn.h"
8#include "core/hle/service/ldn/ldn_results.h" 9#include "core/hle/service/ldn/ldn_results.h"
9#include "core/hle/service/ldn/ldn_types.h" 10#include "core/hle/service/ldn/ldn_types.h"
10#include "core/internal_network/network.h" 11#include "core/internal_network/network.h"
11#include "core/internal_network/network_interface.h" 12#include "core/internal_network/network_interface.h"
13#include "network/network.h"
12 14
13// This is defined by synchapi.h and conflicts with ServiceContext::CreateEvent 15// This is defined by synchapi.h and conflicts with ServiceContext::CreateEvent
14#undef CreateEvent 16#undef CreateEvent
@@ -105,13 +107,13 @@ class IUserLocalCommunicationService final
105public: 107public:
106 explicit IUserLocalCommunicationService(Core::System& system_) 108 explicit IUserLocalCommunicationService(Core::System& system_)
107 : ServiceFramework{system_, "IUserLocalCommunicationService", ServiceThreadType::CreateNew}, 109 : ServiceFramework{system_, "IUserLocalCommunicationService", ServiceThreadType::CreateNew},
108 service_context{system, "IUserLocalCommunicationService"}, room_network{ 110 service_context{system, "IUserLocalCommunicationService"},
109 system_.GetRoomNetwork()} { 111 room_network{system_.GetRoomNetwork()}, lan_discovery{room_network} {
110 // clang-format off 112 // clang-format off
111 static const FunctionInfo functions[] = { 113 static const FunctionInfo functions[] = {
112 {0, &IUserLocalCommunicationService::GetState, "GetState"}, 114 {0, &IUserLocalCommunicationService::GetState, "GetState"},
113 {1, &IUserLocalCommunicationService::GetNetworkInfo, "GetNetworkInfo"}, 115 {1, &IUserLocalCommunicationService::GetNetworkInfo, "GetNetworkInfo"},
114 {2, nullptr, "GetIpv4Address"}, 116 {2, &IUserLocalCommunicationService::GetIpv4Address, "GetIpv4Address"},
115 {3, &IUserLocalCommunicationService::GetDisconnectReason, "GetDisconnectReason"}, 117 {3, &IUserLocalCommunicationService::GetDisconnectReason, "GetDisconnectReason"},
116 {4, &IUserLocalCommunicationService::GetSecurityParameter, "GetSecurityParameter"}, 118 {4, &IUserLocalCommunicationService::GetSecurityParameter, "GetSecurityParameter"},
117 {5, &IUserLocalCommunicationService::GetNetworkConfig, "GetNetworkConfig"}, 119 {5, &IUserLocalCommunicationService::GetNetworkConfig, "GetNetworkConfig"},
@@ -119,7 +121,7 @@ public:
119 {101, &IUserLocalCommunicationService::GetNetworkInfoLatestUpdate, "GetNetworkInfoLatestUpdate"}, 121 {101, &IUserLocalCommunicationService::GetNetworkInfoLatestUpdate, "GetNetworkInfoLatestUpdate"},
120 {102, &IUserLocalCommunicationService::Scan, "Scan"}, 122 {102, &IUserLocalCommunicationService::Scan, "Scan"},
121 {103, &IUserLocalCommunicationService::ScanPrivate, "ScanPrivate"}, 123 {103, &IUserLocalCommunicationService::ScanPrivate, "ScanPrivate"},
122 {104, nullptr, "SetWirelessControllerRestriction"}, 124 {104, &IUserLocalCommunicationService::SetWirelessControllerRestriction, "SetWirelessControllerRestriction"},
123 {200, &IUserLocalCommunicationService::OpenAccessPoint, "OpenAccessPoint"}, 125 {200, &IUserLocalCommunicationService::OpenAccessPoint, "OpenAccessPoint"},
124 {201, &IUserLocalCommunicationService::CloseAccessPoint, "CloseAccessPoint"}, 126 {201, &IUserLocalCommunicationService::CloseAccessPoint, "CloseAccessPoint"},
125 {202, &IUserLocalCommunicationService::CreateNetwork, "CreateNetwork"}, 127 {202, &IUserLocalCommunicationService::CreateNetwork, "CreateNetwork"},
@@ -148,16 +150,30 @@ public:
148 } 150 }
149 151
150 ~IUserLocalCommunicationService() { 152 ~IUserLocalCommunicationService() {
153 if (is_initialized) {
154 if (auto room_member = room_network.GetRoomMember().lock()) {
155 room_member->Unbind(ldn_packet_received);
156 }
157 }
158
151 service_context.CloseEvent(state_change_event); 159 service_context.CloseEvent(state_change_event);
152 } 160 }
153 161
162 /// Callback to parse and handle a received LDN packet.
163 void OnLDNPacketReceived(const Network::LDNPacket& packet) {
164 lan_discovery.ReceivePacket(packet);
165 }
166
154 void OnEventFired() { 167 void OnEventFired() {
155 state_change_event->GetWritableEvent().Signal(); 168 state_change_event->GetWritableEvent().Signal();
156 } 169 }
157 170
158 void GetState(Kernel::HLERequestContext& ctx) { 171 void GetState(Kernel::HLERequestContext& ctx) {
159 State state = State::Error; 172 State state = State::Error;
160 LOG_WARNING(Service_LDN, "(STUBBED) called, state = {}", state); 173
174 if (is_initialized) {
175 state = lan_discovery.GetState();
176 }
161 177
162 IPC::ResponseBuilder rb{ctx, 3}; 178 IPC::ResponseBuilder rb{ctx, 3};
163 rb.Push(ResultSuccess); 179 rb.Push(ResultSuccess);
@@ -175,7 +191,7 @@ public:
175 } 191 }
176 192
177 NetworkInfo network_info{}; 193 NetworkInfo network_info{};
178 const auto rc = ResultSuccess; 194 const auto rc = lan_discovery.GetNetworkInfo(network_info);
179 if (rc.IsError()) { 195 if (rc.IsError()) {
180 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); 196 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
181 IPC::ResponseBuilder rb{ctx, 2}; 197 IPC::ResponseBuilder rb{ctx, 2};
@@ -183,28 +199,50 @@ public:
183 return; 199 return;
184 } 200 }
185 201
186 LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}",
187 network_info.common.ssid.GetStringValue(), network_info.ldn.node_count);
188
189 ctx.WriteBuffer<NetworkInfo>(network_info); 202 ctx.WriteBuffer<NetworkInfo>(network_info);
190 IPC::ResponseBuilder rb{ctx, 2}; 203 IPC::ResponseBuilder rb{ctx, 2};
191 rb.Push(rc); 204 rb.Push(ResultSuccess);
192 } 205 }
193 206
194 void GetDisconnectReason(Kernel::HLERequestContext& ctx) { 207 void GetIpv4Address(Kernel::HLERequestContext& ctx) {
195 const auto disconnect_reason = DisconnectReason::None; 208 const auto network_interface = Network::GetSelectedNetworkInterface();
209
210 if (!network_interface) {
211 LOG_ERROR(Service_LDN, "No network interface available");
212 IPC::ResponseBuilder rb{ctx, 2};
213 rb.Push(ResultNoIpAddress);
214 return;
215 }
196 216
197 LOG_WARNING(Service_LDN, "(STUBBED) called, disconnect_reason={}", disconnect_reason); 217 Ipv4Address current_address{Network::TranslateIPv4(network_interface->ip_address)};
218 Ipv4Address subnet_mask{Network::TranslateIPv4(network_interface->subnet_mask)};
219
220 // When we're connected to a room, spoof the hosts IP address
221 if (auto room_member = room_network.GetRoomMember().lock()) {
222 if (room_member->IsConnected()) {
223 current_address = room_member->GetFakeIpAddress();
224 }
225 }
226
227 std::reverse(std::begin(current_address), std::end(current_address)); // ntohl
228 std::reverse(std::begin(subnet_mask), std::end(subnet_mask)); // ntohl
229
230 IPC::ResponseBuilder rb{ctx, 4};
231 rb.Push(ResultSuccess);
232 rb.PushRaw(current_address);
233 rb.PushRaw(subnet_mask);
234 }
198 235
236 void GetDisconnectReason(Kernel::HLERequestContext& ctx) {
199 IPC::ResponseBuilder rb{ctx, 3}; 237 IPC::ResponseBuilder rb{ctx, 3};
200 rb.Push(ResultSuccess); 238 rb.Push(ResultSuccess);
201 rb.PushEnum(disconnect_reason); 239 rb.PushEnum(lan_discovery.GetDisconnectReason());
202 } 240 }
203 241
204 void GetSecurityParameter(Kernel::HLERequestContext& ctx) { 242 void GetSecurityParameter(Kernel::HLERequestContext& ctx) {
205 SecurityParameter security_parameter{}; 243 SecurityParameter security_parameter{};
206 NetworkInfo info{}; 244 NetworkInfo info{};
207 const Result rc = ResultSuccess; 245 const Result rc = lan_discovery.GetNetworkInfo(info);
208 246
209 if (rc.IsError()) { 247 if (rc.IsError()) {
210 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); 248 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
@@ -217,8 +255,6 @@ public:
217 std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(), 255 std::memcpy(security_parameter.data.data(), info.ldn.security_parameter.data(),
218 sizeof(SecurityParameter::data)); 256 sizeof(SecurityParameter::data));
219 257
220 LOG_WARNING(Service_LDN, "(STUBBED) called");
221
222 IPC::ResponseBuilder rb{ctx, 10}; 258 IPC::ResponseBuilder rb{ctx, 10};
223 rb.Push(rc); 259 rb.Push(rc);
224 rb.PushRaw<SecurityParameter>(security_parameter); 260 rb.PushRaw<SecurityParameter>(security_parameter);
@@ -227,7 +263,7 @@ public:
227 void GetNetworkConfig(Kernel::HLERequestContext& ctx) { 263 void GetNetworkConfig(Kernel::HLERequestContext& ctx) {
228 NetworkConfig config{}; 264 NetworkConfig config{};
229 NetworkInfo info{}; 265 NetworkInfo info{};
230 const Result rc = ResultSuccess; 266 const Result rc = lan_discovery.GetNetworkInfo(info);
231 267
232 if (rc.IsError()) { 268 if (rc.IsError()) {
233 LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw); 269 LOG_ERROR(Service_LDN, "NetworkConfig is not valid {}", rc.raw);
@@ -241,12 +277,6 @@ public:
241 config.node_count_max = info.ldn.node_count_max; 277 config.node_count_max = info.ldn.node_count_max;
242 config.local_communication_version = info.ldn.nodes[0].local_communication_version; 278 config.local_communication_version = info.ldn.nodes[0].local_communication_version;
243 279
244 LOG_WARNING(Service_LDN,
245 "(STUBBED) called, intent_id={}/{}, channel={}, node_count_max={}, "
246 "local_communication_version={}",
247 config.intent_id.local_communication_id, config.intent_id.scene_id,
248 config.channel, config.node_count_max, config.local_communication_version);
249
250 IPC::ResponseBuilder rb{ctx, 10}; 280 IPC::ResponseBuilder rb{ctx, 10};
251 rb.Push(rc); 281 rb.Push(rc);
252 rb.PushRaw<NetworkConfig>(config); 282 rb.PushRaw<NetworkConfig>(config);
@@ -265,17 +295,17 @@ public:
265 const std::size_t node_buffer_count = ctx.GetWriteBufferSize(1) / sizeof(NodeLatestUpdate); 295 const std::size_t node_buffer_count = ctx.GetWriteBufferSize(1) / sizeof(NodeLatestUpdate);
266 296
267 if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) { 297 if (node_buffer_count == 0 || network_buffer_size != sizeof(NetworkInfo)) {
268 LOG_ERROR(Service_LDN, "Invalid buffer size {}, {}", network_buffer_size, 298 LOG_ERROR(Service_LDN, "Invalid buffer, size = {}, count = {}", network_buffer_size,
269 node_buffer_count); 299 node_buffer_count);
270 IPC::ResponseBuilder rb{ctx, 2}; 300 IPC::ResponseBuilder rb{ctx, 2};
271 rb.Push(ResultBadInput); 301 rb.Push(ResultBadInput);
272 return; 302 return;
273 } 303 }
274 304
275 NetworkInfo info; 305 NetworkInfo info{};
276 std::vector<NodeLatestUpdate> latest_update(node_buffer_count); 306 std::vector<NodeLatestUpdate> latest_update(node_buffer_count);
277 307
278 const auto rc = ResultSuccess; 308 const auto rc = lan_discovery.GetNetworkInfo(info, latest_update, latest_update.size());
279 if (rc.IsError()) { 309 if (rc.IsError()) {
280 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw); 310 LOG_ERROR(Service_LDN, "NetworkInfo is not valid {}", rc.raw);
281 IPC::ResponseBuilder rb{ctx, 2}; 311 IPC::ResponseBuilder rb{ctx, 2};
@@ -283,9 +313,6 @@ public:
283 return; 313 return;
284 } 314 }
285 315
286 LOG_WARNING(Service_LDN, "(STUBBED) called, ssid='{}', nodes={}",
287 info.common.ssid.GetStringValue(), info.ldn.node_count);
288
289 ctx.WriteBuffer(info, 0); 316 ctx.WriteBuffer(info, 0);
290 ctx.WriteBuffer(latest_update, 1); 317 ctx.WriteBuffer(latest_update, 1);
291 318
@@ -317,92 +344,78 @@ public:
317 344
318 u16 count = 0; 345 u16 count = 0;
319 std::vector<NetworkInfo> network_infos(network_info_size); 346 std::vector<NetworkInfo> network_infos(network_info_size);
347 Result rc = lan_discovery.Scan(network_infos, count, scan_filter);
320 348
321 LOG_WARNING(Service_LDN, 349 LOG_INFO(Service_LDN,
322 "(STUBBED) called, channel={}, filter_scan_flag={}, filter_network_type={}", 350 "called, channel={}, filter_scan_flag={}, filter_network_type={}, is_private={}",
323 channel, scan_filter.flag, scan_filter.network_type); 351 channel, scan_filter.flag, scan_filter.network_type, is_private);
324 352
325 ctx.WriteBuffer(network_infos); 353 ctx.WriteBuffer(network_infos);
326 354
327 IPC::ResponseBuilder rb{ctx, 3}; 355 IPC::ResponseBuilder rb{ctx, 3};
328 rb.Push(ResultSuccess); 356 rb.Push(rc);
329 rb.Push<u32>(count); 357 rb.Push<u32>(count);
330 } 358 }
331 359
332 void OpenAccessPoint(Kernel::HLERequestContext& ctx) { 360 void SetWirelessControllerRestriction(Kernel::HLERequestContext& ctx) {
333 LOG_WARNING(Service_LDN, "(STUBBED) called"); 361 LOG_WARNING(Service_LDN, "(STUBBED) called");
334 362
335 IPC::ResponseBuilder rb{ctx, 2}; 363 IPC::ResponseBuilder rb{ctx, 2};
336 rb.Push(ResultSuccess); 364 rb.Push(ResultSuccess);
337 } 365 }
338 366
367 void OpenAccessPoint(Kernel::HLERequestContext& ctx) {
368 LOG_INFO(Service_LDN, "called");
369
370 IPC::ResponseBuilder rb{ctx, 2};
371 rb.Push(lan_discovery.OpenAccessPoint());
372 }
373
339 void CloseAccessPoint(Kernel::HLERequestContext& ctx) { 374 void CloseAccessPoint(Kernel::HLERequestContext& ctx) {
340 LOG_WARNING(Service_LDN, "(STUBBED) called"); 375 LOG_INFO(Service_LDN, "called");
341 376
342 IPC::ResponseBuilder rb{ctx, 2}; 377 IPC::ResponseBuilder rb{ctx, 2};
343 rb.Push(ResultSuccess); 378 rb.Push(lan_discovery.CloseAccessPoint());
344 } 379 }
345 380
346 void CreateNetwork(Kernel::HLERequestContext& ctx) { 381 void CreateNetwork(Kernel::HLERequestContext& ctx) {
347 IPC::RequestParser rp{ctx}; 382 LOG_INFO(Service_LDN, "called");
348 struct Parameters {
349 SecurityConfig security_config;
350 UserConfig user_config;
351 INSERT_PADDING_WORDS_NOINIT(1);
352 NetworkConfig network_config;
353 };
354 static_assert(sizeof(Parameters) == 0x98, "Parameters has incorrect size.");
355 383
356 const auto parameters{rp.PopRaw<Parameters>()}; 384 CreateNetworkImpl(ctx);
385 }
357 386
358 LOG_WARNING(Service_LDN, 387 void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) {
359 "(STUBBED) called, passphrase_size={}, security_mode={}, " 388 LOG_INFO(Service_LDN, "called");
360 "local_communication_version={}",
361 parameters.security_config.passphrase_size,
362 parameters.security_config.security_mode,
363 parameters.network_config.local_communication_version);
364 389
365 IPC::ResponseBuilder rb{ctx, 2}; 390 CreateNetworkImpl(ctx, true);
366 rb.Push(ResultSuccess);
367 } 391 }
368 392
369 void CreateNetworkPrivate(Kernel::HLERequestContext& ctx) { 393 void CreateNetworkImpl(Kernel::HLERequestContext& ctx, bool is_private = false) {
370 IPC::RequestParser rp{ctx}; 394 IPC::RequestParser rp{ctx};
371 struct Parameters {
372 SecurityConfig security_config;
373 SecurityParameter security_parameter;
374 UserConfig user_config;
375 NetworkConfig network_config;
376 };
377 static_assert(sizeof(Parameters) == 0xB8, "Parameters has incorrect size.");
378
379 const auto parameters{rp.PopRaw<Parameters>()};
380 395
381 LOG_WARNING(Service_LDN, 396 const auto security_config{rp.PopRaw<SecurityConfig>()};
382 "(STUBBED) called, passphrase_size={}, security_mode={}, " 397 [[maybe_unused]] const auto security_parameter{is_private ? rp.PopRaw<SecurityParameter>()
383 "local_communication_version={}", 398 : SecurityParameter{}};
384 parameters.security_config.passphrase_size, 399 const auto user_config{rp.PopRaw<UserConfig>()};
385 parameters.security_config.security_mode, 400 rp.Pop<u32>(); // Padding
386 parameters.network_config.local_communication_version); 401 const auto network_Config{rp.PopRaw<NetworkConfig>()};
387 402
388 IPC::ResponseBuilder rb{ctx, 2}; 403 IPC::ResponseBuilder rb{ctx, 2};
389 rb.Push(ResultSuccess); 404 rb.Push(lan_discovery.CreateNetwork(security_config, user_config, network_Config));
390 } 405 }
391 406
392 void DestroyNetwork(Kernel::HLERequestContext& ctx) { 407 void DestroyNetwork(Kernel::HLERequestContext& ctx) {
393 LOG_WARNING(Service_LDN, "(STUBBED) called"); 408 LOG_INFO(Service_LDN, "called");
394 409
395 IPC::ResponseBuilder rb{ctx, 2}; 410 IPC::ResponseBuilder rb{ctx, 2};
396 rb.Push(ResultSuccess); 411 rb.Push(lan_discovery.DestroyNetwork());
397 } 412 }
398 413
399 void SetAdvertiseData(Kernel::HLERequestContext& ctx) { 414 void SetAdvertiseData(Kernel::HLERequestContext& ctx) {
400 std::vector<u8> read_buffer = ctx.ReadBuffer(); 415 std::vector<u8> read_buffer = ctx.ReadBuffer();
401 416
402 LOG_WARNING(Service_LDN, "(STUBBED) called, size {}", read_buffer.size());
403
404 IPC::ResponseBuilder rb{ctx, 2}; 417 IPC::ResponseBuilder rb{ctx, 2};
405 rb.Push(ResultSuccess); 418 rb.Push(lan_discovery.SetAdvertiseData(read_buffer));
406 } 419 }
407 420
408 void SetStationAcceptPolicy(Kernel::HLERequestContext& ctx) { 421 void SetStationAcceptPolicy(Kernel::HLERequestContext& ctx) {
@@ -420,17 +433,17 @@ public:
420 } 433 }
421 434
422 void OpenStation(Kernel::HLERequestContext& ctx) { 435 void OpenStation(Kernel::HLERequestContext& ctx) {
423 LOG_WARNING(Service_LDN, "(STUBBED) called"); 436 LOG_INFO(Service_LDN, "called");
424 437
425 IPC::ResponseBuilder rb{ctx, 2}; 438 IPC::ResponseBuilder rb{ctx, 2};
426 rb.Push(ResultSuccess); 439 rb.Push(lan_discovery.OpenStation());
427 } 440 }
428 441
429 void CloseStation(Kernel::HLERequestContext& ctx) { 442 void CloseStation(Kernel::HLERequestContext& ctx) {
430 LOG_WARNING(Service_LDN, "(STUBBED) called"); 443 LOG_INFO(Service_LDN, "called");
431 444
432 IPC::ResponseBuilder rb{ctx, 2}; 445 IPC::ResponseBuilder rb{ctx, 2};
433 rb.Push(ResultSuccess); 446 rb.Push(lan_discovery.CloseStation());
434 } 447 }
435 448
436 void Connect(Kernel::HLERequestContext& ctx) { 449 void Connect(Kernel::HLERequestContext& ctx) {
@@ -445,16 +458,13 @@ public:
445 458
446 const auto parameters{rp.PopRaw<Parameters>()}; 459 const auto parameters{rp.PopRaw<Parameters>()};
447 460
448 LOG_WARNING(Service_LDN, 461 LOG_INFO(Service_LDN,
449 "(STUBBED) called, passphrase_size={}, security_mode={}, " 462 "called, passphrase_size={}, security_mode={}, "
450 "local_communication_version={}", 463 "local_communication_version={}",
451 parameters.security_config.passphrase_size, 464 parameters.security_config.passphrase_size,
452 parameters.security_config.security_mode, 465 parameters.security_config.security_mode, parameters.local_communication_version);
453 parameters.local_communication_version);
454 466
455 const std::vector<u8> read_buffer = ctx.ReadBuffer(); 467 const std::vector<u8> read_buffer = ctx.ReadBuffer();
456 NetworkInfo network_info{};
457
458 if (read_buffer.size() != sizeof(NetworkInfo)) { 468 if (read_buffer.size() != sizeof(NetworkInfo)) {
459 LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!"); 469 LOG_ERROR(Frontend, "NetworkInfo doesn't match read_buffer size!");
460 IPC::ResponseBuilder rb{ctx, 2}; 470 IPC::ResponseBuilder rb{ctx, 2};
@@ -462,40 +472,47 @@ public:
462 return; 472 return;
463 } 473 }
464 474
475 NetworkInfo network_info{};
465 std::memcpy(&network_info, read_buffer.data(), read_buffer.size()); 476 std::memcpy(&network_info, read_buffer.data(), read_buffer.size());
466 477
467 IPC::ResponseBuilder rb{ctx, 2}; 478 IPC::ResponseBuilder rb{ctx, 2};
468 rb.Push(ResultSuccess); 479 rb.Push(lan_discovery.Connect(network_info, parameters.user_config,
480 static_cast<u16>(parameters.local_communication_version)));
469 } 481 }
470 482
471 void Disconnect(Kernel::HLERequestContext& ctx) { 483 void Disconnect(Kernel::HLERequestContext& ctx) {
472 LOG_WARNING(Service_LDN, "(STUBBED) called"); 484 LOG_INFO(Service_LDN, "called");
473 485
474 IPC::ResponseBuilder rb{ctx, 2}; 486 IPC::ResponseBuilder rb{ctx, 2};
475 rb.Push(ResultSuccess); 487 rb.Push(lan_discovery.Disconnect());
476 } 488 }
477 void Initialize(Kernel::HLERequestContext& ctx) {
478 LOG_WARNING(Service_LDN, "(STUBBED) called");
479 489
490 void Initialize(Kernel::HLERequestContext& ctx) {
480 const auto rc = InitializeImpl(ctx); 491 const auto rc = InitializeImpl(ctx);
492 if (rc.IsError()) {
493 LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
494 }
481 495
482 IPC::ResponseBuilder rb{ctx, 2}; 496 IPC::ResponseBuilder rb{ctx, 2};
483 rb.Push(rc); 497 rb.Push(rc);
484 } 498 }
485 499
486 void Finalize(Kernel::HLERequestContext& ctx) { 500 void Finalize(Kernel::HLERequestContext& ctx) {
487 LOG_WARNING(Service_LDN, "(STUBBED) called"); 501 if (auto room_member = room_network.GetRoomMember().lock()) {
502 room_member->Unbind(ldn_packet_received);
503 }
488 504
489 is_initialized = false; 505 is_initialized = false;
490 506
491 IPC::ResponseBuilder rb{ctx, 2}; 507 IPC::ResponseBuilder rb{ctx, 2};
492 rb.Push(ResultSuccess); 508 rb.Push(lan_discovery.Finalize());
493 } 509 }
494 510
495 void Initialize2(Kernel::HLERequestContext& ctx) { 511 void Initialize2(Kernel::HLERequestContext& ctx) {
496 LOG_WARNING(Service_LDN, "(STUBBED) called");
497
498 const auto rc = InitializeImpl(ctx); 512 const auto rc = InitializeImpl(ctx);
513 if (rc.IsError()) {
514 LOG_ERROR(Service_LDN, "Network isn't initialized, rc={}", rc.raw);
515 }
499 516
500 IPC::ResponseBuilder rb{ctx, 2}; 517 IPC::ResponseBuilder rb{ctx, 2};
501 rb.Push(rc); 518 rb.Push(rc);
@@ -508,14 +525,26 @@ public:
508 return ResultAirplaneModeEnabled; 525 return ResultAirplaneModeEnabled;
509 } 526 }
510 527
528 if (auto room_member = room_network.GetRoomMember().lock()) {
529 ldn_packet_received = room_member->BindOnLdnPacketReceived(
530 [this](const Network::LDNPacket& packet) { OnLDNPacketReceived(packet); });
531 } else {
532 LOG_ERROR(Service_LDN, "Couldn't bind callback!");
533 return ResultAirplaneModeEnabled;
534 }
535
536 lan_discovery.Initialize([&]() { OnEventFired(); });
511 is_initialized = true; 537 is_initialized = true;
512 // TODO (flTobi): Change this to ResultSuccess when LDN is fully implemented 538 return ResultSuccess;
513 return ResultAirplaneModeEnabled;
514 } 539 }
515 540
516 KernelHelpers::ServiceContext service_context; 541 KernelHelpers::ServiceContext service_context;
517 Kernel::KEvent* state_change_event; 542 Kernel::KEvent* state_change_event;
518 Network::RoomNetwork& room_network; 543 Network::RoomNetwork& room_network;
544 LANDiscovery lan_discovery;
545
546 // Callback identifier for the OnLDNPacketReceived event.
547 Network::RoomMember::CallbackHandle<Network::LDNPacket> ldn_packet_received;
519 548
520 bool is_initialized{}; 549 bool is_initialized{};
521}; 550};
diff --git a/src/core/hle/service/ldn/ldn_types.h b/src/core/hle/service/ldn/ldn_types.h
index 6231e936d..44c2c773b 100644
--- a/src/core/hle/service/ldn/ldn_types.h
+++ b/src/core/hle/service/ldn/ldn_types.h
@@ -31,6 +31,8 @@ enum class NodeStateChange : u8 {
31 DisconnectAndConnect, 31 DisconnectAndConnect,
32}; 32};
33 33
34DECLARE_ENUM_FLAG_OPERATORS(NodeStateChange)
35
34enum class ScanFilterFlag : u32 { 36enum class ScanFilterFlag : u32 {
35 None = 0, 37 None = 0,
36 LocalCommunicationId = 1 << 0, 38 LocalCommunicationId = 1 << 0,
@@ -100,13 +102,13 @@ enum class AcceptPolicy : u8 {
100 102
101enum class WifiChannel : s16 { 103enum class WifiChannel : s16 {
102 Default = 0, 104 Default = 0,
103 wifi24_1 = 1, 105 Wifi24_1 = 1,
104 wifi24_6 = 6, 106 Wifi24_6 = 6,
105 wifi24_11 = 11, 107 Wifi24_11 = 11,
106 wifi50_36 = 36, 108 Wifi50_36 = 36,
107 wifi50_40 = 40, 109 Wifi50_40 = 40,
108 wifi50_44 = 44, 110 Wifi50_44 = 44,
109 wifi50_48 = 48, 111 Wifi50_48 = 48,
110}; 112};
111 113
112enum class LinkLevel : s8 { 114enum class LinkLevel : s8 {
@@ -116,6 +118,11 @@ enum class LinkLevel : s8 {
116 Excellent, 118 Excellent,
117}; 119};
118 120
121enum class NodeStatus : u8 {
122 Disconnected,
123 Connected,
124};
125
119struct NodeLatestUpdate { 126struct NodeLatestUpdate {
120 NodeStateChange state_change; 127 NodeStateChange state_change;
121 INSERT_PADDING_BYTES(0x7); // Unknown 128 INSERT_PADDING_BYTES(0x7); // Unknown
@@ -150,7 +157,7 @@ struct Ssid {
150 157
151 Ssid() = default; 158 Ssid() = default;
152 159
153 explicit Ssid(std::string_view data) { 160 constexpr explicit Ssid(std::string_view data) {
154 length = static_cast<u8>(std::min(data.size(), SsidLengthMax)); 161 length = static_cast<u8>(std::min(data.size(), SsidLengthMax));
155 data.copy(raw.data(), length); 162 data.copy(raw.data(), length);
156 raw[length] = 0; 163 raw[length] = 0;
@@ -159,19 +166,18 @@ struct Ssid {
159 std::string GetStringValue() const { 166 std::string GetStringValue() const {
160 return std::string(raw.data()); 167 return std::string(raw.data());
161 } 168 }
162};
163static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size");
164 169
165struct Ipv4Address { 170 bool operator==(const Ssid& b) const {
166 union { 171 return (length == b.length) && (std::memcmp(raw.data(), b.raw.data(), length) == 0);
167 u32 raw{}; 172 }
168 std::array<u8, 4> bytes;
169 };
170 173
171 std::string GetStringValue() const { 174 bool operator!=(const Ssid& b) const {
172 return fmt::format("{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]); 175 return !operator==(b);
173 } 176 }
174}; 177};
178static_assert(sizeof(Ssid) == 0x22, "Ssid is an invalid size");
179
180using Ipv4Address = std::array<u8, 4>;
175static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size"); 181static_assert(sizeof(Ipv4Address) == 0x4, "Ipv4Address is an invalid size");
176 182
177struct MacAddress { 183struct MacAddress {
@@ -181,6 +187,14 @@ struct MacAddress {
181}; 187};
182static_assert(sizeof(MacAddress) == 0x6, "MacAddress is an invalid size"); 188static_assert(sizeof(MacAddress) == 0x6, "MacAddress is an invalid size");
183 189
190struct MACAddressHash {
191 size_t operator()(const MacAddress& address) const {
192 u64 value{};
193 std::memcpy(&value, address.raw.data(), sizeof(address.raw));
194 return value;
195 }
196};
197
184struct ScanFilter { 198struct ScanFilter {
185 NetworkId network_id; 199 NetworkId network_id;
186 NetworkType network_type; 200 NetworkType network_type;
diff --git a/src/core/hle/service/mii/mii_manager.cpp b/src/core/hle/service/mii/mii_manager.cpp
index c484a9c8d..3a2fe938f 100644
--- a/src/core/hle/service/mii/mii_manager.cpp
+++ b/src/core/hle/service/mii/mii_manager.cpp
@@ -427,12 +427,11 @@ CharInfo MiiManager::BuildDefault(std::size_t index) {
427 return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); 427 return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id));
428} 428}
429 429
430CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const { 430CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const {
431 Service::Mii::MiiManager manager; 431 Service::Mii::MiiManager manager;
432 auto mii = manager.BuildDefault(0); 432 auto mii = manager.BuildDefault(0);
433 433
434 // Check if mii data exist 434 if (!ValidateV3Info(mii_v3)) {
435 if (mii_v3.mii_name[0] == 0) {
436 return mii; 435 return mii;
437 } 436 }
438 437
@@ -443,8 +442,15 @@ CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
443 mii.height = mii_v3.height; 442 mii.height = mii_v3.height;
444 mii.build = mii_v3.build; 443 mii.build = mii_v3.build;
445 444
446 memset(mii.name.data(), 0, sizeof(mii.name)); 445 // Copy name until string terminator
447 memcpy(mii.name.data(), mii_v3.mii_name.data(), sizeof(mii_v3.mii_name)); 446 mii.name = {};
447 for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
448 mii.name[index] = mii_v3.mii_name[index];
449 if (mii.name[index] == 0) {
450 break;
451 }
452 }
453
448 mii.font_region = mii_v3.region_information.character_set; 454 mii.font_region = mii_v3.region_information.character_set;
449 455
450 mii.faceline_type = mii_v3.appearance_bits1.face_shape; 456 mii.faceline_type = mii_v3.appearance_bits1.face_shape;
@@ -504,6 +510,151 @@ CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
504 return mii; 510 return mii;
505} 511}
506 512
513Ver3StoreData MiiManager::ConvertCharInfoToV3(const CharInfo& mii) const {
514 Service::Mii::MiiManager manager;
515 Ver3StoreData mii_v3{};
516
517 // TODO: We are ignoring a bunch of data from the mii_v3
518
519 mii_v3.version = 1;
520 mii_v3.mii_information.gender.Assign(mii.gender);
521 mii_v3.mii_information.favorite_color.Assign(mii.favorite_color);
522 mii_v3.height = mii.height;
523 mii_v3.build = mii.build;
524
525 // Copy name until string terminator
526 mii_v3.mii_name = {};
527 for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
528 mii_v3.mii_name[index] = mii.name[index];
529 if (mii_v3.mii_name[index] == 0) {
530 break;
531 }
532 }
533
534 mii_v3.region_information.character_set.Assign(mii.font_region);
535
536 mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type);
537 mii_v3.appearance_bits1.skin_color.Assign(mii.faceline_color);
538 mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle);
539 mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make);
540
541 mii_v3.hair_style = mii.hair_type;
542 mii_v3.appearance_bits3.hair_color.Assign(mii.hair_color);
543 mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip);
544
545 mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type);
546 mii_v3.appearance_bits4.eye_color.Assign(mii.eye_color);
547 mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale);
548 mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect);
549 mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate);
550 mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x);
551 mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y);
552
553 mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type);
554 mii_v3.appearance_bits5.eyebrow_color.Assign(mii.eyebrow_color);
555 mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale);
556 mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect);
557 mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate);
558 mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x);
559 mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y);
560
561 mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type);
562 mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale);
563 mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y);
564
565 mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type);
566 mii_v3.appearance_bits7.mouth_color.Assign(mii.mouth_color);
567 mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale);
568 mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect);
569 mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y);
570
571 mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type);
572 mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale);
573 mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y);
574
575 mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type);
576 mii_v3.appearance_bits9.facial_hair_color.Assign(mii.beard_color);
577
578 mii_v3.appearance_bits10.glasses_type.Assign(mii.glasses_type);
579 mii_v3.appearance_bits10.glasses_color.Assign(mii.glasses_color);
580 mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale);
581 mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y);
582
583 mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type);
584 mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale);
585 mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x);
586 mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y);
587
588 // TODO: Validate mii_v3 data
589
590 return mii_v3;
591}
592
593bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const {
594 bool is_valid = mii_v3.version == 0 || mii_v3.version == 3;
595
596 is_valid = is_valid && (mii_v3.mii_name[0] != 0);
597
598 is_valid = is_valid && (mii_v3.mii_information.birth_month < 13);
599 is_valid = is_valid && (mii_v3.mii_information.birth_day < 32);
600 is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12);
601 is_valid = is_valid && (mii_v3.height < 128);
602 is_valid = is_valid && (mii_v3.build < 128);
603
604 is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12);
605 is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7);
606 is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12);
607 is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12);
608
609 is_valid = is_valid && (mii_v3.hair_style < 132);
610 is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8);
611
612 is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60);
613 is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6);
614 is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8);
615 is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7);
616 is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8);
617 is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13);
618 is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19);
619
620 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25);
621 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8);
622 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9);
623 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7);
624 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12);
625 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12);
626 is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19);
627
628 is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18);
629 is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9);
630 is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19);
631
632 is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36);
633 is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5);
634 is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9);
635 is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7);
636 is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19);
637
638 is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6);
639 is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7);
640 is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17);
641
642 is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6);
643 is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8);
644
645 is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9);
646 is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6);
647 is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8);
648 is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21);
649
650 is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2);
651 is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9);
652 is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17);
653 is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31);
654
655 return is_valid;
656}
657
507ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) { 658ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
508 std::vector<MiiInfoElement> result; 659 std::vector<MiiInfoElement> result;
509 660
diff --git a/src/core/hle/service/mii/mii_manager.h b/src/core/hle/service/mii/mii_manager.h
index d847de0bd..83ad3d343 100644
--- a/src/core/hle/service/mii/mii_manager.h
+++ b/src/core/hle/service/mii/mii_manager.h
@@ -22,7 +22,9 @@ public:
22 ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag); 22 ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag);
23 CharInfo BuildRandom(Age age, Gender gender, Race race); 23 CharInfo BuildRandom(Age age, Gender gender, Race race);
24 CharInfo BuildDefault(std::size_t index); 24 CharInfo BuildDefault(std::size_t index);
25 CharInfo ConvertV3ToCharInfo(Ver3StoreData mii_v3) const; 25 CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const;
26 Ver3StoreData ConvertCharInfoToV3(const CharInfo& mii) const;
27 bool ValidateV3Info(const Ver3StoreData& mii_v3) const;
26 ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag); 28 ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag);
27 Result GetIndex(const CharInfo& info, u32& index); 29 Result GetIndex(const CharInfo& info, u32& index);
28 30
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index 13a843a28..046c5f18f 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -106,10 +106,10 @@ public:
106 {1, &IUser::FinalizeOld, "FinalizeOld"}, 106 {1, &IUser::FinalizeOld, "FinalizeOld"},
107 {2, &IUser::GetStateOld, "GetStateOld"}, 107 {2, &IUser::GetStateOld, "GetStateOld"},
108 {3, &IUser::IsNfcEnabledOld, "IsNfcEnabledOld"}, 108 {3, &IUser::IsNfcEnabledOld, "IsNfcEnabledOld"},
109 {400, nullptr, "Initialize"}, 109 {400, &IUser::InitializeOld, "Initialize"},
110 {401, nullptr, "Finalize"}, 110 {401, &IUser::FinalizeOld, "Finalize"},
111 {402, nullptr, "GetState"}, 111 {402, &IUser::GetStateOld, "GetState"},
112 {403, nullptr, "IsNfcEnabled"}, 112 {403, &IUser::IsNfcEnabledOld, "IsNfcEnabled"},
113 {404, nullptr, "ListDevices"}, 113 {404, nullptr, "ListDevices"},
114 {405, nullptr, "GetDeviceState"}, 114 {405, nullptr, "GetDeviceState"},
115 {406, nullptr, "GetNpadId"}, 115 {406, nullptr, "GetNpadId"},
diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfp/amiibo_crypto.cpp
index 31dd3a307..ce0bc3f75 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.cpp
+++ b/src/core/hle/service/nfp/amiibo_crypto.cpp
@@ -20,13 +20,14 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
20 const auto& amiibo_data = ntag_file.user_memory; 20 const auto& amiibo_data = ntag_file.user_memory;
21 LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock); 21 LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock);
22 LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", ntag_file.compability_container); 22 LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", ntag_file.compability_container);
23 LOG_INFO(Service_NFP, "write_count={}", amiibo_data.write_counter); 23 LOG_DEBUG(Service_NFP, "write_count={}", static_cast<u16>(amiibo_data.write_counter));
24 24
25 LOG_INFO(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id); 25 LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
26 LOG_INFO(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant); 26 LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
27 LOG_INFO(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type); 27 LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
28 LOG_INFO(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number); 28 LOG_DEBUG(Service_NFP, "model_number=0x{0:x}",
29 LOG_INFO(Service_NFP, "series={}", amiibo_data.model_info.series); 29 static_cast<u16>(amiibo_data.model_info.model_number));
30 LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series);
30 LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.constant_value); 31 LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.constant_value);
31 32
32 LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock); 33 LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock);
@@ -35,11 +36,12 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
35 36
36 // Validate UUID 37 // Validate UUID
37 constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3` 38 constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3`
38 if ((CT ^ ntag_file.uuid[0] ^ ntag_file.uuid[1] ^ ntag_file.uuid[2]) != ntag_file.uuid[3]) { 39 if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) !=
40 ntag_file.uuid.uid[3]) {
39 return false; 41 return false;
40 } 42 }
41 if ((ntag_file.uuid[4] ^ ntag_file.uuid[5] ^ ntag_file.uuid[6] ^ ntag_file.uuid[7]) != 43 if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^
42 ntag_file.uuid[8]) { 44 ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) {
43 return false; 45 return false;
44 } 46 }
45 47
@@ -56,8 +58,9 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
56 if (amiibo_data.model_info.constant_value != 0x02) { 58 if (amiibo_data.model_info.constant_value != 0x02) {
57 return false; 59 return false;
58 } 60 }
59 // dynamic_lock value apparently is not constant 61 if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001U) {
60 // ntag_file.dynamic_lock == 0x0F0001 62 return false;
63 }
61 if (ntag_file.CFG0 != 0x04000000U) { 64 if (ntag_file.CFG0 != 0x04000000U) {
62 return false; 65 return false;
63 } 66 }
@@ -70,7 +73,8 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
70NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) { 73NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
71 NTAG215File encoded_data{}; 74 NTAG215File encoded_data{};
72 75
73 memcpy(encoded_data.uuid2.data(), nfc_data.uuid.data() + 0x8, sizeof(encoded_data.uuid2)); 76 encoded_data.uid = nfc_data.uuid.uid;
77 encoded_data.nintendo_id = nfc_data.uuid.nintendo_id;
74 encoded_data.static_lock = nfc_data.static_lock; 78 encoded_data.static_lock = nfc_data.static_lock;
75 encoded_data.compability_container = nfc_data.compability_container; 79 encoded_data.compability_container = nfc_data.compability_container;
76 encoded_data.hmac_data = nfc_data.user_memory.hmac_data; 80 encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
@@ -82,10 +86,10 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
82 encoded_data.applicaton_write_counter = nfc_data.user_memory.applicaton_write_counter; 86 encoded_data.applicaton_write_counter = nfc_data.user_memory.applicaton_write_counter;
83 encoded_data.application_area_id = nfc_data.user_memory.application_area_id; 87 encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
84 encoded_data.unknown = nfc_data.user_memory.unknown; 88 encoded_data.unknown = nfc_data.user_memory.unknown;
85 encoded_data.hash = nfc_data.user_memory.hash; 89 encoded_data.unknown2 = nfc_data.user_memory.unknown2;
86 encoded_data.application_area = nfc_data.user_memory.application_area; 90 encoded_data.application_area = nfc_data.user_memory.application_area;
87 encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag; 91 encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
88 memcpy(encoded_data.uuid.data(), nfc_data.uuid.data(), sizeof(encoded_data.uuid)); 92 encoded_data.lock_bytes = nfc_data.uuid.lock_bytes;
89 encoded_data.model_info = nfc_data.user_memory.model_info; 93 encoded_data.model_info = nfc_data.user_memory.model_info;
90 encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt; 94 encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt;
91 encoded_data.dynamic_lock = nfc_data.dynamic_lock; 95 encoded_data.dynamic_lock = nfc_data.dynamic_lock;
@@ -99,8 +103,9 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
99EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) { 103EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
100 EncryptedNTAG215File nfc_data{}; 104 EncryptedNTAG215File nfc_data{};
101 105
102 memcpy(nfc_data.uuid.data() + 0x8, encoded_data.uuid2.data(), sizeof(encoded_data.uuid2)); 106 nfc_data.uuid.uid = encoded_data.uid;
103 memcpy(nfc_data.uuid.data(), encoded_data.uuid.data(), sizeof(encoded_data.uuid)); 107 nfc_data.uuid.nintendo_id = encoded_data.nintendo_id;
108 nfc_data.uuid.lock_bytes = encoded_data.lock_bytes;
104 nfc_data.static_lock = encoded_data.static_lock; 109 nfc_data.static_lock = encoded_data.static_lock;
105 nfc_data.compability_container = encoded_data.compability_container; 110 nfc_data.compability_container = encoded_data.compability_container;
106 nfc_data.user_memory.hmac_data = encoded_data.hmac_data; 111 nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
@@ -112,7 +117,7 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
112 nfc_data.user_memory.applicaton_write_counter = encoded_data.applicaton_write_counter; 117 nfc_data.user_memory.applicaton_write_counter = encoded_data.applicaton_write_counter;
113 nfc_data.user_memory.application_area_id = encoded_data.application_area_id; 118 nfc_data.user_memory.application_area_id = encoded_data.application_area_id;
114 nfc_data.user_memory.unknown = encoded_data.unknown; 119 nfc_data.user_memory.unknown = encoded_data.unknown;
115 nfc_data.user_memory.hash = encoded_data.hash; 120 nfc_data.user_memory.unknown2 = encoded_data.unknown2;
116 nfc_data.user_memory.application_area = encoded_data.application_area; 121 nfc_data.user_memory.application_area = encoded_data.application_area;
117 nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag; 122 nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag;
118 nfc_data.user_memory.model_info = encoded_data.model_info; 123 nfc_data.user_memory.model_info = encoded_data.model_info;
@@ -127,10 +132,10 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
127 132
128u32 GetTagPassword(const TagUuid& uuid) { 133u32 GetTagPassword(const TagUuid& uuid) {
129 // Verifiy that the generated password is correct 134 // Verifiy that the generated password is correct
130 u32 password = 0xAA ^ (uuid[1] ^ uuid[3]); 135 u32 password = 0xAA ^ (uuid.uid[1] ^ uuid.uid[3]);
131 password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8; 136 password &= (0x55 ^ (uuid.uid[2] ^ uuid.uid[4])) << 8;
132 password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16; 137 password &= (0xAA ^ (uuid.uid[3] ^ uuid.uid[5])) << 16;
133 password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24; 138 password &= (0x55 ^ (uuid.uid[4] ^ uuid.uid[6])) << 24;
134 return password; 139 return password;
135} 140}
136 141
@@ -138,15 +143,13 @@ HashSeed GetSeed(const NTAG215File& data) {
138 HashSeed seed{ 143 HashSeed seed{
139 .magic = data.write_counter, 144 .magic = data.write_counter,
140 .padding = {}, 145 .padding = {},
141 .uuid1 = {}, 146 .uid_1 = data.uid,
142 .uuid2 = {}, 147 .nintendo_id_1 = data.nintendo_id,
148 .uid_2 = data.uid,
149 .nintendo_id_2 = data.nintendo_id,
143 .keygen_salt = data.keygen_salt, 150 .keygen_salt = data.keygen_salt,
144 }; 151 };
145 152
146 // Copy the first 8 bytes of uuid
147 memcpy(seed.uuid1.data(), data.uuid.data(), sizeof(seed.uuid1));
148 memcpy(seed.uuid2.data(), data.uuid.data(), sizeof(seed.uuid2));
149
150 return seed; 153 return seed;
151} 154}
152 155
@@ -165,8 +168,10 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed
165 output.insert(output.end(), key.magic_bytes.begin(), 168 output.insert(output.end(), key.magic_bytes.begin(),
166 key.magic_bytes.begin() + key.magic_length); 169 key.magic_bytes.begin() + key.magic_length);
167 170
168 output.insert(output.end(), seed.uuid1.begin(), seed.uuid1.end()); 171 output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end());
169 output.insert(output.end(), seed.uuid2.begin(), seed.uuid2.end()); 172 output.emplace_back(seed.nintendo_id_1);
173 output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end());
174 output.emplace_back(seed.nintendo_id_2);
170 175
171 for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) { 176 for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) {
172 output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i])); 177 output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i]));
@@ -177,7 +182,6 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed
177 182
178void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key, 183void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
179 const std::vector<u8>& seed) { 184 const std::vector<u8>& seed) {
180
181 // Initialize context 185 // Initialize context
182 ctx.used = false; 186 ctx.used = false;
183 ctx.counter = 0; 187 ctx.counter = 0;
@@ -250,14 +254,15 @@ void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& ou
250 reinterpret_cast<unsigned char*>(&out_data.settings)); 254 reinterpret_cast<unsigned char*>(&out_data.settings));
251 255
252 // Copy the rest of the data directly 256 // Copy the rest of the data directly
253 out_data.uuid2 = in_data.uuid2; 257 out_data.uid = in_data.uid;
258 out_data.nintendo_id = in_data.nintendo_id;
259 out_data.lock_bytes = in_data.lock_bytes;
254 out_data.static_lock = in_data.static_lock; 260 out_data.static_lock = in_data.static_lock;
255 out_data.compability_container = in_data.compability_container; 261 out_data.compability_container = in_data.compability_container;
256 262
257 out_data.constant_value = in_data.constant_value; 263 out_data.constant_value = in_data.constant_value;
258 out_data.write_counter = in_data.write_counter; 264 out_data.write_counter = in_data.write_counter;
259 265
260 out_data.uuid = in_data.uuid;
261 out_data.model_info = in_data.model_info; 266 out_data.model_info = in_data.model_info;
262 out_data.keygen_salt = in_data.keygen_salt; 267 out_data.keygen_salt = in_data.keygen_salt;
263 out_data.dynamic_lock = in_data.dynamic_lock; 268 out_data.dynamic_lock = in_data.dynamic_lock;
@@ -309,7 +314,7 @@ bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& t
309 // Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC! 314 // Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
310 constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; 315 constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
311 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(), 316 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
312 sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid), 317 sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
313 input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag)); 318 input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag));
314 319
315 // Regenerate data HMAC 320 // Regenerate data HMAC
@@ -350,7 +355,7 @@ bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_t
350 constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START; 355 constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
351 constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START; 356 constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START;
352 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(), 357 mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
353 sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid), 358 sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
354 input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag)); 359 input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag));
355 360
356 // Init mbedtls HMAC context 361 // Init mbedtls HMAC context
@@ -364,7 +369,7 @@ bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_t
364 input_length2); // Data 369 input_length2); // Data
365 mbedtls_md_hmac_update(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag), 370 mbedtls_md_hmac_update(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
366 sizeof(HashData)); // Tag HMAC 371 sizeof(HashData)); // Tag HMAC
367 mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uuid), 372 mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uid),
368 input_length); 373 input_length);
369 mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data)); 374 mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data));
370 375
diff --git a/src/core/hle/service/nfp/amiibo_crypto.h b/src/core/hle/service/nfp/amiibo_crypto.h
index af7335912..0175ced91 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.h
+++ b/src/core/hle/service/nfp/amiibo_crypto.h
@@ -5,7 +5,7 @@
5 5
6#include <array> 6#include <array>
7 7
8#include "core/hle/service/nfp/amiibo_types.h" 8#include "core/hle/service/nfp/nfp_types.h"
9 9
10struct mbedtls_md_context_t; 10struct mbedtls_md_context_t;
11 11
@@ -22,10 +22,12 @@ using HmacKey = std::array<u8, 0x10>;
22using DrgbOutput = std::array<u8, 0x20>; 22using DrgbOutput = std::array<u8, 0x20>;
23 23
24struct HashSeed { 24struct HashSeed {
25 u16 magic; 25 u16_be magic;
26 std::array<u8, 0xE> padding; 26 std::array<u8, 0xE> padding;
27 std::array<u8, 0x8> uuid1; 27 UniqueSerialNumber uid_1;
28 std::array<u8, 0x8> uuid2; 28 u8 nintendo_id_1;
29 UniqueSerialNumber uid_2;
30 u8 nintendo_id_2;
29 std::array<u8, 0x20> keygen_salt; 31 std::array<u8, 0x20> keygen_salt;
30}; 32};
31static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size"); 33static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 037b86653..0cb55ca49 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -1,1098 +1,43 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <array>
5#include <atomic>
6
7#include "common/fs/file.h"
8#include "common/fs/path_util.h"
9#include "common/logging/log.h" 4#include "common/logging/log.h"
10#include "common/string_util.h"
11#include "core/core.h"
12#include "core/hid/emulated_controller.h"
13#include "core/hid/hid_core.h"
14#include "core/hid/hid_types.h"
15#include "core/hle/ipc_helpers.h" 5#include "core/hle/ipc_helpers.h"
16#include "core/hle/kernel/k_event.h"
17#include "core/hle/service/mii/mii_manager.h"
18#include "core/hle/service/nfp/amiibo_crypto.h"
19#include "core/hle/service/nfp/nfp.h" 6#include "core/hle/service/nfp/nfp.h"
20#include "core/hle/service/nfp/nfp_user.h" 7#include "core/hle/service/nfp/nfp_user.h"
21 8
22namespace Service::NFP { 9namespace Service::NFP {
23namespace ErrCodes {
24constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
25constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
26constexpr Result NfcDisabled(ErrorModule::NFP, 80);
27constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88);
28constexpr Result TagRemoved(ErrorModule::NFP, 97);
29constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
30constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152);
31constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
32} // namespace ErrCodes
33
34IUser::IUser(Module::Interface& nfp_interface_, Core::System& system_)
35 : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name},
36 nfp_interface{nfp_interface_} {
37 static const FunctionInfo functions[] = {
38 {0, &IUser::Initialize, "Initialize"},
39 {1, &IUser::Finalize, "Finalize"},
40 {2, &IUser::ListDevices, "ListDevices"},
41 {3, &IUser::StartDetection, "StartDetection"},
42 {4, &IUser::StopDetection, "StopDetection"},
43 {5, &IUser::Mount, "Mount"},
44 {6, &IUser::Unmount, "Unmount"},
45 {7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
46 {8, &IUser::GetApplicationArea, "GetApplicationArea"},
47 {9, &IUser::SetApplicationArea, "SetApplicationArea"},
48 {10, &IUser::Flush, "Flush"},
49 {11, nullptr, "Restore"},
50 {12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
51 {13, &IUser::GetTagInfo, "GetTagInfo"},
52 {14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
53 {15, &IUser::GetCommonInfo, "GetCommonInfo"},
54 {16, &IUser::GetModelInfo, "GetModelInfo"},
55 {17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
56 {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
57 {19, &IUser::GetState, "GetState"},
58 {20, &IUser::GetDeviceState, "GetDeviceState"},
59 {21, &IUser::GetNpadId, "GetNpadId"},
60 {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
61 {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
62 {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
63 };
64 RegisterHandlers(functions);
65
66 availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
67}
68
69void IUser::Initialize(Kernel::HLERequestContext& ctx) {
70 LOG_INFO(Service_NFC, "called");
71
72 state = State::Initialized;
73
74 // TODO(german77): Loop through all interfaces
75 nfp_interface.Initialize();
76
77 IPC::ResponseBuilder rb{ctx, 2, 0};
78 rb.Push(ResultSuccess);
79}
80
81void IUser::Finalize(Kernel::HLERequestContext& ctx) {
82 LOG_INFO(Service_NFP, "called");
83
84 state = State::NonInitialized;
85
86 // TODO(german77): Loop through all interfaces
87 nfp_interface.Finalize();
88
89 IPC::ResponseBuilder rb{ctx, 2};
90 rb.Push(ResultSuccess);
91}
92
93void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
94 LOG_INFO(Service_NFP, "called");
95
96 if (state == State::NonInitialized) {
97 IPC::ResponseBuilder rb{ctx, 2};
98 rb.Push(ErrCodes::NfcDisabled);
99 return;
100 }
101
102 std::vector<u64> devices;
103
104 // TODO(german77): Loop through all interfaces
105 devices.push_back(nfp_interface.GetHandle());
106
107 if (devices.size() == 0) {
108 IPC::ResponseBuilder rb{ctx, 2};
109 rb.Push(ErrCodes::DeviceNotFound);
110 return;
111 }
112
113 ctx.WriteBuffer(devices);
114
115 IPC::ResponseBuilder rb{ctx, 3};
116 rb.Push(ResultSuccess);
117 rb.Push(static_cast<s32>(devices.size()));
118}
119
120void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
121 IPC::RequestParser rp{ctx};
122 const auto device_handle{rp.Pop<u64>()};
123 const auto nfp_protocol{rp.Pop<s32>()};
124 LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
125
126 if (state == State::NonInitialized) {
127 IPC::ResponseBuilder rb{ctx, 2};
128 rb.Push(ErrCodes::NfcDisabled);
129 return;
130 }
131
132 // TODO(german77): Loop through all interfaces
133 if (device_handle == nfp_interface.GetHandle()) {
134 const auto result = nfp_interface.StartDetection(nfp_protocol);
135 IPC::ResponseBuilder rb{ctx, 2};
136 rb.Push(result);
137 return;
138 }
139
140 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
141
142 IPC::ResponseBuilder rb{ctx, 2};
143 rb.Push(ErrCodes::DeviceNotFound);
144}
145
146void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
147 IPC::RequestParser rp{ctx};
148 const auto device_handle{rp.Pop<u64>()};
149 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
150
151 if (state == State::NonInitialized) {
152 IPC::ResponseBuilder rb{ctx, 2};
153 rb.Push(ErrCodes::NfcDisabled);
154 return;
155 }
156
157 // TODO(german77): Loop through all interfaces
158 if (device_handle == nfp_interface.GetHandle()) {
159 const auto result = nfp_interface.StopDetection();
160 IPC::ResponseBuilder rb{ctx, 2};
161 rb.Push(result);
162 return;
163 }
164
165 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
166
167 IPC::ResponseBuilder rb{ctx, 2};
168 rb.Push(ErrCodes::DeviceNotFound);
169}
170
171void IUser::Mount(Kernel::HLERequestContext& ctx) {
172 IPC::RequestParser rp{ctx};
173 const auto device_handle{rp.Pop<u64>()};
174 const auto model_type{rp.PopEnum<ModelType>()};
175 const auto mount_target{rp.PopEnum<MountTarget>()};
176 LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle,
177 model_type, mount_target);
178
179 if (state == State::NonInitialized) {
180 IPC::ResponseBuilder rb{ctx, 2};
181 rb.Push(ErrCodes::NfcDisabled);
182 return;
183 }
184
185 // TODO(german77): Loop through all interfaces
186 if (device_handle == nfp_interface.GetHandle()) {
187 const auto result = nfp_interface.Mount();
188 IPC::ResponseBuilder rb{ctx, 2};
189 rb.Push(result);
190 return;
191 }
192
193 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
194
195 IPC::ResponseBuilder rb{ctx, 2};
196 rb.Push(ErrCodes::DeviceNotFound);
197}
198
199void IUser::Unmount(Kernel::HLERequestContext& ctx) {
200 IPC::RequestParser rp{ctx};
201 const auto device_handle{rp.Pop<u64>()};
202 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
203
204 if (state == State::NonInitialized) {
205 IPC::ResponseBuilder rb{ctx, 2};
206 rb.Push(ErrCodes::NfcDisabled);
207 return;
208 }
209
210 // TODO(german77): Loop through all interfaces
211 if (device_handle == nfp_interface.GetHandle()) {
212 const auto result = nfp_interface.Unmount();
213 IPC::ResponseBuilder rb{ctx, 2};
214 rb.Push(result);
215 return;
216 }
217
218 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
219
220 IPC::ResponseBuilder rb{ctx, 2};
221 rb.Push(ErrCodes::DeviceNotFound);
222}
223
224void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) {
225 IPC::RequestParser rp{ctx};
226 const auto device_handle{rp.Pop<u64>()};
227 const auto access_id{rp.Pop<u32>()};
228 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, access_id={}", device_handle,
229 access_id);
230
231 if (state == State::NonInitialized) {
232 IPC::ResponseBuilder rb{ctx, 2};
233 rb.Push(ErrCodes::NfcDisabled);
234 return;
235 }
236
237 // TODO(german77): Loop through all interfaces
238 if (device_handle == nfp_interface.GetHandle()) {
239 const auto result = nfp_interface.OpenApplicationArea(access_id);
240 IPC::ResponseBuilder rb{ctx, 2};
241 rb.Push(result);
242 return;
243 }
244
245 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
246
247 IPC::ResponseBuilder rb{ctx, 2};
248 rb.Push(ErrCodes::DeviceNotFound);
249}
250
251void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) {
252 IPC::RequestParser rp{ctx};
253 const auto device_handle{rp.Pop<u64>()};
254 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
255
256 if (state == State::NonInitialized) {
257 IPC::ResponseBuilder rb{ctx, 2};
258 rb.Push(ErrCodes::NfcDisabled);
259 return;
260 }
261
262 // TODO(german77): Loop through all interfaces
263 if (device_handle == nfp_interface.GetHandle()) {
264 ApplicationArea data{};
265 const auto result = nfp_interface.GetApplicationArea(data);
266 ctx.WriteBuffer(data);
267 IPC::ResponseBuilder rb{ctx, 3};
268 rb.Push(result);
269 rb.Push(static_cast<u32>(data.size()));
270 return;
271 }
272
273 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
274
275 IPC::ResponseBuilder rb{ctx, 2};
276 rb.Push(ErrCodes::DeviceNotFound);
277}
278
279void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
280 IPC::RequestParser rp{ctx};
281 const auto device_handle{rp.Pop<u64>()};
282 const auto data{ctx.ReadBuffer()};
283 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}", device_handle,
284 data.size());
285
286 if (state == State::NonInitialized) {
287 IPC::ResponseBuilder rb{ctx, 2};
288 rb.Push(ErrCodes::NfcDisabled);
289 return;
290 }
291
292 // TODO(german77): Loop through all interfaces
293 if (device_handle == nfp_interface.GetHandle()) {
294 const auto result = nfp_interface.SetApplicationArea(data);
295 IPC::ResponseBuilder rb{ctx, 2};
296 rb.Push(result);
297 return;
298 }
299
300 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
301
302 IPC::ResponseBuilder rb{ctx, 2};
303 rb.Push(ErrCodes::DeviceNotFound);
304}
305
306void IUser::Flush(Kernel::HLERequestContext& ctx) {
307 IPC::RequestParser rp{ctx};
308 const auto device_handle{rp.Pop<u64>()};
309 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
310
311 if (state == State::NonInitialized) {
312 IPC::ResponseBuilder rb{ctx, 2};
313 rb.Push(ErrCodes::NfcDisabled);
314 return;
315 }
316
317 // TODO(german77): Loop through all interfaces
318 if (device_handle == nfp_interface.GetHandle()) {
319 const auto result = nfp_interface.Flush();
320 IPC::ResponseBuilder rb{ctx, 2};
321 rb.Push(result);
322 return;
323 }
324
325 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
326
327 IPC::ResponseBuilder rb{ctx, 2};
328 rb.Push(ErrCodes::DeviceNotFound);
329}
330
331void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
332 IPC::RequestParser rp{ctx};
333 const auto device_handle{rp.Pop<u64>()};
334 const auto access_id{rp.Pop<u32>()};
335 const auto data{ctx.ReadBuffer()};
336 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}",
337 device_handle, access_id, data.size());
338
339 if (state == State::NonInitialized) {
340 IPC::ResponseBuilder rb{ctx, 2};
341 rb.Push(ErrCodes::NfcDisabled);
342 return;
343 }
344
345 // TODO(german77): Loop through all interfaces
346 if (device_handle == nfp_interface.GetHandle()) {
347 const auto result = nfp_interface.CreateApplicationArea(access_id, data);
348 IPC::ResponseBuilder rb{ctx, 2};
349 rb.Push(result);
350 return;
351 }
352
353 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
354
355 IPC::ResponseBuilder rb{ctx, 2};
356 rb.Push(ErrCodes::DeviceNotFound);
357}
358
359void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
360 IPC::RequestParser rp{ctx};
361 const auto device_handle{rp.Pop<u64>()};
362 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
363
364 if (state == State::NonInitialized) {
365 IPC::ResponseBuilder rb{ctx, 2};
366 rb.Push(ErrCodes::NfcDisabled);
367 return;
368 }
369
370 // TODO(german77): Loop through all interfaces
371 if (device_handle == nfp_interface.GetHandle()) {
372 TagInfo tag_info{};
373 const auto result = nfp_interface.GetTagInfo(tag_info);
374 ctx.WriteBuffer(tag_info);
375 IPC::ResponseBuilder rb{ctx, 2};
376 rb.Push(result);
377 return;
378 }
379
380 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
381
382 IPC::ResponseBuilder rb{ctx, 2};
383 rb.Push(ErrCodes::DeviceNotFound);
384}
385
386void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
387 IPC::RequestParser rp{ctx};
388 const auto device_handle{rp.Pop<u64>()};
389 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
390
391 if (state == State::NonInitialized) {
392 IPC::ResponseBuilder rb{ctx, 2};
393 rb.Push(ErrCodes::NfcDisabled);
394 return;
395 }
396
397 // TODO(german77): Loop through all interfaces
398 if (device_handle == nfp_interface.GetHandle()) {
399 RegisterInfo register_info{};
400 const auto result = nfp_interface.GetRegisterInfo(register_info);
401 ctx.WriteBuffer(register_info);
402 IPC::ResponseBuilder rb{ctx, 2};
403 rb.Push(result);
404 return;
405 }
406
407 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
408
409 IPC::ResponseBuilder rb{ctx, 2};
410 rb.Push(ErrCodes::DeviceNotFound);
411}
412
413void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) {
414 IPC::RequestParser rp{ctx};
415 const auto device_handle{rp.Pop<u64>()};
416 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
417
418 if (state == State::NonInitialized) {
419 IPC::ResponseBuilder rb{ctx, 2};
420 rb.Push(ErrCodes::NfcDisabled);
421 return;
422 }
423
424 // TODO(german77): Loop through all interfaces
425 if (device_handle == nfp_interface.GetHandle()) {
426 CommonInfo common_info{};
427 const auto result = nfp_interface.GetCommonInfo(common_info);
428 ctx.WriteBuffer(common_info);
429 IPC::ResponseBuilder rb{ctx, 2};
430 rb.Push(result);
431 return;
432 }
433
434 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
435
436 IPC::ResponseBuilder rb{ctx, 2};
437 rb.Push(ErrCodes::DeviceNotFound);
438}
439
440void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) {
441 IPC::RequestParser rp{ctx};
442 const auto device_handle{rp.Pop<u64>()};
443 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
444
445 if (state == State::NonInitialized) {
446 IPC::ResponseBuilder rb{ctx, 2};
447 rb.Push(ErrCodes::NfcDisabled);
448 return;
449 }
450
451 // TODO(german77): Loop through all interfaces
452 if (device_handle == nfp_interface.GetHandle()) {
453 ModelInfo model_info{};
454 const auto result = nfp_interface.GetModelInfo(model_info);
455 ctx.WriteBuffer(model_info);
456 IPC::ResponseBuilder rb{ctx, 2};
457 rb.Push(result);
458 return;
459 }
460
461 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
462
463 IPC::ResponseBuilder rb{ctx, 2};
464 rb.Push(ErrCodes::DeviceNotFound);
465}
466
467void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
468 IPC::RequestParser rp{ctx};
469 const auto device_handle{rp.Pop<u64>()};
470 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
471
472 if (state == State::NonInitialized) {
473 IPC::ResponseBuilder rb{ctx, 2};
474 rb.Push(ErrCodes::NfcDisabled);
475 return;
476 }
477
478 // TODO(german77): Loop through all interfaces
479 if (device_handle == nfp_interface.GetHandle()) {
480 IPC::ResponseBuilder rb{ctx, 2, 1};
481 rb.Push(ResultSuccess);
482 rb.PushCopyObjects(nfp_interface.GetActivateEvent());
483 return;
484 }
485
486 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
487
488 IPC::ResponseBuilder rb{ctx, 2};
489 rb.Push(ErrCodes::DeviceNotFound);
490}
491
492void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
493 IPC::RequestParser rp{ctx};
494 const auto device_handle{rp.Pop<u64>()};
495 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
496
497 if (state == State::NonInitialized) {
498 IPC::ResponseBuilder rb{ctx, 2};
499 rb.Push(ErrCodes::NfcDisabled);
500 return;
501 }
502
503 // TODO(german77): Loop through all interfaces
504 if (device_handle == nfp_interface.GetHandle()) {
505 IPC::ResponseBuilder rb{ctx, 2, 1};
506 rb.Push(ResultSuccess);
507 rb.PushCopyObjects(nfp_interface.GetDeactivateEvent());
508 return;
509 }
510
511 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
512
513 IPC::ResponseBuilder rb{ctx, 2};
514 rb.Push(ErrCodes::DeviceNotFound);
515}
516
517void IUser::GetState(Kernel::HLERequestContext& ctx) {
518 LOG_DEBUG(Service_NFC, "called");
519
520 IPC::ResponseBuilder rb{ctx, 3, 0};
521 rb.Push(ResultSuccess);
522 rb.PushEnum(state);
523}
524
525void IUser::GetDeviceState(Kernel::HLERequestContext& ctx) {
526 IPC::RequestParser rp{ctx};
527 const auto device_handle{rp.Pop<u64>()};
528 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
529
530 // TODO(german77): Loop through all interfaces
531 if (device_handle == nfp_interface.GetHandle()) {
532 IPC::ResponseBuilder rb{ctx, 3};
533 rb.Push(ResultSuccess);
534 rb.PushEnum(nfp_interface.GetCurrentState());
535 return;
536 }
537
538 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
539
540 IPC::ResponseBuilder rb{ctx, 2};
541 rb.Push(ErrCodes::DeviceNotFound);
542}
543
544void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
545 IPC::RequestParser rp{ctx};
546 const auto device_handle{rp.Pop<u64>()};
547 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
548
549 if (state == State::NonInitialized) {
550 IPC::ResponseBuilder rb{ctx, 2};
551 rb.Push(ErrCodes::NfcDisabled);
552 return;
553 }
554
555 // TODO(german77): Loop through all interfaces
556 if (device_handle == nfp_interface.GetHandle()) {
557 IPC::ResponseBuilder rb{ctx, 3};
558 rb.Push(ResultSuccess);
559 rb.PushEnum(nfp_interface.GetNpadId());
560 return;
561 }
562
563 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
564
565 IPC::ResponseBuilder rb{ctx, 2};
566 rb.Push(ErrCodes::DeviceNotFound);
567}
568
569void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
570 IPC::RequestParser rp{ctx};
571 const auto device_handle{rp.Pop<u64>()};
572 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
573
574 // TODO(german77): Loop through all interfaces
575 if (device_handle == nfp_interface.GetHandle()) {
576 IPC::ResponseBuilder rb{ctx, 3};
577 rb.Push(ResultSuccess);
578 rb.Push(sizeof(ApplicationArea));
579 return;
580 }
581
582 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
583
584 IPC::ResponseBuilder rb{ctx, 2};
585 rb.Push(ErrCodes::DeviceNotFound);
586}
587
588void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
589 LOG_DEBUG(Service_NFP, "(STUBBED) called");
590
591 if (state == State::NonInitialized) {
592 IPC::ResponseBuilder rb{ctx, 2};
593 rb.Push(ErrCodes::NfcDisabled);
594 return;
595 }
596
597 IPC::ResponseBuilder rb{ctx, 2, 1};
598 rb.Push(ResultSuccess);
599 rb.PushCopyObjects(availability_change_event->GetReadableEvent());
600}
601
602void IUser::RecreateApplicationArea(Kernel::HLERequestContext& ctx) {
603 IPC::RequestParser rp{ctx};
604 const auto device_handle{rp.Pop<u64>()};
605 const auto access_id{rp.Pop<u32>()};
606 const auto data{ctx.ReadBuffer()};
607 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, data_size={}, access_id={}",
608 device_handle, access_id, data.size());
609
610 if (state == State::NonInitialized) {
611 IPC::ResponseBuilder rb{ctx, 2};
612 rb.Push(ErrCodes::NfcDisabled);
613 return;
614 }
615
616 // TODO(german77): Loop through all interfaces
617 if (device_handle == nfp_interface.GetHandle()) {
618 const auto result = nfp_interface.RecreateApplicationArea(access_id, data);
619 IPC::ResponseBuilder rb{ctx, 2};
620 rb.Push(result);
621 return;
622 }
623
624 LOG_ERROR(Service_NFP, "Handle not found, device_handle={}", device_handle);
625
626 IPC::ResponseBuilder rb{ctx, 2};
627 rb.Push(ErrCodes::DeviceNotFound);
628}
629
630Module::Interface::Interface(std::shared_ptr<Module> module_, Core::System& system_,
631 const char* name)
632 : ServiceFramework{system_, name}, module{std::move(module_)},
633 npad_id{Core::HID::NpadIdType::Player1}, service_context{system_, service_name} {
634 activate_event = service_context.CreateEvent("IUser:NFPActivateEvent");
635 deactivate_event = service_context.CreateEvent("IUser:NFPDeactivateEvent");
636}
637
638Module::Interface::~Interface() = default;
639
640void Module::Interface::CreateUserInterface(Kernel::HLERequestContext& ctx) {
641 LOG_DEBUG(Service_NFP, "called");
642
643 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
644 rb.Push(ResultSuccess);
645 rb.PushIpcInterface<IUser>(*this, system);
646}
647
648bool Module::Interface::LoadAmiiboFile(const std::string& filename) {
649 constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
650 const Common::FS::IOFile amiibo_file{filename, Common::FS::FileAccessMode::Read,
651 Common::FS::FileType::BinaryFile};
652
653 if (!amiibo_file.IsOpen()) {
654 LOG_ERROR(Service_NFP, "Amiibo is already on use");
655 return false;
656 }
657
658 // Workaround for files with missing password data
659 std::array<u8, sizeof(EncryptedNTAG215File)> buffer{};
660 if (amiibo_file.Read(buffer) < tag_size_without_password) {
661 LOG_ERROR(Service_NFP, "Failed to read amiibo file");
662 return false;
663 }
664 memcpy(&encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File));
665
666 if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
667 LOG_INFO(Service_NFP, "Invalid amiibo");
668 return false;
669 }
670
671 file_path = filename;
672 return true;
673}
674
675bool Module::Interface::LoadAmiibo(const std::string& filename) {
676 if (device_state != DeviceState::SearchingForTag) {
677 LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
678 return false;
679 }
680
681 if (!LoadAmiiboFile(filename)) {
682 return false;
683 }
684
685 device_state = DeviceState::TagFound;
686 activate_event->GetWritableEvent().Signal();
687 return true;
688}
689
690void Module::Interface::CloseAmiibo() {
691 LOG_INFO(Service_NFP, "Remove amiibo");
692 device_state = DeviceState::TagRemoved;
693 is_data_decoded = false;
694 is_application_area_initialized = false;
695 encrypted_tag_data = {};
696 tag_data = {};
697 deactivate_event->GetWritableEvent().Signal();
698}
699
700Kernel::KReadableEvent& Module::Interface::GetActivateEvent() const {
701 return activate_event->GetReadableEvent();
702}
703
704Kernel::KReadableEvent& Module::Interface::GetDeactivateEvent() const {
705 return deactivate_event->GetReadableEvent();
706}
707
708void Module::Interface::Initialize() {
709 device_state = DeviceState::Initialized;
710 is_data_decoded = false;
711 is_application_area_initialized = false;
712 encrypted_tag_data = {};
713 tag_data = {};
714}
715
716void Module::Interface::Finalize() {
717 if (device_state == DeviceState::TagMounted) {
718 Unmount();
719 }
720 if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
721 StopDetection();
722 }
723 device_state = DeviceState::Unaviable;
724}
725
726Result Module::Interface::StartDetection(s32 protocol_) {
727 auto npad_device = system.HIDCore().GetEmulatedController(npad_id);
728
729 // TODO(german77): Add callback for when nfc data is available
730
731 if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
732 npad_device->SetPollingMode(Common::Input::PollingMode::NFC);
733 device_state = DeviceState::SearchingForTag;
734 protocol = protocol_;
735 return ResultSuccess;
736 }
737
738 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
739 return ErrCodes::WrongDeviceState;
740}
741
742Result Module::Interface::StopDetection() {
743 auto npad_device = system.HIDCore().GetEmulatedController(npad_id);
744 npad_device->SetPollingMode(Common::Input::PollingMode::Active);
745
746 if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
747 CloseAmiibo();
748 return ResultSuccess;
749 }
750 if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
751 device_state = DeviceState::Initialized;
752 return ResultSuccess;
753 }
754
755 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
756 return ErrCodes::WrongDeviceState;
757}
758
759Result Module::Interface::Flush() {
760 // Ignore write command if we can't encrypt the data
761 if (!is_data_decoded) {
762 return ResultSuccess;
763 }
764
765 constexpr auto tag_size_without_password = sizeof(NTAG215File) - sizeof(NTAG215Password);
766 EncryptedNTAG215File tmp_encrypted_tag_data{};
767 const Common::FS::IOFile amiibo_file{file_path, Common::FS::FileAccessMode::ReadWrite,
768 Common::FS::FileType::BinaryFile};
769
770 if (!amiibo_file.IsOpen()) {
771 LOG_ERROR(Core, "Amiibo is already on use");
772 return ErrCodes::WriteAmiiboFailed;
773 }
774
775 // Workaround for files with missing password data
776 std::array<u8, sizeof(EncryptedNTAG215File)> buffer{};
777 if (amiibo_file.Read(buffer) < tag_size_without_password) {
778 LOG_ERROR(Core, "Failed to read amiibo file");
779 return ErrCodes::WriteAmiiboFailed;
780 }
781 memcpy(&tmp_encrypted_tag_data, buffer.data(), sizeof(EncryptedNTAG215File));
782
783 if (!AmiiboCrypto::IsAmiiboValid(tmp_encrypted_tag_data)) {
784 LOG_INFO(Service_NFP, "Invalid amiibo");
785 return ErrCodes::WriteAmiiboFailed;
786 }
787
788 bool is_uuid_equal = memcmp(tmp_encrypted_tag_data.uuid.data(), tag_data.uuid.data(), 8) == 0;
789 bool is_character_equal = tmp_encrypted_tag_data.user_memory.model_info.character_id ==
790 tag_data.model_info.character_id;
791 if (!is_uuid_equal || !is_character_equal) {
792 LOG_ERROR(Service_NFP, "Not the same amiibo");
793 return ErrCodes::WriteAmiiboFailed;
794 }
795
796 if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
797 LOG_ERROR(Service_NFP, "Failed to encode data");
798 return ErrCodes::WriteAmiiboFailed;
799 }
800
801 // Return to the start of the file
802 if (!amiibo_file.Seek(0)) {
803 LOG_ERROR(Service_NFP, "Error writing to file");
804 return ErrCodes::WriteAmiiboFailed;
805 }
806
807 if (!amiibo_file.Write(encrypted_tag_data)) {
808 LOG_ERROR(Service_NFP, "Error writing to file");
809 return ErrCodes::WriteAmiiboFailed;
810 }
811
812 return ResultSuccess;
813}
814
815Result Module::Interface::Mount() {
816 if (device_state != DeviceState::TagFound) {
817 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
818 return ErrCodes::WrongDeviceState;
819 }
820 10
821 is_data_decoded = AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data); 11class IUserManager final : public ServiceFramework<IUserManager> {
822 LOG_INFO(Service_NFP, "Is amiibo decoded {}", is_data_decoded); 12public:
823 13 explicit IUserManager(Core::System& system_) : ServiceFramework{system_, "nfp:user"} {
824 is_application_area_initialized = false; 14 // clang-format off
825 device_state = DeviceState::TagMounted; 15 static const FunctionInfo functions[] = {
826 return ResultSuccess; 16 {0, &IUserManager::CreateUserInterface, "CreateUserInterface"},
827}
828
829Result Module::Interface::Unmount() {
830 if (device_state != DeviceState::TagMounted) {
831 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
832 return ErrCodes::WrongDeviceState;
833 }
834
835 is_data_decoded = false;
836 is_application_area_initialized = false;
837 device_state = DeviceState::TagFound;
838 return ResultSuccess;
839}
840
841Result Module::Interface::GetTagInfo(TagInfo& tag_info) const {
842 if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
843 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
844 return ErrCodes::WrongDeviceState;
845 }
846
847 tag_info = {
848 .uuid = encrypted_tag_data.uuid,
849 .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.size()),
850 .protocol = protocol,
851 .tag_type = static_cast<u32>(encrypted_tag_data.user_memory.model_info.amiibo_type),
852 };
853
854 return ResultSuccess;
855}
856
857Result Module::Interface::GetCommonInfo(CommonInfo& common_info) const {
858 if (device_state != DeviceState::TagMounted) {
859 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
860 return ErrCodes::WrongDeviceState;
861 }
862
863 if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) {
864 const auto& settings = tag_data.settings;
865 // TODO: Validate this data
866 common_info = {
867 .last_write_year = settings.write_date.GetYear(),
868 .last_write_month = settings.write_date.GetMonth(),
869 .last_write_day = settings.write_date.GetDay(),
870 .write_counter = settings.crc_counter,
871 .version = 1,
872 .application_area_size = sizeof(ApplicationArea),
873 };
874 return ResultSuccess;
875 }
876
877 // Generate a generic answer
878 common_info = {
879 .last_write_year = 2022,
880 .last_write_month = 2,
881 .last_write_day = 7,
882 .write_counter = 0,
883 .version = 1,
884 .application_area_size = sizeof(ApplicationArea),
885 };
886 return ResultSuccess;
887}
888
889Result Module::Interface::GetModelInfo(ModelInfo& model_info) const {
890 if (device_state != DeviceState::TagMounted) {
891 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
892 return ErrCodes::WrongDeviceState;
893 }
894
895 const auto& model_info_data = encrypted_tag_data.user_memory.model_info;
896 model_info = {
897 .character_id = model_info_data.character_id,
898 .character_variant = model_info_data.character_variant,
899 .amiibo_type = model_info_data.amiibo_type,
900 .model_number = model_info_data.model_number,
901 .series = model_info_data.series,
902 .constant_value = model_info_data.constant_value,
903 };
904 return ResultSuccess;
905}
906
907Result Module::Interface::GetRegisterInfo(RegisterInfo& register_info) const {
908 if (device_state != DeviceState::TagMounted) {
909 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
910 if (device_state == DeviceState::TagRemoved) {
911 return ErrCodes::TagRemoved;
912 }
913 return ErrCodes::WrongDeviceState;
914 }
915
916 Service::Mii::MiiManager manager;
917
918 if (is_data_decoded && tag_data.settings.settings.amiibo_initialized != 0) {
919 const auto& settings = tag_data.settings;
920
921 // TODO: Validate this data
922 register_info = {
923 .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
924 .first_write_year = settings.init_date.GetYear(),
925 .first_write_month = settings.init_date.GetMonth(),
926 .first_write_day = settings.init_date.GetDay(),
927 .amiibo_name = GetAmiiboName(settings),
928 .font_region = {},
929 }; 17 };
18 // clang-format on
930 19
931 return ResultSuccess; 20 RegisterHandlers(functions);
932 } 21 }
933 22
934 // Generate a generic answer 23private:
935 register_info = { 24 void CreateUserInterface(Kernel::HLERequestContext& ctx) {
936 .mii_char_info = manager.BuildDefault(0), 25 LOG_DEBUG(Service_NFP, "called");
937 .first_write_year = 2022,
938 .first_write_month = 2,
939 .first_write_day = 7,
940 .amiibo_name = {'Y', 'u', 'z', 'u', 'A', 'm', 'i', 'i', 'b', 'o', 0},
941 .font_region = {},
942 };
943 return ResultSuccess;
944}
945 26
946Result Module::Interface::OpenApplicationArea(u32 access_id) { 27 if (user_interface == nullptr) {
947 if (device_state != DeviceState::TagMounted) { 28 user_interface = std::make_shared<IUser>(system);
948 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
949 if (device_state == DeviceState::TagRemoved) {
950 return ErrCodes::TagRemoved;
951 } 29 }
952 return ErrCodes::WrongDeviceState;
953 }
954
955 // Fallback for lack of amiibo keys
956 if (!is_data_decoded) {
957 LOG_WARNING(Service_NFP, "Application area is not initialized");
958 return ErrCodes::ApplicationAreaIsNotInitialized;
959 }
960 30
961 if (tag_data.settings.settings.appdata_initialized == 0) { 31 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
962 LOG_WARNING(Service_NFP, "Application area is not initialized"); 32 rb.Push(ResultSuccess);
963 return ErrCodes::ApplicationAreaIsNotInitialized; 33 rb.PushIpcInterface<IUser>(user_interface);
964 }
965
966 if (tag_data.application_area_id != access_id) {
967 LOG_WARNING(Service_NFP, "Wrong application area id");
968 return ErrCodes::WrongApplicationAreaId;
969 }
970
971 is_application_area_initialized = true;
972 return ResultSuccess;
973}
974
975Result Module::Interface::GetApplicationArea(ApplicationArea& data) const {
976 if (device_state != DeviceState::TagMounted) {
977 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
978 if (device_state == DeviceState::TagRemoved) {
979 return ErrCodes::TagRemoved;
980 }
981 return ErrCodes::WrongDeviceState;
982 }
983
984 if (!is_application_area_initialized) {
985 LOG_ERROR(Service_NFP, "Application area is not initialized");
986 return ErrCodes::ApplicationAreaIsNotInitialized;
987 }
988
989 data = tag_data.application_area;
990
991 return ResultSuccess;
992}
993
994Result Module::Interface::SetApplicationArea(const std::vector<u8>& data) {
995 if (device_state != DeviceState::TagMounted) {
996 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
997 if (device_state == DeviceState::TagRemoved) {
998 return ErrCodes::TagRemoved;
999 }
1000 return ErrCodes::WrongDeviceState;
1001 }
1002
1003 if (!is_application_area_initialized) {
1004 LOG_ERROR(Service_NFP, "Application area is not initialized");
1005 return ErrCodes::ApplicationAreaIsNotInitialized;
1006 }
1007
1008 if (data.size() != sizeof(ApplicationArea)) {
1009 LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
1010 return ResultUnknown;
1011 }
1012
1013 std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
1014 return ResultSuccess;
1015}
1016
1017Result Module::Interface::CreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
1018 if (device_state != DeviceState::TagMounted) {
1019 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
1020 if (device_state == DeviceState::TagRemoved) {
1021 return ErrCodes::TagRemoved;
1022 }
1023 return ErrCodes::WrongDeviceState;
1024 }
1025
1026 if (tag_data.settings.settings.appdata_initialized != 0) {
1027 LOG_ERROR(Service_NFP, "Application area already exist");
1028 return ErrCodes::ApplicationAreaExist;
1029 }
1030
1031 if (data.size() != sizeof(ApplicationArea)) {
1032 LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
1033 return ResultUnknown;
1034 }
1035
1036 std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
1037 tag_data.application_area_id = access_id;
1038
1039 return ResultSuccess;
1040}
1041
1042Result Module::Interface::RecreateApplicationArea(u32 access_id, const std::vector<u8>& data) {
1043 if (device_state != DeviceState::TagMounted) {
1044 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
1045 if (device_state == DeviceState::TagRemoved) {
1046 return ErrCodes::TagRemoved;
1047 }
1048 return ErrCodes::WrongDeviceState;
1049 }
1050
1051 if (data.size() != sizeof(ApplicationArea)) {
1052 LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
1053 return ResultUnknown;
1054 }
1055
1056 std::memcpy(&tag_data.application_area, data.data(), sizeof(ApplicationArea));
1057 tag_data.application_area_id = access_id;
1058
1059 return ResultSuccess;
1060}
1061
1062u64 Module::Interface::GetHandle() const {
1063 // Generate a handle based of the npad id
1064 return static_cast<u64>(npad_id);
1065}
1066
1067DeviceState Module::Interface::GetCurrentState() const {
1068 return device_state;
1069}
1070
1071Core::HID::NpadIdType Module::Interface::GetNpadId() const {
1072 // Return first connected npad id as a workaround for lack of a single nfc interface per
1073 // controller
1074 return system.HIDCore().GetFirstNpadId();
1075}
1076
1077AmiiboName Module::Interface::GetAmiiboName(const AmiiboSettings& settings) const {
1078 std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
1079 AmiiboName amiibo_name{};
1080
1081 // Convert from big endian to little endian
1082 for (std::size_t i = 0; i < amiibo_name_length; i++) {
1083 settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]);
1084 } 34 }
1085 35
1086 // Convert from utf16 to utf8 36 std::shared_ptr<IUser> user_interface;
1087 const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data()); 37};
1088 memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
1089
1090 return amiibo_name;
1091}
1092 38
1093void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) { 39void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system) {
1094 auto module = std::make_shared<Module>(); 40 std::make_shared<IUserManager>(system)->InstallAsService(service_manager);
1095 std::make_shared<NFP_User>(module, system)->InstallAsService(service_manager);
1096} 41}
1097 42
1098} // namespace Service::NFP 43} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp.h b/src/core/hle/service/nfp/nfp.h
index 0de0b48e7..a25c362b8 100644
--- a/src/core/hle/service/nfp/nfp.h
+++ b/src/core/hle/service/nfp/nfp.h
@@ -3,170 +3,9 @@
3 3
4#pragma once 4#pragma once
5 5
6#include <array>
7#include <vector>
8
9#include "common/common_funcs.h"
10#include "core/hle/service/kernel_helpers.h"
11#include "core/hle/service/mii/types.h"
12#include "core/hle/service/nfp/amiibo_types.h"
13#include "core/hle/service/service.h" 6#include "core/hle/service/service.h"
14 7
15namespace Kernel {
16class KEvent;
17class KReadableEvent;
18} // namespace Kernel
19
20namespace Core::HID {
21enum class NpadIdType : u32;
22} // namespace Core::HID
23
24namespace Service::NFP { 8namespace Service::NFP {
25using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
26
27struct TagInfo {
28 TagUuid uuid;
29 u8 uuid_length;
30 INSERT_PADDING_BYTES(0x15);
31 s32 protocol;
32 u32 tag_type;
33 INSERT_PADDING_BYTES(0x30);
34};
35static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
36
37struct CommonInfo {
38 u16 last_write_year;
39 u8 last_write_month;
40 u8 last_write_day;
41 u16 write_counter;
42 u16 version;
43 u32 application_area_size;
44 INSERT_PADDING_BYTES(0x34);
45};
46static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
47
48struct ModelInfo {
49 u16 character_id;
50 u8 character_variant;
51 AmiiboType amiibo_type;
52 u16 model_number;
53 AmiiboSeries series;
54 u8 constant_value; // Must be 02
55 INSERT_PADDING_BYTES(0x38); // Unknown
56};
57static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
58
59struct RegisterInfo {
60 Service::Mii::CharInfo mii_char_info;
61 u16 first_write_year;
62 u8 first_write_month;
63 u8 first_write_day;
64 AmiiboName amiibo_name;
65 u8 font_region;
66 INSERT_PADDING_BYTES(0x7A);
67};
68static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
69
70class Module final {
71public:
72 class Interface : public ServiceFramework<Interface> {
73 public:
74 explicit Interface(std::shared_ptr<Module> module_, Core::System& system_,
75 const char* name);
76 ~Interface() override;
77
78 void CreateUserInterface(Kernel::HLERequestContext& ctx);
79 bool LoadAmiibo(const std::string& filename);
80 bool LoadAmiiboFile(const std::string& filename);
81 void CloseAmiibo();
82
83 void Initialize();
84 void Finalize();
85
86 Result StartDetection(s32 protocol_);
87 Result StopDetection();
88 Result Mount();
89 Result Unmount();
90 Result Flush();
91
92 Result GetTagInfo(TagInfo& tag_info) const;
93 Result GetCommonInfo(CommonInfo& common_info) const;
94 Result GetModelInfo(ModelInfo& model_info) const;
95 Result GetRegisterInfo(RegisterInfo& register_info) const;
96
97 Result OpenApplicationArea(u32 access_id);
98 Result GetApplicationArea(ApplicationArea& data) const;
99 Result SetApplicationArea(const std::vector<u8>& data);
100 Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data);
101 Result RecreateApplicationArea(u32 access_id, const std::vector<u8>& data);
102
103 u64 GetHandle() const;
104 DeviceState GetCurrentState() const;
105 Core::HID::NpadIdType GetNpadId() const;
106
107 Kernel::KReadableEvent& GetActivateEvent() const;
108 Kernel::KReadableEvent& GetDeactivateEvent() const;
109
110 protected:
111 std::shared_ptr<Module> module;
112
113 private:
114 AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
115
116 const Core::HID::NpadIdType npad_id;
117
118 bool is_data_decoded{};
119 bool is_application_area_initialized{};
120 s32 protocol;
121 std::string file_path{};
122 Kernel::KEvent* activate_event;
123 Kernel::KEvent* deactivate_event;
124 DeviceState device_state{DeviceState::Unaviable};
125 KernelHelpers::ServiceContext service_context;
126
127 NTAG215File tag_data{};
128 EncryptedNTAG215File encrypted_tag_data{};
129 };
130};
131
132class IUser final : public ServiceFramework<IUser> {
133public:
134 explicit IUser(Module::Interface& nfp_interface_, Core::System& system_);
135
136private:
137 void Initialize(Kernel::HLERequestContext& ctx);
138 void Finalize(Kernel::HLERequestContext& ctx);
139 void ListDevices(Kernel::HLERequestContext& ctx);
140 void StartDetection(Kernel::HLERequestContext& ctx);
141 void StopDetection(Kernel::HLERequestContext& ctx);
142 void Mount(Kernel::HLERequestContext& ctx);
143 void Unmount(Kernel::HLERequestContext& ctx);
144 void OpenApplicationArea(Kernel::HLERequestContext& ctx);
145 void GetApplicationArea(Kernel::HLERequestContext& ctx);
146 void SetApplicationArea(Kernel::HLERequestContext& ctx);
147 void Flush(Kernel::HLERequestContext& ctx);
148 void CreateApplicationArea(Kernel::HLERequestContext& ctx);
149 void GetTagInfo(Kernel::HLERequestContext& ctx);
150 void GetRegisterInfo(Kernel::HLERequestContext& ctx);
151 void GetCommonInfo(Kernel::HLERequestContext& ctx);
152 void GetModelInfo(Kernel::HLERequestContext& ctx);
153 void AttachActivateEvent(Kernel::HLERequestContext& ctx);
154 void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
155 void GetState(Kernel::HLERequestContext& ctx);
156 void GetDeviceState(Kernel::HLERequestContext& ctx);
157 void GetNpadId(Kernel::HLERequestContext& ctx);
158 void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
159 void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
160 void RecreateApplicationArea(Kernel::HLERequestContext& ctx);
161
162 KernelHelpers::ServiceContext service_context;
163
164 // TODO(german77): We should have a vector of interfaces
165 Module::Interface& nfp_interface;
166
167 State state{State::NonInitialized};
168 Kernel::KEvent* availability_change_event;
169};
170 9
171void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); 10void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);
172 11
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfp/nfp_device.cpp
new file mode 100644
index 000000000..0d4ffd3a5
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_device.cpp
@@ -0,0 +1,676 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include <array>
5#include <atomic>
6
7#include "common/fs/file.h"
8#include "common/fs/path_util.h"
9#include "common/input.h"
10#include "common/logging/log.h"
11#include "common/string_util.h"
12#include "common/tiny_mt.h"
13#include "core/core.h"
14#include "core/hid/emulated_controller.h"
15#include "core/hid/hid_core.h"
16#include "core/hid/hid_types.h"
17#include "core/hle/ipc_helpers.h"
18#include "core/hle/kernel/k_event.h"
19#include "core/hle/service/mii/mii_manager.h"
20#include "core/hle/service/nfp/amiibo_crypto.h"
21#include "core/hle/service/nfp/nfp.h"
22#include "core/hle/service/nfp/nfp_device.h"
23#include "core/hle/service/nfp/nfp_result.h"
24#include "core/hle/service/nfp/nfp_user.h"
25#include "core/hle/service/time/time_manager.h"
26#include "core/hle/service/time/time_zone_content_manager.h"
27#include "core/hle/service/time/time_zone_types.h"
28
29namespace Service::NFP {
30NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
31 KernelHelpers::ServiceContext& service_context_,
32 Kernel::KEvent* availability_change_event_)
33 : npad_id{npad_id_}, system{system_}, service_context{service_context_},
34 availability_change_event{availability_change_event_} {
35 activate_event = service_context.CreateEvent("IUser:NFPActivateEvent");
36 deactivate_event = service_context.CreateEvent("IUser:NFPDeactivateEvent");
37 npad_device = system.HIDCore().GetEmulatedController(npad_id);
38
39 Core::HID::ControllerUpdateCallback engine_callback{
40 .on_change = [this](Core::HID::ControllerTriggerType type) { NpadUpdate(type); },
41 .is_npad_service = false,
42 };
43 is_controller_set = true;
44 callback_key = npad_device->SetCallback(engine_callback);
45
46 auto& standard_steady_clock{system.GetTimeManager().GetStandardSteadyClockCore()};
47 current_posix_time = standard_steady_clock.GetCurrentTimePoint(system).time_point;
48}
49
50NfpDevice::~NfpDevice() {
51 if (!is_controller_set) {
52 return;
53 }
54 npad_device->DeleteCallback(callback_key);
55 is_controller_set = false;
56};
57
58void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
59 if (type == Core::HID::ControllerTriggerType::Connected ||
60 type == Core::HID::ControllerTriggerType::Disconnected) {
61 availability_change_event->GetWritableEvent().Signal();
62 return;
63 }
64
65 if (type != Core::HID::ControllerTriggerType::Nfc) {
66 return;
67 }
68
69 if (!npad_device->IsConnected()) {
70 return;
71 }
72
73 const auto nfc_status = npad_device->GetNfc();
74 switch (nfc_status.state) {
75 case Common::Input::NfcState::NewAmiibo:
76 LoadAmiibo(nfc_status.data);
77 break;
78 case Common::Input::NfcState::AmiiboRemoved:
79 if (device_state != DeviceState::SearchingForTag) {
80 CloseAmiibo();
81 }
82 break;
83 default:
84 break;
85 }
86}
87
88bool NfpDevice::LoadAmiibo(std::span<const u8> data) {
89 if (device_state != DeviceState::SearchingForTag) {
90 LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
91 return false;
92 }
93
94 if (data.size() != sizeof(EncryptedNTAG215File)) {
95 LOG_ERROR(Service_NFP, "Not an amiibo, size={}", data.size());
96 return false;
97 }
98
99 memcpy(&encrypted_tag_data, data.data(), sizeof(EncryptedNTAG215File));
100
101 if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
102 LOG_INFO(Service_NFP, "Invalid amiibo");
103 return false;
104 }
105
106 device_state = DeviceState::TagFound;
107 deactivate_event->GetReadableEvent().Clear();
108 activate_event->GetWritableEvent().Signal();
109 return true;
110}
111
112void NfpDevice::CloseAmiibo() {
113 LOG_INFO(Service_NFP, "Remove amiibo");
114
115 if (device_state == DeviceState::TagMounted) {
116 Unmount();
117 }
118
119 device_state = DeviceState::TagRemoved;
120 encrypted_tag_data = {};
121 tag_data = {};
122 activate_event->GetReadableEvent().Clear();
123 deactivate_event->GetWritableEvent().Signal();
124}
125
126Kernel::KReadableEvent& NfpDevice::GetActivateEvent() const {
127 return activate_event->GetReadableEvent();
128}
129
130Kernel::KReadableEvent& NfpDevice::GetDeactivateEvent() const {
131 return deactivate_event->GetReadableEvent();
132}
133
134void NfpDevice::Initialize() {
135 device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable;
136 encrypted_tag_data = {};
137 tag_data = {};
138}
139
140void NfpDevice::Finalize() {
141 if (device_state == DeviceState::TagMounted) {
142 Unmount();
143 }
144 if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
145 StopDetection();
146 }
147 device_state = DeviceState::Unavailable;
148}
149
150Result NfpDevice::StartDetection(s32 protocol_) {
151 if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
152 npad_device->SetPollingMode(Common::Input::PollingMode::NFC);
153 device_state = DeviceState::SearchingForTag;
154 protocol = protocol_;
155 return ResultSuccess;
156 }
157
158 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
159 return WrongDeviceState;
160}
161
162Result NfpDevice::StopDetection() {
163 npad_device->SetPollingMode(Common::Input::PollingMode::Active);
164
165 if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
166 CloseAmiibo();
167 return ResultSuccess;
168 }
169 if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
170 device_state = DeviceState::Initialized;
171 return ResultSuccess;
172 }
173
174 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
175 return WrongDeviceState;
176}
177
178Result NfpDevice::Flush() {
179 if (device_state != DeviceState::TagMounted) {
180 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
181 if (device_state == DeviceState::TagRemoved) {
182 return TagRemoved;
183 }
184 return WrongDeviceState;
185 }
186
187 if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
188 LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
189 return WrongDeviceState;
190 }
191
192 auto& settings = tag_data.settings;
193
194 const auto& current_date = GetAmiiboDate(current_posix_time);
195 if (settings.write_date.raw_date != current_date.raw_date) {
196 settings.write_date = current_date;
197 settings.crc_counter++;
198 // TODO: Find how to calculate the crc check
199 // settings.crc = CalculateCRC(settings);
200 }
201
202 tag_data.write_counter++;
203
204 if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
205 LOG_ERROR(Service_NFP, "Failed to encode data");
206 return WriteAmiiboFailed;
207 }
208
209 std::vector<u8> data(sizeof(encrypted_tag_data));
210 memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
211
212 if (!npad_device->WriteNfc(data)) {
213 LOG_ERROR(Service_NFP, "Error writing to file");
214 return WriteAmiiboFailed;
215 }
216
217 is_data_moddified = false;
218
219 return ResultSuccess;
220}
221
222Result NfpDevice::Mount(MountTarget mount_target_) {
223 if (device_state != DeviceState::TagFound) {
224 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
225 return WrongDeviceState;
226 }
227
228 if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
229 LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state);
230 return CorruptedData;
231 }
232
233 device_state = DeviceState::TagMounted;
234 mount_target = mount_target_;
235 return ResultSuccess;
236}
237
238Result NfpDevice::Unmount() {
239 if (device_state != DeviceState::TagMounted) {
240 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
241 return WrongDeviceState;
242 }
243
244 // Save data before unloading the amiibo
245 if (is_data_moddified) {
246 Flush();
247 }
248
249 device_state = DeviceState::TagFound;
250 mount_target = MountTarget::None;
251 is_app_area_open = false;
252
253 return ResultSuccess;
254}
255
256Result NfpDevice::GetTagInfo(TagInfo& tag_info) const {
257 if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
258 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
259 return WrongDeviceState;
260 }
261
262 tag_info = {
263 .uuid = encrypted_tag_data.uuid.uid,
264 .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
265 .protocol = TagProtocol::TypeA,
266 .tag_type = TagType::Type2,
267 };
268
269 return ResultSuccess;
270}
271
272Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
273 if (device_state != DeviceState::TagMounted) {
274 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
275 if (device_state == DeviceState::TagRemoved) {
276 return TagRemoved;
277 }
278 return WrongDeviceState;
279 }
280
281 if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
282 LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
283 return WrongDeviceState;
284 }
285
286 const auto& settings = tag_data.settings;
287
288 // TODO: Validate this data
289 common_info = {
290 .last_write_date =
291 {
292 settings.write_date.GetYear(),
293 settings.write_date.GetMonth(),
294 settings.write_date.GetDay(),
295 },
296 .write_counter = tag_data.write_counter,
297 .version = 0,
298 .application_area_size = sizeof(ApplicationArea),
299 };
300 return ResultSuccess;
301}
302
303Result NfpDevice::GetModelInfo(ModelInfo& model_info) const {
304 if (device_state != DeviceState::TagMounted) {
305 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
306 return WrongDeviceState;
307 }
308
309 const auto& model_info_data = encrypted_tag_data.user_memory.model_info;
310 model_info = {
311 .character_id = model_info_data.character_id,
312 .character_variant = model_info_data.character_variant,
313 .amiibo_type = model_info_data.amiibo_type,
314 .model_number = model_info_data.model_number,
315 .series = model_info_data.series,
316 };
317 return ResultSuccess;
318}
319
320Result NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const {
321 if (device_state != DeviceState::TagMounted) {
322 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
323 if (device_state == DeviceState::TagRemoved) {
324 return TagRemoved;
325 }
326 return WrongDeviceState;
327 }
328
329 if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
330 LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
331 return WrongDeviceState;
332 }
333
334 if (tag_data.settings.settings.amiibo_initialized == 0) {
335 return RegistrationIsNotInitialized;
336 }
337
338 Service::Mii::MiiManager manager;
339 const auto& settings = tag_data.settings;
340
341 // TODO: Validate this data
342 register_info = {
343 .mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii),
344 .creation_date =
345 {
346 settings.init_date.GetYear(),
347 settings.init_date.GetMonth(),
348 settings.init_date.GetDay(),
349 },
350 .amiibo_name = GetAmiiboName(settings),
351 .font_region = {},
352 };
353
354 return ResultSuccess;
355}
356
357Result NfpDevice::SetNicknameAndOwner(const AmiiboName& amiibo_name) {
358 if (device_state != DeviceState::TagMounted) {
359 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
360 if (device_state == DeviceState::TagRemoved) {
361 return TagRemoved;
362 }
363 return WrongDeviceState;
364 }
365
366 if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
367 LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
368 return WrongDeviceState;
369 }
370
371 Service::Mii::MiiManager manager;
372 auto& settings = tag_data.settings;
373
374 settings.init_date = GetAmiiboDate(current_posix_time);
375 settings.write_date = GetAmiiboDate(current_posix_time);
376 settings.crc_counter++;
377 // TODO: Find how to calculate the crc check
378 // settings.crc = CalculateCRC(settings);
379
380 SetAmiiboName(settings, amiibo_name);
381 tag_data.owner_mii = manager.ConvertCharInfoToV3(manager.BuildDefault(0));
382 settings.settings.amiibo_initialized.Assign(1);
383
384 return Flush();
385}
386
387Result NfpDevice::RestoreAmiibo() {
388 if (device_state != DeviceState::TagMounted) {
389 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
390 if (device_state == DeviceState::TagRemoved) {
391 return TagRemoved;
392 }
393 return WrongDeviceState;
394 }
395
396 if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
397 LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
398 return WrongDeviceState;
399 }
400
401 // TODO: Load amiibo from backup on system
402 LOG_ERROR(Service_NFP, "Not Implemented");
403 return ResultSuccess;
404}
405
406Result NfpDevice::DeleteAllData() {
407 const auto result = DeleteApplicationArea();
408 if (result.IsError()) {
409 return result;
410 }
411
412 if (device_state != DeviceState::TagMounted) {
413 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
414 if (device_state == DeviceState::TagRemoved) {
415 return TagRemoved;
416 }
417 return WrongDeviceState;
418 }
419
420 Common::TinyMT rng{};
421 rng.GenerateRandomBytes(&tag_data.owner_mii, sizeof(tag_data.owner_mii));
422 tag_data.settings.settings.amiibo_initialized.Assign(0);
423
424 return Flush();
425}
426
427Result NfpDevice::OpenApplicationArea(u32 access_id) {
428 if (device_state != DeviceState::TagMounted) {
429 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
430 if (device_state == DeviceState::TagRemoved) {
431 return TagRemoved;
432 }
433 return WrongDeviceState;
434 }
435
436 if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
437 LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
438 return WrongDeviceState;
439 }
440
441 if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
442 LOG_WARNING(Service_NFP, "Application area is not initialized");
443 return ApplicationAreaIsNotInitialized;
444 }
445
446 if (tag_data.application_area_id != access_id) {
447 LOG_WARNING(Service_NFP, "Wrong application area id");
448 return WrongApplicationAreaId;
449 }
450
451 is_app_area_open = true;
452
453 return ResultSuccess;
454}
455
456Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const {
457 if (device_state != DeviceState::TagMounted) {
458 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
459 if (device_state == DeviceState::TagRemoved) {
460 return TagRemoved;
461 }
462 return WrongDeviceState;
463 }
464
465 if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
466 LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
467 return WrongDeviceState;
468 }
469
470 if (!is_app_area_open) {
471 LOG_ERROR(Service_NFP, "Application area is not open");
472 return WrongDeviceState;
473 }
474
475 if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
476 LOG_ERROR(Service_NFP, "Application area is not initialized");
477 return ApplicationAreaIsNotInitialized;
478 }
479
480 if (data.size() > sizeof(ApplicationArea)) {
481 LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
482 return ResultUnknown;
483 }
484
485 memcpy(data.data(), tag_data.application_area.data(), data.size());
486
487 return ResultSuccess;
488}
489
490Result NfpDevice::SetApplicationArea(std::span<const u8> data) {
491 if (device_state != DeviceState::TagMounted) {
492 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
493 if (device_state == DeviceState::TagRemoved) {
494 return TagRemoved;
495 }
496 return WrongDeviceState;
497 }
498
499 if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
500 LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
501 return WrongDeviceState;
502 }
503
504 if (!is_app_area_open) {
505 LOG_ERROR(Service_NFP, "Application area is not open");
506 return WrongDeviceState;
507 }
508
509 if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
510 LOG_ERROR(Service_NFP, "Application area is not initialized");
511 return ApplicationAreaIsNotInitialized;
512 }
513
514 if (data.size() > sizeof(ApplicationArea)) {
515 LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
516 return ResultUnknown;
517 }
518
519 Common::TinyMT rng{};
520 std::memcpy(tag_data.application_area.data(), data.data(), data.size());
521 // HW seems to fill excess data with garbage
522 rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
523 sizeof(ApplicationArea) - data.size());
524
525 tag_data.applicaton_write_counter++;
526 is_data_moddified = true;
527
528 return ResultSuccess;
529}
530
531Result NfpDevice::CreateApplicationArea(u32 access_id, std::span<const u8> data) {
532 if (device_state != DeviceState::TagMounted) {
533 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
534 if (device_state == DeviceState::TagRemoved) {
535 return TagRemoved;
536 }
537 return WrongDeviceState;
538 }
539
540 if (tag_data.settings.settings.appdata_initialized.Value() != 0) {
541 LOG_ERROR(Service_NFP, "Application area already exist");
542 return ApplicationAreaExist;
543 }
544
545 return RecreateApplicationArea(access_id, data);
546}
547
548Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> data) {
549 if (device_state != DeviceState::TagMounted) {
550 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
551 if (device_state == DeviceState::TagRemoved) {
552 return TagRemoved;
553 }
554 return WrongDeviceState;
555 }
556
557 if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
558 LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
559 return WrongDeviceState;
560 }
561
562 if (data.size() > sizeof(ApplicationArea)) {
563 LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
564 return ResultUnknown;
565 }
566
567 Common::TinyMT rng{};
568 std::memcpy(tag_data.application_area.data(), data.data(), data.size());
569 // HW seems to fill excess data with garbage
570 rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
571 sizeof(ApplicationArea) - data.size());
572
573 // TODO: Investigate why the title id needs to be moddified
574 tag_data.title_id = system.GetCurrentProcessProgramID();
575 tag_data.title_id = tag_data.title_id | 0x30000000ULL;
576 tag_data.settings.settings.appdata_initialized.Assign(1);
577 tag_data.application_area_id = access_id;
578 tag_data.applicaton_write_counter++;
579 tag_data.unknown = {};
580
581 return Flush();
582}
583
584Result NfpDevice::DeleteApplicationArea() {
585 if (device_state != DeviceState::TagMounted) {
586 LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
587 if (device_state == DeviceState::TagRemoved) {
588 return TagRemoved;
589 }
590 return WrongDeviceState;
591 }
592
593 if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
594 LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
595 return WrongDeviceState;
596 }
597
598 Common::TinyMT rng{};
599 rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(ApplicationArea));
600 rng.GenerateRandomBytes(&tag_data.title_id, sizeof(u64));
601 rng.GenerateRandomBytes(&tag_data.application_area_id, sizeof(u32));
602 tag_data.settings.settings.appdata_initialized.Assign(0);
603 tag_data.applicaton_write_counter++;
604 tag_data.unknown = {};
605
606 return Flush();
607}
608
609u64 NfpDevice::GetHandle() const {
610 // Generate a handle based of the npad id
611 return static_cast<u64>(npad_id);
612}
613
614u32 NfpDevice::GetApplicationAreaSize() const {
615 // Investigate if this value is really constant
616 return sizeof(ApplicationArea);
617}
618
619DeviceState NfpDevice::GetCurrentState() const {
620 return device_state;
621}
622
623Core::HID::NpadIdType NfpDevice::GetNpadId() const {
624 return npad_id;
625}
626
627AmiiboName NfpDevice::GetAmiiboName(const AmiiboSettings& settings) const {
628 std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
629 AmiiboName amiibo_name{};
630
631 // Convert from big endian to little endian
632 for (std::size_t i = 0; i < amiibo_name_length; i++) {
633 settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]);
634 }
635
636 // Convert from utf16 to utf8
637 const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data());
638 memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size());
639
640 return amiibo_name;
641}
642
643void NfpDevice::SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name) {
644 std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
645
646 // Convert from utf8 to utf16
647 const auto amiibo_name_utf16 = Common::UTF8ToUTF16(amiibo_name.data());
648 memcpy(settings_amiibo_name.data(), amiibo_name_utf16.data(),
649 amiibo_name_utf16.size() * sizeof(char16_t));
650
651 // Convert from little endian to big endian
652 for (std::size_t i = 0; i < amiibo_name_length; i++) {
653 settings.amiibo_name[i] = static_cast<u16_be>(settings_amiibo_name[i]);
654 }
655}
656
657AmiiboDate NfpDevice::GetAmiiboDate(s64 posix_time) const {
658 const auto& time_zone_manager =
659 system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
660 Time::TimeZone::CalendarInfo calendar_info{};
661 AmiiboDate amiibo_date{};
662
663 amiibo_date.SetYear(2000);
664 amiibo_date.SetMonth(1);
665 amiibo_date.SetDay(1);
666
667 if (time_zone_manager.ToCalendarTime({}, posix_time, calendar_info) == ResultSuccess) {
668 amiibo_date.SetYear(calendar_info.time.year);
669 amiibo_date.SetMonth(calendar_info.time.month);
670 amiibo_date.SetDay(calendar_info.time.day);
671 }
672
673 return amiibo_date;
674}
675
676} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h
new file mode 100644
index 000000000..a5b72cf19
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_device.h
@@ -0,0 +1,101 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#pragma once
5
6#include <array>
7#include <vector>
8
9#include "common/common_funcs.h"
10#include "core/hle/service/kernel_helpers.h"
11#include "core/hle/service/mii/types.h"
12#include "core/hle/service/nfp/nfp_types.h"
13#include "core/hle/service/service.h"
14
15namespace Kernel {
16class KEvent;
17class KReadableEvent;
18} // namespace Kernel
19
20namespace Core {
21class System;
22} // namespace Core
23
24namespace Core::HID {
25class EmulatedController;
26enum class ControllerTriggerType;
27enum class NpadIdType : u32;
28} // namespace Core::HID
29
30namespace Service::NFP {
31class NfpDevice {
32public:
33 NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
34 KernelHelpers::ServiceContext& service_context_,
35 Kernel::KEvent* availability_change_event_);
36 ~NfpDevice();
37
38 void Initialize();
39 void Finalize();
40
41 Result StartDetection(s32 protocol_);
42 Result StopDetection();
43 Result Mount(MountTarget mount_target);
44 Result Unmount();
45 Result Flush();
46
47 Result GetTagInfo(TagInfo& tag_info) const;
48 Result GetCommonInfo(CommonInfo& common_info) const;
49 Result GetModelInfo(ModelInfo& model_info) const;
50 Result GetRegisterInfo(RegisterInfo& register_info) const;
51
52 Result SetNicknameAndOwner(const AmiiboName& amiibo_name);
53 Result RestoreAmiibo();
54 Result DeleteAllData();
55
56 Result OpenApplicationArea(u32 access_id);
57 Result GetApplicationArea(std::vector<u8>& data) const;
58 Result SetApplicationArea(std::span<const u8> data);
59 Result CreateApplicationArea(u32 access_id, std::span<const u8> data);
60 Result RecreateApplicationArea(u32 access_id, std::span<const u8> data);
61 Result DeleteApplicationArea();
62
63 u64 GetHandle() const;
64 u32 GetApplicationAreaSize() const;
65 DeviceState GetCurrentState() const;
66 Core::HID::NpadIdType GetNpadId() const;
67
68 Kernel::KReadableEvent& GetActivateEvent() const;
69 Kernel::KReadableEvent& GetDeactivateEvent() const;
70
71private:
72 void NpadUpdate(Core::HID::ControllerTriggerType type);
73 bool LoadAmiibo(std::span<const u8> data);
74 void CloseAmiibo();
75
76 AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
77 void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name);
78 AmiiboDate GetAmiiboDate(s64 posix_time) const;
79
80 bool is_controller_set{};
81 int callback_key;
82 const Core::HID::NpadIdType npad_id;
83 Core::System& system;
84 Core::HID::EmulatedController* npad_device = nullptr;
85 KernelHelpers::ServiceContext& service_context;
86 Kernel::KEvent* activate_event = nullptr;
87 Kernel::KEvent* deactivate_event = nullptr;
88 Kernel::KEvent* availability_change_event = nullptr;
89
90 bool is_data_moddified{};
91 bool is_app_area_open{};
92 s32 protocol{};
93 s64 current_posix_time{};
94 MountTarget mount_target{MountTarget::None};
95 DeviceState device_state{DeviceState::Unavailable};
96
97 NTAG215File tag_data{};
98 EncryptedNTAG215File encrypted_tag_data{};
99};
100
101} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_result.h b/src/core/hle/service/nfp/nfp_result.h
new file mode 100644
index 000000000..ac259e2ff
--- /dev/null
+++ b/src/core/hle/service/nfp/nfp_result.h
@@ -0,0 +1,22 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include "core/hle/result.h"
7
8namespace Service::NFP {
9
10constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
11constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
12constexpr Result NfcDisabled(ErrorModule::NFP, 80);
13constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88);
14constexpr Result TagRemoved(ErrorModule::NFP, 97);
15constexpr Result RegistrationIsNotInitialized(ErrorModule::NFP, 120);
16constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
17constexpr Result CorruptedData(ErrorModule::NFP, 144);
18constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152);
19constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
20constexpr Result NotAnAmiibo(ErrorModule::NFP, 178);
21
22} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/amiibo_types.h b/src/core/hle/service/nfp/nfp_types.h
index bf2de811a..dd4525b61 100644
--- a/src/core/hle/service/nfp/amiibo_types.h
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -5,6 +5,7 @@
5 5
6#include <array> 6#include <array>
7 7
8#include "common/swap.h"
8#include "core/hle/service/mii/types.h" 9#include "core/hle/service/mii/types.h"
9 10
10namespace Service::NFP { 11namespace Service::NFP {
@@ -27,7 +28,7 @@ enum class DeviceState : u32 {
27 TagFound, 28 TagFound,
28 TagRemoved, 29 TagRemoved,
29 TagMounted, 30 TagMounted,
30 Unaviable, 31 Unavailable,
31 Finalized, 32 Finalized,
32}; 33};
33 34
@@ -36,6 +37,7 @@ enum class ModelType : u32 {
36}; 37};
37 38
38enum class MountTarget : u32 { 39enum class MountTarget : u32 {
40 None,
39 Rom, 41 Rom,
40 Ram, 42 Ram,
41 All, 43 All,
@@ -73,21 +75,63 @@ enum class AmiiboSeries : u8 {
73 Diablo, 75 Diablo,
74}; 76};
75 77
76using TagUuid = std::array<u8, 10>; 78enum class TagType : u32 {
79 None,
80 Type1, // ISO14443A RW 96-2k bytes 106kbit/s
81 Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
82 Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
83 Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
84 Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
85};
86
87enum class TagProtocol : u32 {
88 None,
89 TypeA, // ISO14443A
90 TypeB, // ISO14443B
91 TypeF, // Sony Felica
92};
93
94using UniqueSerialNumber = std::array<u8, 7>;
95using LockBytes = std::array<u8, 2>;
77using HashData = std::array<u8, 0x20>; 96using HashData = std::array<u8, 0x20>;
78using ApplicationArea = std::array<u8, 0xD8>; 97using ApplicationArea = std::array<u8, 0xD8>;
98using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
99
100struct TagUuid {
101 UniqueSerialNumber uid;
102 u8 nintendo_id;
103 LockBytes lock_bytes;
104};
105static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size");
79 106
80struct AmiiboDate { 107struct AmiiboDate {
81 u16 raw_date{}; 108 u16 raw_date{};
82 109
110 u16 GetValue() const {
111 return Common::swap16(raw_date);
112 }
113
83 u16 GetYear() const { 114 u16 GetYear() const {
84 return static_cast<u16>(((raw_date & 0xFE00) >> 9) + 2000); 115 return static_cast<u16>(((GetValue() & 0xFE00) >> 9) + 2000);
85 } 116 }
86 u8 GetMonth() const { 117 u8 GetMonth() const {
87 return static_cast<u8>(((raw_date & 0x01E0) >> 5) - 1); 118 return static_cast<u8>((GetValue() & 0x01E0) >> 5);
88 } 119 }
89 u8 GetDay() const { 120 u8 GetDay() const {
90 return static_cast<u8>(raw_date & 0x001F); 121 return static_cast<u8>(GetValue() & 0x001F);
122 }
123
124 void SetYear(u16 year) {
125 const u16 year_converted = static_cast<u16>((year - 2000) << 9);
126 raw_date = Common::swap16((GetValue() & ~0xFE00) | year_converted);
127 }
128 void SetMonth(u8 month) {
129 const u16 month_converted = static_cast<u16>(month << 5);
130 raw_date = Common::swap16((GetValue() & ~0x01E0) | month_converted);
131 }
132 void SetDay(u8 day) {
133 const u16 day_converted = static_cast<u16>(day);
134 raw_date = Common::swap16((GetValue() & ~0x001F) | day_converted);
91 } 135 }
92}; 136};
93static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size"); 137static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
@@ -117,7 +161,7 @@ struct AmiiboModelInfo {
117 u16 character_id; 161 u16 character_id;
118 u8 character_variant; 162 u8 character_variant;
119 AmiiboType amiibo_type; 163 AmiiboType amiibo_type;
120 u16 model_number; 164 u16_be model_number;
121 AmiiboSeries series; 165 AmiiboSeries series;
122 u8 constant_value; // Must be 02 166 u8 constant_value; // Must be 02
123 INSERT_PADDING_BYTES(0x4); // Unknown 167 INSERT_PADDING_BYTES(0x4); // Unknown
@@ -134,7 +178,7 @@ static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid siz
134#pragma pack(1) 178#pragma pack(1)
135struct EncryptedAmiiboFile { 179struct EncryptedAmiiboFile {
136 u8 constant_value; // Must be A5 180 u8 constant_value; // Must be A5
137 u16 write_counter; // Number of times the amiibo has been written? 181 u16_be write_counter; // Number of times the amiibo has been written?
138 INSERT_PADDING_BYTES(0x1); // Unknown 1 182 INSERT_PADDING_BYTES(0x1); // Unknown 1
139 AmiiboSettings settings; // Encrypted amiibo settings 183 AmiiboSettings settings; // Encrypted amiibo settings
140 HashData hmac_tag; // Hash 184 HashData hmac_tag; // Hash
@@ -146,18 +190,18 @@ struct EncryptedAmiiboFile {
146 u16_be applicaton_write_counter; // Encrypted Counter 190 u16_be applicaton_write_counter; // Encrypted Counter
147 u32_be application_area_id; // Encrypted Game id 191 u32_be application_area_id; // Encrypted Game id
148 std::array<u8, 0x2> unknown; 192 std::array<u8, 0x2> unknown;
149 HashData hash; // Probably a SHA256-HMAC hash? 193 std::array<u32, 0x8> unknown2;
150 ApplicationArea application_area; // Encrypted Game data 194 ApplicationArea application_area; // Encrypted Game data
151}; 195};
152static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size"); 196static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
153 197
154struct NTAG215File { 198struct NTAG215File {
155 std::array<u8, 0x2> uuid2; 199 LockBytes lock_bytes; // Tag UUID
156 u16 static_lock; // Set defined pages as read only 200 u16 static_lock; // Set defined pages as read only
157 u32 compability_container; // Defines available memory 201 u32 compability_container; // Defines available memory
158 HashData hmac_data; // Hash 202 HashData hmac_data; // Hash
159 u8 constant_value; // Must be A5 203 u8 constant_value; // Must be A5
160 u16 write_counter; // Number of times the amiibo has been written? 204 u16_be write_counter; // Number of times the amiibo has been written?
161 INSERT_PADDING_BYTES(0x1); // Unknown 1 205 INSERT_PADDING_BYTES(0x1); // Unknown 1
162 AmiiboSettings settings; 206 AmiiboSettings settings;
163 Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data 207 Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
@@ -165,10 +209,11 @@ struct NTAG215File {
165 u16_be applicaton_write_counter; // Encrypted Counter 209 u16_be applicaton_write_counter; // Encrypted Counter
166 u32_be application_area_id; 210 u32_be application_area_id;
167 std::array<u8, 0x2> unknown; 211 std::array<u8, 0x2> unknown;
168 HashData hash; // Probably a SHA256-HMAC hash? 212 std::array<u32, 0x8> unknown2;
169 ApplicationArea application_area; // Encrypted Game data 213 ApplicationArea application_area; // Encrypted Game data
170 HashData hmac_tag; // Hash 214 HashData hmac_tag; // Hash
171 std::array<u8, 0x8> uuid; 215 UniqueSerialNumber uid; // Unique serial number
216 u8 nintendo_id; // Tag UUID
172 AmiiboModelInfo model_info; 217 AmiiboModelInfo model_info;
173 HashData keygen_salt; // Salt 218 HashData keygen_salt; // Salt
174 u32 dynamic_lock; // Dynamic lock 219 u32 dynamic_lock; // Dynamic lock
@@ -194,4 +239,51 @@ static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an
194static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>, 239static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
195 "EncryptedNTAG215File must be trivially copyable."); 240 "EncryptedNTAG215File must be trivially copyable.");
196 241
242struct TagInfo {
243 UniqueSerialNumber uuid;
244 INSERT_PADDING_BYTES(0x3);
245 u8 uuid_length;
246 INSERT_PADDING_BYTES(0x15);
247 TagProtocol protocol;
248 TagType tag_type;
249 INSERT_PADDING_BYTES(0x30);
250};
251static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
252
253struct WriteDate {
254 u16 year;
255 u8 month;
256 u8 day;
257};
258static_assert(sizeof(WriteDate) == 0x4, "WriteDate is an invalid size");
259
260struct CommonInfo {
261 WriteDate last_write_date;
262 u16 write_counter;
263 u8 version;
264 INSERT_PADDING_BYTES(0x1);
265 u32 application_area_size;
266 INSERT_PADDING_BYTES(0x34);
267};
268static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
269
270struct ModelInfo {
271 u16 character_id;
272 u8 character_variant;
273 AmiiboType amiibo_type;
274 u16 model_number;
275 AmiiboSeries series;
276 INSERT_PADDING_BYTES(0x39); // Unknown
277};
278static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
279
280struct RegisterInfo {
281 Service::Mii::CharInfo mii_char_info;
282 WriteDate creation_date;
283 AmiiboName amiibo_name;
284 u8 font_region;
285 INSERT_PADDING_BYTES(0x7A);
286};
287static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
288
197} // namespace Service::NFP 289} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp
index 2d7b156cf..c61df9401 100644
--- a/src/core/hle/service/nfp/nfp_user.cpp
+++ b/src/core/hle/service/nfp/nfp_user.cpp
@@ -1,18 +1,644 @@
1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project 1// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later 2// SPDX-License-Identifier: GPL-2.0-or-later
3 3
4#include <array>
5#include <atomic>
6
7#include "common/logging/log.h"
8#include "core/core.h"
9#include "core/hid/emulated_controller.h"
10#include "core/hid/hid_core.h"
11#include "core/hid/hid_types.h"
12#include "core/hle/ipc_helpers.h"
13#include "core/hle/kernel/k_event.h"
14#include "core/hle/service/mii/mii_manager.h"
15#include "core/hle/service/nfp/nfp_device.h"
16#include "core/hle/service/nfp/nfp_result.h"
4#include "core/hle/service/nfp/nfp_user.h" 17#include "core/hle/service/nfp/nfp_user.h"
5 18
6namespace Service::NFP { 19namespace Service::NFP {
7 20
8NFP_User::NFP_User(std::shared_ptr<Module> module_, Core::System& system_) 21IUser::IUser(Core::System& system_)
9 : Interface(std::move(module_), system_, "nfp:user") { 22 : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name} {
10 static const FunctionInfo functions[] = { 23 static const FunctionInfo functions[] = {
11 {0, &NFP_User::CreateUserInterface, "CreateUserInterface"}, 24 {0, &IUser::Initialize, "Initialize"},
25 {1, &IUser::Finalize, "Finalize"},
26 {2, &IUser::ListDevices, "ListDevices"},
27 {3, &IUser::StartDetection, "StartDetection"},
28 {4, &IUser::StopDetection, "StopDetection"},
29 {5, &IUser::Mount, "Mount"},
30 {6, &IUser::Unmount, "Unmount"},
31 {7, &IUser::OpenApplicationArea, "OpenApplicationArea"},
32 {8, &IUser::GetApplicationArea, "GetApplicationArea"},
33 {9, &IUser::SetApplicationArea, "SetApplicationArea"},
34 {10, &IUser::Flush, "Flush"},
35 {11, &IUser::Restore, "Restore"},
36 {12, &IUser::CreateApplicationArea, "CreateApplicationArea"},
37 {13, &IUser::GetTagInfo, "GetTagInfo"},
38 {14, &IUser::GetRegisterInfo, "GetRegisterInfo"},
39 {15, &IUser::GetCommonInfo, "GetCommonInfo"},
40 {16, &IUser::GetModelInfo, "GetModelInfo"},
41 {17, &IUser::AttachActivateEvent, "AttachActivateEvent"},
42 {18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
43 {19, &IUser::GetState, "GetState"},
44 {20, &IUser::GetDeviceState, "GetDeviceState"},
45 {21, &IUser::GetNpadId, "GetNpadId"},
46 {22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
47 {23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
48 {24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
12 }; 49 };
13 RegisterHandlers(functions); 50 RegisterHandlers(functions);
51
52 availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
53
54 for (u32 device_index = 0; device_index < 10; device_index++) {
55 devices[device_index] =
56 std::make_shared<NfpDevice>(Core::HID::IndexToNpadIdType(device_index), system,
57 service_context, availability_change_event);
58 }
59}
60
61void IUser::Initialize(Kernel::HLERequestContext& ctx) {
62 LOG_INFO(Service_NFC, "called");
63
64 state = State::Initialized;
65
66 for (auto& device : devices) {
67 device->Initialize();
68 }
69
70 IPC::ResponseBuilder rb{ctx, 2, 0};
71 rb.Push(ResultSuccess);
72}
73
74void IUser::Finalize(Kernel::HLERequestContext& ctx) {
75 LOG_INFO(Service_NFP, "called");
76
77 state = State::NonInitialized;
78
79 for (auto& device : devices) {
80 device->Finalize();
81 }
82
83 IPC::ResponseBuilder rb{ctx, 2};
84 rb.Push(ResultSuccess);
85}
86
87void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
88 LOG_INFO(Service_NFP, "called");
89
90 if (state == State::NonInitialized) {
91 IPC::ResponseBuilder rb{ctx, 2};
92 rb.Push(NfcDisabled);
93 return;
94 }
95
96 std::vector<u64> nfp_devices;
97 const std::size_t max_allowed_devices = ctx.GetWriteBufferSize() / sizeof(u64);
98
99 for (auto& device : devices) {
100 if (nfp_devices.size() >= max_allowed_devices) {
101 continue;
102 }
103 if (device->GetCurrentState() != DeviceState::Unavailable) {
104 nfp_devices.push_back(device->GetHandle());
105 }
106 }
107
108 if (nfp_devices.size() == 0) {
109 IPC::ResponseBuilder rb{ctx, 2};
110 rb.Push(DeviceNotFound);
111 return;
112 }
113
114 ctx.WriteBuffer(nfp_devices);
115
116 IPC::ResponseBuilder rb{ctx, 3};
117 rb.Push(ResultSuccess);
118 rb.Push(static_cast<s32>(nfp_devices.size()));
119}
120
121void IUser::StartDetection(Kernel::HLERequestContext& ctx) {
122 IPC::RequestParser rp{ctx};
123 const auto device_handle{rp.Pop<u64>()};
124 const auto nfp_protocol{rp.Pop<s32>()};
125 LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
126
127 if (state == State::NonInitialized) {
128 IPC::ResponseBuilder rb{ctx, 2};
129 rb.Push(NfcDisabled);
130 return;
131 }
132
133 auto device = GetNfpDevice(device_handle);
134
135 if (!device.has_value()) {
136 IPC::ResponseBuilder rb{ctx, 2};
137 rb.Push(DeviceNotFound);
138 return;
139 }
140
141 const auto result = device.value()->StartDetection(nfp_protocol);
142 IPC::ResponseBuilder rb{ctx, 2};
143 rb.Push(result);
144}
145
146void IUser::StopDetection(Kernel::HLERequestContext& ctx) {
147 IPC::RequestParser rp{ctx};
148 const auto device_handle{rp.Pop<u64>()};
149 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
150
151 if (state == State::NonInitialized) {
152 IPC::ResponseBuilder rb{ctx, 2};
153 rb.Push(NfcDisabled);
154 return;
155 }
156
157 auto device = GetNfpDevice(device_handle);
158
159 if (!device.has_value()) {
160 IPC::ResponseBuilder rb{ctx, 2};
161 rb.Push(DeviceNotFound);
162 return;
163 }
164
165 const auto result = device.value()->StopDetection();
166 IPC::ResponseBuilder rb{ctx, 2};
167 rb.Push(result);
168}
169
170void IUser::Mount(Kernel::HLERequestContext& ctx) {
171 IPC::RequestParser rp{ctx};
172 const auto device_handle{rp.Pop<u64>()};
173 const auto model_type{rp.PopEnum<ModelType>()};
174 const auto mount_target{rp.PopEnum<MountTarget>()};
175 LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle,
176 model_type, mount_target);
177
178 if (state == State::NonInitialized) {
179 IPC::ResponseBuilder rb{ctx, 2};
180 rb.Push(NfcDisabled);
181 return;
182 }
183
184 auto device = GetNfpDevice(device_handle);
185
186 if (!device.has_value()) {
187 IPC::ResponseBuilder rb{ctx, 2};
188 rb.Push(DeviceNotFound);
189 return;
190 }
191
192 const auto result = device.value()->Mount(mount_target);
193 IPC::ResponseBuilder rb{ctx, 2};
194 rb.Push(result);
195}
196
197void IUser::Unmount(Kernel::HLERequestContext& ctx) {
198 IPC::RequestParser rp{ctx};
199 const auto device_handle{rp.Pop<u64>()};
200 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
201
202 if (state == State::NonInitialized) {
203 IPC::ResponseBuilder rb{ctx, 2};
204 rb.Push(NfcDisabled);
205 return;
206 }
207
208 auto device = GetNfpDevice(device_handle);
209
210 if (!device.has_value()) {
211 IPC::ResponseBuilder rb{ctx, 2};
212 rb.Push(DeviceNotFound);
213 return;
214 }
215
216 const auto result = device.value()->Unmount();
217 IPC::ResponseBuilder rb{ctx, 2};
218 rb.Push(result);
14} 219}
15 220
16NFP_User::~NFP_User() = default; 221void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) {
222 IPC::RequestParser rp{ctx};
223 const auto device_handle{rp.Pop<u64>()};
224 const auto access_id{rp.Pop<u32>()};
225 LOG_INFO(Service_NFP, "called, device_handle={}, access_id={}", device_handle, access_id);
226
227 if (state == State::NonInitialized) {
228 IPC::ResponseBuilder rb{ctx, 2};
229 rb.Push(NfcDisabled);
230 return;
231 }
232
233 auto device = GetNfpDevice(device_handle);
234
235 if (!device.has_value()) {
236 IPC::ResponseBuilder rb{ctx, 2};
237 rb.Push(DeviceNotFound);
238 return;
239 }
240
241 const auto result = device.value()->OpenApplicationArea(access_id);
242 IPC::ResponseBuilder rb{ctx, 2};
243 rb.Push(result);
244}
245
246void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) {
247 IPC::RequestParser rp{ctx};
248 const auto device_handle{rp.Pop<u64>()};
249 const auto data_size = ctx.GetWriteBufferSize();
250 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
251
252 if (state == State::NonInitialized) {
253 IPC::ResponseBuilder rb{ctx, 2};
254 rb.Push(NfcDisabled);
255 return;
256 }
257
258 auto device = GetNfpDevice(device_handle);
259
260 if (!device.has_value()) {
261 IPC::ResponseBuilder rb{ctx, 2};
262 rb.Push(DeviceNotFound);
263 return;
264 }
265
266 std::vector<u8> data(data_size);
267 const auto result = device.value()->GetApplicationArea(data);
268 ctx.WriteBuffer(data);
269 IPC::ResponseBuilder rb{ctx, 3};
270 rb.Push(result);
271 rb.Push(static_cast<u32>(data_size));
272}
273
274void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) {
275 IPC::RequestParser rp{ctx};
276 const auto device_handle{rp.Pop<u64>()};
277 const auto data{ctx.ReadBuffer()};
278 LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}", device_handle, data.size());
279
280 if (state == State::NonInitialized) {
281 IPC::ResponseBuilder rb{ctx, 2};
282 rb.Push(NfcDisabled);
283 return;
284 }
285
286 auto device = GetNfpDevice(device_handle);
287
288 if (!device.has_value()) {
289 IPC::ResponseBuilder rb{ctx, 2};
290 rb.Push(DeviceNotFound);
291 return;
292 }
293
294 const auto result = device.value()->SetApplicationArea(data);
295 IPC::ResponseBuilder rb{ctx, 2};
296 rb.Push(result);
297}
298
299void IUser::Flush(Kernel::HLERequestContext& ctx) {
300 IPC::RequestParser rp{ctx};
301 const auto device_handle{rp.Pop<u64>()};
302 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
303
304 if (state == State::NonInitialized) {
305 IPC::ResponseBuilder rb{ctx, 2};
306 rb.Push(NfcDisabled);
307 return;
308 }
309
310 auto device = GetNfpDevice(device_handle);
311
312 if (!device.has_value()) {
313 IPC::ResponseBuilder rb{ctx, 2};
314 rb.Push(DeviceNotFound);
315 return;
316 }
317
318 const auto result = device.value()->Flush();
319 IPC::ResponseBuilder rb{ctx, 2};
320 rb.Push(result);
321}
322
323void IUser::Restore(Kernel::HLERequestContext& ctx) {
324 IPC::RequestParser rp{ctx};
325 const auto device_handle{rp.Pop<u64>()};
326 LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
327
328 if (state == State::NonInitialized) {
329 IPC::ResponseBuilder rb{ctx, 2};
330 rb.Push(NfcDisabled);
331 return;
332 }
333
334 auto device = GetNfpDevice(device_handle);
335
336 if (!device.has_value()) {
337 IPC::ResponseBuilder rb{ctx, 2};
338 rb.Push(DeviceNotFound);
339 return;
340 }
341
342 const auto result = device.value()->RestoreAmiibo();
343 IPC::ResponseBuilder rb{ctx, 2};
344 rb.Push(result);
345}
346
347void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) {
348 IPC::RequestParser rp{ctx};
349 const auto device_handle{rp.Pop<u64>()};
350 const auto access_id{rp.Pop<u32>()};
351 const auto data{ctx.ReadBuffer()};
352 LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle,
353 access_id, data.size());
354
355 if (state == State::NonInitialized) {
356 IPC::ResponseBuilder rb{ctx, 2};
357 rb.Push(NfcDisabled);
358 return;
359 }
360
361 auto device = GetNfpDevice(device_handle);
362
363 if (!device.has_value()) {
364 IPC::ResponseBuilder rb{ctx, 2};
365 rb.Push(DeviceNotFound);
366 return;
367 }
368
369 const auto result = device.value()->CreateApplicationArea(access_id, data);
370 IPC::ResponseBuilder rb{ctx, 2};
371 rb.Push(result);
372}
373
374void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) {
375 IPC::RequestParser rp{ctx};
376 const auto device_handle{rp.Pop<u64>()};
377 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
378
379 if (state == State::NonInitialized) {
380 IPC::ResponseBuilder rb{ctx, 2};
381 rb.Push(NfcDisabled);
382 return;
383 }
384
385 auto device = GetNfpDevice(device_handle);
386
387 if (!device.has_value()) {
388 IPC::ResponseBuilder rb{ctx, 2};
389 rb.Push(DeviceNotFound);
390 return;
391 }
392
393 TagInfo tag_info{};
394 const auto result = device.value()->GetTagInfo(tag_info);
395 ctx.WriteBuffer(tag_info);
396 IPC::ResponseBuilder rb{ctx, 2};
397 rb.Push(result);
398}
399
400void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) {
401 IPC::RequestParser rp{ctx};
402 const auto device_handle{rp.Pop<u64>()};
403 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
404
405 if (state == State::NonInitialized) {
406 IPC::ResponseBuilder rb{ctx, 2};
407 rb.Push(NfcDisabled);
408 return;
409 }
410
411 auto device = GetNfpDevice(device_handle);
412
413 if (!device.has_value()) {
414 IPC::ResponseBuilder rb{ctx, 2};
415 rb.Push(DeviceNotFound);
416 return;
417 }
418
419 RegisterInfo register_info{};
420 const auto result = device.value()->GetRegisterInfo(register_info);
421 ctx.WriteBuffer(register_info);
422 IPC::ResponseBuilder rb{ctx, 2};
423 rb.Push(result);
424}
425
426void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) {
427 IPC::RequestParser rp{ctx};
428 const auto device_handle{rp.Pop<u64>()};
429 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
430
431 if (state == State::NonInitialized) {
432 IPC::ResponseBuilder rb{ctx, 2};
433 rb.Push(NfcDisabled);
434 return;
435 }
436
437 auto device = GetNfpDevice(device_handle);
438
439 if (!device.has_value()) {
440 IPC::ResponseBuilder rb{ctx, 2};
441 rb.Push(DeviceNotFound);
442 return;
443 }
444
445 CommonInfo common_info{};
446 const auto result = device.value()->GetCommonInfo(common_info);
447 ctx.WriteBuffer(common_info);
448 IPC::ResponseBuilder rb{ctx, 2};
449 rb.Push(result);
450}
451
452void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) {
453 IPC::RequestParser rp{ctx};
454 const auto device_handle{rp.Pop<u64>()};
455 LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
456
457 if (state == State::NonInitialized) {
458 IPC::ResponseBuilder rb{ctx, 2};
459 rb.Push(NfcDisabled);
460 return;
461 }
462
463 auto device = GetNfpDevice(device_handle);
464
465 if (!device.has_value()) {
466 IPC::ResponseBuilder rb{ctx, 2};
467 rb.Push(DeviceNotFound);
468 return;
469 }
470
471 ModelInfo model_info{};
472 const auto result = device.value()->GetModelInfo(model_info);
473 ctx.WriteBuffer(model_info);
474 IPC::ResponseBuilder rb{ctx, 2};
475 rb.Push(result);
476}
477
478void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) {
479 IPC::RequestParser rp{ctx};
480 const auto device_handle{rp.Pop<u64>()};
481 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
482
483 if (state == State::NonInitialized) {
484 IPC::ResponseBuilder rb{ctx, 2};
485 rb.Push(NfcDisabled);
486 return;
487 }
488
489 auto device = GetNfpDevice(device_handle);
490
491 if (!device.has_value()) {
492 IPC::ResponseBuilder rb{ctx, 2};
493 rb.Push(DeviceNotFound);
494 return;
495 }
496
497 IPC::ResponseBuilder rb{ctx, 2, 1};
498 rb.Push(ResultSuccess);
499 rb.PushCopyObjects(device.value()->GetActivateEvent());
500}
501
502void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) {
503 IPC::RequestParser rp{ctx};
504 const auto device_handle{rp.Pop<u64>()};
505 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
506
507 if (state == State::NonInitialized) {
508 IPC::ResponseBuilder rb{ctx, 2};
509 rb.Push(NfcDisabled);
510 return;
511 }
512
513 auto device = GetNfpDevice(device_handle);
514
515 if (!device.has_value()) {
516 IPC::ResponseBuilder rb{ctx, 2};
517 rb.Push(DeviceNotFound);
518 return;
519 }
520
521 IPC::ResponseBuilder rb{ctx, 2, 1};
522 rb.Push(ResultSuccess);
523 rb.PushCopyObjects(device.value()->GetDeactivateEvent());
524}
525
526void IUser::GetState(Kernel::HLERequestContext& ctx) {
527 LOG_DEBUG(Service_NFC, "called");
528
529 IPC::ResponseBuilder rb{ctx, 3, 0};
530 rb.Push(ResultSuccess);
531 rb.PushEnum(state);
532}
533
534void IUser::GetDeviceState(Kernel::HLERequestContext& ctx) {
535 IPC::RequestParser rp{ctx};
536 const auto device_handle{rp.Pop<u64>()};
537 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
538
539 auto device = GetNfpDevice(device_handle);
540
541 if (!device.has_value()) {
542 IPC::ResponseBuilder rb{ctx, 2};
543 rb.Push(DeviceNotFound);
544 return;
545 }
546
547 IPC::ResponseBuilder rb{ctx, 3};
548 rb.Push(ResultSuccess);
549 rb.PushEnum(device.value()->GetCurrentState());
550}
551
552void IUser::GetNpadId(Kernel::HLERequestContext& ctx) {
553 IPC::RequestParser rp{ctx};
554 const auto device_handle{rp.Pop<u64>()};
555 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
556
557 if (state == State::NonInitialized) {
558 IPC::ResponseBuilder rb{ctx, 2};
559 rb.Push(NfcDisabled);
560 return;
561 }
562
563 auto device = GetNfpDevice(device_handle);
564
565 if (!device.has_value()) {
566 IPC::ResponseBuilder rb{ctx, 2};
567 rb.Push(DeviceNotFound);
568 return;
569 }
570
571 IPC::ResponseBuilder rb{ctx, 3};
572 rb.Push(ResultSuccess);
573 rb.PushEnum(device.value()->GetNpadId());
574}
575
576void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) {
577 IPC::RequestParser rp{ctx};
578 const auto device_handle{rp.Pop<u64>()};
579 LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
580
581 auto device = GetNfpDevice(device_handle);
582
583 if (!device.has_value()) {
584 IPC::ResponseBuilder rb{ctx, 2};
585 rb.Push(DeviceNotFound);
586 return;
587 }
588
589 IPC::ResponseBuilder rb{ctx, 3};
590 rb.Push(ResultSuccess);
591 rb.Push(device.value()->GetApplicationAreaSize());
592}
593
594void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) {
595 LOG_INFO(Service_NFP, "called");
596
597 if (state == State::NonInitialized) {
598 IPC::ResponseBuilder rb{ctx, 2};
599 rb.Push(NfcDisabled);
600 return;
601 }
602
603 IPC::ResponseBuilder rb{ctx, 2, 1};
604 rb.Push(ResultSuccess);
605 rb.PushCopyObjects(availability_change_event->GetReadableEvent());
606}
607
608void IUser::RecreateApplicationArea(Kernel::HLERequestContext& ctx) {
609 IPC::RequestParser rp{ctx};
610 const auto device_handle{rp.Pop<u64>()};
611 const auto access_id{rp.Pop<u32>()};
612 const auto data{ctx.ReadBuffer()};
613 LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle,
614 access_id, data.size());
615
616 if (state == State::NonInitialized) {
617 IPC::ResponseBuilder rb{ctx, 2};
618 rb.Push(NfcDisabled);
619 return;
620 }
621
622 auto device = GetNfpDevice(device_handle);
623
624 if (!device.has_value()) {
625 IPC::ResponseBuilder rb{ctx, 2};
626 rb.Push(DeviceNotFound);
627 return;
628 }
629
630 const auto result = device.value()->RecreateApplicationArea(access_id, data);
631 IPC::ResponseBuilder rb{ctx, 2};
632 rb.Push(result);
633}
634
635std::optional<std::shared_ptr<NfpDevice>> IUser::GetNfpDevice(u64 handle) {
636 for (auto& device : devices) {
637 if (device->GetHandle() == handle) {
638 return device;
639 }
640 }
641 return std::nullopt;
642}
17 643
18} // namespace Service::NFP 644} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_user.h b/src/core/hle/service/nfp/nfp_user.h
index 519ff56ee..68c60ae82 100644
--- a/src/core/hle/service/nfp/nfp_user.h
+++ b/src/core/hle/service/nfp/nfp_user.h
@@ -3,14 +3,52 @@
3 3
4#pragma once 4#pragma once
5 5
6#include "core/hle/service/kernel_helpers.h"
6#include "core/hle/service/nfp/nfp.h" 7#include "core/hle/service/nfp/nfp.h"
8#include "core/hle/service/nfp/nfp_types.h"
7 9
8namespace Service::NFP { 10namespace Service::NFP {
11class NfpDevice;
9 12
10class NFP_User final : public Module::Interface { 13class IUser final : public ServiceFramework<IUser> {
11public: 14public:
12 explicit NFP_User(std::shared_ptr<Module> module_, Core::System& system_); 15 explicit IUser(Core::System& system_);
13 ~NFP_User() override; 16
17private:
18 void Initialize(Kernel::HLERequestContext& ctx);
19 void Finalize(Kernel::HLERequestContext& ctx);
20 void ListDevices(Kernel::HLERequestContext& ctx);
21 void StartDetection(Kernel::HLERequestContext& ctx);
22 void StopDetection(Kernel::HLERequestContext& ctx);
23 void Mount(Kernel::HLERequestContext& ctx);
24 void Unmount(Kernel::HLERequestContext& ctx);
25 void OpenApplicationArea(Kernel::HLERequestContext& ctx);
26 void GetApplicationArea(Kernel::HLERequestContext& ctx);
27 void SetApplicationArea(Kernel::HLERequestContext& ctx);
28 void Flush(Kernel::HLERequestContext& ctx);
29 void Restore(Kernel::HLERequestContext& ctx);
30 void CreateApplicationArea(Kernel::HLERequestContext& ctx);
31 void GetTagInfo(Kernel::HLERequestContext& ctx);
32 void GetRegisterInfo(Kernel::HLERequestContext& ctx);
33 void GetCommonInfo(Kernel::HLERequestContext& ctx);
34 void GetModelInfo(Kernel::HLERequestContext& ctx);
35 void AttachActivateEvent(Kernel::HLERequestContext& ctx);
36 void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
37 void GetState(Kernel::HLERequestContext& ctx);
38 void GetDeviceState(Kernel::HLERequestContext& ctx);
39 void GetNpadId(Kernel::HLERequestContext& ctx);
40 void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
41 void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
42 void RecreateApplicationArea(Kernel::HLERequestContext& ctx);
43
44 std::optional<std::shared_ptr<NfpDevice>> GetNfpDevice(u64 handle);
45
46 KernelHelpers::ServiceContext service_context;
47
48 std::array<std::shared_ptr<NfpDevice>, 10> devices{};
49
50 State state{State::NonInitialized};
51 Kernel::KEvent* availability_change_event;
14}; 52};
15 53
16} // namespace Service::NFP 54} // namespace Service::NFP
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp
index 9b382bf56..93057e800 100644
--- a/src/core/hle/service/nvflinger/nvflinger.cpp
+++ b/src/core/hle/service/nvflinger/nvflinger.cpp
@@ -22,6 +22,7 @@
22#include "core/hle/service/nvflinger/ui/graphic_buffer.h" 22#include "core/hle/service/nvflinger/ui/graphic_buffer.h"
23#include "core/hle/service/vi/display/vi_display.h" 23#include "core/hle/service/vi/display/vi_display.h"
24#include "core/hle/service/vi/layer/vi_layer.h" 24#include "core/hle/service/vi/layer/vi_layer.h"
25#include "core/hle/service/vi/vi_results.h"
25#include "video_core/gpu.h" 26#include "video_core/gpu.h"
26 27
27namespace Service::NVFlinger { 28namespace Service::NVFlinger {
@@ -163,15 +164,15 @@ std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) {
163 return layer->GetBinderId(); 164 return layer->GetBinderId();
164} 165}
165 166
166Kernel::KReadableEvent* NVFlinger::FindVsyncEvent(u64 display_id) { 167ResultVal<Kernel::KReadableEvent*> NVFlinger::FindVsyncEvent(u64 display_id) {
167 const auto lock_guard = Lock(); 168 const auto lock_guard = Lock();
168 auto* const display = FindDisplay(display_id); 169 auto* const display = FindDisplay(display_id);
169 170
170 if (display == nullptr) { 171 if (display == nullptr) {
171 return nullptr; 172 return VI::ResultNotFound;
172 } 173 }
173 174
174 return &display->GetVSyncEvent(); 175 return display->GetVSyncEvent();
175} 176}
176 177
177VI::Display* NVFlinger::FindDisplay(u64 display_id) { 178VI::Display* NVFlinger::FindDisplay(u64 display_id) {
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h
index 044ac6ac8..3bbe5d92b 100644
--- a/src/core/hle/service/nvflinger/nvflinger.h
+++ b/src/core/hle/service/nvflinger/nvflinger.h
@@ -11,6 +11,7 @@
11#include <vector> 11#include <vector>
12 12
13#include "common/common_types.h" 13#include "common/common_types.h"
14#include "core/hle/result.h"
14#include "core/hle/service/kernel_helpers.h" 15#include "core/hle/service/kernel_helpers.h"
15 16
16namespace Common { 17namespace Common {
@@ -71,8 +72,9 @@ public:
71 72
72 /// Gets the vsync event for the specified display. 73 /// Gets the vsync event for the specified display.
73 /// 74 ///
74 /// If an invalid display ID is provided, then nullptr is returned. 75 /// If an invalid display ID is provided, then VI::ResultNotFound is returned.
75 [[nodiscard]] Kernel::KReadableEvent* FindVsyncEvent(u64 display_id); 76 /// If the vsync event has already been retrieved, then VI::ResultPermissionDenied is returned.
77 [[nodiscard]] ResultVal<Kernel::KReadableEvent*> FindVsyncEvent(u64 display_id);
76 78
77 /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when 79 /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
78 /// finished. 80 /// finished.
diff --git a/src/core/hle/service/vi/display/vi_display.cpp b/src/core/hle/service/vi/display/vi_display.cpp
index b34febb50..aa49aa775 100644
--- a/src/core/hle/service/vi/display/vi_display.cpp
+++ b/src/core/hle/service/vi/display/vi_display.cpp
@@ -19,6 +19,7 @@
19#include "core/hle/service/nvflinger/hos_binder_driver_server.h" 19#include "core/hle/service/nvflinger/hos_binder_driver_server.h"
20#include "core/hle/service/vi/display/vi_display.h" 20#include "core/hle/service/vi/display/vi_display.h"
21#include "core/hle/service/vi/layer/vi_layer.h" 21#include "core/hle/service/vi/layer/vi_layer.h"
22#include "core/hle/service/vi/vi_results.h"
22 23
23namespace Service::VI { 24namespace Service::VI {
24 25
@@ -55,8 +56,18 @@ const Layer& Display::GetLayer(std::size_t index) const {
55 return *layers.at(index); 56 return *layers.at(index);
56} 57}
57 58
58Kernel::KReadableEvent& Display::GetVSyncEvent() { 59ResultVal<Kernel::KReadableEvent*> Display::GetVSyncEvent() {
59 return vsync_event->GetReadableEvent(); 60 if (got_vsync_event) {
61 return ResultPermissionDenied;
62 }
63
64 got_vsync_event = true;
65
66 return GetVSyncEventUnchecked();
67}
68
69Kernel::KReadableEvent* Display::GetVSyncEventUnchecked() {
70 return &vsync_event->GetReadableEvent();
60} 71}
61 72
62void Display::SignalVSyncEvent() { 73void Display::SignalVSyncEvent() {
diff --git a/src/core/hle/service/vi/display/vi_display.h b/src/core/hle/service/vi/display/vi_display.h
index 3838bb599..8dbb0ef80 100644
--- a/src/core/hle/service/vi/display/vi_display.h
+++ b/src/core/hle/service/vi/display/vi_display.h
@@ -9,6 +9,7 @@
9 9
10#include "common/common_funcs.h" 10#include "common/common_funcs.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "core/hle/result.h"
12 13
13namespace Kernel { 14namespace Kernel {
14class KEvent; 15class KEvent;
@@ -73,8 +74,16 @@ public:
73 return layers.size(); 74 return layers.size();
74 } 75 }
75 76
76 /// Gets the readable vsync event. 77 /**
77 Kernel::KReadableEvent& GetVSyncEvent(); 78 * Gets the internal vsync event.
79 *
80 * @returns The internal Vsync event if it has not yet been retrieved,
81 * VI::ResultPermissionDenied otherwise.
82 */
83 [[nodiscard]] ResultVal<Kernel::KReadableEvent*> GetVSyncEvent();
84
85 /// Gets the internal vsync event.
86 Kernel::KReadableEvent* GetVSyncEventUnchecked();
78 87
79 /// Signals the internal vsync event. 88 /// Signals the internal vsync event.
80 void SignalVSyncEvent(); 89 void SignalVSyncEvent();
@@ -118,6 +127,7 @@ private:
118 127
119 std::vector<std::unique_ptr<Layer>> layers; 128 std::vector<std::unique_ptr<Layer>> layers;
120 Kernel::KEvent* vsync_event{}; 129 Kernel::KEvent* vsync_event{};
130 bool got_vsync_event{false};
121}; 131};
122 132
123} // namespace Service::VI 133} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 546879648..f083811ec 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -29,16 +29,12 @@
29#include "core/hle/service/service.h" 29#include "core/hle/service/service.h"
30#include "core/hle/service/vi/vi.h" 30#include "core/hle/service/vi/vi.h"
31#include "core/hle/service/vi/vi_m.h" 31#include "core/hle/service/vi/vi_m.h"
32#include "core/hle/service/vi/vi_results.h"
32#include "core/hle/service/vi/vi_s.h" 33#include "core/hle/service/vi/vi_s.h"
33#include "core/hle/service/vi/vi_u.h" 34#include "core/hle/service/vi/vi_u.h"
34 35
35namespace Service::VI { 36namespace Service::VI {
36 37
37constexpr Result ERR_OPERATION_FAILED{ErrorModule::VI, 1};
38constexpr Result ERR_PERMISSION_DENIED{ErrorModule::VI, 5};
39constexpr Result ERR_UNSUPPORTED{ErrorModule::VI, 6};
40constexpr Result ERR_NOT_FOUND{ErrorModule::VI, 7};
41
42struct DisplayInfo { 38struct DisplayInfo {
43 /// The name of this particular display. 39 /// The name of this particular display.
44 char display_name[0x40]{"Default"}; 40 char display_name[0x40]{"Default"};
@@ -348,7 +344,7 @@ private:
348 if (!layer_id) { 344 if (!layer_id) {
349 LOG_ERROR(Service_VI, "Layer not found! display=0x{:016X}", display); 345 LOG_ERROR(Service_VI, "Layer not found! display=0x{:016X}", display);
350 IPC::ResponseBuilder rb{ctx, 2}; 346 IPC::ResponseBuilder rb{ctx, 2};
351 rb.Push(ERR_NOT_FOUND); 347 rb.Push(ResultNotFound);
352 return; 348 return;
353 } 349 }
354 350
@@ -498,7 +494,7 @@ private:
498 if (!display_id) { 494 if (!display_id) {
499 LOG_ERROR(Service_VI, "Display not found! display_name={}", name); 495 LOG_ERROR(Service_VI, "Display not found! display_name={}", name);
500 IPC::ResponseBuilder rb{ctx, 2}; 496 IPC::ResponseBuilder rb{ctx, 2};
501 rb.Push(ERR_NOT_FOUND); 497 rb.Push(ResultNotFound);
502 return; 498 return;
503 } 499 }
504 500
@@ -554,14 +550,14 @@ private:
554 550
555 if (scaling_mode > NintendoScaleMode::PreserveAspectRatio) { 551 if (scaling_mode > NintendoScaleMode::PreserveAspectRatio) {
556 LOG_ERROR(Service_VI, "Invalid scaling mode provided."); 552 LOG_ERROR(Service_VI, "Invalid scaling mode provided.");
557 rb.Push(ERR_OPERATION_FAILED); 553 rb.Push(ResultOperationFailed);
558 return; 554 return;
559 } 555 }
560 556
561 if (scaling_mode != NintendoScaleMode::ScaleToWindow && 557 if (scaling_mode != NintendoScaleMode::ScaleToWindow &&
562 scaling_mode != NintendoScaleMode::PreserveAspectRatio) { 558 scaling_mode != NintendoScaleMode::PreserveAspectRatio) {
563 LOG_ERROR(Service_VI, "Unsupported scaling mode supplied."); 559 LOG_ERROR(Service_VI, "Unsupported scaling mode supplied.");
564 rb.Push(ERR_UNSUPPORTED); 560 rb.Push(ResultNotSupported);
565 return; 561 return;
566 } 562 }
567 563
@@ -594,7 +590,7 @@ private:
594 if (!display_id) { 590 if (!display_id) {
595 LOG_ERROR(Service_VI, "Layer not found! layer_id={}", layer_id); 591 LOG_ERROR(Service_VI, "Layer not found! layer_id={}", layer_id);
596 IPC::ResponseBuilder rb{ctx, 2}; 592 IPC::ResponseBuilder rb{ctx, 2};
597 rb.Push(ERR_NOT_FOUND); 593 rb.Push(ResultNotFound);
598 return; 594 return;
599 } 595 }
600 596
@@ -602,7 +598,7 @@ private:
602 if (!buffer_queue_id) { 598 if (!buffer_queue_id) {
603 LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", *display_id); 599 LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", *display_id);
604 IPC::ResponseBuilder rb{ctx, 2}; 600 IPC::ResponseBuilder rb{ctx, 2};
605 rb.Push(ERR_NOT_FOUND); 601 rb.Push(ResultNotFound);
606 return; 602 return;
607 } 603 }
608 604
@@ -640,7 +636,7 @@ private:
640 if (!layer_id) { 636 if (!layer_id) {
641 LOG_ERROR(Service_VI, "Layer not found! display_id={}", display_id); 637 LOG_ERROR(Service_VI, "Layer not found! display_id={}", display_id);
642 IPC::ResponseBuilder rb{ctx, 2}; 638 IPC::ResponseBuilder rb{ctx, 2};
643 rb.Push(ERR_NOT_FOUND); 639 rb.Push(ResultNotFound);
644 return; 640 return;
645 } 641 }
646 642
@@ -648,7 +644,7 @@ private:
648 if (!buffer_queue_id) { 644 if (!buffer_queue_id) {
649 LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", display_id); 645 LOG_ERROR(Service_VI, "Buffer queue id not found! display_id={}", display_id);
650 IPC::ResponseBuilder rb{ctx, 2}; 646 IPC::ResponseBuilder rb{ctx, 2};
651 rb.Push(ERR_NOT_FOUND); 647 rb.Push(ResultNotFound);
652 return; 648 return;
653 } 649 }
654 650
@@ -675,19 +671,23 @@ private:
675 IPC::RequestParser rp{ctx}; 671 IPC::RequestParser rp{ctx};
676 const u64 display_id = rp.Pop<u64>(); 672 const u64 display_id = rp.Pop<u64>();
677 673
678 LOG_WARNING(Service_VI, "(STUBBED) called. display_id=0x{:016X}", display_id); 674 LOG_DEBUG(Service_VI, "called. display_id={}", display_id);
679 675
680 const auto vsync_event = nv_flinger.FindVsyncEvent(display_id); 676 const auto vsync_event = nv_flinger.FindVsyncEvent(display_id);
681 if (!vsync_event) { 677 if (vsync_event.Failed()) {
682 LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id); 678 const auto result = vsync_event.Code();
679 if (result == ResultNotFound) {
680 LOG_ERROR(Service_VI, "Vsync event was not found for display_id={}", display_id);
681 }
682
683 IPC::ResponseBuilder rb{ctx, 2}; 683 IPC::ResponseBuilder rb{ctx, 2};
684 rb.Push(ERR_NOT_FOUND); 684 rb.Push(result);
685 return; 685 return;
686 } 686 }
687 687
688 IPC::ResponseBuilder rb{ctx, 2, 1}; 688 IPC::ResponseBuilder rb{ctx, 2, 1};
689 rb.Push(ResultSuccess); 689 rb.Push(ResultSuccess);
690 rb.PushCopyObjects(vsync_event); 690 rb.PushCopyObjects(*vsync_event);
691 } 691 }
692 692
693 void ConvertScalingMode(Kernel::HLERequestContext& ctx) { 693 void ConvertScalingMode(Kernel::HLERequestContext& ctx) {
@@ -764,7 +764,7 @@ private:
764 return ConvertedScaleMode::PreserveAspectRatio; 764 return ConvertedScaleMode::PreserveAspectRatio;
765 default: 765 default:
766 LOG_ERROR(Service_VI, "Invalid scaling mode specified, mode={}", mode); 766 LOG_ERROR(Service_VI, "Invalid scaling mode specified, mode={}", mode);
767 return ERR_OPERATION_FAILED; 767 return ResultOperationFailed;
768 } 768 }
769 } 769 }
770 770
@@ -794,7 +794,7 @@ void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx, Core::System&
794 if (!IsValidServiceAccess(permission, policy)) { 794 if (!IsValidServiceAccess(permission, policy)) {
795 LOG_ERROR(Service_VI, "Permission denied for policy {}", policy); 795 LOG_ERROR(Service_VI, "Permission denied for policy {}", policy);
796 IPC::ResponseBuilder rb{ctx, 2}; 796 IPC::ResponseBuilder rb{ctx, 2};
797 rb.Push(ERR_PERMISSION_DENIED); 797 rb.Push(ResultPermissionDenied);
798 return; 798 return;
799 } 799 }
800 800
diff --git a/src/core/hle/service/vi/vi_results.h b/src/core/hle/service/vi/vi_results.h
new file mode 100644
index 000000000..a46c247d2
--- /dev/null
+++ b/src/core/hle/service/vi/vi_results.h
@@ -0,0 +1,13 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4#include "core/hle/result.h"
5
6namespace Service::VI {
7
8constexpr Result ResultOperationFailed{ErrorModule::VI, 1};
9constexpr Result ResultPermissionDenied{ErrorModule::VI, 5};
10constexpr Result ResultNotSupported{ErrorModule::VI, 6};
11constexpr Result ResultNotFound{ErrorModule::VI, 7};
12
13} // namespace Service::VI
diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp
index 0f0a66160..057fd3661 100644
--- a/src/core/internal_network/network_interface.cpp
+++ b/src/core/internal_network/network_interface.cpp
@@ -188,7 +188,7 @@ std::vector<NetworkInterface> GetAvailableNetworkInterfaces() {
188std::optional<NetworkInterface> GetSelectedNetworkInterface() { 188std::optional<NetworkInterface> GetSelectedNetworkInterface() {
189 const auto& selected_network_interface = Settings::values.network_interface.GetValue(); 189 const auto& selected_network_interface = Settings::values.network_interface.GetValue();
190 const auto network_interfaces = Network::GetAvailableNetworkInterfaces(); 190 const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
191 if (network_interfaces.size() == 0) { 191 if (network_interfaces.empty()) {
192 LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces"); 192 LOG_ERROR(Network, "GetAvailableNetworkInterfaces returned no interfaces");
193 return std::nullopt; 193 return std::nullopt;
194 } 194 }
@@ -206,4 +206,14 @@ std::optional<NetworkInterface> GetSelectedNetworkInterface() {
206 return *res; 206 return *res;
207} 207}
208 208
209void SelectFirstNetworkInterface() {
210 const auto network_interfaces = Network::GetAvailableNetworkInterfaces();
211
212 if (network_interfaces.empty()) {
213 return;
214 }
215
216 Settings::values.network_interface.SetValue(network_interfaces[0].name);
217}
218
209} // namespace Network 219} // namespace Network
diff --git a/src/core/internal_network/network_interface.h b/src/core/internal_network/network_interface.h
index 9b98b6b42..175e61b1f 100644
--- a/src/core/internal_network/network_interface.h
+++ b/src/core/internal_network/network_interface.h
@@ -24,5 +24,6 @@ struct NetworkInterface {
24 24
25std::vector<NetworkInterface> GetAvailableNetworkInterfaces(); 25std::vector<NetworkInterface> GetAvailableNetworkInterfaces();
26std::optional<NetworkInterface> GetSelectedNetworkInterface(); 26std::optional<NetworkInterface> GetSelectedNetworkInterface();
27void SelectFirstNetworkInterface();
27 28
28} // namespace Network 29} // namespace Network
diff --git a/src/core/internal_network/socket_proxy.cpp b/src/core/internal_network/socket_proxy.cpp
index 0c746bd82..7d5d37bbc 100644
--- a/src/core/internal_network/socket_proxy.cpp
+++ b/src/core/internal_network/socket_proxy.cpp
@@ -6,6 +6,7 @@
6 6
7#include "common/assert.h" 7#include "common/assert.h"
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "common/zstd_compression.h"
9#include "core/internal_network/network.h" 10#include "core/internal_network/network.h"
10#include "core/internal_network/network_interface.h" 11#include "core/internal_network/network_interface.h"
11#include "core/internal_network/socket_proxy.h" 12#include "core/internal_network/socket_proxy.h"
@@ -32,8 +33,11 @@ void ProxySocket::HandleProxyPacket(const ProxyPacket& packet) {
32 return; 33 return;
33 } 34 }
34 35
36 auto decompressed = packet;
37 decompressed.data = Common::Compression::DecompressDataZSTD(packet.data);
38
35 std::lock_guard guard(packets_mutex); 39 std::lock_guard guard(packets_mutex);
36 received_packets.push(packet); 40 received_packets.push(decompressed);
37} 41}
38 42
39template <typename T> 43template <typename T>
@@ -185,6 +189,8 @@ std::pair<s32, Errno> ProxySocket::Send(const std::vector<u8>& message, int flag
185void ProxySocket::SendPacket(ProxyPacket& packet) { 189void ProxySocket::SendPacket(ProxyPacket& packet) {
186 if (auto room_member = room_network.GetRoomMember().lock()) { 190 if (auto room_member = room_network.GetRoomMember().lock()) {
187 if (room_member->IsConnected()) { 191 if (room_member->IsConnected()) {
192 packet.data = Common::Compression::CompressDataZSTDDefault(packet.data.data(),
193 packet.data.size());
188 room_member->SendProxyPacket(packet); 194 room_member->SendProxyPacket(packet);
189 } 195 }
190 } 196 }
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 104d16efa..f24474ed8 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -244,6 +244,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(Core::System& system, FileSys::V
244 244
245std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file, 245std::unique_ptr<AppLoader> GetLoader(Core::System& system, FileSys::VirtualFile file,
246 u64 program_id, std::size_t program_index) { 246 u64 program_id, std::size_t program_index) {
247 if (!file) {
248 return nullptr;
249 }
250
247 FileType type = IdentifyFile(file); 251 FileType type = IdentifyFile(file);
248 const FileType filename_type = GuessFromFilename(file->GetName()); 252 const FileType filename_type = GuessFromFilename(file->GetName());
249 253
diff --git a/src/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp
index 7b6deba41..359891883 100644
--- a/src/dedicated_room/yuzu_room.cpp
+++ b/src/dedicated_room/yuzu_room.cpp
@@ -76,7 +76,18 @@ static constexpr char BanListMagic[] = "YuzuRoom-BanList-1";
76static constexpr char token_delimiter{':'}; 76static constexpr char token_delimiter{':'};
77 77
78static void PadToken(std::string& token) { 78static void PadToken(std::string& token) {
79 while (token.size() % 4 != 0) { 79 std::size_t outlen = 0;
80
81 std::array<unsigned char, 512> output{};
82 std::array<unsigned char, 2048> roundtrip{};
83 for (size_t i = 0; i < 3; i++) {
84 mbedtls_base64_decode(output.data(), output.size(), &outlen,
85 reinterpret_cast<const unsigned char*>(token.c_str()),
86 token.length());
87 mbedtls_base64_encode(roundtrip.data(), roundtrip.size(), &outlen, output.data(), outlen);
88 if (memcmp(roundtrip.data(), token.data(), token.size()) == 0) {
89 break;
90 }
80 token.push_back('='); 91 token.push_back('=');
81 } 92 }
82} 93}
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 4b91b88ce..2cf9eb97f 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -18,6 +18,8 @@ add_library(input_common STATIC
18 drivers/touch_screen.h 18 drivers/touch_screen.h
19 drivers/udp_client.cpp 19 drivers/udp_client.cpp
20 drivers/udp_client.h 20 drivers/udp_client.h
21 drivers/virtual_amiibo.cpp
22 drivers/virtual_amiibo.h
21 helpers/stick_from_buttons.cpp 23 helpers/stick_from_buttons.cpp
22 helpers/stick_from_buttons.h 24 helpers/stick_from_buttons.h
23 helpers/touch_from_buttons.cpp 25 helpers/touch_from_buttons.cpp
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp
new file mode 100644
index 000000000..0cd5129da
--- /dev/null
+++ b/src/input_common/drivers/virtual_amiibo.cpp
@@ -0,0 +1,101 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#include <cstring>
5#include <fmt/format.h>
6
7#include "common/fs/file.h"
8#include "common/fs/fs.h"
9#include "common/fs/path_util.h"
10#include "common/logging/log.h"
11#include "common/settings.h"
12#include "input_common/drivers/virtual_amiibo.h"
13
14namespace InputCommon {
15constexpr PadIdentifier identifier = {
16 .guid = Common::UUID{},
17 .port = 0,
18 .pad = 0,
19};
20
21VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move(input_engine_)) {}
22
23VirtualAmiibo::~VirtualAmiibo() = default;
24
25Common::Input::PollingError VirtualAmiibo::SetPollingMode(
26 [[maybe_unused]] const PadIdentifier& identifier_,
27 const Common::Input::PollingMode polling_mode_) {
28 polling_mode = polling_mode_;
29
30 if (polling_mode == Common::Input::PollingMode::NFC) {
31 if (state == State::Initialized) {
32 state = State::WaitingForAmiibo;
33 }
34 } else {
35 if (state == State::AmiiboIsOpen) {
36 CloseAmiibo();
37 }
38 }
39
40 return Common::Input::PollingError::None;
41}
42
43Common::Input::NfcState VirtualAmiibo::SupportsNfc(
44 [[maybe_unused]] const PadIdentifier& identifier_) const {
45 return Common::Input::NfcState::Success;
46}
47
48Common::Input::NfcState VirtualAmiibo::WriteNfcData(
49 [[maybe_unused]] const PadIdentifier& identifier_, const std::vector<u8>& data) {
50 const Common::FS::IOFile amiibo_file{file_path, Common::FS::FileAccessMode::ReadWrite,
51 Common::FS::FileType::BinaryFile};
52
53 if (!amiibo_file.IsOpen()) {
54 LOG_ERROR(Core, "Amiibo is already on use");
55 return Common::Input::NfcState::WriteFailed;
56 }
57
58 if (!amiibo_file.Write(data)) {
59 LOG_ERROR(Service_NFP, "Error writting to file");
60 return Common::Input::NfcState::WriteFailed;
61 }
62
63 return Common::Input::NfcState::Success;
64}
65
66VirtualAmiibo::State VirtualAmiibo::GetCurrentState() const {
67 return state;
68}
69
70VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) {
71 const Common::FS::IOFile amiibo_file{filename, Common::FS::FileAccessMode::Read,
72 Common::FS::FileType::BinaryFile};
73
74 if (state != State::WaitingForAmiibo) {
75 return Info::WrongDeviceState;
76 }
77
78 if (!amiibo_file.IsOpen()) {
79 return Info::UnableToLoad;
80 }
81
82 amiibo_data.resize(amiibo_size);
83
84 if (amiibo_file.Read(amiibo_data) < amiibo_size_without_password) {
85 return Info::NotAnAmiibo;
86 }
87
88 file_path = filename;
89 state = State::AmiiboIsOpen;
90 SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data});
91 return Info::Success;
92}
93
94VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() {
95 state = polling_mode == Common::Input::PollingMode::NFC ? State::WaitingForAmiibo
96 : State::Initialized;
97 SetNfc(identifier, {Common::Input::NfcState::AmiiboRemoved, {}});
98 return Info::Success;
99}
100
101} // namespace InputCommon
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h
new file mode 100644
index 000000000..9eac07544
--- /dev/null
+++ b/src/input_common/drivers/virtual_amiibo.h
@@ -0,0 +1,61 @@
1// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <array>
7#include <string>
8#include <vector>
9
10#include "common/common_types.h"
11#include "input_common/input_engine.h"
12
13namespace Common::FS {
14class IOFile;
15}
16
17namespace InputCommon {
18
19class VirtualAmiibo final : public InputEngine {
20public:
21 enum class State {
22 Initialized,
23 WaitingForAmiibo,
24 AmiiboIsOpen,
25 };
26
27 enum class Info {
28 Success,
29 UnableToLoad,
30 NotAnAmiibo,
31 WrongDeviceState,
32 Unknown,
33 };
34
35 explicit VirtualAmiibo(std::string input_engine_);
36 ~VirtualAmiibo() override;
37
38 // Sets polling mode to a controller
39 Common::Input::PollingError SetPollingMode(
40 const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;
41
42 Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
43
44 Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
45 const std::vector<u8>& data) override;
46
47 State GetCurrentState() const;
48
49 Info LoadAmiibo(const std::string& amiibo_file);
50 Info CloseAmiibo();
51
52private:
53 static constexpr std::size_t amiibo_size = 0x21C;
54 static constexpr std::size_t amiibo_size_without_password = amiibo_size - 0x8;
55
56 std::string file_path{};
57 State state{State::Initialized};
58 std::vector<u8> amiibo_data;
59 Common::Input::PollingMode polling_mode{Common::Input::PollingMode::Pasive};
60};
61} // namespace InputCommon
diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp
index 6ede0e4b0..61cfd0911 100644
--- a/src/input_common/input_engine.cpp
+++ b/src/input_common/input_engine.cpp
@@ -102,6 +102,17 @@ void InputEngine::SetCamera(const PadIdentifier& identifier,
102 TriggerOnCameraChange(identifier, value); 102 TriggerOnCameraChange(identifier, value);
103} 103}
104 104
105void InputEngine::SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value) {
106 {
107 std::scoped_lock lock{mutex};
108 ControllerData& controller = controller_list.at(identifier);
109 if (!configuring) {
110 controller.nfc = value;
111 }
112 }
113 TriggerOnNfcChange(identifier, value);
114}
115
105bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const { 116bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const {
106 std::scoped_lock lock{mutex}; 117 std::scoped_lock lock{mutex};
107 const auto controller_iter = controller_list.find(identifier); 118 const auto controller_iter = controller_list.find(identifier);
@@ -189,6 +200,18 @@ Common::Input::CameraStatus InputEngine::GetCamera(const PadIdentifier& identifi
189 return controller.camera; 200 return controller.camera;
190} 201}
191 202
203Common::Input::NfcStatus InputEngine::GetNfc(const PadIdentifier& identifier) const {
204 std::scoped_lock lock{mutex};
205 const auto controller_iter = controller_list.find(identifier);
206 if (controller_iter == controller_list.cend()) {
207 LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
208 identifier.pad, identifier.port);
209 return {};
210 }
211 const ControllerData& controller = controller_iter->second;
212 return controller.nfc;
213}
214
192void InputEngine::ResetButtonState() { 215void InputEngine::ResetButtonState() {
193 for (const auto& controller : controller_list) { 216 for (const auto& controller : controller_list) {
194 for (const auto& button : controller.second.buttons) { 217 for (const auto& button : controller.second.buttons) {
@@ -355,6 +378,20 @@ void InputEngine::TriggerOnCameraChange(const PadIdentifier& identifier,
355 } 378 }
356} 379}
357 380
381void InputEngine::TriggerOnNfcChange(const PadIdentifier& identifier,
382 [[maybe_unused]] const Common::Input::NfcStatus& value) {
383 std::scoped_lock lock{mutex_callback};
384 for (const auto& poller_pair : callback_list) {
385 const InputIdentifier& poller = poller_pair.second;
386 if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Nfc, 0)) {
387 continue;
388 }
389 if (poller.callback.on_change) {
390 poller.callback.on_change();
391 }
392 }
393}
394
358bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier, 395bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier,
359 const PadIdentifier& identifier, EngineInputType type, 396 const PadIdentifier& identifier, EngineInputType type,
360 int index) const { 397 int index) const {
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
index f6b3c4610..cfbdb26bd 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -42,6 +42,7 @@ enum class EngineInputType {
42 Camera, 42 Camera,
43 HatButton, 43 HatButton,
44 Motion, 44 Motion,
45 Nfc,
45}; 46};
46 47
47namespace std { 48namespace std {
@@ -127,6 +128,18 @@ public:
127 return Common::Input::CameraError::NotSupported; 128 return Common::Input::CameraError::NotSupported;
128 } 129 }
129 130
131 // Request nfc data from a controller
132 virtual Common::Input::NfcState SupportsNfc(
133 [[maybe_unused]] const PadIdentifier& identifier) const {
134 return Common::Input::NfcState::NotSupported;
135 }
136
137 // Writes data to an nfc tag
138 virtual Common::Input::NfcState WriteNfcData([[maybe_unused]] const PadIdentifier& identifier,
139 [[maybe_unused]] const std::vector<u8>& data) {
140 return Common::Input::NfcState::NotSupported;
141 }
142
130 // Returns the engine name 143 // Returns the engine name
131 [[nodiscard]] const std::string& GetEngineName() const; 144 [[nodiscard]] const std::string& GetEngineName() const;
132 145
@@ -183,6 +196,7 @@ public:
183 Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const; 196 Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const;
184 BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const; 197 BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
185 Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const; 198 Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const;
199 Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const;
186 200
187 int SetCallback(InputIdentifier input_identifier); 201 int SetCallback(InputIdentifier input_identifier);
188 void SetMappingCallback(MappingCallback callback); 202 void SetMappingCallback(MappingCallback callback);
@@ -195,6 +209,7 @@ protected:
195 void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value); 209 void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
196 void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value); 210 void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);
197 void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value); 211 void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value);
212 void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);
198 213
199 virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const { 214 virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
200 return "Unknown"; 215 return "Unknown";
@@ -208,6 +223,7 @@ private:
208 std::unordered_map<int, BasicMotion> motions; 223 std::unordered_map<int, BasicMotion> motions;
209 Common::Input::BatteryLevel battery{}; 224 Common::Input::BatteryLevel battery{};
210 Common::Input::CameraStatus camera{}; 225 Common::Input::CameraStatus camera{};
226 Common::Input::NfcStatus nfc{};
211 }; 227 };
212 228
213 void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value); 229 void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
@@ -218,6 +234,7 @@ private:
218 const BasicMotion& value); 234 const BasicMotion& value);
219 void TriggerOnCameraChange(const PadIdentifier& identifier, 235 void TriggerOnCameraChange(const PadIdentifier& identifier,
220 const Common::Input::CameraStatus& value); 236 const Common::Input::CameraStatus& value);
237 void TriggerOnNfcChange(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);
221 238
222 bool IsInputIdentifierEqual(const InputIdentifier& input_identifier, 239 bool IsInputIdentifierEqual(const InputIdentifier& input_identifier,
223 const PadIdentifier& identifier, EngineInputType type, 240 const PadIdentifier& identifier, EngineInputType type,
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index ffb9b945e..75705b67e 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -705,6 +705,47 @@ private:
705 InputEngine* input_engine; 705 InputEngine* input_engine;
706}; 706};
707 707
708class InputFromNfc final : public Common::Input::InputDevice {
709public:
710 explicit InputFromNfc(PadIdentifier identifier_, InputEngine* input_engine_)
711 : identifier(identifier_), input_engine(input_engine_) {
712 UpdateCallback engine_callback{[this]() { OnChange(); }};
713 const InputIdentifier input_identifier{
714 .identifier = identifier,
715 .type = EngineInputType::Nfc,
716 .index = 0,
717 .callback = engine_callback,
718 };
719 callback_key = input_engine->SetCallback(input_identifier);
720 }
721
722 ~InputFromNfc() override {
723 input_engine->DeleteCallback(callback_key);
724 }
725
726 Common::Input::NfcStatus GetStatus() const {
727 return input_engine->GetNfc(identifier);
728 }
729
730 void ForceUpdate() override {
731 OnChange();
732 }
733
734 void OnChange() {
735 const Common::Input::CallbackStatus status{
736 .type = Common::Input::InputType::Nfc,
737 .nfc_status = GetStatus(),
738 };
739
740 TriggerOnChange(status);
741 }
742
743private:
744 const PadIdentifier identifier;
745 int callback_key;
746 InputEngine* input_engine;
747};
748
708class OutputFromIdentifier final : public Common::Input::OutputDevice { 749class OutputFromIdentifier final : public Common::Input::OutputDevice {
709public: 750public:
710 explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_) 751 explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
@@ -727,6 +768,14 @@ public:
727 return input_engine->SetCameraFormat(identifier, camera_format); 768 return input_engine->SetCameraFormat(identifier, camera_format);
728 } 769 }
729 770
771 Common::Input::NfcState SupportsNfc() const override {
772 return input_engine->SupportsNfc(identifier);
773 }
774
775 Common::Input::NfcState WriteNfcData(const std::vector<u8>& data) override {
776 return input_engine->WriteNfcData(identifier, data);
777 }
778
730private: 779private:
731 const PadIdentifier identifier; 780 const PadIdentifier identifier;
732 InputEngine* input_engine; 781 InputEngine* input_engine;
@@ -978,6 +1027,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateCameraDevice(
978 return std::make_unique<InputFromCamera>(identifier, input_engine.get()); 1027 return std::make_unique<InputFromCamera>(identifier, input_engine.get());
979} 1028}
980 1029
1030std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateNfcDevice(
1031 const Common::ParamPackage& params) {
1032 const PadIdentifier identifier = {
1033 .guid = Common::UUID{params.Get("guid", "")},
1034 .port = static_cast<std::size_t>(params.Get("port", 0)),
1035 .pad = static_cast<std::size_t>(params.Get("pad", 0)),
1036 };
1037
1038 input_engine->PreSetController(identifier);
1039 return std::make_unique<InputFromNfc>(identifier, input_engine.get());
1040}
1041
981InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_) 1042InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_)
982 : input_engine(std::move(input_engine_)) {} 1043 : input_engine(std::move(input_engine_)) {}
983 1044
@@ -989,6 +1050,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
989 if (params.Has("camera")) { 1050 if (params.Has("camera")) {
990 return CreateCameraDevice(params); 1051 return CreateCameraDevice(params);
991 } 1052 }
1053 if (params.Has("nfc")) {
1054 return CreateNfcDevice(params);
1055 }
992 if (params.Has("button") && params.Has("axis")) { 1056 if (params.Has("button") && params.Has("axis")) {
993 return CreateTriggerDevice(params); 1057 return CreateTriggerDevice(params);
994 } 1058 }
diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h
index 4410a8415..d7db13ce4 100644
--- a/src/input_common/input_poller.h
+++ b/src/input_common/input_poller.h
@@ -222,6 +222,16 @@ private:
222 std::unique_ptr<Common::Input::InputDevice> CreateCameraDevice( 222 std::unique_ptr<Common::Input::InputDevice> CreateCameraDevice(
223 const Common::ParamPackage& params); 223 const Common::ParamPackage& params);
224 224
225 /**
226 * Creates a nfc device from the parameters given.
227 * @param params contains parameters for creating the device:
228 * - "guid": text string for identifying controllers
229 * - "port": port of the connected device
230 * - "pad": slot of the connected controller
231 * @returns a unique input device with the parameters specified
232 */
233 std::unique_ptr<Common::Input::InputDevice> CreateNfcDevice(const Common::ParamPackage& params);
234
225 std::shared_ptr<InputEngine> input_engine; 235 std::shared_ptr<InputEngine> input_engine;
226}; 236};
227} // namespace InputCommon 237} // namespace InputCommon
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 75a57b9fc..b2064ef95 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -11,6 +11,7 @@
11#include "input_common/drivers/tas_input.h" 11#include "input_common/drivers/tas_input.h"
12#include "input_common/drivers/touch_screen.h" 12#include "input_common/drivers/touch_screen.h"
13#include "input_common/drivers/udp_client.h" 13#include "input_common/drivers/udp_client.h"
14#include "input_common/drivers/virtual_amiibo.h"
14#include "input_common/helpers/stick_from_buttons.h" 15#include "input_common/helpers/stick_from_buttons.h"
15#include "input_common/helpers/touch_from_buttons.h" 16#include "input_common/helpers/touch_from_buttons.h"
16#include "input_common/input_engine.h" 17#include "input_common/input_engine.h"
@@ -87,6 +88,15 @@ struct InputSubsystem::Impl {
87 Common::Input::RegisterFactory<Common::Input::OutputDevice>(camera->GetEngineName(), 88 Common::Input::RegisterFactory<Common::Input::OutputDevice>(camera->GetEngineName(),
88 camera_output_factory); 89 camera_output_factory);
89 90
91 virtual_amiibo = std::make_shared<VirtualAmiibo>("virtual_amiibo");
92 virtual_amiibo->SetMappingCallback(mapping_callback);
93 virtual_amiibo_input_factory = std::make_shared<InputFactory>(virtual_amiibo);
94 virtual_amiibo_output_factory = std::make_shared<OutputFactory>(virtual_amiibo);
95 Common::Input::RegisterFactory<Common::Input::InputDevice>(virtual_amiibo->GetEngineName(),
96 virtual_amiibo_input_factory);
97 Common::Input::RegisterFactory<Common::Input::OutputDevice>(virtual_amiibo->GetEngineName(),
98 virtual_amiibo_output_factory);
99
90#ifdef HAVE_SDL2 100#ifdef HAVE_SDL2
91 sdl = std::make_shared<SDLDriver>("sdl"); 101 sdl = std::make_shared<SDLDriver>("sdl");
92 sdl->SetMappingCallback(mapping_callback); 102 sdl->SetMappingCallback(mapping_callback);
@@ -327,6 +337,7 @@ struct InputSubsystem::Impl {
327 std::shared_ptr<TasInput::Tas> tas_input; 337 std::shared_ptr<TasInput::Tas> tas_input;
328 std::shared_ptr<CemuhookUDP::UDPClient> udp_client; 338 std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
329 std::shared_ptr<Camera> camera; 339 std::shared_ptr<Camera> camera;
340 std::shared_ptr<VirtualAmiibo> virtual_amiibo;
330 341
331 std::shared_ptr<InputFactory> keyboard_factory; 342 std::shared_ptr<InputFactory> keyboard_factory;
332 std::shared_ptr<InputFactory> mouse_factory; 343 std::shared_ptr<InputFactory> mouse_factory;
@@ -335,6 +346,7 @@ struct InputSubsystem::Impl {
335 std::shared_ptr<InputFactory> udp_client_input_factory; 346 std::shared_ptr<InputFactory> udp_client_input_factory;
336 std::shared_ptr<InputFactory> tas_input_factory; 347 std::shared_ptr<InputFactory> tas_input_factory;
337 std::shared_ptr<InputFactory> camera_input_factory; 348 std::shared_ptr<InputFactory> camera_input_factory;
349 std::shared_ptr<InputFactory> virtual_amiibo_input_factory;
338 350
339 std::shared_ptr<OutputFactory> keyboard_output_factory; 351 std::shared_ptr<OutputFactory> keyboard_output_factory;
340 std::shared_ptr<OutputFactory> mouse_output_factory; 352 std::shared_ptr<OutputFactory> mouse_output_factory;
@@ -342,6 +354,7 @@ struct InputSubsystem::Impl {
342 std::shared_ptr<OutputFactory> udp_client_output_factory; 354 std::shared_ptr<OutputFactory> udp_client_output_factory;
343 std::shared_ptr<OutputFactory> tas_output_factory; 355 std::shared_ptr<OutputFactory> tas_output_factory;
344 std::shared_ptr<OutputFactory> camera_output_factory; 356 std::shared_ptr<OutputFactory> camera_output_factory;
357 std::shared_ptr<OutputFactory> virtual_amiibo_output_factory;
345 358
346#ifdef HAVE_SDL2 359#ifdef HAVE_SDL2
347 std::shared_ptr<SDLDriver> sdl; 360 std::shared_ptr<SDLDriver> sdl;
@@ -402,6 +415,14 @@ const Camera* InputSubsystem::GetCamera() const {
402 return impl->camera.get(); 415 return impl->camera.get();
403} 416}
404 417
418VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() {
419 return impl->virtual_amiibo.get();
420}
421
422const VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() const {
423 return impl->virtual_amiibo.get();
424}
425
405std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { 426std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
406 return impl->GetInputDevices(); 427 return impl->GetInputDevices();
407} 428}
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 9a969e747..ced252383 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -33,6 +33,7 @@ class Camera;
33class Keyboard; 33class Keyboard;
34class Mouse; 34class Mouse;
35class TouchScreen; 35class TouchScreen;
36class VirtualAmiibo;
36struct MappingData; 37struct MappingData;
37} // namespace InputCommon 38} // namespace InputCommon
38 39
@@ -101,6 +102,12 @@ public:
101 /// Retrieves the underlying camera input device. 102 /// Retrieves the underlying camera input device.
102 [[nodiscard]] const Camera* GetCamera() const; 103 [[nodiscard]] const Camera* GetCamera() const;
103 104
105 /// Retrieves the underlying virtual amiibo input device.
106 [[nodiscard]] VirtualAmiibo* GetVirtualAmiibo();
107
108 /// Retrieves the underlying virtual amiibo input device.
109 [[nodiscard]] const VirtualAmiibo* GetVirtualAmiibo() const;
110
104 /** 111 /**
105 * Returns all available input devices that this Factory can create a new device with. 112 * Returns all available input devices that this Factory can create a new device with.
106 * Each returned ParamPackage should have a `display` field used for display, a `engine` field 113 * Each returned ParamPackage should have a `display` field used for display, a `engine` field
diff --git a/src/network/room.cpp b/src/network/room.cpp
index 8c63b255b..dc5dbce7f 100644
--- a/src/network/room.cpp
+++ b/src/network/room.cpp
@@ -212,6 +212,12 @@ public:
212 void HandleProxyPacket(const ENetEvent* event); 212 void HandleProxyPacket(const ENetEvent* event);
213 213
214 /** 214 /**
215 * Broadcasts this packet to all members except the sender.
216 * @param event The ENet event containing the data
217 */
218 void HandleLdnPacket(const ENetEvent* event);
219
220 /**
215 * Extracts a chat entry from a received ENet packet and adds it to the chat queue. 221 * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
216 * @param event The ENet event that was received. 222 * @param event The ENet event that was received.
217 */ 223 */
@@ -247,6 +253,9 @@ void Room::RoomImpl::ServerLoop() {
247 case IdProxyPacket: 253 case IdProxyPacket:
248 HandleProxyPacket(&event); 254 HandleProxyPacket(&event);
249 break; 255 break;
256 case IdLdnPacket:
257 HandleLdnPacket(&event);
258 break;
250 case IdChatMessage: 259 case IdChatMessage:
251 HandleChatPacket(&event); 260 HandleChatPacket(&event);
252 break; 261 break;
@@ -861,6 +870,60 @@ void Room::RoomImpl::HandleProxyPacket(const ENetEvent* event) {
861 enet_host_flush(server); 870 enet_host_flush(server);
862} 871}
863 872
873void Room::RoomImpl::HandleLdnPacket(const ENetEvent* event) {
874 Packet in_packet;
875 in_packet.Append(event->packet->data, event->packet->dataLength);
876
877 in_packet.IgnoreBytes(sizeof(u8)); // Message type
878
879 in_packet.IgnoreBytes(sizeof(u8)); // LAN packet type
880 in_packet.IgnoreBytes(sizeof(IPv4Address)); // Local IP
881
882 IPv4Address remote_ip;
883 in_packet.Read(remote_ip); // Remote IP
884
885 bool broadcast;
886 in_packet.Read(broadcast); // Broadcast
887
888 Packet out_packet;
889 out_packet.Append(event->packet->data, event->packet->dataLength);
890 ENetPacket* enet_packet = enet_packet_create(out_packet.GetData(), out_packet.GetDataSize(),
891 ENET_PACKET_FLAG_RELIABLE);
892
893 const auto& destination_address = remote_ip;
894 if (broadcast) { // Send the data to everyone except the sender
895 std::lock_guard lock(member_mutex);
896 bool sent_packet = false;
897 for (const auto& member : members) {
898 if (member.peer != event->peer) {
899 sent_packet = true;
900 enet_peer_send(member.peer, 0, enet_packet);
901 }
902 }
903
904 if (!sent_packet) {
905 enet_packet_destroy(enet_packet);
906 }
907 } else {
908 std::lock_guard lock(member_mutex);
909 auto member = std::find_if(members.begin(), members.end(),
910 [destination_address](const Member& member_entry) -> bool {
911 return member_entry.fake_ip == destination_address;
912 });
913 if (member != members.end()) {
914 enet_peer_send(member->peer, 0, enet_packet);
915 } else {
916 LOG_ERROR(Network,
917 "Attempting to send to unknown IP address: "
918 "{}.{}.{}.{}",
919 destination_address[0], destination_address[1], destination_address[2],
920 destination_address[3]);
921 enet_packet_destroy(enet_packet);
922 }
923 }
924 enet_host_flush(server);
925}
926
864void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) { 927void Room::RoomImpl::HandleChatPacket(const ENetEvent* event) {
865 Packet in_packet; 928 Packet in_packet;
866 in_packet.Append(event->packet->data, event->packet->dataLength); 929 in_packet.Append(event->packet->data, event->packet->dataLength);
diff --git a/src/network/room.h b/src/network/room.h
index c2a4b1a70..edbd3ecfb 100644
--- a/src/network/room.h
+++ b/src/network/room.h
@@ -40,6 +40,7 @@ enum RoomMessageTypes : u8 {
40 IdRoomInformation, 40 IdRoomInformation,
41 IdSetGameInfo, 41 IdSetGameInfo,
42 IdProxyPacket, 42 IdProxyPacket,
43 IdLdnPacket,
43 IdChatMessage, 44 IdChatMessage,
44 IdNameCollision, 45 IdNameCollision,
45 IdIpCollision, 46 IdIpCollision,
diff --git a/src/network/room_member.cpp b/src/network/room_member.cpp
index 06818af78..b94cb24ad 100644
--- a/src/network/room_member.cpp
+++ b/src/network/room_member.cpp
@@ -58,6 +58,7 @@ public:
58 58
59 private: 59 private:
60 CallbackSet<ProxyPacket> callback_set_proxy_packet; 60 CallbackSet<ProxyPacket> callback_set_proxy_packet;
61 CallbackSet<LDNPacket> callback_set_ldn_packet;
61 CallbackSet<ChatEntry> callback_set_chat_messages; 62 CallbackSet<ChatEntry> callback_set_chat_messages;
62 CallbackSet<StatusMessageEntry> callback_set_status_messages; 63 CallbackSet<StatusMessageEntry> callback_set_status_messages;
63 CallbackSet<RoomInformation> callback_set_room_information; 64 CallbackSet<RoomInformation> callback_set_room_information;
@@ -108,6 +109,12 @@ public:
108 void HandleProxyPackets(const ENetEvent* event); 109 void HandleProxyPackets(const ENetEvent* event);
109 110
110 /** 111 /**
112 * Extracts an LdnPacket from a received ENet packet.
113 * @param event The ENet event that was received.
114 */
115 void HandleLdnPackets(const ENetEvent* event);
116
117 /**
111 * Extracts a chat entry from a received ENet packet and adds it to the chat queue. 118 * Extracts a chat entry from a received ENet packet and adds it to the chat queue.
112 * @param event The ENet event that was received. 119 * @param event The ENet event that was received.
113 */ 120 */
@@ -166,6 +173,9 @@ void RoomMember::RoomMemberImpl::MemberLoop() {
166 case IdProxyPacket: 173 case IdProxyPacket:
167 HandleProxyPackets(&event); 174 HandleProxyPackets(&event);
168 break; 175 break;
176 case IdLdnPacket:
177 HandleLdnPackets(&event);
178 break;
169 case IdChatMessage: 179 case IdChatMessage:
170 HandleChatPacket(&event); 180 HandleChatPacket(&event);
171 break; 181 break;
@@ -372,6 +382,27 @@ void RoomMember::RoomMemberImpl::HandleProxyPackets(const ENetEvent* event) {
372 Invoke<ProxyPacket>(proxy_packet); 382 Invoke<ProxyPacket>(proxy_packet);
373} 383}
374 384
385void RoomMember::RoomMemberImpl::HandleLdnPackets(const ENetEvent* event) {
386 LDNPacket ldn_packet{};
387 Packet packet;
388 packet.Append(event->packet->data, event->packet->dataLength);
389
390 // Ignore the first byte, which is the message id.
391 packet.IgnoreBytes(sizeof(u8)); // Ignore the message type
392
393 u8 packet_type;
394 packet.Read(packet_type);
395 ldn_packet.type = static_cast<LDNPacketType>(packet_type);
396
397 packet.Read(ldn_packet.local_ip);
398 packet.Read(ldn_packet.remote_ip);
399 packet.Read(ldn_packet.broadcast);
400
401 packet.Read(ldn_packet.data);
402
403 Invoke<LDNPacket>(ldn_packet);
404}
405
375void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) { 406void RoomMember::RoomMemberImpl::HandleChatPacket(const ENetEvent* event) {
376 Packet packet; 407 Packet packet;
377 packet.Append(event->packet->data, event->packet->dataLength); 408 packet.Append(event->packet->data, event->packet->dataLength);
@@ -450,6 +481,11 @@ RoomMember::RoomMemberImpl::CallbackSet<ProxyPacket>& RoomMember::RoomMemberImpl
450} 481}
451 482
452template <> 483template <>
484RoomMember::RoomMemberImpl::CallbackSet<LDNPacket>& RoomMember::RoomMemberImpl::Callbacks::Get() {
485 return callback_set_ldn_packet;
486}
487
488template <>
453RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>& 489RoomMember::RoomMemberImpl::CallbackSet<RoomMember::State>&
454RoomMember::RoomMemberImpl::Callbacks::Get() { 490RoomMember::RoomMemberImpl::Callbacks::Get() {
455 return callback_set_state; 491 return callback_set_state;
@@ -607,6 +643,21 @@ void RoomMember::SendProxyPacket(const ProxyPacket& proxy_packet) {
607 room_member_impl->Send(std::move(packet)); 643 room_member_impl->Send(std::move(packet));
608} 644}
609 645
646void RoomMember::SendLdnPacket(const LDNPacket& ldn_packet) {
647 Packet packet;
648 packet.Write(static_cast<u8>(IdLdnPacket));
649
650 packet.Write(static_cast<u8>(ldn_packet.type));
651
652 packet.Write(ldn_packet.local_ip);
653 packet.Write(ldn_packet.remote_ip);
654 packet.Write(ldn_packet.broadcast);
655
656 packet.Write(ldn_packet.data);
657
658 room_member_impl->Send(std::move(packet));
659}
660
610void RoomMember::SendChatMessage(const std::string& message) { 661void RoomMember::SendChatMessage(const std::string& message) {
611 Packet packet; 662 Packet packet;
612 packet.Write(static_cast<u8>(IdChatMessage)); 663 packet.Write(static_cast<u8>(IdChatMessage));
@@ -663,6 +714,11 @@ RoomMember::CallbackHandle<ProxyPacket> RoomMember::BindOnProxyPacketReceived(
663 return room_member_impl->Bind(callback); 714 return room_member_impl->Bind(callback);
664} 715}
665 716
717RoomMember::CallbackHandle<LDNPacket> RoomMember::BindOnLdnPacketReceived(
718 std::function<void(const LDNPacket&)> callback) {
719 return room_member_impl->Bind(std::move(callback));
720}
721
666RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged( 722RoomMember::CallbackHandle<RoomInformation> RoomMember::BindOnRoomInformationChanged(
667 std::function<void(const RoomInformation&)> callback) { 723 std::function<void(const RoomInformation&)> callback) {
668 return room_member_impl->Bind(callback); 724 return room_member_impl->Bind(callback);
@@ -699,6 +755,7 @@ void RoomMember::Leave() {
699} 755}
700 756
701template void RoomMember::Unbind(CallbackHandle<ProxyPacket>); 757template void RoomMember::Unbind(CallbackHandle<ProxyPacket>);
758template void RoomMember::Unbind(CallbackHandle<LDNPacket>);
702template void RoomMember::Unbind(CallbackHandle<RoomMember::State>); 759template void RoomMember::Unbind(CallbackHandle<RoomMember::State>);
703template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>); 760template void RoomMember::Unbind(CallbackHandle<RoomMember::Error>);
704template void RoomMember::Unbind(CallbackHandle<RoomInformation>); 761template void RoomMember::Unbind(CallbackHandle<RoomInformation>);
diff --git a/src/network/room_member.h b/src/network/room_member.h
index f578f7f6a..0d6417294 100644
--- a/src/network/room_member.h
+++ b/src/network/room_member.h
@@ -17,7 +17,24 @@ namespace Network {
17using AnnounceMultiplayerRoom::GameInfo; 17using AnnounceMultiplayerRoom::GameInfo;
18using AnnounceMultiplayerRoom::RoomInformation; 18using AnnounceMultiplayerRoom::RoomInformation;
19 19
20/// Information about the received WiFi packets. 20enum class LDNPacketType : u8 {
21 Scan,
22 ScanResp,
23 Connect,
24 SyncNetwork,
25 Disconnect,
26 DestroyNetwork,
27};
28
29struct LDNPacket {
30 LDNPacketType type;
31 IPv4Address local_ip;
32 IPv4Address remote_ip;
33 bool broadcast;
34 std::vector<u8> data;
35};
36
37/// Information about the received proxy packets.
21struct ProxyPacket { 38struct ProxyPacket {
22 SockAddrIn local_endpoint; 39 SockAddrIn local_endpoint;
23 SockAddrIn remote_endpoint; 40 SockAddrIn remote_endpoint;
@@ -152,6 +169,12 @@ public:
152 void SendProxyPacket(const ProxyPacket& packet); 169 void SendProxyPacket(const ProxyPacket& packet);
153 170
154 /** 171 /**
172 * Sends an LDN packet to the room.
173 * @param packet The WiFi packet to send.
174 */
175 void SendLdnPacket(const LDNPacket& packet);
176
177 /**
155 * Sends a chat message to the room. 178 * Sends a chat message to the room.
156 * @param message The contents of the message. 179 * @param message The contents of the message.
157 */ 180 */
@@ -205,6 +228,16 @@ public:
205 std::function<void(const ProxyPacket&)> callback); 228 std::function<void(const ProxyPacket&)> callback);
206 229
207 /** 230 /**
231 * Binds a function to an event that will be triggered every time an LDNPacket is received.
232 * The function wil be called everytime the event is triggered.
233 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
234 * @param callback The function to call
235 * @return A handle used for removing the function from the registered list
236 */
237 CallbackHandle<LDNPacket> BindOnLdnPacketReceived(
238 std::function<void(const LDNPacket&)> callback);
239
240 /**
208 * Binds a function to an event that will be triggered every time the RoomInformation changes. 241 * Binds a function to an event that will be triggered every time the RoomInformation changes.
209 * The function wil be called every time the event is triggered. 242 * The function wil be called every time the event is triggered.
210 * The callback function must not bind or unbind a function. Doing so will cause a deadlock 243 * The callback function must not bind or unbind a function. Doing so will cause a deadlock
diff --git a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
index b5c08d611..7e8f37563 100644
--- a/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
+++ b/src/shader_recompiler/backend/glasm/emit_glasm_context_get_set.cpp
@@ -13,9 +13,6 @@ namespace Shader::Backend::GLASM {
13namespace { 13namespace {
14void GetCbuf(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding, ScalarU32 offset, 14void GetCbuf(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding, ScalarU32 offset,
15 std::string_view size) { 15 std::string_view size) {
16 if (!binding.IsImmediate()) {
17 throw NotImplementedException("Indirect constant buffer loading");
18 }
19 const Register ret{ctx.reg_alloc.Define(inst)}; 16 const Register ret{ctx.reg_alloc.Define(inst)};
20 if (offset.type == Type::U32) { 17 if (offset.type == Type::U32) {
21 // Avoid reading arrays out of bounds, matching hardware's behavior 18 // Avoid reading arrays out of bounds, matching hardware's behavior
@@ -24,7 +21,27 @@ void GetCbuf(EmitContext& ctx, IR::Inst& inst, const IR::Value& binding, ScalarU
24 return; 21 return;
25 } 22 }
26 } 23 }
27 ctx.Add("LDC.{} {},c{}[{}];", size, ret, binding.U32(), offset); 24
25 if (binding.IsImmediate()) {
26 ctx.Add("LDC.{} {},c{}[{}];", size, ret, binding.U32(), offset);
27 return;
28 }
29
30 const ScalarU32 idx{ctx.reg_alloc.Consume(binding)};
31 for (u32 i = 0; i < Info::MAX_INDIRECT_CBUFS; i++) {
32 ctx.Add("SEQ.S.CC RC.x,{},{};"
33 "IF NE.x;"
34 "LDC.{} {},c{}[{}];",
35 idx, i, size, ret, i, offset);
36
37 if (i != Info::MAX_INDIRECT_CBUFS - 1) {
38 ctx.Add("ELSE;");
39 }
40 }
41
42 for (u32 i = 0; i < Info::MAX_INDIRECT_CBUFS; i++) {
43 ctx.Add("ENDIF;");
44 }
28} 45}
29 46
30bool IsInputArray(Stage stage) { 47bool IsInputArray(Stage stage) {
diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp
index 3441a5fe5..d608678a3 100644
--- a/src/video_core/host_shaders/astc_decoder.comp
+++ b/src/video_core/host_shaders/astc_decoder.comp
@@ -1065,7 +1065,7 @@ TexelWeightParams DecodeBlockInfo() {
1065void FillError(ivec3 coord) { 1065void FillError(ivec3 coord) {
1066 for (uint j = 0; j < block_dims.y; j++) { 1066 for (uint j = 0; j < block_dims.y; j++) {
1067 for (uint i = 0; i < block_dims.x; i++) { 1067 for (uint i = 0; i < block_dims.x; i++) {
1068 imageStore(dest_image, coord + ivec3(i, j, 0), vec4(1.0, 1.0, 0.0, 1.0)); 1068 imageStore(dest_image, coord + ivec3(i, j, 0), vec4(0.0, 0.0, 0.0, 0.0));
1069 } 1069 }
1070 } 1070 }
1071} 1071}
diff --git a/src/video_core/macro/macro_hle.cpp b/src/video_core/macro/macro_hle.cpp
index 58382755b..cabe8dcbf 100644
--- a/src/video_core/macro/macro_hle.cpp
+++ b/src/video_core/macro/macro_hle.cpp
@@ -3,6 +3,8 @@
3 3
4#include <array> 4#include <array>
5#include <vector> 5#include <vector>
6#include "common/scope_exit.h"
7#include "video_core/dirty_flags.h"
6#include "video_core/engines/maxwell_3d.h" 8#include "video_core/engines/maxwell_3d.h"
7#include "video_core/macro/macro.h" 9#include "video_core/macro/macro.h"
8#include "video_core/macro/macro_hle.h" 10#include "video_core/macro/macro_hle.h"
@@ -58,6 +60,7 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
58 maxwell3d.regs.index_array.first = parameters[3]; 60 maxwell3d.regs.index_array.first = parameters[3];
59 maxwell3d.regs.reg_array[0x446] = element_base; // vertex id base? 61 maxwell3d.regs.reg_array[0x446] = element_base; // vertex id base?
60 maxwell3d.regs.index_array.count = parameters[1]; 62 maxwell3d.regs.index_array.count = parameters[1];
63 maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
61 maxwell3d.regs.vb_element_base = element_base; 64 maxwell3d.regs.vb_element_base = element_base;
62 maxwell3d.regs.vb_base_instance = base_instance; 65 maxwell3d.regs.vb_base_instance = base_instance;
63 maxwell3d.mme_draw.instance_count = instance_count; 66 maxwell3d.mme_draw.instance_count = instance_count;
@@ -80,10 +83,67 @@ void HLE_0217920100488FF7(Engines::Maxwell3D& maxwell3d, const std::vector<u32>&
80 maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined; 83 maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
81} 84}
82 85
83constexpr std::array<std::pair<u64, HLEFunction>, 3> hle_funcs{{ 86// Multidraw Indirect
87void HLE_3F5E74B9C9A50164(Engines::Maxwell3D& maxwell3d, const std::vector<u32>& parameters) {
88 SCOPE_EXIT({
89 // Clean everything.
90 maxwell3d.regs.reg_array[0x446] = 0x0; // vertex id base?
91 maxwell3d.regs.index_array.count = 0;
92 maxwell3d.regs.vb_element_base = 0x0;
93 maxwell3d.regs.vb_base_instance = 0x0;
94 maxwell3d.mme_draw.instance_count = 0;
95 maxwell3d.CallMethodFromMME(0x8e3, 0x640);
96 maxwell3d.CallMethodFromMME(0x8e4, 0x0);
97 maxwell3d.CallMethodFromMME(0x8e5, 0x0);
98 maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
99 maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
100 });
101 const u32 start_indirect = parameters[0];
102 const u32 end_indirect = parameters[1];
103 if (start_indirect >= end_indirect) {
104 // Nothing to do.
105 return;
106 }
107 const auto topology =
108 static_cast<Tegra::Engines::Maxwell3D::Regs::PrimitiveTopology>(parameters[2]);
109 maxwell3d.regs.draw.topology.Assign(topology);
110 const u32 padding = parameters[3];
111 const std::size_t max_draws = parameters[4];
112
113 const u32 indirect_words = 5 + padding;
114 const std::size_t first_draw = start_indirect;
115 const std::size_t effective_draws = end_indirect - start_indirect;
116 const std::size_t last_draw = start_indirect + std::min(effective_draws, max_draws);
117
118 for (std::size_t index = first_draw; index < last_draw; index++) {
119 const std::size_t base = index * indirect_words + 5;
120 const u32 num_vertices = parameters[base];
121 const u32 instance_count = parameters[base + 1];
122 const u32 first_index = parameters[base + 2];
123 const u32 base_vertex = parameters[base + 3];
124 const u32 base_instance = parameters[base + 4];
125 maxwell3d.regs.index_array.first = first_index;
126 maxwell3d.regs.reg_array[0x446] = base_vertex;
127 maxwell3d.regs.index_array.count = num_vertices;
128 maxwell3d.regs.vb_element_base = base_vertex;
129 maxwell3d.regs.vb_base_instance = base_instance;
130 maxwell3d.mme_draw.instance_count = instance_count;
131 maxwell3d.CallMethodFromMME(0x8e3, 0x640);
132 maxwell3d.CallMethodFromMME(0x8e4, base_vertex);
133 maxwell3d.CallMethodFromMME(0x8e5, base_instance);
134 maxwell3d.dirty.flags[VideoCommon::Dirty::IndexBuffer] = true;
135 if (maxwell3d.ShouldExecute()) {
136 maxwell3d.Rasterizer().Draw(true, true);
137 }
138 maxwell3d.mme_draw.current_mode = Engines::Maxwell3D::MMEDrawMode::Undefined;
139 }
140}
141
142constexpr std::array<std::pair<u64, HLEFunction>, 4> hle_funcs{{
84 {0x771BB18C62444DA0, &HLE_771BB18C62444DA0}, 143 {0x771BB18C62444DA0, &HLE_771BB18C62444DA0},
85 {0x0D61FC9FAAC9FCAD, &HLE_0D61FC9FAAC9FCAD}, 144 {0x0D61FC9FAAC9FCAD, &HLE_0D61FC9FAAC9FCAD},
86 {0x0217920100488FF7, &HLE_0217920100488FF7}, 145 {0x0217920100488FF7, &HLE_0217920100488FF7},
146 {0x3F5E74B9C9A50164, &HLE_3F5E74B9C9A50164},
87}}; 147}};
88 148
89class HLEMacroImpl final : public CachedMacro { 149class HLEMacroImpl final : public CachedMacro {
@@ -99,6 +159,7 @@ private:
99 Engines::Maxwell3D& maxwell3d; 159 Engines::Maxwell3D& maxwell3d;
100 HLEFunction func; 160 HLEFunction func;
101}; 161};
162
102} // Anonymous namespace 163} // Anonymous namespace
103 164
104HLEMacro::HLEMacro(Engines::Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {} 165HLEMacro::HLEMacro(Engines::Maxwell3D& maxwell3d_) : maxwell3d{maxwell3d_} {}
diff --git a/src/video_core/macro/macro_jit_x64.cpp b/src/video_core/macro/macro_jit_x64.cpp
index aca25d902..a302a9603 100644
--- a/src/video_core/macro/macro_jit_x64.cpp
+++ b/src/video_core/macro/macro_jit_x64.cpp
@@ -279,28 +279,13 @@ void MacroJITx64Impl::Compile_ExtractInsert(Macro::Opcode opcode) {
279 auto dst = Compile_GetRegister(opcode.src_a, RESULT); 279 auto dst = Compile_GetRegister(opcode.src_a, RESULT);
280 auto src = Compile_GetRegister(opcode.src_b, eax); 280 auto src = Compile_GetRegister(opcode.src_b, eax);
281 281
282 if (opcode.bf_src_bit != 0 && opcode.bf_src_bit != 31) {
283 shr(src, opcode.bf_src_bit);
284 } else if (opcode.bf_src_bit == 31) {
285 xor_(src, src);
286 }
287 // Don't bother masking the whole register since we're using a 32 bit register
288 if (opcode.bf_size != 31 && opcode.bf_size != 0) {
289 and_(src, opcode.GetBitfieldMask());
290 } else if (opcode.bf_size == 0) {
291 xor_(src, src);
292 }
293 if (opcode.bf_dst_bit != 31 && opcode.bf_dst_bit != 0) {
294 shl(src, opcode.bf_dst_bit);
295 } else if (opcode.bf_dst_bit == 31) {
296 xor_(src, src);
297 }
298
299 const u32 mask = ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit); 282 const u32 mask = ~(opcode.GetBitfieldMask() << opcode.bf_dst_bit);
300 if (mask != 0xffffffff) { 283 and_(dst, mask);
301 and_(dst, mask); 284 shr(src, opcode.bf_src_bit);
302 } 285 and_(src, opcode.GetBitfieldMask());
286 shl(src, opcode.bf_dst_bit);
303 or_(dst, src); 287 or_(dst, src);
288
304 Compile_ProcessResult(opcode.result_operation, opcode.dst); 289 Compile_ProcessResult(opcode.result_operation, opcode.dst);
305} 290}
306 291
@@ -309,17 +294,9 @@ void MacroJITx64Impl::Compile_ExtractShiftLeftImmediate(Macro::Opcode opcode) {
309 const auto src = Compile_GetRegister(opcode.src_b, RESULT); 294 const auto src = Compile_GetRegister(opcode.src_b, RESULT);
310 295
311 shr(src, dst.cvt8()); 296 shr(src, dst.cvt8());
312 if (opcode.bf_size != 0 && opcode.bf_size != 31) { 297 and_(src, opcode.GetBitfieldMask());
313 and_(src, opcode.GetBitfieldMask()); 298 shl(src, opcode.bf_dst_bit);
314 } else if (opcode.bf_size == 0) {
315 xor_(src, src);
316 }
317 299
318 if (opcode.bf_dst_bit != 0 && opcode.bf_dst_bit != 31) {
319 shl(src, opcode.bf_dst_bit);
320 } else if (opcode.bf_dst_bit == 31) {
321 xor_(src, src);
322 }
323 Compile_ProcessResult(opcode.result_operation, opcode.dst); 300 Compile_ProcessResult(opcode.result_operation, opcode.dst);
324} 301}
325 302
@@ -327,13 +304,8 @@ void MacroJITx64Impl::Compile_ExtractShiftLeftRegister(Macro::Opcode opcode) {
327 const auto dst = Compile_GetRegister(opcode.src_a, ecx); 304 const auto dst = Compile_GetRegister(opcode.src_a, ecx);
328 const auto src = Compile_GetRegister(opcode.src_b, RESULT); 305 const auto src = Compile_GetRegister(opcode.src_b, RESULT);
329 306
330 if (opcode.bf_src_bit != 0) { 307 shr(src, opcode.bf_src_bit);
331 shr(src, opcode.bf_src_bit); 308 and_(src, opcode.GetBitfieldMask());
332 }
333
334 if (opcode.bf_size != 31) {
335 and_(src, opcode.GetBitfieldMask());
336 }
337 shl(src, dst.cvt8()); 309 shl(src, dst.cvt8());
338 310
339 Compile_ProcessResult(opcode.result_operation, opcode.dst); 311 Compile_ProcessResult(opcode.result_operation, opcode.dst);
@@ -429,17 +401,11 @@ void MacroJITx64Impl::Compile_Branch(Macro::Opcode opcode) {
429 Xbyak::Label handle_post_exit{}; 401 Xbyak::Label handle_post_exit{};
430 Xbyak::Label skip{}; 402 Xbyak::Label skip{};
431 jmp(skip, T_NEAR); 403 jmp(skip, T_NEAR);
432 if (opcode.is_exit) { 404
433 L(handle_post_exit); 405 L(handle_post_exit);
434 // Execute 1 instruction 406 xor_(BRANCH_HOLDER, BRANCH_HOLDER);
435 mov(BRANCH_HOLDER, end_of_code); 407 jmp(labels[jump_address], T_NEAR);
436 // Jump to next instruction to skip delay slot check 408
437 jmp(labels[jump_address], T_NEAR);
438 } else {
439 L(handle_post_exit);
440 xor_(BRANCH_HOLDER, BRANCH_HOLDER);
441 jmp(labels[jump_address], T_NEAR);
442 }
443 L(skip); 409 L(skip);
444 mov(BRANCH_HOLDER, handle_post_exit); 410 mov(BRANCH_HOLDER, handle_post_exit);
445 jmp(delay_skip[pc], T_NEAR); 411 jmp(delay_skip[pc], T_NEAR);
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index 32450ee1d..08f4d69ab 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -168,7 +168,7 @@ void BufferCacheRuntime::BindIndexBuffer(Buffer& buffer, u32 offset, u32 size) {
168 if (has_unified_vertex_buffers) { 168 if (has_unified_vertex_buffers) {
169 buffer.MakeResident(GL_READ_ONLY); 169 buffer.MakeResident(GL_READ_ONLY);
170 glBufferAddressRangeNV(GL_ELEMENT_ARRAY_ADDRESS_NV, 0, buffer.HostGpuAddr() + offset, 170 glBufferAddressRangeNV(GL_ELEMENT_ARRAY_ADDRESS_NV, 0, buffer.HostGpuAddr() + offset,
171 static_cast<GLsizeiptr>(size)); 171 static_cast<GLsizeiptr>(Common::AlignUp(size, 4)));
172 } else { 172 } else {
173 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.Handle()); 173 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer.Handle());
174 index_buffer_offset = offset; 174 index_buffer_offset = offset;
diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp
index b159494c5..e3742ddf5 100644
--- a/src/video_core/textures/astc.cpp
+++ b/src/video_core/textures/astc.cpp
@@ -1413,7 +1413,7 @@ static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 b
1413static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) { 1413static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) {
1414 for (u32 j = 0; j < blockHeight; j++) { 1414 for (u32 j = 0; j < blockHeight; j++) {
1415 for (u32 i = 0; i < blockWidth; i++) { 1415 for (u32 i = 0; i < blockWidth; i++) {
1416 outBuf[j * blockWidth + i] = 0xFFFF00FF; 1416 outBuf[j * blockWidth + i] = 0x00000000;
1417 } 1417 }
1418 } 1418 }
1419} 1419}
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp
index 1d8072243..12efdc216 100644
--- a/src/yuzu/applets/qt_controller.cpp
+++ b/src/yuzu/applets/qt_controller.cpp
@@ -291,7 +291,7 @@ bool QtControllerSelectorDialog::CheckIfParametersMet() {
291 // Here, we check and validate the current configuration against all applicable parameters. 291 // Here, we check and validate the current configuration against all applicable parameters.
292 const auto num_connected_players = static_cast<int>( 292 const auto num_connected_players = static_cast<int>(
293 std::count_if(player_groupboxes.begin(), player_groupboxes.end(), 293 std::count_if(player_groupboxes.begin(), player_groupboxes.end(),
294 [this](const QGroupBox* player) { return player->isChecked(); })); 294 [](const QGroupBox* player) { return player->isChecked(); }));
295 295
296 const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players; 296 const auto min_supported_players = parameters.enable_single_mode ? 1 : parameters.min_players;
297 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players; 297 const auto max_supported_players = parameters.enable_single_mode ? 1 : parameters.max_players;
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp
index cb55472c9..1db374d4a 100644
--- a/src/yuzu/configuration/configure_input.cpp
+++ b/src/yuzu/configuration/configure_input.cpp
@@ -163,10 +163,9 @@ void ConfigureInput::Initialize(InputCommon::InputSubsystem* input_subsystem,
163 [this, input_subsystem, &hid_core] { 163 [this, input_subsystem, &hid_core] {
164 CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core); 164 CallConfigureDialog<ConfigureRingController>(*this, input_subsystem, hid_core);
165 }); 165 });
166 connect(advanced, &ConfigureInputAdvanced::CallCameraDialog, 166 connect(advanced, &ConfigureInputAdvanced::CallCameraDialog, [this, input_subsystem] {
167 [this, input_subsystem, &hid_core] { 167 CallConfigureDialog<ConfigureCamera>(*this, input_subsystem);
168 CallConfigureDialog<ConfigureCamera>(*this, input_subsystem); 168 });
169 });
170 169
171 connect(ui->vibrationButton, &QPushButton::clicked, 170 connect(ui->vibrationButton, &QPushButton::clicked,
172 [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); }); 171 [this, &hid_core] { CallConfigureDialog<ConfigureVibration>(*this, hid_core); });
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 23245a976..c63ce3a30 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -105,12 +105,12 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
105#include "core/hle/kernel/k_process.h" 105#include "core/hle/kernel/k_process.h"
106#include "core/hle/service/am/am.h" 106#include "core/hle/service/am/am.h"
107#include "core/hle/service/filesystem/filesystem.h" 107#include "core/hle/service/filesystem/filesystem.h"
108#include "core/hle/service/nfp/nfp.h"
109#include "core/hle/service/sm/sm.h" 108#include "core/hle/service/sm/sm.h"
110#include "core/loader/loader.h" 109#include "core/loader/loader.h"
111#include "core/perf_stats.h" 110#include "core/perf_stats.h"
112#include "core/telemetry_session.h" 111#include "core/telemetry_session.h"
113#include "input_common/drivers/tas_input.h" 112#include "input_common/drivers/tas_input.h"
113#include "input_common/drivers/virtual_amiibo.h"
114#include "input_common/main.h" 114#include "input_common/main.h"
115#include "ui_main.h" 115#include "ui_main.h"
116#include "util/overlay_dialog.h" 116#include "util/overlay_dialog.h"
@@ -899,8 +899,8 @@ void GMainWindow::InitializeWidgets() {
899 } 899 }
900 900
901 // TODO (flTobi): Add the widget when multiplayer is fully implemented 901 // TODO (flTobi): Add the widget when multiplayer is fully implemented
902 // statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0); 902 statusBar()->addPermanentWidget(multiplayer_state->GetStatusText(), 0);
903 // statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0); 903 statusBar()->addPermanentWidget(multiplayer_state->GetStatusIcon(), 0);
904 904
905 tas_label = new QLabel(); 905 tas_label = new QLabel();
906 tas_label->setObjectName(QStringLiteral("TASlabel")); 906 tas_label->setObjectName(QStringLiteral("TASlabel"));
@@ -1299,6 +1299,7 @@ void GMainWindow::ConnectMenuEvents() {
1299 &MultiplayerState::OnDirectConnectToRoom); 1299 &MultiplayerState::OnDirectConnectToRoom);
1300 connect(ui->action_Show_Room, &QAction::triggered, multiplayer_state, 1300 connect(ui->action_Show_Room, &QAction::triggered, multiplayer_state,
1301 &MultiplayerState::OnOpenNetworkRoom); 1301 &MultiplayerState::OnOpenNetworkRoom);
1302 connect(multiplayer_state, &MultiplayerState::SaveConfig, this, &GMainWindow::OnSaveConfig);
1302 1303
1303 // Tools 1304 // Tools
1304 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this, 1305 connect_menu(ui->action_Rederive, std::bind(&GMainWindow::OnReinitializeKeys, this,
@@ -1339,6 +1340,8 @@ void GMainWindow::UpdateMenuState() {
1339 } else { 1340 } else {
1340 ui->action_Pause->setText(tr("&Pause")); 1341 ui->action_Pause->setText(tr("&Pause"));
1341 } 1342 }
1343
1344 multiplayer_state->UpdateNotificationStatus();
1342} 1345}
1343 1346
1344void GMainWindow::OnDisplayTitleBars(bool show) { 1347void GMainWindow::OnDisplayTitleBars(bool show) {
@@ -2000,7 +2003,7 @@ static bool RomFSRawCopy(QProgressDialog& dialog, const FileSys::VirtualDir& src
2000} 2003}
2001 2004
2002void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) { 2005void GMainWindow::OnGameListRemoveInstalledEntry(u64 program_id, InstalledEntryType type) {
2003 const QString entry_type = [this, type] { 2006 const QString entry_type = [type] {
2004 switch (type) { 2007 switch (type) {
2005 case InstalledEntryType::Game: 2008 case InstalledEntryType::Game:
2006 return tr("Contents"); 2009 return tr("Contents");
@@ -2097,7 +2100,7 @@ void GMainWindow::RemoveAddOnContent(u64 program_id, const QString& entry_type)
2097 2100
2098void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, 2101void GMainWindow::OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target,
2099 const std::string& game_path) { 2102 const std::string& game_path) {
2100 const QString question = [this, target] { 2103 const QString question = [target] {
2101 switch (target) { 2104 switch (target) {
2102 case GameListRemoveTarget::GlShaderCache: 2105 case GameListRemoveTarget::GlShaderCache:
2103 return tr("Delete OpenGL Transferable Shader Cache?"); 2106 return tr("Delete OpenGL Transferable Shader Cache?");
@@ -2770,6 +2773,11 @@ void GMainWindow::OnExit() {
2770 OnStopGame(); 2773 OnStopGame();
2771} 2774}
2772 2775
2776void GMainWindow::OnSaveConfig() {
2777 system->ApplySettings();
2778 config->Save();
2779}
2780
2773void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) { 2781void GMainWindow::ErrorDisplayDisplayError(QString error_code, QString error_text) {
2774 OverlayDialog dialog(render_window, *system, error_code, error_text, QString{}, tr("OK"), 2782 OverlayDialog dialog(render_window, *system, error_code, error_text, QString{}, tr("OK"),
2775 Qt::AlignLeft | Qt::AlignVCenter); 2783 Qt::AlignLeft | Qt::AlignVCenter);
@@ -3211,21 +3219,16 @@ void GMainWindow::OnLoadAmiibo() {
3211 return; 3219 return;
3212 } 3220 }
3213 3221
3214 Service::SM::ServiceManager& sm = system->ServiceManager(); 3222 auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
3215 auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user"); 3223
3216 if (nfc == nullptr) { 3224 // Remove amiibo if one is connected
3217 QMessageBox::warning(this, tr("Error"), tr("The current game is not looking for amiibos")); 3225 if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) {
3218 return; 3226 virtual_amiibo->CloseAmiibo();
3219 }
3220 const auto nfc_state = nfc->GetCurrentState();
3221 if (nfc_state == Service::NFP::DeviceState::TagFound ||
3222 nfc_state == Service::NFP::DeviceState::TagMounted) {
3223 nfc->CloseAmiibo();
3224 QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed")); 3227 QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
3225 return; 3228 return;
3226 } 3229 }
3227 3230
3228 if (nfc_state != Service::NFP::DeviceState::SearchingForTag) { 3231 if (virtual_amiibo->GetCurrentState() != InputCommon::VirtualAmiibo::State::WaitingForAmiibo) {
3229 QMessageBox::warning(this, tr("Error"), tr("The current game is not looking for amiibos")); 3232 QMessageBox::warning(this, tr("Error"), tr("The current game is not looking for amiibos"));
3230 return; 3233 return;
3231 } 3234 }
@@ -3244,24 +3247,30 @@ void GMainWindow::OnLoadAmiibo() {
3244} 3247}
3245 3248
3246void GMainWindow::LoadAmiibo(const QString& filename) { 3249void GMainWindow::LoadAmiibo(const QString& filename) {
3247 Service::SM::ServiceManager& sm = system->ServiceManager(); 3250 auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
3248 auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user"); 3251 const QString title = tr("Error loading Amiibo data");
3249 if (nfc == nullptr) {
3250 return;
3251 }
3252
3253 // Remove amiibo if one is connected 3252 // Remove amiibo if one is connected
3254 const auto nfc_state = nfc->GetCurrentState(); 3253 if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) {
3255 if (nfc_state == Service::NFP::DeviceState::TagFound || 3254 virtual_amiibo->CloseAmiibo();
3256 nfc_state == Service::NFP::DeviceState::TagMounted) {
3257 nfc->CloseAmiibo();
3258 QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed")); 3255 QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
3259 return; 3256 return;
3260 } 3257 }
3261 3258
3262 if (!nfc->LoadAmiibo(filename.toStdString())) { 3259 switch (virtual_amiibo->LoadAmiibo(filename.toStdString())) {
3263 QMessageBox::warning(this, tr("Error loading Amiibo data"), 3260 case InputCommon::VirtualAmiibo::Info::NotAnAmiibo:
3264 tr("Unable to load Amiibo data.")); 3261 QMessageBox::warning(this, title, tr("The selected file is not a valid amiibo"));
3262 break;
3263 case InputCommon::VirtualAmiibo::Info::UnableToLoad:
3264 QMessageBox::warning(this, title, tr("The selected file is already on use"));
3265 break;
3266 case InputCommon::VirtualAmiibo::Info::WrongDeviceState:
3267 QMessageBox::warning(this, title, tr("The current game is not looking for amiibos"));
3268 break;
3269 case InputCommon::VirtualAmiibo::Info::Unknown:
3270 QMessageBox::warning(this, title, tr("An unkown error occured"));
3271 break;
3272 default:
3273 break;
3265 } 3274 }
3266} 3275}
3267 3276
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 716aef063..f7aa8e417 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -169,6 +169,7 @@ public slots:
169 void OnLoadComplete(); 169 void OnLoadComplete();
170 void OnExecuteProgram(std::size_t program_index); 170 void OnExecuteProgram(std::size_t program_index);
171 void OnExit(); 171 void OnExit();
172 void OnSaveConfig();
172 void ControllerSelectorReconfigureControllers( 173 void ControllerSelectorReconfigureControllers(
173 const Core::Frontend::ControllerParameters& parameters); 174 const Core::Frontend::ControllerParameters& parameters);
174 void SoftwareKeyboardInitialize( 175 void SoftwareKeyboardInitialize(
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index cdf31b417..74d49dbd4 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -120,6 +120,20 @@
120 <addaction name="menu_Reset_Window_Size"/> 120 <addaction name="menu_Reset_Window_Size"/>
121 <addaction name="menu_View_Debugging"/> 121 <addaction name="menu_View_Debugging"/>
122 </widget> 122 </widget>
123 <widget class="QMenu" name="menu_Multiplayer">
124 <property name="enabled">
125 <bool>true</bool>
126 </property>
127 <property name="title">
128 <string>&amp;Multiplayer</string>
129 </property>
130 <addaction name="action_View_Lobby"/>
131 <addaction name="action_Start_Room"/>
132 <addaction name="action_Connect_To_Room"/>
133 <addaction name="separator"/>
134 <addaction name="action_Show_Room"/>
135 <addaction name="action_Leave_Room"/>
136 </widget>
123 <widget class="QMenu" name="menu_Tools"> 137 <widget class="QMenu" name="menu_Tools">
124 <property name="title"> 138 <property name="title">
125 <string>&amp;Tools</string> 139 <string>&amp;Tools</string>
@@ -251,7 +265,7 @@
251 <bool>true</bool> 265 <bool>true</bool>
252 </property> 266 </property>
253 <property name="text"> 267 <property name="text">
254 <string>Browse Public Game Lobby</string> 268 <string>&amp;Browse Public Game Lobby</string>
255 </property> 269 </property>
256 </action> 270 </action>
257 <action name="action_Start_Room"> 271 <action name="action_Start_Room">
@@ -259,7 +273,7 @@
259 <bool>true</bool> 273 <bool>true</bool>
260 </property> 274 </property>
261 <property name="text"> 275 <property name="text">
262 <string>Create Room</string> 276 <string>&amp;Create Room</string>
263 </property> 277 </property>
264 </action> 278 </action>
265 <action name="action_Leave_Room"> 279 <action name="action_Leave_Room">
@@ -267,12 +281,12 @@
267 <bool>false</bool> 281 <bool>false</bool>
268 </property> 282 </property>
269 <property name="text"> 283 <property name="text">
270 <string>Leave Room</string> 284 <string>&amp;Leave Room</string>
271 </property> 285 </property>
272 </action> 286 </action>
273 <action name="action_Connect_To_Room"> 287 <action name="action_Connect_To_Room">
274 <property name="text"> 288 <property name="text">
275 <string>Direct Connect to Room</string> 289 <string>&amp;Direct Connect to Room</string>
276 </property> 290 </property>
277 </action> 291 </action>
278 <action name="action_Show_Room"> 292 <action name="action_Show_Room">
@@ -280,7 +294,7 @@
280 <bool>false</bool> 294 <bool>false</bool>
281 </property> 295 </property>
282 <property name="text"> 296 <property name="text">
283 <string>Show Current Room</string> 297 <string>&amp;Show Current Room</string>
284 </property> 298 </property>
285 </action> 299 </action>
286 <action name="action_Fullscreen"> 300 <action name="action_Fullscreen">
diff --git a/src/yuzu/multiplayer/chat_room.cpp b/src/yuzu/multiplayer/chat_room.cpp
index 9e672f82e..dec9696c1 100644
--- a/src/yuzu/multiplayer/chat_room.cpp
+++ b/src/yuzu/multiplayer/chat_room.cpp
@@ -61,7 +61,10 @@ public:
61 61
62 /// Format the message using the players color 62 /// Format the message using the players color
63 QString GetPlayerChatMessage(u16 player) const { 63 QString GetPlayerChatMessage(u16 player) const {
64 auto color = player_color[player % 16]; 64 const bool is_dark_theme = QIcon::themeName().contains(QStringLiteral("dark")) ||
65 QIcon::themeName().contains(QStringLiteral("midnight"));
66 auto color =
67 is_dark_theme ? player_color_dark[player % 16] : player_color_default[player % 16];
65 QString name; 68 QString name;
66 if (username.isEmpty() || username == nickname) { 69 if (username.isEmpty() || username == nickname) {
67 name = nickname; 70 name = nickname;
@@ -84,9 +87,12 @@ public:
84 } 87 }
85 88
86private: 89private:
87 static constexpr std::array<const char*, 16> player_color = { 90 static constexpr std::array<const char*, 16> player_color_default = {
88 {"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222", 91 {"#0000FF", "#FF0000", "#8A2BE2", "#FF69B4", "#1E90FF", "#008000", "#00FF7F", "#B22222",
89 "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "FFFF00"}}; 92 "#DAA520", "#FF4500", "#2E8B57", "#5F9EA0", "#D2691E", "#9ACD32", "#FF7F50", "#FFFF00"}};
93 static constexpr std::array<const char*, 16> player_color_dark = {
94 {"#559AD1", "#4EC9A8", "#D69D85", "#C6C923", "#B975B5", "#D81F1F", "#7EAE39", "#4F8733",
95 "#F7CD8A", "#6FCACF", "#CE4897", "#8A2BE2", "#D2691E", "#9ACD32", "#FF7F50", "#152ccd"}};
90 static constexpr char ping_color[] = "#FFFF00"; 96 static constexpr char ping_color[] = "#FFFF00";
91 97
92 QString timestamp; 98 QString timestamp;
diff --git a/src/yuzu/multiplayer/client_room.cpp b/src/yuzu/multiplayer/client_room.cpp
index b34a8d004..caf34a414 100644
--- a/src/yuzu/multiplayer/client_room.cpp
+++ b/src/yuzu/multiplayer/client_room.cpp
@@ -97,8 +97,9 @@ void ClientRoomWindow::UpdateView() {
97 auto memberlist = member->GetMemberInformation(); 97 auto memberlist = member->GetMemberInformation();
98 ui->chat->SetPlayerList(memberlist); 98 ui->chat->SetPlayerList(memberlist);
99 const auto information = member->GetRoomInformation(); 99 const auto information = member->GetRoomInformation();
100 setWindowTitle(QString(tr("%1 (%2/%3 members) - connected")) 100 setWindowTitle(QString(tr("%1 - %2 (%3/%4 members) - connected"))
101 .arg(QString::fromStdString(information.name)) 101 .arg(QString::fromStdString(information.name))
102 .arg(QString::fromStdString(information.preferred_game.name))
102 .arg(memberlist.size()) 103 .arg(memberlist.size())
103 .arg(information.member_slots)); 104 .arg(information.member_slots));
104 ui->description->setText(QString::fromStdString(information.description)); 105 ui->description->setText(QString::fromStdString(information.description));
diff --git a/src/yuzu/multiplayer/direct_connect.cpp b/src/yuzu/multiplayer/direct_connect.cpp
index 017063074..10bf0a4fb 100644
--- a/src/yuzu/multiplayer/direct_connect.cpp
+++ b/src/yuzu/multiplayer/direct_connect.cpp
@@ -106,6 +106,8 @@ void DirectConnectWindow::Connect() {
106 UISettings::values.multiplayer_port = UISettings::values.multiplayer_port.GetDefault(); 106 UISettings::values.multiplayer_port = UISettings::values.multiplayer_port.GetDefault();
107 } 107 }
108 108
109 emit SaveConfig();
110
109 // attempt to connect in a different thread 111 // attempt to connect in a different thread
110 QFuture<void> f = QtConcurrent::run([&] { 112 QFuture<void> f = QtConcurrent::run([&] {
111 if (auto room_member = room_network.GetRoomMember().lock()) { 113 if (auto room_member = room_network.GetRoomMember().lock()) {
diff --git a/src/yuzu/multiplayer/direct_connect.h b/src/yuzu/multiplayer/direct_connect.h
index e39dd1e0d..b8f66cfb2 100644
--- a/src/yuzu/multiplayer/direct_connect.h
+++ b/src/yuzu/multiplayer/direct_connect.h
@@ -31,6 +31,7 @@ signals:
31 * connections that it might have. 31 * connections that it might have.
32 */ 32 */
33 void Closed(); 33 void Closed();
34 void SaveConfig();
34 35
35private slots: 36private slots:
36 void OnConnection(); 37 void OnConnection();
diff --git a/src/yuzu/multiplayer/host_room.cpp b/src/yuzu/multiplayer/host_room.cpp
index 0c6adfd04..a8faa5b24 100644
--- a/src/yuzu/multiplayer/host_room.cpp
+++ b/src/yuzu/multiplayer/host_room.cpp
@@ -232,6 +232,7 @@ void HostRoomWindow::Host() {
232 } 232 }
233 UISettings::values.multiplayer_room_description = ui->room_description->toPlainText(); 233 UISettings::values.multiplayer_room_description = ui->room_description->toPlainText();
234 ui->host->setEnabled(true); 234 ui->host->setEnabled(true);
235 emit SaveConfig();
235 close(); 236 close();
236 } 237 }
237} 238}
diff --git a/src/yuzu/multiplayer/host_room.h b/src/yuzu/multiplayer/host_room.h
index 034cb2eef..ae816e2e0 100644
--- a/src/yuzu/multiplayer/host_room.h
+++ b/src/yuzu/multiplayer/host_room.h
@@ -46,6 +46,9 @@ public:
46 void UpdateGameList(QStandardItemModel* list); 46 void UpdateGameList(QStandardItemModel* list);
47 void RetranslateUi(); 47 void RetranslateUi();
48 48
49signals:
50 void SaveConfig();
51
49private: 52private:
50 void Host(); 53 void Host();
51 std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) const; 54 std::unique_ptr<Network::VerifyUser::Backend> CreateVerifyBackend(bool use_validation) const;
diff --git a/src/yuzu/multiplayer/lobby.cpp b/src/yuzu/multiplayer/lobby.cpp
index 107d40547..08c275696 100644
--- a/src/yuzu/multiplayer/lobby.cpp
+++ b/src/yuzu/multiplayer/lobby.cpp
@@ -7,6 +7,7 @@
7#include "common/logging/log.h" 7#include "common/logging/log.h"
8#include "common/settings.h" 8#include "common/settings.h"
9#include "core/core.h" 9#include "core/core.h"
10#include "core/hle/service/acc/profile_manager.h"
10#include "core/internal_network/network_interface.h" 11#include "core/internal_network/network_interface.h"
11#include "network/network.h" 12#include "network/network.h"
12#include "ui_lobby.h" 13#include "ui_lobby.h"
@@ -26,9 +27,9 @@
26Lobby::Lobby(QWidget* parent, QStandardItemModel* list, 27Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
27 std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_) 28 std::shared_ptr<Core::AnnounceMultiplayerSession> session, Core::System& system_)
28 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint), 29 : QDialog(parent, Qt::WindowTitleHint | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint),
29 ui(std::make_unique<Ui::Lobby>()), 30 ui(std::make_unique<Ui::Lobby>()), announce_multiplayer_session(session),
30 announce_multiplayer_session(session), system{system_}, room_network{ 31 profile_manager(std::make_unique<Service::Account::ProfileManager>()), system{system_},
31 system.GetRoomNetwork()} { 32 room_network{system.GetRoomNetwork()} {
32 ui->setupUi(this); 33 ui->setupUi(this);
33 34
34 // setup the watcher for background connections 35 // setup the watcher for background connections
@@ -60,9 +61,17 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
60 61
61 ui->nickname->setValidator(validation.GetNickname()); 62 ui->nickname->setValidator(validation.GetNickname());
62 ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue()); 63 ui->nickname->setText(UISettings::values.multiplayer_nickname.GetValue());
63 if (ui->nickname->text().isEmpty() && !Settings::values.yuzu_username.GetValue().empty()) { 64
64 // Use yuzu Web Service user name as nickname by default 65 // Try find the best nickname by default
65 ui->nickname->setText(QString::fromStdString(Settings::values.yuzu_username.GetValue())); 66 if (ui->nickname->text().isEmpty() || ui->nickname->text() == QStringLiteral("yuzu")) {
67 if (!Settings::values.yuzu_username.GetValue().empty()) {
68 ui->nickname->setText(
69 QString::fromStdString(Settings::values.yuzu_username.GetValue()));
70 } else if (!GetProfileUsername().empty()) {
71 ui->nickname->setText(QString::fromStdString(GetProfileUsername()));
72 } else {
73 ui->nickname->setText(QStringLiteral("yuzu"));
74 }
66 } 75 }
67 76
68 // UI Buttons 77 // UI Buttons
@@ -76,12 +85,6 @@ Lobby::Lobby(QWidget* parent, QStandardItemModel* list,
76 // Actions 85 // Actions
77 connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this, 86 connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this,
78 &Lobby::OnRefreshLobby); 87 &Lobby::OnRefreshLobby);
79
80 // manually start a refresh when the window is opening
81 // TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as
82 // part of the constructor, but offload the refresh until after the window shown. perhaps emit a
83 // refreshroomlist signal from places that open the lobby
84 RefreshLobby();
85} 88}
86 89
87Lobby::~Lobby() = default; 90Lobby::~Lobby() = default;
@@ -96,6 +99,7 @@ void Lobby::UpdateGameList(QStandardItemModel* list) {
96 } 99 }
97 if (proxy) 100 if (proxy)
98 proxy->UpdateGameList(game_list); 101 proxy->UpdateGameList(game_list);
102 ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder);
99} 103}
100 104
101void Lobby::RetranslateUi() { 105void Lobby::RetranslateUi() {
@@ -117,6 +121,11 @@ void Lobby::OnExpandRoom(const QModelIndex& index) {
117 121
118void Lobby::OnJoinRoom(const QModelIndex& source) { 122void Lobby::OnJoinRoom(const QModelIndex& source) {
119 if (!Network::GetSelectedNetworkInterface()) { 123 if (!Network::GetSelectedNetworkInterface()) {
124 LOG_INFO(WebService, "Automatically selected network interface for room network.");
125 Network::SelectFirstNetworkInterface();
126 }
127
128 if (!Network::GetSelectedNetworkInterface()) {
120 NetworkMessage::ErrorManager::ShowError( 129 NetworkMessage::ErrorManager::ShowError(
121 NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED); 130 NetworkMessage::ErrorManager::NO_INTERFACE_SELECTED);
122 return; 131 return;
@@ -197,16 +206,16 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
197 proxy->data(connection_index, LobbyItemHost::HostIPRole).toString(); 206 proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
198 UISettings::values.multiplayer_port = 207 UISettings::values.multiplayer_port =
199 proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt(); 208 proxy->data(connection_index, LobbyItemHost::HostPortRole).toInt();
209 emit SaveConfig();
200} 210}
201 211
202void Lobby::ResetModel() { 212void Lobby::ResetModel() {
203 model->clear(); 213 model->clear();
204 model->insertColumns(0, Column::TOTAL); 214 model->insertColumns(0, Column::TOTAL);
205 model->setHeaderData(Column::EXPAND, Qt::Horizontal, QString(), Qt::DisplayRole); 215 model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole);
206 model->setHeaderData(Column::ROOM_NAME, Qt::Horizontal, tr("Room Name"), Qt::DisplayRole); 216 model->setHeaderData(Column::ROOM_NAME, Qt::Horizontal, tr("Room Name"), Qt::DisplayRole);
207 model->setHeaderData(Column::GAME_NAME, Qt::Horizontal, tr("Preferred Game"), Qt::DisplayRole); 217 model->setHeaderData(Column::GAME_NAME, Qt::Horizontal, tr("Preferred Game"), Qt::DisplayRole);
208 model->setHeaderData(Column::HOST, Qt::Horizontal, tr("Host"), Qt::DisplayRole); 218 model->setHeaderData(Column::HOST, Qt::Horizontal, tr("Host"), Qt::DisplayRole);
209 model->setHeaderData(Column::MEMBER, Qt::Horizontal, tr("Players"), Qt::DisplayRole);
210} 219}
211 220
212void Lobby::RefreshLobby() { 221void Lobby::RefreshLobby() {
@@ -229,6 +238,7 @@ void Lobby::OnRefreshLobby() {
229 for (int r = 0; r < game_list->rowCount(); ++r) { 238 for (int r = 0; r < game_list->rowCount(); ++r) {
230 auto index = game_list->index(r, 0); 239 auto index = game_list->index(r, 0);
231 auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong(); 240 auto game_id = game_list->data(index, GameListItemPath::ProgramIdRole).toULongLong();
241
232 if (game_id != 0 && room.information.preferred_game.id == game_id) { 242 if (game_id != 0 && room.information.preferred_game.id == game_id) {
233 smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>(); 243 smdh_icon = game_list->data(index, Qt::DecorationRole).value<QPixmap>();
234 } 244 }
@@ -243,17 +253,16 @@ void Lobby::OnRefreshLobby() {
243 members.append(var); 253 members.append(var);
244 } 254 }
245 255
246 auto first_item = new LobbyItem(); 256 auto first_item = new LobbyItemGame(
257 room.information.preferred_game.id,
258 QString::fromStdString(room.information.preferred_game.name), smdh_icon);
247 auto row = QList<QStandardItem*>({ 259 auto row = QList<QStandardItem*>({
248 first_item, 260 first_item,
249 new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)), 261 new LobbyItemName(room.has_password, QString::fromStdString(room.information.name)),
250 new LobbyItemGame(room.information.preferred_game.id, 262 new LobbyItemMemberList(members, room.information.member_slots),
251 QString::fromStdString(room.information.preferred_game.name),
252 smdh_icon),
253 new LobbyItemHost(QString::fromStdString(room.information.host_username), 263 new LobbyItemHost(QString::fromStdString(room.information.host_username),
254 QString::fromStdString(room.ip), room.information.port, 264 QString::fromStdString(room.ip), room.information.port,
255 QString::fromStdString(room.verify_uid)), 265 QString::fromStdString(room.verify_uid)),
256 new LobbyItemMemberList(members, room.information.member_slots),
257 }); 266 });
258 model->appendRow(row); 267 model->appendRow(row);
259 // To make the rows expandable, add the member data as a child of the first column of the 268 // To make the rows expandable, add the member data as a child of the first column of the
@@ -283,6 +292,26 @@ void Lobby::OnRefreshLobby() {
283 ui->room_list->setFirstColumnSpanned(j, proxy->index(i, 0), true); 292 ui->room_list->setFirstColumnSpanned(j, proxy->index(i, 0), true);
284 } 293 }
285 } 294 }
295
296 ui->room_list->sortByColumn(Column::GAME_NAME, Qt::AscendingOrder);
297}
298
299std::string Lobby::GetProfileUsername() {
300 const auto& current_user = profile_manager->GetUser(Settings::values.current_user.GetValue());
301 Service::Account::ProfileBase profile{};
302
303 if (!current_user.has_value()) {
304 return "";
305 }
306
307 if (!profile_manager->GetProfileBase(*current_user, profile)) {
308 return "";
309 }
310
311 const auto text = Common::StringFromFixedZeroTerminatedBuffer(
312 reinterpret_cast<const char*>(profile.username.data()), profile.username.size());
313
314 return text;
286} 315}
287 316
288LobbyFilterProxyModel::LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list) 317LobbyFilterProxyModel::LobbyFilterProxyModel(QWidget* parent, QStandardItemModel* list)
diff --git a/src/yuzu/multiplayer/lobby.h b/src/yuzu/multiplayer/lobby.h
index 2696aec21..300dad13e 100644
--- a/src/yuzu/multiplayer/lobby.h
+++ b/src/yuzu/multiplayer/lobby.h
@@ -24,6 +24,10 @@ namespace Core {
24class System; 24class System;
25} 25}
26 26
27namespace Service::Account {
28class ProfileManager;
29}
30
27/** 31/**
28 * Listing of all public games pulled from services. The lobby should be simple enough for users to 32 * Listing of all public games pulled from services. The lobby should be simple enough for users to
29 * find the game they want to play, and join it. 33 * find the game they want to play, and join it.
@@ -75,8 +79,11 @@ private slots:
75 79
76signals: 80signals:
77 void StateChanged(const Network::RoomMember::State&); 81 void StateChanged(const Network::RoomMember::State&);
82 void SaveConfig();
78 83
79private: 84private:
85 std::string GetProfileUsername();
86
80 /** 87 /**
81 * Removes all entries in the Lobby before refreshing. 88 * Removes all entries in the Lobby before refreshing.
82 */ 89 */
@@ -96,6 +103,7 @@ private:
96 103
97 QFutureWatcher<AnnounceMultiplayerRoom::RoomList> room_list_watcher; 104 QFutureWatcher<AnnounceMultiplayerRoom::RoomList> room_list_watcher;
98 std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; 105 std::weak_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
106 std::unique_ptr<Service::Account::ProfileManager> profile_manager;
99 QFutureWatcher<void>* watcher; 107 QFutureWatcher<void>* watcher;
100 Validation validation; 108 Validation validation;
101 Core::System& system; 109 Core::System& system;
diff --git a/src/yuzu/multiplayer/lobby_p.h b/src/yuzu/multiplayer/lobby_p.h
index 8071cede4..068c95aca 100644
--- a/src/yuzu/multiplayer/lobby_p.h
+++ b/src/yuzu/multiplayer/lobby_p.h
@@ -11,11 +11,10 @@
11 11
12namespace Column { 12namespace Column {
13enum List { 13enum List {
14 EXPAND,
15 ROOM_NAME,
16 GAME_NAME, 14 GAME_NAME,
17 HOST, 15 ROOM_NAME,
18 MEMBER, 16 MEMBER,
17 HOST,
19 TOTAL, 18 TOTAL,
20}; 19};
21} 20}
@@ -91,6 +90,8 @@ public:
91 setData(game_name, GameNameRole); 90 setData(game_name, GameNameRole);
92 if (!smdh_icon.isNull()) { 91 if (!smdh_icon.isNull()) {
93 setData(smdh_icon, GameIconRole); 92 setData(smdh_icon, GameIconRole);
93 } else {
94 setData(QIcon::fromTheme(QStringLiteral("chip")).pixmap(32), GameIconRole);
94 } 95 }
95 } 96 }
96 97
@@ -98,7 +99,12 @@ public:
98 if (role == Qt::DecorationRole) { 99 if (role == Qt::DecorationRole) {
99 auto val = data(GameIconRole); 100 auto val = data(GameIconRole);
100 if (val.isValid()) { 101 if (val.isValid()) {
101 val = val.value<QPixmap>().scaled(16, 16, Qt::KeepAspectRatio); 102 val = val.value<QPixmap>().scaled(32, 32, Qt::KeepAspectRatio,
103 Qt::TransformationMode::SmoothTransformation);
104 } else {
105 auto blank_image = QPixmap(32, 32);
106 blank_image.fill(Qt::black);
107 val = blank_image;
102 } 108 }
103 return val; 109 return val;
104 } else if (role != Qt::DisplayRole) { 110 } else if (role != Qt::DisplayRole) {
@@ -191,8 +197,8 @@ public:
191 return LobbyItem::data(role); 197 return LobbyItem::data(role);
192 } 198 }
193 auto members = data(MemberListRole).toList(); 199 auto members = data(MemberListRole).toList();
194 return QStringLiteral("%1 / %2").arg(QString::number(members.size()), 200 return QStringLiteral("%1 / %2 ")
195 data(MaxPlayerRole).toString()); 201 .arg(QString::number(members.size()), data(MaxPlayerRole).toString());
196 } 202 }
197 203
198 bool operator<(const QStandardItem& other) const override { 204 bool operator<(const QStandardItem& other) const override {
diff --git a/src/yuzu/multiplayer/message.cpp b/src/yuzu/multiplayer/message.cpp
index 758b5b731..6d8f18274 100644
--- a/src/yuzu/multiplayer/message.cpp
+++ b/src/yuzu/multiplayer/message.cpp
@@ -49,9 +49,9 @@ const ConnectionError ErrorManager::PERMISSION_DENIED(
49 QT_TR_NOOP("You do not have enough permission to perform this action.")); 49 QT_TR_NOOP("You do not have enough permission to perform this action."));
50const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP( 50const ConnectionError ErrorManager::NO_SUCH_USER(QT_TR_NOOP(
51 "The user you are trying to kick/ban could not be found.\nThey may have left the room.")); 51 "The user you are trying to kick/ban could not be found.\nThey may have left the room."));
52const ConnectionError ErrorManager::NO_INTERFACE_SELECTED( 52const ConnectionError ErrorManager::NO_INTERFACE_SELECTED(QT_TR_NOOP(
53 QT_TR_NOOP("No network interface is selected.\nPlease go to Configure -> System -> Network and " 53 "No valid network interface is selected.\nPlease go to Configure -> System -> Network and "
54 "make a selection.")); 54 "make a selection."));
55 55
56static bool WarnMessage(const std::string& title, const std::string& text) { 56static bool WarnMessage(const std::string& title, const std::string& text) {
57 return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()), 57 return QMessageBox::Ok == QMessageBox::warning(nullptr, QObject::tr(title.c_str()),
diff --git a/src/yuzu/multiplayer/state.cpp b/src/yuzu/multiplayer/state.cpp
index 66e098296..ae2738ad4 100644
--- a/src/yuzu/multiplayer/state.cpp
+++ b/src/yuzu/multiplayer/state.cpp
@@ -44,9 +44,6 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis
44 44
45 status_text = new ClickableLabel(this); 45 status_text = new ClickableLabel(this);
46 status_icon = new ClickableLabel(this); 46 status_icon = new ClickableLabel(this);
47 status_text->setToolTip(tr("Current connection status"));
48 status_text->setText(tr("Not Connected. Click here to find a room!"));
49 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
50 47
51 connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); 48 connect(status_text, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
52 connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom); 49 connect(status_icon, &ClickableLabel::clicked, this, &MultiplayerState::OnOpenNetworkRoom);
@@ -57,6 +54,8 @@ MultiplayerState::MultiplayerState(QWidget* parent, QStandardItemModel* game_lis
57 HideNotification(); 54 HideNotification();
58 } 55 }
59 }); 56 });
57
58 retranslateUi();
60} 59}
61 60
62MultiplayerState::~MultiplayerState() = default; 61MultiplayerState::~MultiplayerState() = default;
@@ -90,14 +89,7 @@ void MultiplayerState::Close() {
90void MultiplayerState::retranslateUi() { 89void MultiplayerState::retranslateUi() {
91 status_text->setToolTip(tr("Current connection status")); 90 status_text->setToolTip(tr("Current connection status"));
92 91
93 if (current_state == Network::RoomMember::State::Uninitialized) { 92 UpdateNotificationStatus();
94 status_text->setText(tr("Not Connected. Click here to find a room!"));
95 } else if (current_state == Network::RoomMember::State::Joined ||
96 current_state == Network::RoomMember::State::Moderator) {
97 status_text->setText(tr("Connected"));
98 } else {
99 status_text->setText(tr("Not Connected"));
100 }
101 93
102 if (lobby) { 94 if (lobby) {
103 lobby->RetranslateUi(); 95 lobby->RetranslateUi();
@@ -113,21 +105,55 @@ void MultiplayerState::retranslateUi() {
113 } 105 }
114} 106}
115 107
108void MultiplayerState::SetNotificationStatus(NotificationStatus status) {
109 notification_status = status;
110 UpdateNotificationStatus();
111}
112
113void MultiplayerState::UpdateNotificationStatus() {
114 switch (notification_status) {
115 case NotificationStatus::Unitialized:
116 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
117 status_text->setText(tr("Not Connected. Click here to find a room!"));
118 leave_room->setEnabled(false);
119 show_room->setEnabled(false);
120 break;
121 case NotificationStatus::Disconnected:
122 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16));
123 status_text->setText(tr("Not Connected"));
124 leave_room->setEnabled(false);
125 show_room->setEnabled(false);
126 break;
127 case NotificationStatus::Connected:
128 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16));
129 status_text->setText(tr("Connected"));
130 leave_room->setEnabled(true);
131 show_room->setEnabled(true);
132 break;
133 case NotificationStatus::Notification:
134 status_icon->setPixmap(
135 QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16));
136 status_text->setText(tr("New Messages Received"));
137 leave_room->setEnabled(true);
138 show_room->setEnabled(true);
139 break;
140 }
141
142 // Clean up status bar if game is running
143 if (system.IsPoweredOn()) {
144 status_text->clear();
145 }
146}
147
116void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) { 148void MultiplayerState::OnNetworkStateChanged(const Network::RoomMember::State& state) {
117 LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state)); 149 LOG_DEBUG(Frontend, "Network State: {}", Network::GetStateStr(state));
118 if (state == Network::RoomMember::State::Joined || 150 if (state == Network::RoomMember::State::Joined ||
119 state == Network::RoomMember::State::Moderator) { 151 state == Network::RoomMember::State::Moderator) {
120 152
121 OnOpenNetworkRoom(); 153 OnOpenNetworkRoom();
122 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); 154 SetNotificationStatus(NotificationStatus::Connected);
123 status_text->setText(tr("Connected"));
124 leave_room->setEnabled(true);
125 show_room->setEnabled(true);
126 } else { 155 } else {
127 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("disconnected")).pixmap(16)); 156 SetNotificationStatus(NotificationStatus::Disconnected);
128 status_text->setText(tr("Not Connected"));
129 leave_room->setEnabled(false);
130 show_room->setEnabled(false);
131 } 157 }
132 158
133 current_state = state; 159 current_state = state;
@@ -185,6 +211,10 @@ void MultiplayerState::OnAnnounceFailed(const WebService::WebResult& result) {
185 QMessageBox::Ok); 211 QMessageBox::Ok);
186} 212}
187 213
214void MultiplayerState::OnSaveConfig() {
215 emit SaveConfig();
216}
217
188void MultiplayerState::UpdateThemedIcons() { 218void MultiplayerState::UpdateThemedIcons() {
189 if (show_notification) { 219 if (show_notification) {
190 status_icon->setPixmap( 220 status_icon->setPixmap(
@@ -209,13 +239,16 @@ static void BringWidgetToFront(QWidget* widget) {
209void MultiplayerState::OnViewLobby() { 239void MultiplayerState::OnViewLobby() {
210 if (lobby == nullptr) { 240 if (lobby == nullptr) {
211 lobby = new Lobby(this, game_list_model, announce_multiplayer_session, system); 241 lobby = new Lobby(this, game_list_model, announce_multiplayer_session, system);
242 connect(lobby, &Lobby::SaveConfig, this, &MultiplayerState::OnSaveConfig);
212 } 243 }
244 lobby->RefreshLobby();
213 BringWidgetToFront(lobby); 245 BringWidgetToFront(lobby);
214} 246}
215 247
216void MultiplayerState::OnCreateRoom() { 248void MultiplayerState::OnCreateRoom() {
217 if (host_room == nullptr) { 249 if (host_room == nullptr) {
218 host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session, system); 250 host_room = new HostRoomWindow(this, game_list_model, announce_multiplayer_session, system);
251 connect(host_room, &HostRoomWindow::SaveConfig, this, &MultiplayerState::OnSaveConfig);
219 } 252 }
220 BringWidgetToFront(host_room); 253 BringWidgetToFront(host_room);
221} 254}
@@ -249,14 +282,13 @@ void MultiplayerState::ShowNotification() {
249 return; // Do not show notification if the chat window currently has focus 282 return; // Do not show notification if the chat window currently has focus
250 show_notification = true; 283 show_notification = true;
251 QApplication::alert(nullptr); 284 QApplication::alert(nullptr);
252 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected_notification")).pixmap(16)); 285 QApplication::beep();
253 status_text->setText(tr("New Messages Received")); 286 SetNotificationStatus(NotificationStatus::Notification);
254} 287}
255 288
256void MultiplayerState::HideNotification() { 289void MultiplayerState::HideNotification() {
257 show_notification = false; 290 show_notification = false;
258 status_icon->setPixmap(QIcon::fromTheme(QStringLiteral("connected")).pixmap(16)); 291 SetNotificationStatus(NotificationStatus::Connected);
259 status_text->setText(tr("Connected"));
260} 292}
261 293
262void MultiplayerState::OnOpenNetworkRoom() { 294void MultiplayerState::OnOpenNetworkRoom() {
@@ -279,6 +311,8 @@ void MultiplayerState::OnOpenNetworkRoom() {
279void MultiplayerState::OnDirectConnectToRoom() { 311void MultiplayerState::OnDirectConnectToRoom() {
280 if (direct_connect == nullptr) { 312 if (direct_connect == nullptr) {
281 direct_connect = new DirectConnectWindow(system, this); 313 direct_connect = new DirectConnectWindow(system, this);
314 connect(direct_connect, &DirectConnectWindow::SaveConfig, this,
315 &MultiplayerState::OnSaveConfig);
282 } 316 }
283 BringWidgetToFront(direct_connect); 317 BringWidgetToFront(direct_connect);
284} 318}
diff --git a/src/yuzu/multiplayer/state.h b/src/yuzu/multiplayer/state.h
index c92496413..5d681c5c6 100644
--- a/src/yuzu/multiplayer/state.h
+++ b/src/yuzu/multiplayer/state.h
@@ -22,6 +22,13 @@ class MultiplayerState : public QWidget {
22 Q_OBJECT; 22 Q_OBJECT;
23 23
24public: 24public:
25 enum class NotificationStatus {
26 Unitialized,
27 Disconnected,
28 Connected,
29 Notification,
30 };
31
25 explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room, 32 explicit MultiplayerState(QWidget* parent, QStandardItemModel* game_list, QAction* leave_room,
26 QAction* show_room, Core::System& system_); 33 QAction* show_room, Core::System& system_);
27 ~MultiplayerState(); 34 ~MultiplayerState();
@@ -31,6 +38,10 @@ public:
31 */ 38 */
32 void Close(); 39 void Close();
33 40
41 void SetNotificationStatus(NotificationStatus state);
42
43 void UpdateNotificationStatus();
44
34 ClickableLabel* GetStatusText() const { 45 ClickableLabel* GetStatusText() const {
35 return status_text; 46 return status_text;
36 } 47 }
@@ -64,6 +75,7 @@ public slots:
64 void OnOpenNetworkRoom(); 75 void OnOpenNetworkRoom();
65 void OnDirectConnectToRoom(); 76 void OnDirectConnectToRoom();
66 void OnAnnounceFailed(const WebService::WebResult&); 77 void OnAnnounceFailed(const WebService::WebResult&);
78 void OnSaveConfig();
67 void UpdateThemedIcons(); 79 void UpdateThemedIcons();
68 void ShowNotification(); 80 void ShowNotification();
69 void HideNotification(); 81 void HideNotification();
@@ -72,6 +84,7 @@ signals:
72 void NetworkStateChanged(const Network::RoomMember::State&); 84 void NetworkStateChanged(const Network::RoomMember::State&);
73 void NetworkError(const Network::RoomMember::Error&); 85 void NetworkError(const Network::RoomMember::Error&);
74 void AnnounceFailed(const WebService::WebResult&); 86 void AnnounceFailed(const WebService::WebResult&);
87 void SaveConfig();
75 88
76private: 89private:
77 Lobby* lobby = nullptr; 90 Lobby* lobby = nullptr;
@@ -85,6 +98,7 @@ private:
85 QAction* show_room; 98 QAction* show_room;
86 std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session; 99 std::shared_ptr<Core::AnnounceMultiplayerSession> announce_multiplayer_session;
87 Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized; 100 Network::RoomMember::State current_state = Network::RoomMember::State::Uninitialized;
101 NotificationStatus notification_status = NotificationStatus::Unitialized;
88 bool has_mod_perms = false; 102 bool has_mod_perms = false;
89 Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle; 103 Network::RoomMember::CallbackHandle<Network::RoomMember::State> state_callback_handle;
90 Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle; 104 Network::RoomMember::CallbackHandle<Network::RoomMember::Error> error_callback_handle;
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index e12d414d9..753797efc 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -102,7 +102,7 @@ struct Values {
102 Settings::Setting<uint32_t> callout_flags{0, "calloutFlags"}; 102 Settings::Setting<uint32_t> callout_flags{0, "calloutFlags"};
103 103
104 // multiplayer settings 104 // multiplayer settings
105 Settings::Setting<QString> multiplayer_nickname{QStringLiteral("yuzu"), "nickname"}; 105 Settings::Setting<QString> multiplayer_nickname{{}, "nickname"};
106 Settings::Setting<QString> multiplayer_ip{{}, "ip"}; 106 Settings::Setting<QString> multiplayer_ip{{}, "ip"};
107 Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, UINT16_MAX, "port"}; 107 Settings::SwitchableSetting<uint, true> multiplayer_port{24872, 0, UINT16_MAX, "port"};
108 Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"}; 108 Settings::Setting<QString> multiplayer_room_nickname{{}, "room_nickname"};