From 7d5a10eb6b312d1dbad6c2a00a339e0ed1e0b81b Mon Sep 17 00:00:00 2001 From: pineappleEA Date: Thu, 22 Jun 2023 07:53:33 +0200 Subject: [PATCH] early-access version 3702 --- README.md | 2 +- src/common/fs/fs.cpp | 8 +- src/common/fs/fs_types.h | 2 +- src/common/input.h | 43 +- src/common/settings.h | 11 +- src/core/file_sys/patch_manager.cpp | 9 +- src/core/file_sys/vfs_real.cpp | 46 +- src/core/file_sys/vfs_real.h | 11 +- src/core/hid/emulated_controller.cpp | 82 +++- src/core/hid/emulated_controller.h | 28 +- src/core/hid/input_converter.cpp | 6 +- .../hle/service/am/applets/applet_cabinet.cpp | 2 +- src/core/hle/service/nfc/common/device.cpp | 182 ++++---- src/core/hle/service/nfc/common/device.h | 10 +- .../hle/service/nfc/common/device_manager.cpp | 11 +- .../hle/service/nfc/common/device_manager.h | 2 +- src/core/hle/service/nfc/mifare_types.h | 11 +- src/core/hle/service/nfc/nfc_interface.cpp | 7 +- src/input_common/drivers/joycon.cpp | 133 +++++- src/input_common/drivers/joycon.h | 18 +- src/input_common/drivers/mouse.cpp | 99 +++-- src/input_common/drivers/mouse.h | 2 - src/input_common/drivers/virtual_amiibo.cpp | 130 +++++- src/input_common/drivers/virtual_amiibo.h | 16 +- src/input_common/helpers/joycon_driver.cpp | 122 +++++- src/input_common/helpers/joycon_driver.h | 6 + .../helpers/joycon_protocol/joycon_types.h | 59 ++- .../helpers/joycon_protocol/nfc.cpp | 396 +++++++++++++++++- .../helpers/joycon_protocol/nfc.h | 34 +- .../helpers/joycon_protocol/poller.cpp | 4 +- .../helpers/joycon_protocol/poller.h | 4 +- src/input_common/input_engine.h | 34 ++ src/input_common/input_poller.cpp | 24 +- src/yuzu/CMakeLists.txt | 3 + src/yuzu/configuration/config.cpp | 33 +- src/yuzu/configuration/config.h | 2 + .../configure_input_advanced.cpp | 7 - .../configuration/configure_input_advanced.ui | 37 +- .../configuration/configure_input_player.cpp | 16 + .../configuration/configure_input_player.ui | 96 +++++ .../configuration/configure_mouse_panning.cpp | 79 ++++ .../configuration/configure_mouse_panning.h | 35 ++ .../configuration/configure_mouse_panning.ui | 238 +++++++++++ src/yuzu/main.cpp | 4 +- src/yuzu_cmd/default_ini.h | 26 +- 45 files changed, 1799 insertions(+), 331 deletions(-) create mode 100755 src/yuzu/configuration/configure_mouse_panning.cpp create mode 100755 src/yuzu/configuration/configure_mouse_panning.h create mode 100755 src/yuzu/configuration/configure_mouse_panning.ui diff --git a/README.md b/README.md index 6b44ff660..a06c6dcbf 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ yuzu emulator early access ============= -This is the source code for early-access 3701. +This is the source code for early-access 3702. ## Legal Notice diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp index 307f5cf07..ca2c744c6 100755 --- a/src/common/fs/fs.cpp +++ b/src/common/fs/fs.cpp @@ -436,7 +436,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable if (True(filter & DirEntryFilter::File) && entry.status().type() == fs::file_type::regular) { - if (!callback(entry.path())) { + if (!callback(entry)) { callback_error = true; break; } @@ -444,7 +444,7 @@ void IterateDirEntries(const std::filesystem::path& path, const DirEntryCallable if (True(filter & DirEntryFilter::Directory) && entry.status().type() == fs::file_type::directory) { - if (!callback(entry.path())) { + if (!callback(entry)) { callback_error = true; break; } @@ -493,7 +493,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path, if (True(filter & DirEntryFilter::File) && entry.status().type() == fs::file_type::regular) { - if (!callback(entry.path())) { + if (!callback(entry)) { callback_error = true; break; } @@ -501,7 +501,7 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path, if (True(filter & DirEntryFilter::Directory) && entry.status().type() == fs::file_type::directory) { - if (!callback(entry.path())) { + if (!callback(entry)) { callback_error = true; break; } diff --git a/src/common/fs/fs_types.h b/src/common/fs/fs_types.h index d5fb35d90..a810b6b07 100755 --- a/src/common/fs/fs_types.h +++ b/src/common/fs/fs_types.h @@ -66,6 +66,6 @@ DECLARE_ENUM_FLAG_OPERATORS(DirEntryFilter); * @returns A boolean value. * Return true to indicate whether the callback is successful, false otherwise. */ -using DirEntryCallable = std::function; +using DirEntryCallable = std::function; } // namespace Common::FS diff --git a/src/common/input.h b/src/common/input.h index e5c928aea..e307de607 100755 --- a/src/common/input.h +++ b/src/common/input.h @@ -86,7 +86,7 @@ enum class NfcState { NewAmiibo, WaitingForAmiibo, AmiiboRemoved, - NotAnAmiibo, + InvalidTagType, NotSupported, WrongDeviceState, WriteFailed, @@ -218,8 +218,22 @@ struct CameraStatus { }; struct NfcStatus { - NfcState state{}; - std::vector data{}; + NfcState state{NfcState::Unknown}; + u8 uuid_length; + u8 protocol; + u8 tag_type; + std::array uuid; +}; + +struct MifareData { + u8 command; + u8 sector; + std::array key; + std::array data; +}; + +struct MifareRequest { + std::array data; }; // List of buttons to be passed to Qt that can be translated @@ -294,7 +308,7 @@ struct CallbackStatus { BatteryStatus battery_status{}; VibrationStatus vibration_status{}; CameraFormat camera_status{CameraFormat::None}; - NfcState nfc_status{NfcState::Unknown}; + NfcStatus nfc_status{}; std::vector raw_data{}; }; @@ -356,9 +370,30 @@ public: return NfcState::NotSupported; } + virtual NfcState StartNfcPolling() { + return NfcState::NotSupported; + } + + virtual NfcState StopNfcPolling() { + return NfcState::NotSupported; + } + + virtual NfcState ReadAmiiboData([[maybe_unused]] std::vector& out_data) { + return NfcState::NotSupported; + } + virtual NfcState WriteNfcData([[maybe_unused]] const std::vector& data) { return NfcState::NotSupported; } + + virtual NfcState ReadMifareData([[maybe_unused]] const MifareRequest& request, + [[maybe_unused]] MifareRequest& out_data) { + return NfcState::NotSupported; + } + + virtual NfcState WriteMifareData([[maybe_unused]] const MifareRequest& request) { + return NfcState::NotSupported; + } }; /// An abstract class template for a factory that can create input devices. diff --git a/src/common/settings.h b/src/common/settings.h index d04fc7a06..a22d39cf2 100755 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -525,9 +525,16 @@ struct Values { Setting tas_loop{false, "tas_loop"}; Setting mouse_panning{false, "mouse_panning"}; - Setting mouse_panning_sensitivity{50, 1, 100, "mouse_panning_sensitivity"}; - Setting mouse_enabled{false, "mouse_enabled"}; + Setting mouse_panning_x_sensitivity{50, 1, 100, "mouse_panning_x_sensitivity"}; + Setting mouse_panning_y_sensitivity{50, 1, 100, "mouse_panning_y_sensitivity"}; + Setting mouse_panning_deadzone_x_counterweight{ + 0, 0, 100, "mouse_panning_deadzone_x_counterweight"}; + Setting mouse_panning_deadzone_y_counterweight{ + 0, 0, 100, "mouse_panning_deadzone_y_counterweight"}; + Setting mouse_panning_decay_strength{22, 0, 100, "mouse_panning_decay_strength"}; + Setting mouse_panning_min_decay{5, 0, 100, "mouse_panning_min_decay"}; + Setting mouse_enabled{false, "mouse_enabled"}; Setting emulate_analog_keyboard{false, "emulate_analog_keyboard"}; Setting keyboard_enabled{false, "keyboard_enabled"}; diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 084021059..00fa7931c 100755 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -153,7 +153,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); std::vector patch_dirs = {sdmc_load_dir}; - if (load_dir != nullptr && load_dir->GetSize() > 0) { + if (load_dir != nullptr) { const auto load_patch_dirs = load_dir->GetSubdirectories(); patch_dirs.insert(patch_dirs.end(), load_patch_dirs.begin(), load_patch_dirs.end()); } @@ -354,8 +354,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); if ((type != ContentRecordType::Program && type != ContentRecordType::Data) || - ((load_dir == nullptr || load_dir->GetSize() <= 0) && - (sdmc_load_dir == nullptr || sdmc_load_dir->GetSize() <= 0))) { + (load_dir == nullptr && sdmc_load_dir == nullptr)) { return; } @@ -496,7 +495,7 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u // General Mods (LayeredFS and IPS) const auto mod_dir = fs_controller.GetModificationLoadRoot(title_id); - if (mod_dir != nullptr && mod_dir->GetSize() > 0) { + if (mod_dir != nullptr) { for (const auto& mod : mod_dir->GetSubdirectories()) { std::string types; @@ -540,7 +539,7 @@ PatchManager::PatchVersionNames PatchManager::GetPatchVersionNames(VirtualFile u // SDMC mod directory (RomFS LayeredFS) const auto sdmc_mod_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); - if (sdmc_mod_dir != nullptr && sdmc_mod_dir->GetSize() > 0) { + if (sdmc_mod_dir != nullptr) { std::string types; if (IsDirValidAndNonEmpty(FindSubdirectoryCaseless(sdmc_mod_dir, "exefs"))) { AppendCommaIfNotEmpty(types, "LayeredExeFS"); diff --git a/src/core/file_sys/vfs_real.cpp b/src/core/file_sys/vfs_real.cpp index 83d28f2b4..1240cabc9 100755 --- a/src/core/file_sys/vfs_real.cpp +++ b/src/core/file_sys/vfs_real.cpp @@ -10,6 +10,7 @@ #include "common/fs/fs.h" #include "common/fs/path_util.h" #include "common/logging/log.h" +#include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" // For FileTimeStampRaw @@ -72,7 +73,8 @@ VfsEntryType RealVfsFilesystem::GetEntryType(std::string_view path_) const { return VfsEntryType::File; } -VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { +VirtualFile RealVfsFilesystem::OpenFileFromEntry(std::string_view path_, std::optional size, + Mode perms) { const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); if (auto it = cache.find(path); it != cache.end()) { @@ -81,20 +83,24 @@ VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { } } - if (!FS::Exists(path) || !FS::IsFile(path)) { + if (!size && !FS::IsFile(path)) { return nullptr; } auto reference = std::make_unique(); this->InsertReferenceIntoList(*reference); - auto file = - std::shared_ptr(new RealVfsFile(*this, std::move(reference), path, perms)); + auto file = std::shared_ptr( + new RealVfsFile(*this, std::move(reference), path, perms, size)); cache[path] = file; return file; } +VirtualFile RealVfsFilesystem::OpenFile(std::string_view path_, Mode perms) { + return OpenFileFromEntry(path_, {}, perms); +} + VirtualFile RealVfsFilesystem::CreateFile(std::string_view path_, Mode perms) { const auto path = FS::SanitizePath(path_, FS::DirectorySeparator::PlatformDefault); cache.erase(path); @@ -243,10 +249,10 @@ void RealVfsFilesystem::RemoveReferenceFromList(FileReference& reference) { } RealVfsFile::RealVfsFile(RealVfsFilesystem& base_, std::unique_ptr reference_, - const std::string& path_, Mode perms_) + const std::string& path_, Mode perms_, std::optional size_) : base(base_), reference(std::move(reference_)), path(path_), parent_path(FS::GetParentPath(path_)), path_components(FS::SplitPathComponents(path_)), - perms(perms_) {} + size(size_), perms(perms_) {} RealVfsFile::~RealVfsFile() { base.DropReference(std::move(reference)); @@ -257,11 +263,14 @@ std::string RealVfsFile::GetName() const { } std::size_t RealVfsFile::GetSize() const { - base.RefreshReference(path, perms, *reference); - return reference->file ? reference->file->GetSize() : 0; + if (size) { + return *size; + } + return FS::GetSize(path); } bool RealVfsFile::Resize(std::size_t new_size) { + size.reset(); base.RefreshReference(path, perms, *reference); return reference->file ? reference->file->SetSize(new_size) : false; } @@ -287,6 +296,7 @@ std::size_t RealVfsFile::Read(u8* data, std::size_t length, std::size_t offset) } std::size_t RealVfsFile::Write(const u8* data, std::size_t length, std::size_t offset) { + size.reset(); base.RefreshReference(path, perms, *reference); if (!reference->file || !reference->file->Seek(static_cast(offset))) { return 0; @@ -309,10 +319,11 @@ std::vector RealVfsDirectory::IterateEntries( std::vector out; - const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) { - const auto full_path_string = FS::PathToUTF8String(full_path); + const FS::DirEntryCallable callback = [this, + &out](const std::filesystem::directory_entry& entry) { + const auto full_path_string = FS::PathToUTF8String(entry.path()); - out.emplace_back(base.OpenFile(full_path_string, perms)); + out.emplace_back(base.OpenFileFromEntry(full_path_string, entry.file_size(), perms)); return true; }; @@ -330,8 +341,9 @@ std::vector RealVfsDirectory::IterateEntries out; - const FS::DirEntryCallable callback = [this, &out](const std::filesystem::path& full_path) { - const auto full_path_string = FS::PathToUTF8String(full_path); + const FS::DirEntryCallable callback = [this, + &out](const std::filesystem::directory_entry& entry) { + const auto full_path_string = FS::PathToUTF8String(entry.path()); out.emplace_back(base.OpenDirectory(full_path_string, perms)); @@ -483,12 +495,10 @@ std::map> RealVfsDirectory::GetEntries() std::map> out; - const FS::DirEntryCallable callback = [&out](const std::filesystem::path& full_path) { - const auto filename = FS::PathToUTF8String(full_path.filename()); - + const FS::DirEntryCallable callback = [&out](const std::filesystem::directory_entry& entry) { + const auto filename = FS::PathToUTF8String(entry.path().filename()); out.insert_or_assign(filename, - FS::IsDir(full_path) ? VfsEntryType::Directory : VfsEntryType::File); - + entry.is_directory() ? VfsEntryType::Directory : VfsEntryType::File); return true; }; diff --git a/src/core/file_sys/vfs_real.h b/src/core/file_sys/vfs_real.h index fcf2c9a25..28d899cf7 100755 --- a/src/core/file_sys/vfs_real.h +++ b/src/core/file_sys/vfs_real.h @@ -4,6 +4,7 @@ #pragma once #include +#include #include #include "common/intrusive_list.h" #include "core/file_sys/mode.h" @@ -20,6 +21,8 @@ struct FileReference : public Common::IntrusiveListBaseNode { }; class RealVfsFile; +class RealVfsDirectory; + class RealVfsFilesystem : public VfsFilesystem { public: RealVfsFilesystem(); @@ -56,6 +59,11 @@ private: private: void InsertReferenceIntoList(FileReference& reference); void RemoveReferenceFromList(FileReference& reference); + +private: + friend class RealVfsDirectory; + VirtualFile OpenFileFromEntry(std::string_view path, std::optional size, + Mode perms = Mode::Read); }; // An implementation of VfsFile that represents a file on the user's computer. @@ -78,13 +86,14 @@ public: private: RealVfsFile(RealVfsFilesystem& base, std::unique_ptr reference, - const std::string& path, Mode perms = Mode::Read); + const std::string& path, Mode perms = Mode::Read, std::optional size = {}); RealVfsFilesystem& base; std::unique_ptr reference; std::string path; std::string parent_path; std::vector path_components; + std::optional size; Mode perms; }; diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 48ab1bec8..827eb7a06 100755 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -149,12 +149,16 @@ void EmulatedController::LoadDevices() { camera_params[0] = right_joycon; camera_params[0].Set("camera", true); - camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"}; - ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"}; - nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"}; nfc_params[1] = right_joycon; nfc_params[1].Set("nfc", true); + // Only map virtual devices to the first controller + if (npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld) { + camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"}; + ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"}; + nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"}; + } + output_params[LeftIndex] = left_joycon; output_params[RightIndex] = right_joycon; output_params[2] = camera_params[1]; @@ -1176,10 +1180,7 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) { return; } - controller.nfc_state = { - controller.nfc_values.state, - controller.nfc_values.data, - }; + controller.nfc_state = controller.nfc_values; } bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { @@ -1308,6 +1309,73 @@ bool EmulatedController::HasNfc() const { return is_connected && (has_virtual_nfc && is_virtual_nfc_supported); } +bool EmulatedController::AddNfcHandle() { + nfc_handles++; + return SetPollingMode(EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::NFC) == + Common::Input::DriverResult::Success; +} + +bool EmulatedController::RemoveNfcHandle() { + nfc_handles--; + if (nfc_handles <= 0) { + return SetPollingMode(EmulatedDeviceIndex::RightIndex, + Common::Input::PollingMode::Active) == + Common::Input::DriverResult::Success; + } + return true; +} + +bool EmulatedController::StartNfcPolling() { + auto& nfc_output_device = output_devices[static_cast(DeviceIndex::Right)]; + auto& nfc_virtual_output_device = output_devices[3]; + + return nfc_output_device->StartNfcPolling() == Common::Input::NfcState::Success || + nfc_virtual_output_device->StartNfcPolling() == Common::Input::NfcState::Success; +} + +bool EmulatedController::StopNfcPolling() { + auto& nfc_output_device = output_devices[static_cast(DeviceIndex::Right)]; + auto& nfc_virtual_output_device = output_devices[3]; + + return nfc_output_device->StopNfcPolling() == Common::Input::NfcState::Success || + nfc_virtual_output_device->StopNfcPolling() == Common::Input::NfcState::Success; +} + +bool EmulatedController::ReadAmiiboData(std::vector& data) { + auto& nfc_output_device = output_devices[static_cast(DeviceIndex::Right)]; + auto& nfc_virtual_output_device = output_devices[3]; + + if (nfc_output_device->ReadAmiiboData(data) == Common::Input::NfcState::Success) { + return true; + } + + return nfc_virtual_output_device->ReadAmiiboData(data) == Common::Input::NfcState::Success; +} + +bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request, + Common::Input::MifareRequest& out_data) { + auto& nfc_output_device = output_devices[static_cast(DeviceIndex::Right)]; + auto& nfc_virtual_output_device = output_devices[3]; + + if (nfc_output_device->ReadMifareData(request, out_data) == Common::Input::NfcState::Success) { + return true; + } + + return nfc_virtual_output_device->ReadMifareData(request, out_data) == + Common::Input::NfcState::Success; +} + +bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) { + auto& nfc_output_device = output_devices[static_cast(DeviceIndex::Right)]; + auto& nfc_virtual_output_device = output_devices[3]; + + if (nfc_output_device->WriteMifareData(request) == Common::Input::NfcState::Success) { + return true; + } + + return nfc_virtual_output_device->WriteMifareData(request) == Common::Input::NfcState::Success; +} + bool EmulatedController::WriteNfc(const std::vector& data) { auto& nfc_output_device = output_devices[static_cast(DeviceIndex::Right)]; auto& nfc_virtual_output_device = output_devices[3]; diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index 7e1c0b8d6..f78543d3e 100755 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h @@ -97,10 +97,7 @@ struct RingSensorForce { f32 force; }; -struct NfcState { - Common::Input::NfcState state{}; - std::vector data{}; -}; +using NfcState = Common::Input::NfcStatus; struct ControllerMotion { Common::Vec3f accel{}; @@ -393,9 +390,31 @@ public: /// Returns true if the device has nfc support bool HasNfc() const; + /// Sets the joycon in nfc mode and increments the handle count + bool AddNfcHandle(); + + /// Decrements the handle count if zero sets the joycon in active mode + bool RemoveNfcHandle(); + + /// Start searching for nfc tags + bool StartNfcPolling(); + + /// Stop searching for nfc tags + bool StopNfcPolling(); + + /// Returns true if the nfc tag was readable + bool ReadAmiiboData(std::vector& data); + /// Returns true if the nfc tag was written bool WriteNfc(const std::vector& data); + /// Returns true if the nfc tag was readable + bool ReadMifareData(const Common::Input::MifareRequest& request, + Common::Input::MifareRequest& out_data); + + /// Returns true if the nfc tag was written + bool WriteMifareData(const Common::Input::MifareRequest& request); + /// Returns the led pattern corresponding to this emulated controller LedPattern GetLedPattern() const; @@ -532,6 +551,7 @@ private: bool system_buttons_enabled{true}; f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard}; u32 turbo_button_state{0}; + std::size_t nfc_handles{0}; // Temporary values to avoid doing changes while the controller is in configuring mode NpadStyleIndex tmp_npad_type{NpadStyleIndex::None}; diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index c4f11c00e..cb06865f4 100755 --- a/src/core/hid/input_converter.cpp +++ b/src/core/hid/input_converter.cpp @@ -299,11 +299,7 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal Common::Input::NfcStatus nfc{}; switch (callback.type) { case Common::Input::InputType::Nfc: - nfc = { - .state = callback.nfc_status, - .data = callback.raw_data, - }; - break; + return callback.nfc_status; default: LOG_ERROR(Input, "Conversion from type {} to NFC not implemented", callback.type); break; diff --git a/src/core/hle/service/am/applets/applet_cabinet.cpp b/src/core/hle/service/am/applets/applet_cabinet.cpp index 8b754e9d4..19ed184e8 100755 --- a/src/core/hle/service/am/applets/applet_cabinet.cpp +++ b/src/core/hle/service/am/applets/applet_cabinet.cpp @@ -141,7 +141,7 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name) applet_output.device_handle = applet_input_common.device_handle; applet_output.result = CabinetResult::Cancel; const auto reg_result = nfp_device->GetRegisterInfo(applet_output.register_info); - const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info, false); + const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info); nfp_device->Finalize(); if (reg_result.IsSuccess()) { diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp index f4b180b06..5bf289818 100755 --- a/src/core/hle/service/nfc/common/device.cpp +++ b/src/core/hle/service/nfc/common/device.cpp @@ -93,7 +93,8 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { const auto nfc_status = npad_device->GetNfc(); switch (nfc_status.state) { case Common::Input::NfcState::NewAmiibo: - LoadNfcTag(nfc_status.data); + LoadNfcTag(nfc_status.protocol, nfc_status.tag_type, nfc_status.uuid_length, + nfc_status.uuid); break; case Common::Input::NfcState::AmiiboRemoved: if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) { @@ -108,28 +109,46 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { } } -bool NfcDevice::LoadNfcTag(std::span data) { +bool NfcDevice::LoadNfcTag(u8 protocol, u8 tag_type, u8 uuid_length, UniqueSerialNumber uuid) { if (device_state != DeviceState::SearchingForTag) { LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state); return false; } + if ((protocol & static_cast(allowed_protocols)) == 0) { + LOG_ERROR(Service_NFC, "Protocol not supported {}", protocol); + return false; + } + + real_tag_info = { + .uuid = uuid, + .uuid_length = uuid_length, + .protocol = static_cast(protocol), + .tag_type = static_cast(tag_type), + }; + + device_state = DeviceState::TagFound; + deactivate_event->GetReadableEvent().Clear(); + activate_event->Signal(); + return true; +} + +bool NfcDevice::LoadAmiiboData() { + std::vector data{}; + + if (!npad_device->ReadAmiiboData(data)) { + return false; + } + if (data.size() < sizeof(NFP::EncryptedNTAG215File)) { LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size()); return false; } - mifare_data.resize(data.size()); - memcpy(mifare_data.data(), data.data(), data.size()); - memcpy(&tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); is_plain_amiibo = NFP::AmiiboCrypto::IsAmiiboValid(tag_data); is_write_protected = false; - device_state = DeviceState::TagFound; - deactivate_event->GetReadableEvent().Clear(); - activate_event->Signal(); - // Fallback for plain amiibos if (is_plain_amiibo) { LOG_INFO(Service_NFP, "Using plain amiibo"); @@ -147,6 +166,7 @@ bool NfcDevice::LoadNfcTag(std::span data) { return true; } + LOG_INFO(Service_NFP, "Using encrypted amiibo"); tag_data = {}; memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File)); return true; @@ -162,7 +182,6 @@ void NfcDevice::CloseNfcTag() { device_state = DeviceState::TagRemoved; encrypted_tag_data = {}; tag_data = {}; - mifare_data = {}; activate_event->GetReadableEvent().Clear(); deactivate_event->Signal(); } @@ -179,8 +198,12 @@ void NfcDevice::Initialize() { device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable; encrypted_tag_data = {}; tag_data = {}; - mifare_data = {}; - is_initalized = true; + + if (device_state != DeviceState::Initialized) { + return; + } + + is_initalized = npad_device->AddNfcHandle(); } void NfcDevice::Finalize() { @@ -190,6 +213,11 @@ void NfcDevice::Finalize() { if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { StopDetection(); } + + if (device_state != DeviceState::Unavailable) { + npad_device->RemoveNfcHandle(); + } + device_state = DeviceState::Unavailable; is_initalized = false; } @@ -200,10 +228,8 @@ Result NfcDevice::StartDetection(NfcProtocol allowed_protocol) { return ResultWrongDeviceState; } - if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, - Common::Input::PollingMode::NFC) != - Common::Input::DriverResult::Success) { - LOG_ERROR(Service_NFC, "Nfc not supported"); + if (!npad_device->StartNfcPolling()) { + LOG_ERROR(Service_NFC, "Nfc polling not supported"); return ResultNfcDisabled; } @@ -213,9 +239,6 @@ Result NfcDevice::StartDetection(NfcProtocol allowed_protocol) { } Result NfcDevice::StopDetection() { - npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex, - Common::Input::PollingMode::Active); - if (device_state == DeviceState::Initialized) { return ResultSuccess; } @@ -225,6 +248,7 @@ Result NfcDevice::StopDetection() { } if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { + npad_device->StopNfcPolling(); device_state = DeviceState::Initialized; return ResultSuccess; } @@ -233,7 +257,7 @@ Result NfcDevice::StopDetection() { return ResultWrongDeviceState; } -Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const { +Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info) const { if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); if (device_state == DeviceState::TagRemoved) { @@ -242,41 +266,15 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const { return ResultWrongDeviceState; } - UniqueSerialNumber uuid{}; - u8 uuid_length{}; - NfcProtocol protocol{NfcProtocol::TypeA}; - TagType tag_type{TagType::Type2}; + tag_info = real_tag_info; - if (is_mifare) { - tag_type = TagType::Mifare; - uuid_length = sizeof(NFP::NtagTagUuid); - memcpy(uuid.data(), mifare_data.data(), uuid_length); - } else { - tag_type = TagType::Type2; - uuid_length = sizeof(NFP::NtagTagUuid); - NFP::NtagTagUuid nUuid{ - .part1 = encrypted_tag_data.uuid.part1, - .part2 = encrypted_tag_data.uuid.part2, - .nintendo_id = encrypted_tag_data.uuid.nintendo_id, - }; - memcpy(uuid.data(), &nUuid, uuid_length); - - // Generate random UUID to bypass amiibo load limits - if (Settings::values.random_amiibo_id) { - Common::TinyMT rng{}; - rng.Initialize(static_cast(GetCurrentPosixTime())); - rng.GenerateRandomBytes(uuid.data(), uuid_length); - } + // Generate random UUID to bypass amiibo load limits + if (real_tag_info.tag_type == TagType::Type2 && Settings::values.random_amiibo_id) { + Common::TinyMT rng{}; + rng.Initialize(static_cast(GetCurrentPosixTime())); + rng.GenerateRandomBytes(tag_info.uuid.data(), tag_info.uuid_length); } - // Protocol and tag type may change here - tag_info = { - .uuid = uuid, - .uuid_length = uuid_length, - .protocol = protocol, - .tag_type = tag_type, - }; - return ResultSuccess; } @@ -293,7 +291,7 @@ Result NfcDevice::ReadMifare(std::span parameter Result result = ResultSuccess; TagInfo tag_info{}; - result = GetTagInfo(tag_info, true); + result = GetTagInfo(tag_info); if (result.IsError()) { return result; @@ -307,6 +305,8 @@ Result NfcDevice::ReadMifare(std::span parameter return ResultInvalidArgument; } + Common::Input::MifareRequest request{}; + Common::Input::MifareRequest out_data{}; const auto unknown = parameters[0].sector_key.unknown; for (std::size_t i = 0; i < parameters.size(); i++) { if (unknown != parameters[i].sector_key.unknown) { @@ -315,25 +315,29 @@ Result NfcDevice::ReadMifare(std::span parameter } for (std::size_t i = 0; i < parameters.size(); i++) { - result = ReadMifare(parameters[i], read_block_data[i]); - if (result.IsError()) { - break; + if (parameters[i].sector_key.command == MifareCmd::None) { + continue; } + request.data[i].command = static_cast(parameters[i].sector_key.command); + request.data[i].sector = parameters[i].sector_number; + memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(), + sizeof(KeyData)); } - return result; -} - -Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter, - MifareReadBlockData& read_block_data) const { - const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock); - read_block_data.sector_number = parameter.sector_number; - if (mifare_data.size() < sector_index + sizeof(DataBlock)) { + if (!npad_device->ReadMifareData(request, out_data)) { return ResultMifareError288; } - // TODO: Use parameter.sector_key to read encrypted data - memcpy(read_block_data.data.data(), mifare_data.data() + sector_index, sizeof(DataBlock)); + for (std::size_t i = 0; i < read_block_data.size(); i++) { + if (static_cast(out_data.data[i].command) == MifareCmd::None) { + continue; + } + + read_block_data[i] = { + .data = out_data.data[i].data, + .sector_number = out_data.data[i].sector, + }; + } return ResultSuccess; } @@ -342,7 +346,7 @@ Result NfcDevice::WriteMifare(std::span paramet Result result = ResultSuccess; TagInfo tag_info{}; - result = GetTagInfo(tag_info, true); + result = GetTagInfo(tag_info); if (result.IsError()) { return result; @@ -363,42 +367,25 @@ Result NfcDevice::WriteMifare(std::span paramet } } + Common::Input::MifareRequest request{}; for (std::size_t i = 0; i < parameters.size(); i++) { - result = WriteMifare(parameters[i]); - if (result.IsError()) { - break; + if (parameters[i].sector_key.command == MifareCmd::None) { + continue; } + request.data[i].command = static_cast(parameters[i].sector_key.command); + request.data[i].sector = parameters[i].sector_number; + memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(), + sizeof(KeyData)); + memcpy(request.data[i].data.data(), parameters[i].data.data(), sizeof(KeyData)); } - if (!npad_device->WriteNfc(mifare_data)) { - LOG_ERROR(Service_NFP, "Error writing to file"); + if (!npad_device->WriteMifareData(request)) { return ResultMifareError288; } return result; } -Result NfcDevice::WriteMifare(const MifareWriteBlockParameter& parameter) { - const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock); - - if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { - LOG_ERROR(Service_NFC, "Wrong device state {}", device_state); - if (device_state == DeviceState::TagRemoved) { - return ResultTagRemoved; - } - return ResultWrongDeviceState; - } - - if (mifare_data.size() < sector_index + sizeof(DataBlock)) { - return ResultMifareError288; - } - - // TODO: Use parameter.sector_key to encrypt the data - memcpy(mifare_data.data() + sector_index, parameter.data.data(), sizeof(DataBlock)); - - return ResultSuccess; -} - Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout, std::span command_data, std::span out_data) { @@ -412,6 +399,11 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target return ResultWrongDeviceState; } + if (!LoadAmiiboData()) { + LOG_ERROR(Service_NFP, "Not an amiibo"); + return ResultInvalidTagType; + } + if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { LOG_ERROR(Service_NFP, "Not an amiibo"); return ResultInvalidTagType; @@ -562,7 +554,7 @@ Result NfcDevice::Restore() { NFC::TagInfo tag_info{}; std::array data{}; - Result result = GetTagInfo(tag_info, false); + Result result = GetTagInfo(tag_info); if (result.IsError()) { return result; @@ -635,7 +627,7 @@ Result NfcDevice::GetCommonInfo(NFP::CommonInfo& common_info) const { // TODO: Validate this data common_info = { .last_write_date = settings.write_date.GetWriteDate(), - .write_counter = tag_data.write_counter, + .write_counter = tag_data.application_write_counter, .version = tag_data.amiibo_version, .application_area_size = sizeof(NFP::ApplicationArea), }; diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h index 7560210d6..0ed1ff34c 100755 --- a/src/core/hle/service/nfc/common/device.h +++ b/src/core/hle/service/nfc/common/device.h @@ -42,15 +42,12 @@ public: Result StartDetection(NfcProtocol allowed_protocol); Result StopDetection(); - Result GetTagInfo(TagInfo& tag_info, bool is_mifare) const; + Result GetTagInfo(TagInfo& tag_info) const; Result ReadMifare(std::span parameters, std::span read_block_data) const; - Result ReadMifare(const MifareReadBlockParameter& parameter, - MifareReadBlockData& read_block_data) const; Result WriteMifare(std::span parameters); - Result WriteMifare(const MifareWriteBlockParameter& parameter); Result SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout, std::span command_data, std::span out_data); @@ -105,7 +102,8 @@ public: private: void NpadUpdate(Core::HID::ControllerTriggerType type); - bool LoadNfcTag(std::span data); + bool LoadNfcTag(u8 protocol, u8 tag_type, u8 uuid_length, UniqueSerialNumber uuid); + bool LoadAmiiboData(); void CloseNfcTag(); NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const; @@ -140,8 +138,8 @@ private: bool is_write_protected{}; NFP::MountTarget mount_target{NFP::MountTarget::None}; + TagInfo real_tag_info{}; NFP::NTAG215File tag_data{}; - std::vector mifare_data{}; NFP::EncryptedNTAG215File encrypted_tag_data{}; }; diff --git a/src/core/hle/service/nfc/common/device_manager.cpp b/src/core/hle/service/nfc/common/device_manager.cpp index b0456508e..562f3a28e 100755 --- a/src/core/hle/service/nfc/common/device_manager.cpp +++ b/src/core/hle/service/nfc/common/device_manager.cpp @@ -29,6 +29,9 @@ DeviceManager::DeviceManager(Core::System& system_, KernelHelpers::ServiceContex } DeviceManager ::~DeviceManager() { + if (is_initialized) { + Finalize(); + } service_context.CloseEvent(availability_change_event); } @@ -125,14 +128,14 @@ Result DeviceManager::StopDetection(u64 device_handle) { return result; } -Result DeviceManager::GetTagInfo(u64 device_handle, TagInfo& tag_info, bool is_mifare) const { +Result DeviceManager::GetTagInfo(u64 device_handle, TagInfo& tag_info) const { std::scoped_lock lock{mutex}; std::shared_ptr device = nullptr; auto result = GetDeviceHandle(device_handle, device); if (result.IsSuccess()) { - result = device->GetTagInfo(tag_info, is_mifare); + result = device->GetTagInfo(tag_info); result = VerifyDeviceResult(device, result); } @@ -546,7 +549,7 @@ Result DeviceManager::ReadBackupData(u64 device_handle, std::span data) cons NFC::TagInfo tag_info{}; if (result.IsSuccess()) { - result = device->GetTagInfo(tag_info, false); + result = device->GetTagInfo(tag_info); } if (result.IsSuccess()) { @@ -565,7 +568,7 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span dat NFC::TagInfo tag_info{}; if (result.IsSuccess()) { - result = device->GetTagInfo(tag_info, false); + result = device->GetTagInfo(tag_info); } if (result.IsSuccess()) { diff --git a/src/core/hle/service/nfc/common/device_manager.h b/src/core/hle/service/nfc/common/device_manager.h index 2971e280f..c61ba0cf3 100755 --- a/src/core/hle/service/nfc/common/device_manager.h +++ b/src/core/hle/service/nfc/common/device_manager.h @@ -33,7 +33,7 @@ public: Kernel::KReadableEvent& AttachAvailabilityChangeEvent() const; Result StartDetection(u64 device_handle, NfcProtocol tag_protocol); Result StopDetection(u64 device_handle); - Result GetTagInfo(u64 device_handle, NFP::TagInfo& tag_info, bool is_mifare) const; + Result GetTagInfo(u64 device_handle, NFP::TagInfo& tag_info) const; Kernel::KReadableEvent& AttachActivateEvent(u64 device_handle) const; Kernel::KReadableEvent& AttachDeactivateEvent(u64 device_handle) const; Result ReadMifare(u64 device_handle, diff --git a/src/core/hle/service/nfc/mifare_types.h b/src/core/hle/service/nfc/mifare_types.h index 75b59f021..467937399 100755 --- a/src/core/hle/service/nfc/mifare_types.h +++ b/src/core/hle/service/nfc/mifare_types.h @@ -11,9 +11,10 @@ namespace Service::NFC { enum class MifareCmd : u8 { + None = 0x00, + Read = 0x30, AuthA = 0x60, AuthB = 0x61, - Read = 0x30, Write = 0xA0, Transfer = 0xB0, Decrement = 0xC0, @@ -35,17 +36,17 @@ static_assert(sizeof(SectorKey) == 0x10, "SectorKey is an invalid size"); // This is nn::nfc::MifareReadBlockParameter struct MifareReadBlockParameter { - u8 sector_number; + u8 sector_number{}; INSERT_PADDING_BYTES(0x7); - SectorKey sector_key; + SectorKey sector_key{}; }; static_assert(sizeof(MifareReadBlockParameter) == 0x18, "MifareReadBlockParameter is an invalid size"); // This is nn::nfc::MifareReadBlockData struct MifareReadBlockData { - DataBlock data; - u8 sector_number; + DataBlock data{}; + u8 sector_number{}; INSERT_PADDING_BYTES(0x7); }; static_assert(sizeof(MifareReadBlockData) == 0x18, "MifareReadBlockData is an invalid size"); diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp index 130fb7f78..e7ca7582e 100755 --- a/src/core/hle/service/nfc/nfc_interface.cpp +++ b/src/core/hle/service/nfc/nfc_interface.cpp @@ -174,8 +174,7 @@ void NfcInterface::GetTagInfo(HLERequestContext& ctx) { LOG_INFO(Service_NFC, "called, device_handle={}", device_handle); TagInfo tag_info{}; - auto result = - GetManager()->GetTagInfo(device_handle, tag_info, backend_type == BackendType::Mifare); + auto result = GetManager()->GetTagInfo(device_handle, tag_info); result = TranslateResultToServiceError(result); if (result.IsSuccess()) { @@ -216,8 +215,8 @@ void NfcInterface::ReadMifare(HLERequestContext& ctx) { memcpy(read_commands.data(), buffer.data(), number_of_commands * sizeof(MifareReadBlockParameter)); - LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, read_commands_size={}", - device_handle, number_of_commands); + LOG_INFO(Service_NFC, "called, device_handle={}, read_commands_size={}", device_handle, + number_of_commands); std::vector out_data(number_of_commands); auto result = GetManager()->ReadMifare(device_handle, read_commands, out_data); diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp index b2b5677c8..52494e0d9 100755 --- a/src/input_common/drivers/joycon.cpp +++ b/src/input_common/drivers/joycon.cpp @@ -195,8 +195,8 @@ void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) { OnMotionUpdate(port, type, id, value); }}, .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }}, - .on_amiibo_data = {[this, port, type](const std::vector& amiibo_data) { - OnAmiiboUpdate(port, type, amiibo_data); + .on_amiibo_data = {[this, port, type](const Joycon::TagInfo& tag_info) { + OnAmiiboUpdate(port, type, tag_info); }}, .on_camera_data = {[this, port](const std::vector& camera_data, Joycon::IrsResolution format) { @@ -291,13 +291,105 @@ Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) c return Common::Input::NfcState::Success; }; +Common::Input::NfcState Joycons::StartNfcPolling(const PadIdentifier& identifier) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::NfcState::Unknown; + } + return TranslateDriverResult(handle->StartNfcPolling()); +}; + +Common::Input::NfcState Joycons::StopNfcPolling(const PadIdentifier& identifier) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::NfcState::Unknown; + } + return TranslateDriverResult(handle->StopNfcPolling()); +}; + +Common::Input::NfcState Joycons::ReadAmiiboData(const PadIdentifier& identifier, + std::vector& out_data) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::NfcState::Unknown; + } + return TranslateDriverResult(handle->ReadAmiiboData(out_data)); +} + Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier, const std::vector& data) { auto handle = GetHandle(identifier); - if (handle->WriteNfcData(data) != Joycon::DriverResult::Success) { - return Common::Input::NfcState::WriteFailed; + if (handle == nullptr) { + return Common::Input::NfcState::Unknown; } - return Common::Input::NfcState::Success; + return TranslateDriverResult(handle->WriteNfcData(data)); +}; + +Common::Input::NfcState Joycons::ReadMifareData(const PadIdentifier& identifier, + const Common::Input::MifareRequest& request, + Common::Input::MifareRequest& data) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::NfcState::Unknown; + } + + const auto command = static_cast(request.data[0].command); + std::vector read_request{}; + for (const auto& request_data : request.data) { + if (request_data.command == 0) { + continue; + } + Joycon::MifareReadChunk chunk = { + .command = command, + .sector_key = {}, + .sector = request_data.sector, + }; + memcpy(chunk.sector_key.data(), request_data.key.data(), + sizeof(Joycon::MifareReadChunk::sector_key)); + read_request.emplace_back(chunk); + } + + std::vector read_data(read_request.size()); + const auto result = handle->ReadMifareData(read_request, read_data); + if (result == Joycon::DriverResult::Success) { + for (std::size_t i = 0; i < read_request.size(); i++) { + data.data[i] = { + .command = static_cast(command), + .sector = read_data[i].sector, + .key = {}, + .data = read_data[i].data, + }; + } + } + return TranslateDriverResult(result); +}; + +Common::Input::NfcState Joycons::WriteMifareData(const PadIdentifier& identifier, + const Common::Input::MifareRequest& request) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::NfcState::Unknown; + } + + const auto command = static_cast(request.data[0].command); + std::vector write_request{}; + for (const auto& request_data : request.data) { + if (request_data.command == 0) { + continue; + } + Joycon::MifareWriteChunk chunk = { + .command = command, + .sector_key = {}, + .sector = request_data.sector, + .data = {}, + }; + memcpy(chunk.sector_key.data(), request_data.key.data(), + sizeof(Joycon::MifareReadChunk::sector_key)); + memcpy(chunk.data.data(), request_data.data.data(), sizeof(Joycon::MifareWriteChunk::data)); + write_request.emplace_back(chunk); + } + + return TranslateDriverResult(handle->WriteMifareData(write_request)); }; Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier, @@ -403,11 +495,20 @@ void Joycons::OnRingConUpdate(f32 ring_data) { } void Joycons::OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type, - const std::vector& amiibo_data) { + const Joycon::TagInfo& tag_info) { const auto identifier = GetIdentifier(port, type); - const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved - : Common::Input::NfcState::NewAmiibo; - SetNfc(identifier, {nfc_state, amiibo_data}); + const auto nfc_state = tag_info.uuid_length == 0 ? Common::Input::NfcState::AmiiboRemoved + : Common::Input::NfcState::NewAmiibo; + + const Common::Input::NfcStatus nfc_status{ + .state = nfc_state, + .uuid_length = tag_info.uuid_length, + .protocol = tag_info.protocol, + .tag_type = tag_info.tag_type, + .uuid = tag_info.uuid, + }; + + SetNfc(identifier, nfc_status); } void Joycons::OnCameraUpdate(std::size_t port, const std::vector& camera_data, @@ -726,4 +827,18 @@ std::string Joycons::JoyconName(Joycon::ControllerType type) const { return "Unknown Switch Controller"; } } + +Common::Input::NfcState Joycons::TranslateDriverResult(Joycon::DriverResult result) const { + switch (result) { + case Joycon::DriverResult::Success: + return Common::Input::NfcState::Success; + case Joycon::DriverResult::Disabled: + return Common::Input::NfcState::WrongDeviceState; + case Joycon::DriverResult::NotSupported: + return Common::Input::NfcState::NotSupported; + default: + return Common::Input::NfcState::Unknown; + } +} + } // namespace InputCommon diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h index e3f0ad78f..4c323d7d6 100755 --- a/src/input_common/drivers/joycon.h +++ b/src/input_common/drivers/joycon.h @@ -15,6 +15,7 @@ using SerialNumber = std::array; struct Battery; struct Color; struct MotionData; +struct TagInfo; enum class ControllerType : u8; enum class DriverResult; enum class IrsResolution; @@ -39,9 +40,18 @@ public: Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier, Common::Input::CameraFormat camera_format) override; - Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; - Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_, + Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier) const override; + Common::Input::NfcState StartNfcPolling(const PadIdentifier& identifier) override; + Common::Input::NfcState StopNfcPolling(const PadIdentifier& identifier) override; + Common::Input::NfcState ReadAmiiboData(const PadIdentifier& identifier, + std::vector& out_data) override; + Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier, const std::vector& data) override; + Common::Input::NfcState ReadMifareData(const PadIdentifier& identifier, + const Common::Input::MifareRequest& request, + Common::Input::MifareRequest& out_data) override; + Common::Input::NfcState WriteMifareData(const PadIdentifier& identifier, + const Common::Input::MifareRequest& request) override; Common::Input::DriverResult SetPollingMode( const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override; @@ -82,7 +92,7 @@ private: const Joycon::MotionData& value); void OnRingConUpdate(f32 ring_data); void OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type, - const std::vector& amiibo_data); + const Joycon::TagInfo& amiibo_data); void OnCameraUpdate(std::size_t port, const std::vector& camera_data, Joycon::IrsResolution format); @@ -102,6 +112,8 @@ private: /// Returns the name of the device in text format std::string JoyconName(Joycon::ControllerType type) const; + Common::Input::NfcState TranslateDriverResult(Joycon::DriverResult result) const; + std::jthread scan_thread; // Joycon types are split by type to ease supporting dualjoycon configurations diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp index ebf30b60e..a522fdd1c 100755 --- a/src/input_common/drivers/mouse.cpp +++ b/src/input_common/drivers/mouse.cpp @@ -76,9 +76,6 @@ void Mouse::UpdateThread(std::stop_token stop_token) { UpdateStickInput(); UpdateMotionInput(); - if (mouse_panning_timeout++ > 20) { - StopPanning(); - } std::this_thread::sleep_for(std::chrono::milliseconds(update_time)); } } @@ -88,18 +85,45 @@ void Mouse::UpdateStickInput() { return; } - const float sensitivity = - Settings::values.mouse_panning_sensitivity.GetValue() * default_stick_sensitivity; + const float length = last_mouse_change.Length(); - // Slow movement by 4% - last_mouse_change *= 0.96f; - SetAxis(identifier, mouse_axis_x, last_mouse_change.x * sensitivity); - SetAxis(identifier, mouse_axis_y, -last_mouse_change.y * sensitivity); + // Prevent input from exceeding the max range (1.0f) too much, + // but allow some room to make it easier to sustain + if (length > 1.2f) { + last_mouse_change /= length; + last_mouse_change *= 1.2f; + } + + auto mouse_change = last_mouse_change; + + // Bind the mouse change to [0 <= deadzone_counterweight <= 1,1] + if (length < 1.0f) { + const float deadzone_h_counterweight = + Settings::values.mouse_panning_deadzone_x_counterweight.GetValue(); + const float deadzone_v_counterweight = + Settings::values.mouse_panning_deadzone_y_counterweight.GetValue(); + mouse_change /= length; + mouse_change.x *= length + (1 - length) * deadzone_h_counterweight * 0.01f; + mouse_change.y *= length + (1 - length) * deadzone_v_counterweight * 0.01f; + } + + SetAxis(identifier, mouse_axis_x, mouse_change.x); + SetAxis(identifier, mouse_axis_y, -mouse_change.y); + + // Decay input over time + const float clamped_length = std::min(1.0f, length); + const float decay_strength = Settings::values.mouse_panning_decay_strength.GetValue(); + const float decay = 1 - clamped_length * clamped_length * decay_strength * 0.01f; + const float min_decay = Settings::values.mouse_panning_min_decay.GetValue(); + const float clamped_decay = std::min(1 - min_decay / 100.0f, decay); + last_mouse_change *= clamped_decay; } void Mouse::UpdateMotionInput() { - const float sensitivity = - Settings::values.mouse_panning_sensitivity.GetValue() * default_motion_sensitivity; + // This may need its own sensitivity instead of using the average + const float sensitivity = (Settings::values.mouse_panning_x_sensitivity.GetValue() + + Settings::values.mouse_panning_y_sensitivity.GetValue()) / + 2.0f * default_motion_sensitivity; const float rotation_velocity = std::sqrt(last_motion_change.x * last_motion_change.x + last_motion_change.y * last_motion_change.y); @@ -131,49 +155,28 @@ void Mouse::UpdateMotionInput() { void Mouse::Move(int x, int y, int center_x, int center_y) { if (Settings::values.mouse_panning) { - mouse_panning_timeout = 0; - - auto mouse_change = + const auto mouse_change = (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast(); + const float x_sensitivity = + Settings::values.mouse_panning_x_sensitivity.GetValue() * default_stick_sensitivity; + const float y_sensitivity = + Settings::values.mouse_panning_y_sensitivity.GetValue() * default_stick_sensitivity; + last_motion_change += {-mouse_change.y, -mouse_change.x, 0}; - - const auto move_distance = mouse_change.Length(); - if (move_distance == 0) { - return; - } - - // Make slow movements at least 3 units on length - if (move_distance < 3.0f) { - // Normalize value - mouse_change /= move_distance; - mouse_change *= 3.0f; - } - - // Average mouse movements - last_mouse_change = (last_mouse_change * 0.91f) + (mouse_change * 0.09f); - - const auto last_move_distance = last_mouse_change.Length(); - - // Make fast movements clamp to 8 units on length - if (last_move_distance > 8.0f) { - // Normalize value - last_mouse_change /= last_move_distance; - last_mouse_change *= 8.0f; - } - - // Ignore average if it's less than 1 unit and use current movement value - if (last_move_distance < 1.0f) { - last_mouse_change = mouse_change / mouse_change.Length(); - } + last_mouse_change.x += mouse_change.x * x_sensitivity * 0.09f; + last_mouse_change.y += mouse_change.y * y_sensitivity * 0.09f; return; } if (button_pressed) { const auto mouse_move = Common::MakeVec(x, y) - mouse_origin; - const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f; - SetAxis(identifier, mouse_axis_x, static_cast(mouse_move.x) * sensitivity); - SetAxis(identifier, mouse_axis_y, static_cast(-mouse_move.y) * sensitivity); + const float x_sensitivity = Settings::values.mouse_panning_x_sensitivity.GetValue(); + const float y_sensitivity = Settings::values.mouse_panning_y_sensitivity.GetValue(); + SetAxis(identifier, mouse_axis_x, + static_cast(mouse_move.x) * x_sensitivity * 0.0012f); + SetAxis(identifier, mouse_axis_y, + static_cast(-mouse_move.y) * y_sensitivity * 0.0012f); last_motion_change = { static_cast(-mouse_move.y) / 50.0f, @@ -241,10 +244,6 @@ void Mouse::ReleaseAllButtons() { button_pressed = false; } -void Mouse::StopPanning() { - last_mouse_change = {}; -} - std::vector Mouse::GetInputDevices() const { std::vector devices; devices.emplace_back(Common::ParamPackage{ diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h index 4fa2210db..f51c4d00b 100755 --- a/src/input_common/drivers/mouse.h +++ b/src/input_common/drivers/mouse.h @@ -98,7 +98,6 @@ private: void UpdateThread(std::stop_token stop_token); void UpdateStickInput(); void UpdateMotionInput(); - void StopPanning(); Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; @@ -108,7 +107,6 @@ private: Common::Vec3 last_motion_change; Common::Vec2 wheel_position; bool button_pressed; - int mouse_panning_timeout{}; std::jthread update_thread; }; diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp index 4bf9d9fed..b768722f6 100755 --- a/src/input_common/drivers/virtual_amiibo.cpp +++ b/src/input_common/drivers/virtual_amiibo.cpp @@ -29,14 +29,13 @@ Common::Input::DriverResult VirtualAmiibo::SetPollingMode( switch (polling_mode) { case Common::Input::PollingMode::NFC: - if (state == State::Initialized) { - state = State::WaitingForAmiibo; - } + state = State::Initialized; return Common::Input::DriverResult::Success; default: - if (state == State::AmiiboIsOpen) { + if (state == State::TagNearby) { CloseAmiibo(); } + state = State::Disabled; return Common::Input::DriverResult::NotSupported; } } @@ -45,6 +44,39 @@ Common::Input::NfcState VirtualAmiibo::SupportsNfc( [[maybe_unused]] const PadIdentifier& identifier_) const { return Common::Input::NfcState::Success; } +Common::Input::NfcState VirtualAmiibo::StartNfcPolling(const PadIdentifier& identifier_) { + if (state != State::Initialized) { + return Common::Input::NfcState::WrongDeviceState; + } + state = State::WaitingForAmiibo; + return Common::Input::NfcState::Success; +} + +Common::Input::NfcState VirtualAmiibo::StopNfcPolling(const PadIdentifier& identifier_) { + if (state == State::Disabled) { + return Common::Input::NfcState::WrongDeviceState; + } + if (state == State::TagNearby) { + CloseAmiibo(); + } + state = State::Initialized; + return Common::Input::NfcState::Success; +} + +Common::Input::NfcState VirtualAmiibo::ReadAmiiboData(const PadIdentifier& identifier_, + std::vector& out_data) { + if (state != State::TagNearby) { + return Common::Input::NfcState::WrongDeviceState; + } + + if (status.tag_type != 1U << 1) { + return Common::Input::NfcState::InvalidTagType; + } + + out_data.resize(nfc_data.size()); + memcpy(out_data.data(), nfc_data.data(), nfc_data.size()); + return Common::Input::NfcState::Success; +} Common::Input::NfcState VirtualAmiibo::WriteNfcData( [[maybe_unused]] const PadIdentifier& identifier_, const std::vector& data) { @@ -66,6 +98,69 @@ Common::Input::NfcState VirtualAmiibo::WriteNfcData( return Common::Input::NfcState::Success; } +Common::Input::NfcState VirtualAmiibo::ReadMifareData(const PadIdentifier& identifier_, + const Common::Input::MifareRequest& request, + Common::Input::MifareRequest& out_data) { + if (state != State::TagNearby) { + return Common::Input::NfcState::WrongDeviceState; + } + + if (status.tag_type != 1U << 6) { + return Common::Input::NfcState::InvalidTagType; + } + + for (std::size_t i = 0; i < request.data.size(); i++) { + if (request.data[i].command == 0) { + continue; + } + out_data.data[i].command = request.data[i].command; + out_data.data[i].sector = request.data[i].sector; + + const std::size_t sector_index = + request.data[i].sector * sizeof(Common::Input::MifareData::data); + + if (nfc_data.size() < sector_index + sizeof(Common::Input::MifareData::data)) { + return Common::Input::NfcState::WriteFailed; + } + + // Ignore the sector key as we don't support it + memcpy(out_data.data[i].data.data(), nfc_data.data() + sector_index, + sizeof(Common::Input::MifareData::data)); + } + + return Common::Input::NfcState::Success; +} + +Common::Input::NfcState VirtualAmiibo::WriteMifareData( + const PadIdentifier& identifier_, const Common::Input::MifareRequest& request) { + if (state != State::TagNearby) { + return Common::Input::NfcState::WrongDeviceState; + } + + if (status.tag_type != 1U << 6) { + return Common::Input::NfcState::InvalidTagType; + } + + for (std::size_t i = 0; i < request.data.size(); i++) { + if (request.data[i].command == 0) { + continue; + } + + const std::size_t sector_index = + request.data[i].sector * sizeof(Common::Input::MifareData::data); + + if (nfc_data.size() < sector_index + sizeof(Common::Input::MifareData::data)) { + return Common::Input::NfcState::WriteFailed; + } + + // Ignore the sector key as we don't support it + memcpy(nfc_data.data() + sector_index, request.data[i].data.data(), + sizeof(Common::Input::MifareData::data)); + } + + return Common::Input::NfcState::Success; +} + VirtualAmiibo::State VirtualAmiibo::GetCurrentState() const { return state; } @@ -112,23 +207,31 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(std::span data) { case AmiiboSizeWithoutPassword: case AmiiboSizeWithSignature: nfc_data.resize(AmiiboSize); + status.tag_type = 1U << 1; + status.uuid_length = 7; break; case MifareSize: nfc_data.resize(MifareSize); + status.tag_type = 1U << 6; + status.uuid_length = 4; break; default: return Info::NotAnAmiibo; } - state = State::AmiiboIsOpen; + status.uuid = {}; + status.protocol = 1; + state = State::TagNearby; + status.state = Common::Input::NfcState::NewAmiibo, memcpy(nfc_data.data(), data.data(), data.size_bytes()); - SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, nfc_data}); + memcpy(status.uuid.data(), nfc_data.data(), status.uuid_length); + SetNfc(identifier, status); return Info::Success; } VirtualAmiibo::Info VirtualAmiibo::ReloadAmiibo() { - if (state == State::AmiiboIsOpen) { - SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, nfc_data}); + if (state == State::TagNearby) { + SetNfc(identifier, status); return Info::Success; } @@ -136,9 +239,14 @@ VirtualAmiibo::Info VirtualAmiibo::ReloadAmiibo() { } VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() { - state = polling_mode == Common::Input::PollingMode::NFC ? State::WaitingForAmiibo - : State::Initialized; - SetNfc(identifier, {Common::Input::NfcState::AmiiboRemoved, {}}); + if (state != State::TagNearby) { + return Info::Success; + } + + state = State::WaitingForAmiibo; + status.state = Common::Input::NfcState::AmiiboRemoved; + SetNfc(identifier, status); + status.tag_type = 0; return Info::Success; } diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h index aa8b772ee..fb34025b9 100755 --- a/src/input_common/drivers/virtual_amiibo.h +++ b/src/input_common/drivers/virtual_amiibo.h @@ -20,9 +20,10 @@ namespace InputCommon { class VirtualAmiibo final : public InputEngine { public: enum class State { + Disabled, Initialized, WaitingForAmiibo, - AmiiboIsOpen, + TagNearby, }; enum class Info { @@ -41,9 +42,17 @@ public: const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override; Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; - + Common::Input::NfcState StartNfcPolling(const PadIdentifier& identifier_) override; + Common::Input::NfcState StopNfcPolling(const PadIdentifier& identifier_) override; + Common::Input::NfcState ReadAmiiboData(const PadIdentifier& identifier_, + std::vector& out_data) override; Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_, const std::vector& data) override; + Common::Input::NfcState ReadMifareData(const PadIdentifier& identifier_, + const Common::Input::MifareRequest& data, + Common::Input::MifareRequest& out_data) override; + Common::Input::NfcState WriteMifareData(const PadIdentifier& identifier_, + const Common::Input::MifareRequest& data) override; State GetCurrentState() const; @@ -61,8 +70,9 @@ private: static constexpr std::size_t MifareSize = 0x400; std::string file_path{}; - State state{State::Initialized}; + State state{State::Disabled}; std::vector nfc_data; + Common::Input::NfcStatus status; Common::Input::PollingMode polling_mode{Common::Input::PollingMode::Passive}; }; } // namespace InputCommon diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp index 95106f16d..2c8c66951 100755 --- a/src/input_common/helpers/joycon_driver.cpp +++ b/src/input_common/helpers/joycon_driver.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" +#include "common/scope_exit.h" #include "common/swap.h" #include "common/thread.h" #include "input_common/helpers/joycon_driver.h" @@ -112,7 +113,7 @@ DriverResult JoyconDriver::InitializeDevice() { joycon_poller = std::make_unique(device_type, left_stick_calibration, right_stick_calibration, motion_calibration); - // Start pooling for data + // Start polling for data is_connected = true; if (!input_thread_running) { input_thread = @@ -208,7 +209,7 @@ void JoyconDriver::OnNewData(std::span buffer) { joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat()); } - if (nfc_protocol->IsEnabled()) { + if (nfc_protocol->IsPolling()) { if (amiibo_detected) { if (!nfc_protocol->HasAmiibo()) { joycon_poller->UpdateAmiibo({}); @@ -218,10 +219,10 @@ void JoyconDriver::OnNewData(std::span buffer) { } if (!amiibo_detected) { - std::vector data(0x21C); - const auto result = nfc_protocol->ScanAmiibo(data); + Joycon::TagInfo tag_info; + const auto result = nfc_protocol->GetTagInfo(tag_info); if (result == DriverResult::Success) { - joycon_poller->UpdateAmiibo(data); + joycon_poller->UpdateAmiibo(tag_info); amiibo_detected = true; } } @@ -247,6 +248,7 @@ void JoyconDriver::OnNewData(std::span buffer) { } DriverResult JoyconDriver::SetPollingMode() { + SCOPE_EXIT({ disable_input_thread = false; }); disable_input_thread = true; rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration); @@ -276,7 +278,6 @@ DriverResult JoyconDriver::SetPollingMode() { if (irs_enabled && supported_features.irs) { auto result = irs_protocol->EnableIrs(); if (result == DriverResult::Success) { - disable_input_thread = false; return result; } irs_protocol->DisableIrs(); @@ -286,10 +287,6 @@ DriverResult JoyconDriver::SetPollingMode() { if (nfc_enabled && supported_features.nfc) { auto result = nfc_protocol->EnableNfc(); if (result == DriverResult::Success) { - result = nfc_protocol->StartNFCPollingMode(); - } - if (result == DriverResult::Success) { - disable_input_thread = false; return result; } nfc_protocol->DisableNfc(); @@ -303,7 +300,6 @@ DriverResult JoyconDriver::SetPollingMode() { } if (result == DriverResult::Success) { ring_connected = true; - disable_input_thread = false; return result; } ring_connected = false; @@ -314,7 +310,6 @@ DriverResult JoyconDriver::SetPollingMode() { if (passive_enabled && supported_features.passive) { const auto result = generic_protocol->EnablePassiveMode(); if (result == DriverResult::Success) { - disable_input_thread = false; return result; } LOG_ERROR(Input, "Error enabling passive mode"); @@ -328,7 +323,6 @@ DriverResult JoyconDriver::SetPollingMode() { // Switch calls this function after enabling active mode generic_protocol->TriggersElapsed(); - disable_input_thread = false; return result; } @@ -492,9 +486,42 @@ DriverResult JoyconDriver::SetRingConMode() { return result; } -DriverResult JoyconDriver::WriteNfcData(std::span data) { +DriverResult JoyconDriver::StartNfcPolling() { std::scoped_lock lock{mutex}; + + if (!supported_features.nfc) { + return DriverResult::NotSupported; + } + if (!nfc_protocol->IsEnabled()) { + return DriverResult::Disabled; + } + disable_input_thread = true; + const auto result = nfc_protocol->StartNFCPollingMode(); + disable_input_thread = false; + + return result; +} + +DriverResult JoyconDriver::StopNfcPolling() { + std::scoped_lock lock{mutex}; + + if (!supported_features.nfc) { + return DriverResult::NotSupported; + } + if (!nfc_protocol->IsEnabled()) { + return DriverResult::Disabled; + } + + disable_input_thread = true; + const auto result = nfc_protocol->StopNFCPollingMode(); + disable_input_thread = false; + + return result; +} + +DriverResult JoyconDriver::ReadAmiiboData(std::vector& out_data) { + std::scoped_lock lock{mutex}; if (!supported_features.nfc) { return DriverResult::NotSupported; @@ -506,9 +533,72 @@ DriverResult JoyconDriver::WriteNfcData(std::span data) { return DriverResult::ErrorWritingData; } - const auto result = nfc_protocol->WriteAmiibo(data); - + out_data.resize(0x21C); + disable_input_thread = true; + const auto result = nfc_protocol->ReadAmiibo(out_data); disable_input_thread = false; + + return result; +} + +DriverResult JoyconDriver::WriteNfcData(std::span data) { + std::scoped_lock lock{mutex}; + + if (!supported_features.nfc) { + return DriverResult::NotSupported; + } + if (!nfc_protocol->IsEnabled()) { + return DriverResult::Disabled; + } + if (!amiibo_detected) { + return DriverResult::ErrorWritingData; + } + + disable_input_thread = true; + const auto result = nfc_protocol->WriteAmiibo(data); + disable_input_thread = false; + + return result; +} + +DriverResult JoyconDriver::ReadMifareData(std::span data, + std::span out_data) { + std::scoped_lock lock{mutex}; + + if (!supported_features.nfc) { + return DriverResult::NotSupported; + } + if (!nfc_protocol->IsEnabled()) { + return DriverResult::Disabled; + } + if (!amiibo_detected) { + return DriverResult::ErrorWritingData; + } + + disable_input_thread = true; + const auto result = nfc_protocol->ReadMifare(data, out_data); + disable_input_thread = false; + + return result; +} + +DriverResult JoyconDriver::WriteMifareData(std::span data) { + std::scoped_lock lock{mutex}; + + if (!supported_features.nfc) { + return DriverResult::NotSupported; + } + if (!nfc_protocol->IsEnabled()) { + return DriverResult::Disabled; + } + if (!amiibo_detected) { + return DriverResult::ErrorWritingData; + } + + disable_input_thread = true; + const auto result = nfc_protocol->WriteMifare(data); + disable_input_thread = false; + return result; } diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h index e9b2fccbb..bc7025a21 100755 --- a/src/input_common/helpers/joycon_driver.h +++ b/src/input_common/helpers/joycon_driver.h @@ -49,7 +49,13 @@ public: DriverResult SetIrMode(); DriverResult SetNfcMode(); DriverResult SetRingConMode(); + DriverResult StartNfcPolling(); + DriverResult StopNfcPolling(); + DriverResult ReadAmiiboData(std::vector& out_data); DriverResult WriteNfcData(std::span data); + DriverResult ReadMifareData(std::span request, + std::span out_data); + DriverResult WriteMifareData(std::span request); void SetCallbacks(const JoyconCallbacks& callbacks); diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h index 5007b0e18..e0e431156 100755 --- a/src/input_common/helpers/joycon_protocol/joycon_types.h +++ b/src/input_common/helpers/joycon_protocol/joycon_types.h @@ -24,6 +24,7 @@ constexpr std::array DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x using MacAddress = std::array; using SerialNumber = std::array; using TagUUID = std::array; +using MifareUUID = std::array; enum class ControllerType : u8 { None = 0x00, @@ -307,6 +308,19 @@ enum class NFCStatus : u8 { WriteDone = 0x05, TagLost = 0x07, WriteReady = 0x09, + MifareDone = 0x10, +}; + +enum class MifareCmd : u8 { + None = 0x00, + Read = 0x30, + AuthA = 0x60, + AuthB = 0x61, + Write = 0xA0, + Transfer = 0xB0, + Decrement = 0xC0, + Increment = 0xC1, + Store = 0xC2 }; enum class IrsMode : u8 { @@ -592,6 +606,14 @@ struct NFCWriteCommandData { static_assert(sizeof(NFCWriteCommandData) == 0x15, "NFCWriteCommandData is an invalid size"); #pragma pack(pop) +struct MifareCommandData { + u8 unknown1; + u8 unknown2; + u8 number_of_short_bytes; + MifareUUID uid; +}; +static_assert(sizeof(MifareCommandData) == 0x7, "MifareCommandData is an invalid size"); + struct NFCPollingCommandData { u8 enable_mifare; u8 unknown_1; @@ -629,6 +651,41 @@ struct NFCWritePackage { std::array data_chunks; }; +struct MifareReadChunk { + MifareCmd command; + std::array sector_key; + u8 sector; +}; + +struct MifareWriteChunk { + MifareCmd command; + std::array sector_key; + u8 sector; + std::array data; +}; + +struct MifareReadData { + u8 sector; + std::array data; +}; + +struct MifareReadPackage { + MifareCommandData command_data; + std::array data_chunks; +}; + +struct MifareWritePackage { + MifareCommandData command_data; + std::array data_chunks; +}; + +struct TagInfo { + u8 uuid_length; + u8 protocol; + u8 tag_type; + std::array uuid; +}; + struct IrsConfigure { MCUCommand command; MCUSubCommand sub_command; @@ -744,7 +801,7 @@ struct JoyconCallbacks { std::function on_stick_data; std::function on_motion_data; std::function on_ring_data; - std::function&)> on_amiibo_data; + std::function on_amiibo_data; std::function&, IrsResolution)> on_camera_data; }; diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp index f7058c4a7..261f46255 100755 --- a/src/input_common/helpers/joycon_protocol/nfc.cpp +++ b/src/input_common/helpers/joycon_protocol/nfc.cpp @@ -40,6 +40,16 @@ DriverResult NfcProtocol::EnableNfc() { if (result == DriverResult::Success) { result = WaitUntilNfcIs(NFCStatus::Ready); } + if (result == DriverResult::Success) { + MCUCommandResponse output{}; + result = SendStopPollingRequest(output); + } + if (result == DriverResult::Success) { + result = WaitUntilNfcIs(NFCStatus::Ready); + } + if (result == DriverResult::Success) { + is_enabled = true; + } return result; } @@ -54,22 +64,16 @@ DriverResult NfcProtocol::DisableNfc() { } is_enabled = false; + is_polling = false; return result; } DriverResult NfcProtocol::StartNFCPollingMode() { - LOG_DEBUG(Input, "Start NFC pooling Mode"); + LOG_DEBUG(Input, "Start NFC polling Mode"); ScopedSetBlocking sb(this); DriverResult result{DriverResult::Success}; - if (result == DriverResult::Success) { - MCUCommandResponse output{}; - result = SendStopPollingRequest(output); - } - if (result == DriverResult::Success) { - result = WaitUntilNfcIs(NFCStatus::Ready); - } if (result == DriverResult::Success) { MCUCommandResponse output{}; result = SendStartPollingRequest(output); @@ -78,13 +82,32 @@ DriverResult NfcProtocol::StartNFCPollingMode() { result = WaitUntilNfcIs(NFCStatus::Polling); } if (result == DriverResult::Success) { - is_enabled = true; + is_polling = true; } return result; } -DriverResult NfcProtocol::ScanAmiibo(std::vector& data) { +DriverResult NfcProtocol::StopNFCPollingMode() { + LOG_DEBUG(Input, "Stop NFC polling Mode"); + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + + if (result == DriverResult::Success) { + MCUCommandResponse output{}; + result = SendStopPollingRequest(output); + } + if (result == DriverResult::Success) { + result = WaitUntilNfcIs(NFCStatus::WriteReady); + } + if (result == DriverResult::Success) { + is_polling = false; + } + + return result; +} + +DriverResult NfcProtocol::GetTagInfo(Joycon::TagInfo& tag_info) { if (update_counter++ < AMIIBO_UPDATE_DELAY) { return DriverResult::Delayed; } @@ -100,11 +123,41 @@ DriverResult NfcProtocol::ScanAmiibo(std::vector& data) { } if (result == DriverResult::Success) { + tag_info = { + .uuid_length = tag_data.uuid_size, + .protocol = 1, + .tag_type = tag_data.type, + .uuid = {}, + }; + + memcpy(tag_info.uuid.data(), tag_data.uuid.data(), tag_data.uuid_size); + + // Investigate why mifare type is not correct + if (tag_info.tag_type == 144) { + tag_info.tag_type = 1U << 6; + } + std::string uuid_string; for (auto& content : tag_data.uuid) { uuid_string += fmt::format(" {:02x}", content); } LOG_INFO(Input, "Tag detected, type={}, uuid={}", tag_data.type, uuid_string); + } + + return result; +} + +DriverResult NfcProtocol::ReadAmiibo(std::vector& data) { + LOG_DEBUG(Input, "Scan for amiibos"); + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + TagFoundData tag_data{}; + + if (result == DriverResult::Success) { + result = IsTagInRange(tag_data, 7); + } + + if (result == DriverResult::Success) { result = GetAmiiboData(data); } @@ -154,6 +207,69 @@ DriverResult NfcProtocol::WriteAmiibo(std::span data) { return result; } +DriverResult NfcProtocol::ReadMifare(std::span read_request, + std::span out_data) { + LOG_DEBUG(Input, "Read mifare"); + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + TagFoundData tag_data{}; + MifareUUID tag_uuid{}; + + if (result == DriverResult::Success) { + result = IsTagInRange(tag_data, 7); + } + if (result == DriverResult::Success) { + memcpy(tag_uuid.data(), tag_data.uuid.data(), sizeof(MifareUUID)); + result = GetMifareData(tag_uuid, read_request, out_data); + } + if (result == DriverResult::Success) { + MCUCommandResponse output{}; + result = SendStopPollingRequest(output); + } + if (result == DriverResult::Success) { + result = WaitUntilNfcIs(NFCStatus::Ready); + } + if (result == DriverResult::Success) { + MCUCommandResponse output{}; + result = SendStartPollingRequest(output, true); + } + if (result == DriverResult::Success) { + result = WaitUntilNfcIs(NFCStatus::WriteReady); + } + return result; +} + +DriverResult NfcProtocol::WriteMifare(std::span write_request) { + LOG_DEBUG(Input, "Write mifare"); + ScopedSetBlocking sb(this); + DriverResult result{DriverResult::Success}; + TagFoundData tag_data{}; + MifareUUID tag_uuid{}; + + if (result == DriverResult::Success) { + result = IsTagInRange(tag_data, 7); + } + if (result == DriverResult::Success) { + memcpy(tag_uuid.data(), tag_data.uuid.data(), sizeof(MifareUUID)); + result = WriteMifareData(tag_uuid, write_request); + } + if (result == DriverResult::Success) { + MCUCommandResponse output{}; + result = SendStopPollingRequest(output); + } + if (result == DriverResult::Success) { + result = WaitUntilNfcIs(NFCStatus::Ready); + } + if (result == DriverResult::Success) { + MCUCommandResponse output{}; + result = SendStartPollingRequest(output, true); + } + if (result == DriverResult::Success) { + result = WaitUntilNfcIs(NFCStatus::WriteReady); + } + return result; +} + bool NfcProtocol::HasAmiibo() { if (update_counter++ < AMIIBO_UPDATE_DELAY) { return true; @@ -341,6 +457,158 @@ DriverResult NfcProtocol::WriteAmiiboData(const TagUUID& tag_uuid, std::span read_request, + std::span out_data) { + constexpr std::size_t timeout_limit = 60; + const auto nfc_data = MakeMifareReadPackage(tag_uuid, read_request); + const std::vector nfc_buffer_data = SerializeMifareReadPackage(nfc_data); + std::span buffer(nfc_buffer_data); + DriverResult result = DriverResult::Success; + MCUCommandResponse output{}; + u8 block_id = 1; + u8 package_index = 0; + std::size_t tries = 0; + std::size_t current_position = 0; + + LOG_INFO(Input, "Reading Mifare data"); + + // Send data request. Nfc buffer size is 31, Send the data in smaller packages + while (current_position < buffer.size() && tries++ < timeout_limit) { + const std::size_t next_position = + std::min(current_position + sizeof(NFCRequestState::raw_data), buffer.size()); + const std::size_t block_size = next_position - current_position; + const bool is_last_packet = block_size < sizeof(NFCRequestState::raw_data); + + SendReadDataMifareRequest(output, block_id, is_last_packet, + buffer.subspan(current_position, block_size)); + + const auto nfc_status = static_cast(output.mcu_data[6]); + + if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) { + return DriverResult::ErrorReadingData; + } + + // Increase position when data is confirmed by the joycon + if (output.mcu_report == MCUReport::NFCState && + (output.mcu_data[1] << 8) + output.mcu_data[0] == 0x0500 && + output.mcu_data[3] == block_id) { + block_id++; + current_position = next_position; + } + } + + if (result != DriverResult::Success) { + return result; + } + + // Wait for reply and save the output data + while (tries++ < timeout_limit) { + result = SendNextPackageRequest(output, package_index); + const auto nfc_status = static_cast(output.mcu_data[6]); + + if (result != DriverResult::Success) { + return result; + } + + if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) { + return DriverResult::ErrorReadingData; + } + + if (output.mcu_report == MCUReport::NFCState && output.mcu_data[1] == 0x10) { + constexpr std::size_t DATA_LENGHT = 0x10 + 1; + constexpr std::size_t DATA_START = 11; + const u8 number_of_elements = output.mcu_data[10]; + for (std::size_t i = 0; i < number_of_elements; i++) { + out_data[i].sector = output.mcu_data[DATA_START + (i * DATA_LENGHT)]; + memcpy(out_data[i].data.data(), + output.mcu_data.data() + DATA_START + 1 + (i * DATA_LENGHT), + sizeof(MifareReadData::data)); + } + package_index++; + continue; + } + + if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::MifareDone) { + LOG_INFO(Input, "Finished reading mifare"); + break; + } + } + + return result; +} + +DriverResult NfcProtocol::WriteMifareData(const MifareUUID& tag_uuid, + std::span write_request) { + constexpr std::size_t timeout_limit = 60; + const auto nfc_data = MakeMifareWritePackage(tag_uuid, write_request); + const std::vector nfc_buffer_data = SerializeMifareWritePackage(nfc_data); + std::span buffer(nfc_buffer_data); + DriverResult result = DriverResult::Success; + MCUCommandResponse output{}; + u8 block_id = 1; + u8 package_index = 0; + std::size_t tries = 0; + std::size_t current_position = 0; + + LOG_INFO(Input, "Writing Mifare data"); + + // Send data request. Nfc buffer size is 31, Send the data in smaller packages + while (current_position < buffer.size() && tries++ < timeout_limit) { + const std::size_t next_position = + std::min(current_position + sizeof(NFCRequestState::raw_data), buffer.size()); + const std::size_t block_size = next_position - current_position; + const bool is_last_packet = block_size < sizeof(NFCRequestState::raw_data); + + SendReadDataMifareRequest(output, block_id, is_last_packet, + buffer.subspan(current_position, block_size)); + + const auto nfc_status = static_cast(output.mcu_data[6]); + + if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) { + return DriverResult::ErrorReadingData; + } + + // Increase position when data is confirmed by the joycon + if (output.mcu_report == MCUReport::NFCState && + (output.mcu_data[1] << 8) + output.mcu_data[0] == 0x0500 && + output.mcu_data[3] == block_id) { + block_id++; + current_position = next_position; + } + } + + if (result != DriverResult::Success) { + return result; + } + + // Wait for reply and ignore the output data + while (tries++ < timeout_limit) { + result = SendNextPackageRequest(output, package_index); + const auto nfc_status = static_cast(output.mcu_data[6]); + + if (result != DriverResult::Success) { + return result; + } + + if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) { + return DriverResult::ErrorReadingData; + } + + if (output.mcu_report == MCUReport::NFCState && output.mcu_data[1] == 0x10) { + package_index++; + continue; + } + + if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::MifareDone) { + LOG_INFO(Input, "Finished writing mifare"); + break; + } + } + + return result; +} + DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output, bool is_second_attempt) { NFCRequestState request{ @@ -477,6 +745,28 @@ DriverResult NfcProtocol::SendWriteDataAmiiboRequest(MCUCommandResponse& output, output); } +DriverResult NfcProtocol::SendReadDataMifareRequest(MCUCommandResponse& output, u8 block_id, + bool is_last_packet, std::span data) { + const auto data_size = std::min(data.size(), sizeof(NFCRequestState::raw_data)); + NFCRequestState request{ + .command_argument = NFCCommand::Mifare, + .block_id = block_id, + .packet_id = {}, + .packet_flag = + is_last_packet ? MCUPacketFlag::LastCommandPacket : MCUPacketFlag::MorePacketsRemaining, + .data_length = static_cast(data_size), + .raw_data = {}, + .crc = {}, + }; + memcpy(request.raw_data.data(), data.data(), data_size); + + std::array request_data{}; + memcpy(request_data.data(), &request, sizeof(NFCRequestState)); + request_data[36] = CalculateMCU_CRC8(request_data.data(), 36); + return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data, + output); +} + std::vector NfcProtocol::SerializeWritePackage(const NFCWritePackage& package) const { const std::size_t header_size = sizeof(NFCWriteCommandData) + sizeof(NFCWritePackage::number_of_chunks); @@ -498,6 +788,48 @@ std::vector NfcProtocol::SerializeWritePackage(const NFCWritePackage& packag return serialized_data; } +std::vector NfcProtocol::SerializeMifareReadPackage(const MifareReadPackage& package) const { + const std::size_t header_size = sizeof(MifareCommandData); + std::vector serialized_data(header_size); + std::size_t start_index = 0; + + memcpy(serialized_data.data(), &package, header_size); + start_index += header_size; + + for (const auto& data_chunk : package.data_chunks) { + const std::size_t chunk_size = sizeof(MifareReadChunk); + if (data_chunk.command == MifareCmd::None) { + continue; + } + serialized_data.resize(start_index + chunk_size); + memcpy(serialized_data.data() + start_index, &data_chunk, chunk_size); + start_index += chunk_size; + } + + return serialized_data; +} + +std::vector NfcProtocol::SerializeMifareWritePackage(const MifareWritePackage& package) const { + const std::size_t header_size = sizeof(MifareCommandData); + std::vector serialized_data(header_size); + std::size_t start_index = 0; + + memcpy(serialized_data.data(), &package, header_size); + start_index += header_size; + + for (const auto& data_chunk : package.data_chunks) { + const std::size_t chunk_size = sizeof(MifareWriteChunk); + if (data_chunk.command == MifareCmd::None) { + continue; + } + serialized_data.resize(start_index + chunk_size); + memcpy(serialized_data.data() + start_index, &data_chunk, chunk_size); + start_index += chunk_size; + } + + return serialized_data; +} + NFCWritePackage NfcProtocol::MakeAmiiboWritePackage(const TagUUID& tag_uuid, std::span data) const { return { @@ -527,6 +859,46 @@ NFCWritePackage NfcProtocol::MakeAmiiboWritePackage(const TagUUID& tag_uuid, }; } +MifareReadPackage NfcProtocol::MakeMifareReadPackage( + const MifareUUID& tag_uuid, std::span read_request) const { + MifareReadPackage package{ + .command_data{ + .unknown1 = 0xd0, + .unknown2 = 0x07, + .number_of_short_bytes = static_cast( + ((read_request.size() * sizeof(MifareReadChunk)) + sizeof(MifareUUID)) / 2), + .uid = tag_uuid, + }, + .data_chunks = {}, + }; + + for (std::size_t i = 0; i < read_request.size() && i < package.data_chunks.size(); ++i) { + package.data_chunks[i] = read_request[i]; + } + + return package; +} + +MifareWritePackage NfcProtocol::MakeMifareWritePackage( + const MifareUUID& tag_uuid, std::span read_request) const { + MifareWritePackage package{ + .command_data{ + .unknown1 = 0xd0, + .unknown2 = 0x07, + .number_of_short_bytes = static_cast( + ((read_request.size() * sizeof(MifareReadChunk)) + sizeof(MifareUUID) + 2) / 2), + .uid = tag_uuid, + }, + .data_chunks = {}, + }; + + for (std::size_t i = 0; i < read_request.size() && i < package.data_chunks.size(); ++i) { + package.data_chunks[i] = read_request[i]; + } + + return package; +} + NFCDataChunk NfcProtocol::MakeAmiiboChunk(u8 page, u8 size, std::span data) const { constexpr u8 NFC_PAGE_SIZE = 4; @@ -606,4 +978,8 @@ bool NfcProtocol::IsEnabled() const { return is_enabled; } +bool NfcProtocol::IsPolling() const { + return is_polling; +} + } // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h index eb58c427d..0be95e40e 100755 --- a/src/input_common/helpers/joycon_protocol/nfc.h +++ b/src/input_common/helpers/joycon_protocol/nfc.h @@ -25,14 +25,25 @@ public: DriverResult StartNFCPollingMode(); - DriverResult ScanAmiibo(std::vector& data); + DriverResult StopNFCPollingMode(); + + DriverResult GetTagInfo(Joycon::TagInfo& tag_info); + + DriverResult ReadAmiibo(std::vector& data); DriverResult WriteAmiibo(std::span data); + DriverResult ReadMifare(std::span read_request, + std::span out_data); + + DriverResult WriteMifare(std::span write_request); + bool HasAmiibo(); bool IsEnabled() const; + bool IsPolling() const; + private: // Number of times the function will be delayed until it outputs valid data static constexpr std::size_t AMIIBO_UPDATE_DELAY = 15; @@ -51,6 +62,13 @@ private: DriverResult WriteAmiiboData(const TagUUID& tag_uuid, std::span data); + DriverResult GetMifareData(const MifareUUID& tag_uuid, + std::span read_request, + std::span out_data); + + DriverResult WriteMifareData(const MifareUUID& tag_uuid, + std::span write_request); + DriverResult SendStartPollingRequest(MCUCommandResponse& output, bool is_second_attempt = false); @@ -65,17 +83,31 @@ private: DriverResult SendWriteDataAmiiboRequest(MCUCommandResponse& output, u8 block_id, bool is_last_packet, std::span data); + DriverResult SendReadDataMifareRequest(MCUCommandResponse& output, u8 block_id, + bool is_last_packet, std::span data); + std::vector SerializeWritePackage(const NFCWritePackage& package) const; + std::vector SerializeMifareReadPackage(const MifareReadPackage& package) const; + + std::vector SerializeMifareWritePackage(const MifareWritePackage& package) const; + NFCWritePackage MakeAmiiboWritePackage(const TagUUID& tag_uuid, std::span data) const; NFCDataChunk MakeAmiiboChunk(u8 page, u8 size, std::span data) const; + MifareReadPackage MakeMifareReadPackage(const MifareUUID& tag_uuid, + std::span read_request) const; + + MifareWritePackage MakeMifareWritePackage(const MifareUUID& tag_uuid, + std::span read_request) const; + NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const; TagUUID GetTagUUID(std::span data) const; bool is_enabled{}; + bool is_polling{}; std::size_t update_counter{}; }; diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp index dca797f7a..1aab9e12a 100755 --- a/src/input_common/helpers/joycon_protocol/poller.cpp +++ b/src/input_common/helpers/joycon_protocol/poller.cpp @@ -70,8 +70,8 @@ void JoyconPoller::UpdateColor(const Color& color) { callbacks.on_color_data(color); } -void JoyconPoller::UpdateAmiibo(const std::vector& amiibo_data) { - callbacks.on_amiibo_data(amiibo_data); +void JoyconPoller::UpdateAmiibo(const Joycon::TagInfo& tag_info) { + callbacks.on_amiibo_data(tag_info); } void JoyconPoller::UpdateCamera(const std::vector& camera_data, IrsResolution format) { diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h index 0fa72c6db..3746abe5d 100755 --- a/src/input_common/helpers/joycon_protocol/poller.h +++ b/src/input_common/helpers/joycon_protocol/poller.h @@ -36,8 +36,8 @@ public: void UpdateColor(const Color& color); void UpdateRing(s16 value, const RingStatus& ring_status); - void UpdateAmiibo(const std::vector& amiibo_data); - void UpdateCamera(const std::vector& amiibo_data, IrsResolution format); + void UpdateAmiibo(const Joycon::TagInfo& tag_info); + void UpdateCamera(const std::vector& camera_data, IrsResolution format); private: void UpdateActiveLeftPadInput(const InputReportActive& input, diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h index 1148b82fd..706a675c0 100755 --- a/src/input_common/input_engine.h +++ b/src/input_common/input_engine.h @@ -143,12 +143,46 @@ public: return Common::Input::NfcState::NotSupported; } + // Start scanning for nfc tags + virtual Common::Input::NfcState StartNfcPolling( + [[maybe_unused]] const PadIdentifier& identifier_) { + return Common::Input::NfcState::NotSupported; + } + + // Start scanning for nfc tags + virtual Common::Input::NfcState StopNfcPolling( + [[maybe_unused]] const PadIdentifier& identifier_) { + return Common::Input::NfcState::NotSupported; + } + + // Reads data from amiibo tag + virtual Common::Input::NfcState ReadAmiiboData( + [[maybe_unused]] const PadIdentifier& identifier_, + [[maybe_unused]] std::vector& out_data) { + return Common::Input::NfcState::NotSupported; + } + // Writes data to an nfc tag virtual Common::Input::NfcState WriteNfcData([[maybe_unused]] const PadIdentifier& identifier, [[maybe_unused]] const std::vector& data) { return Common::Input::NfcState::NotSupported; } + // Reads data from mifare tag + virtual Common::Input::NfcState ReadMifareData( + [[maybe_unused]] const PadIdentifier& identifier_, + [[maybe_unused]] const Common::Input::MifareRequest& request, + [[maybe_unused]] Common::Input::MifareRequest& out_data) { + return Common::Input::NfcState::NotSupported; + } + + // Write data to mifare tag + virtual Common::Input::NfcState WriteMifareData( + [[maybe_unused]] const PadIdentifier& identifier_, + [[maybe_unused]] const Common::Input::MifareRequest& request) { + return Common::Input::NfcState::NotSupported; + } + // Returns the engine name [[nodiscard]] const std::string& GetEngineName() const; diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index 919f50827..59e0feaae 100755 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp @@ -792,8 +792,7 @@ public: const Common::Input::CallbackStatus status{ .type = Common::Input::InputType::Nfc, - .nfc_status = nfc_status.state, - .raw_data = nfc_status.data, + .nfc_status = nfc_status, }; TriggerOnChange(status); @@ -836,10 +835,31 @@ public: return input_engine->SupportsNfc(identifier); } + Common::Input::NfcState StartNfcPolling() { + return input_engine->StartNfcPolling(identifier); + } + + Common::Input::NfcState StopNfcPolling() { + return input_engine->StopNfcPolling(identifier); + } + + Common::Input::NfcState ReadAmiiboData(std::vector& out_data) { + return input_engine->ReadAmiiboData(identifier, out_data); + } + Common::Input::NfcState WriteNfcData(const std::vector& data) override { return input_engine->WriteNfcData(identifier, data); } + Common::Input::NfcState ReadMifareData(const Common::Input::MifareRequest& request, + Common::Input::MifareRequest& out_data) { + return input_engine->ReadMifareData(identifier, request, out_data); + } + + Common::Input::NfcState WriteMifareData(const Common::Input::MifareRequest& request) { + return input_engine->WriteMifareData(identifier, request); + } + private: const PadIdentifier identifier; InputEngine* input_engine; diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 0d4cf60ce..3c1bd3746 100755 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -98,6 +98,9 @@ add_executable(yuzu configuration/configure_input_profile_dialog.cpp configuration/configure_input_profile_dialog.h configuration/configure_input_profile_dialog.ui + configuration/configure_mouse_panning.cpp + configuration/configure_mouse_panning.h + configuration/configure_mouse_panning.ui configuration/configure_motion_touch.cpp configuration/configure_motion_touch.h configuration/configure_motion_touch.ui diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index bed173af7..317b3c34e 100755 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -351,6 +351,10 @@ void Config::ReadPlayerValue(std::size_t player_index) { player_motions = default_param; } } + + if (player_index == 0) { + ReadMousePanningValues(); + } } void Config::ReadDebugValues() { @@ -471,6 +475,7 @@ void Config::ReadControlValues() { ReadKeyboardValues(); ReadMouseValues(); ReadTouchscreenValues(); + ReadMousePanningValues(); ReadMotionTouchValues(); ReadHidbusValues(); ReadIrCameraValues(); @@ -481,8 +486,6 @@ void Config::ReadControlValues() { Settings::values.enable_raw_input = false; #endif ReadBasicSetting(Settings::values.emulate_analog_keyboard); - Settings::values.mouse_panning = false; - ReadBasicSetting(Settings::values.mouse_panning_sensitivity); ReadBasicSetting(Settings::values.enable_joycon_driver); ReadBasicSetting(Settings::values.enable_procon_driver); ReadBasicSetting(Settings::values.random_amiibo_id); @@ -496,6 +499,16 @@ void Config::ReadControlValues() { qt_config->endGroup(); } +void Config::ReadMousePanningValues() { + ReadBasicSetting(Settings::values.mouse_panning); + ReadBasicSetting(Settings::values.mouse_panning_x_sensitivity); + ReadBasicSetting(Settings::values.mouse_panning_y_sensitivity); + ReadBasicSetting(Settings::values.mouse_panning_deadzone_x_counterweight); + ReadBasicSetting(Settings::values.mouse_panning_deadzone_y_counterweight); + ReadBasicSetting(Settings::values.mouse_panning_decay_strength); + ReadBasicSetting(Settings::values.mouse_panning_min_decay); +} + void Config::ReadMotionTouchValues() { int num_touch_from_button_maps = qt_config->beginReadArray(QStringLiteral("touch_from_button_maps")); @@ -1064,6 +1077,10 @@ void Config::SavePlayerValue(std::size_t player_index) { QString::fromStdString(player.motions[i]), QString::fromStdString(default_param)); } + + if (player_index == 0) { + SaveMousePanningValues(); + } } void Config::SaveDebugValues() { @@ -1100,6 +1117,16 @@ void Config::SaveTouchscreenValues() { WriteSetting(QStringLiteral("touchscreen_diameter_y"), touchscreen.diameter_y, 15); } +void Config::SaveMousePanningValues() { + // Don't overwrite values.mouse_panning + WriteBasicSetting(Settings::values.mouse_panning_x_sensitivity); + WriteBasicSetting(Settings::values.mouse_panning_y_sensitivity); + WriteBasicSetting(Settings::values.mouse_panning_deadzone_x_counterweight); + WriteBasicSetting(Settings::values.mouse_panning_deadzone_y_counterweight); + WriteBasicSetting(Settings::values.mouse_panning_decay_strength); + WriteBasicSetting(Settings::values.mouse_panning_min_decay); +} + void Config::SaveMotionTouchValues() { WriteBasicSetting(Settings::values.touch_device); WriteBasicSetting(Settings::values.touch_from_button_map_index); @@ -1186,6 +1213,7 @@ void Config::SaveControlValues() { SaveDebugValues(); SaveMouseValues(); SaveTouchscreenValues(); + SaveMousePanningValues(); SaveMotionTouchValues(); SaveHidbusValues(); SaveIrCameraValues(); @@ -1200,7 +1228,6 @@ void Config::SaveControlValues() { WriteBasicSetting(Settings::values.random_amiibo_id); WriteBasicSetting(Settings::values.keyboard_enabled); WriteBasicSetting(Settings::values.emulate_analog_keyboard); - WriteBasicSetting(Settings::values.mouse_panning_sensitivity); WriteBasicSetting(Settings::values.controller_navigation); WriteBasicSetting(Settings::values.tas_enable); diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 4a1cb82b5..16a496003 100755 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h @@ -74,6 +74,7 @@ private: void ReadKeyboardValues(); void ReadMouseValues(); void ReadTouchscreenValues(); + void ReadMousePanningValues(); void ReadMotionTouchValues(); void ReadHidbusValues(); void ReadIrCameraValues(); @@ -104,6 +105,7 @@ private: void SaveDebugValues(); void SaveMouseValues(); void SaveTouchscreenValues(); + void SaveMousePanningValues(); void SaveMotionTouchValues(); void SaveHidbusValues(); void SaveIrCameraValues(); diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp index 56bf8d409..7df410c3b 100755 --- a/src/yuzu/configuration/configure_input_advanced.cpp +++ b/src/yuzu/configuration/configure_input_advanced.cpp @@ -129,9 +129,6 @@ void ConfigureInputAdvanced::ApplyConfiguration() { Settings::values.mouse_enabled = ui->mouse_enabled->isChecked(); Settings::values.keyboard_enabled = ui->keyboard_enabled->isChecked(); Settings::values.emulate_analog_keyboard = ui->emulate_analog_keyboard->isChecked(); - Settings::values.mouse_panning = ui->mouse_panning->isChecked(); - Settings::values.mouse_panning_sensitivity = - static_cast(ui->mouse_panning_sensitivity->value()); Settings::values.touchscreen.enabled = ui->touchscreen_enabled->isChecked(); Settings::values.enable_raw_input = ui->enable_raw_input->isChecked(); Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked(); @@ -167,8 +164,6 @@ void ConfigureInputAdvanced::LoadConfiguration() { ui->mouse_enabled->setChecked(Settings::values.mouse_enabled.GetValue()); ui->keyboard_enabled->setChecked(Settings::values.keyboard_enabled.GetValue()); ui->emulate_analog_keyboard->setChecked(Settings::values.emulate_analog_keyboard.GetValue()); - ui->mouse_panning->setChecked(Settings::values.mouse_panning.GetValue()); - ui->mouse_panning_sensitivity->setValue(Settings::values.mouse_panning_sensitivity.GetValue()); ui->touchscreen_enabled->setChecked(Settings::values.touchscreen.enabled); ui->enable_raw_input->setChecked(Settings::values.enable_raw_input.GetValue()); ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue()); @@ -197,8 +192,6 @@ void ConfigureInputAdvanced::RetranslateUI() { void ConfigureInputAdvanced::UpdateUIEnabled() { ui->debug_configure->setEnabled(ui->debug_enabled->isChecked()); ui->touchscreen_advanced->setEnabled(ui->touchscreen_enabled->isChecked()); - ui->mouse_panning->setEnabled(!ui->mouse_enabled->isChecked()); - ui->mouse_panning_sensitivity->setEnabled(!ui->mouse_enabled->isChecked()); ui->ring_controller_configure->setEnabled(ui->enable_ring_controller->isChecked()); #if QT_VERSION > QT_VERSION_CHECK(6, 0, 0) || !defined(YUZU_USE_QT_MULTIMEDIA) ui->enable_ir_sensor->setEnabled(false); diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui index 75a32160f..931d080af 100755 --- a/src/yuzu/configuration/configure_input_advanced.ui +++ b/src/yuzu/configuration/configure_input_advanced.ui @@ -2744,48 +2744,13 @@ - - - - 0 - 23 - - - - Enable mouse panning - - - - - - - Mouse sensitivity - - - Qt::AlignCenter - - - % - - - 1 - - - 100 - - - 100 - - - - Motion / Touch - + Configure diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 4bc0b6a16..fef317283 100755 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -23,6 +23,7 @@ #include "yuzu/configuration/config.h" #include "yuzu/configuration/configure_input_player.h" #include "yuzu/configuration/configure_input_player_widget.h" +#include "yuzu/configuration/configure_mouse_panning.h" #include "yuzu/configuration/input_profiles.h" #include "yuzu/util/limitable_input_dialog.h" @@ -711,6 +712,21 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i }); } + if (player_index_ == 0) { + connect(ui->mousePanningButton, &QPushButton::clicked, [this, input_subsystem_] { + const auto right_stick_param = + emulated_controller->GetStickParam(Settings::NativeAnalog::RStick); + ConfigureMousePanning dialog(this, input_subsystem_, + right_stick_param.Get("deadzone", 0.0f), + right_stick_param.Get("range", 1.0f)); + if (dialog.exec() == QDialog::Accepted) { + dialog.ApplyConfiguration(); + } + }); + } else { + ui->mousePanningWidget->hide(); + } + // Player Connected checkbox connect(ui->groupConnectedController, &QGroupBox::toggled, [this](bool checked) { emit Connected(checked); }); diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui index ae257843d..47b37259b 100755 --- a/src/yuzu/configuration/configure_input_player.ui +++ b/src/yuzu/configuration/configure_input_player.ui @@ -3048,6 +3048,102 @@ + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 3 + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + Mouse panning + + + Qt::AlignCenter + + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + + + + 68 + 0 + + + + + 68 + 16777215 + + + + min-width: 68px; + + + Configure + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + diff --git a/src/yuzu/configuration/configure_mouse_panning.cpp b/src/yuzu/configuration/configure_mouse_panning.cpp new file mode 100755 index 000000000..f183d2740 --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_panning.cpp @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "common/settings.h" +#include "ui_configure_mouse_panning.h" +#include "yuzu/configuration/configure_mouse_panning.h" + +ConfigureMousePanning::ConfigureMousePanning(QWidget* parent, + InputCommon::InputSubsystem* input_subsystem_, + float right_stick_deadzone, float right_stick_range) + : QDialog(parent), input_subsystem{input_subsystem_}, + ui(std::make_unique()) { + ui->setupUi(this); + SetConfiguration(right_stick_deadzone, right_stick_range); + ConnectEvents(); +} + +ConfigureMousePanning::~ConfigureMousePanning() = default; + +void ConfigureMousePanning::closeEvent(QCloseEvent* event) { + event->accept(); +} + +void ConfigureMousePanning::SetConfiguration(float right_stick_deadzone, float right_stick_range) { + ui->enable->setChecked(Settings::values.mouse_panning.GetValue()); + ui->x_sensitivity->setValue(Settings::values.mouse_panning_x_sensitivity.GetValue()); + ui->y_sensitivity->setValue(Settings::values.mouse_panning_y_sensitivity.GetValue()); + ui->deadzone_x_counterweight->setValue( + Settings::values.mouse_panning_deadzone_x_counterweight.GetValue()); + ui->deadzone_y_counterweight->setValue( + Settings::values.mouse_panning_deadzone_y_counterweight.GetValue()); + ui->decay_strength->setValue(Settings::values.mouse_panning_decay_strength.GetValue()); + ui->min_decay->setValue(Settings::values.mouse_panning_min_decay.GetValue()); + + if (right_stick_deadzone > 0.0f || right_stick_range != 1.0f) { + ui->warning_label->setText(QString::fromStdString( + "Mouse panning works better with a deadzone of 0% and a range of 100%.\n" + "Current values are " + + std::to_string(static_cast(right_stick_deadzone * 100.0f)) + "% and " + + std::to_string(static_cast(right_stick_range * 100.0f)) + "% respectively.")); + } else { + ui->warning_label->hide(); + } +} + +void ConfigureMousePanning::SetDefaultConfiguration() { + ui->x_sensitivity->setValue(Settings::values.mouse_panning_x_sensitivity.GetDefault()); + ui->y_sensitivity->setValue(Settings::values.mouse_panning_y_sensitivity.GetDefault()); + ui->deadzone_x_counterweight->setValue( + Settings::values.mouse_panning_deadzone_x_counterweight.GetDefault()); + ui->deadzone_y_counterweight->setValue( + Settings::values.mouse_panning_deadzone_y_counterweight.GetDefault()); + ui->decay_strength->setValue(Settings::values.mouse_panning_decay_strength.GetDefault()); + ui->min_decay->setValue(Settings::values.mouse_panning_min_decay.GetDefault()); +} + +void ConfigureMousePanning::ConnectEvents() { + connect(ui->default_button, &QPushButton::clicked, this, + &ConfigureMousePanning::SetDefaultConfiguration); + connect(ui->button_box, &QDialogButtonBox::accepted, this, + &ConfigureMousePanning::ApplyConfiguration); + connect(ui->button_box, &QDialogButtonBox::rejected, this, [this] { reject(); }); +} + +void ConfigureMousePanning::ApplyConfiguration() { + Settings::values.mouse_panning = ui->enable->isChecked(); + Settings::values.mouse_panning_x_sensitivity = static_cast(ui->x_sensitivity->value()); + Settings::values.mouse_panning_y_sensitivity = static_cast(ui->y_sensitivity->value()); + Settings::values.mouse_panning_deadzone_x_counterweight = + static_cast(ui->deadzone_x_counterweight->value()); + Settings::values.mouse_panning_deadzone_y_counterweight = + static_cast(ui->deadzone_y_counterweight->value()); + Settings::values.mouse_panning_decay_strength = static_cast(ui->decay_strength->value()); + Settings::values.mouse_panning_min_decay = static_cast(ui->min_decay->value()); + + accept(); +} diff --git a/src/yuzu/configuration/configure_mouse_panning.h b/src/yuzu/configuration/configure_mouse_panning.h new file mode 100755 index 000000000..08c6e1f62 --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_panning.h @@ -0,0 +1,35 @@ +// SPDX-FileCopyrightText: 2023 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace InputCommon { +class InputSubsystem; +} + +namespace Ui { +class ConfigureMousePanning; +} + +class ConfigureMousePanning : public QDialog { + Q_OBJECT +public: + explicit ConfigureMousePanning(QWidget* parent, InputCommon::InputSubsystem* input_subsystem_, + float right_stick_deadzone, float right_stick_range); + ~ConfigureMousePanning() override; + +public slots: + void ApplyConfiguration(); + +private: + void closeEvent(QCloseEvent* event) override; + void SetConfiguration(float right_stick_deadzone, float right_stick_range); + void SetDefaultConfiguration(); + void ConnectEvents(); + + InputCommon::InputSubsystem* input_subsystem; + std::unique_ptr ui; +}; diff --git a/src/yuzu/configuration/configure_mouse_panning.ui b/src/yuzu/configuration/configure_mouse_panning.ui new file mode 100755 index 000000000..75795b727 --- /dev/null +++ b/src/yuzu/configuration/configure_mouse_panning.ui @@ -0,0 +1,238 @@ + + + ConfigureMousePanning + + + Configure mouse panning + + + + + + Enable + + + Can be toggled via a hotkey + + + + + + + + + Sensitivity + + + + + + Horizontal + + + + + + + Qt::AlignCenter + + + % + + + 1 + + + 100 + + + 50 + + + + + + + Vertical + + + + + + + Qt::AlignCenter + + + % + + + 1 + + + 100 + + + 50 + + + + + + + + + + Deadzone counterweight + + + Counteracts a game's built-in deadzone + + + + + + Horizontal + + + + + + + Qt::AlignCenter + + + % + + + 0 + + + 100 + + + 0 + + + + + + + Vertical + + + + + + + Qt::AlignCenter + + + % + + + 0 + + + 100 + + + 0 + + + + + + + + + + Stick decay + + + + + + Strength + + + + + + + Qt::AlignCenter + + + % + + + 0 + + + 100 + + + 22 + + + + + + + Minimum + + + + + + + Qt::AlignCenter + + + % + + + 0 + + + 100 + + + 5 + + + + + + + + + + + + + + + + + + + + + Default + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index aa0488ce8..ce821e2be 100755 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -3840,7 +3840,7 @@ void GMainWindow::OnLoadAmiibo() { auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); // Remove amiibo if one is connected - if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) { + if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::TagNearby) { virtual_amiibo->CloseAmiibo(); QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed")); return; @@ -3868,7 +3868,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) { auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo(); const QString title = tr("Error loading Amiibo data"); // Remove amiibo if one is connected - if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) { + if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::TagNearby) { virtual_amiibo->CloseAmiibo(); QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed")); return; diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 1e9549384..5f53d5ac3 100755 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -140,9 +140,29 @@ udp_input_servers = # 0 (default): Off, 1: On mouse_panning = -# Set mouse sensitivity. -# Default: 1.0 -mouse_panning_sensitivity = +# Set mouse panning horizontal sensitivity. +# Default: 50.0 +mouse_panning_x_sensitivity = + +# Set mouse panning vertical sensitivity. +# Default: 50.0 +mouse_panning_y_sensitivity = + +# Set mouse panning deadzone horizontal counterweight. +# Default: 0.0 +mouse_panning_deadzone_x_counterweight = + +# Set mouse panning deadzone vertical counterweight. +# Default: 0.0 +mouse_panning_deadzone_y_counterweight = + +# Set mouse panning stick decay strength. +# Default: 22.0 +mouse_panning_decay_strength = + +# Set mouse panning stick minimum decay. +# Default: 5.0 +mouse_panning_minimum_decay = # Emulate an analog control stick from keyboard inputs. # 0 (default): Disabled, 1: Enabled