mirror of
https://github.com/pineappleEA/pineapple-src.git
synced 2024-11-25 06:48:23 -05:00
early-access version 3702
This commit is contained in:
parent
d8ad4a1c8c
commit
7d5a10eb6b
@ -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
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<bool(const std::filesystem::path& path)>;
|
||||
using DirEntryCallable = std::function<bool(const std::filesystem::directory_entry& entry)>;
|
||||
|
||||
} // namespace Common::FS
|
||||
|
@ -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<u8> data{};
|
||||
NfcState state{NfcState::Unknown};
|
||||
u8 uuid_length;
|
||||
u8 protocol;
|
||||
u8 tag_type;
|
||||
std::array<u8, 10> uuid;
|
||||
};
|
||||
|
||||
struct MifareData {
|
||||
u8 command;
|
||||
u8 sector;
|
||||
std::array<u8, 0x6> key;
|
||||
std::array<u8, 0x10> data;
|
||||
};
|
||||
|
||||
struct MifareRequest {
|
||||
std::array<MifareData, 0x10> data;
|
||||
};
|
||||
|
||||
// List of buttons to be passed to Qt that can be translated
|
||||
@ -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<u8> 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<u8>& out_data) {
|
||||
return NfcState::NotSupported;
|
||||
}
|
||||
|
||||
virtual NfcState WriteNfcData([[maybe_unused]] const std::vector<u8>& 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.
|
||||
|
@ -525,9 +525,16 @@ struct Values {
|
||||
Setting<bool> tas_loop{false, "tas_loop"};
|
||||
|
||||
Setting<bool> mouse_panning{false, "mouse_panning"};
|
||||
Setting<u8, true> mouse_panning_sensitivity{50, 1, 100, "mouse_panning_sensitivity"};
|
||||
Setting<bool> mouse_enabled{false, "mouse_enabled"};
|
||||
Setting<u8, true> mouse_panning_x_sensitivity{50, 1, 100, "mouse_panning_x_sensitivity"};
|
||||
Setting<u8, true> mouse_panning_y_sensitivity{50, 1, 100, "mouse_panning_y_sensitivity"};
|
||||
Setting<u8, true> mouse_panning_deadzone_x_counterweight{
|
||||
0, 0, 100, "mouse_panning_deadzone_x_counterweight"};
|
||||
Setting<u8, true> mouse_panning_deadzone_y_counterweight{
|
||||
0, 0, 100, "mouse_panning_deadzone_y_counterweight"};
|
||||
Setting<u8, true> mouse_panning_decay_strength{22, 0, 100, "mouse_panning_decay_strength"};
|
||||
Setting<u8, true> mouse_panning_min_decay{5, 0, 100, "mouse_panning_min_decay"};
|
||||
|
||||
Setting<bool> mouse_enabled{false, "mouse_enabled"};
|
||||
Setting<bool> emulate_analog_keyboard{false, "emulate_analog_keyboard"};
|
||||
Setting<bool> keyboard_enabled{false, "keyboard_enabled"};
|
||||
|
||||
|
@ -153,7 +153,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id);
|
||||
|
||||
std::vector<VirtualDir> 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");
|
||||
|
@ -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<u64> 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<FileReference>();
|
||||
this->InsertReferenceIntoList(*reference);
|
||||
|
||||
auto file =
|
||||
std::shared_ptr<RealVfsFile>(new RealVfsFile(*this, std::move(reference), path, perms));
|
||||
auto file = std::shared_ptr<RealVfsFile>(
|
||||
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<FileReference> reference_,
|
||||
const std::string& path_, Mode perms_)
|
||||
const std::string& path_, Mode perms_, std::optional<u64> 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<s64>(offset))) {
|
||||
return 0;
|
||||
@ -309,10 +319,11 @@ std::vector<VirtualFile> RealVfsDirectory::IterateEntries<RealVfsFile, VfsFile>(
|
||||
|
||||
std::vector<VirtualFile> 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<VirtualDir> RealVfsDirectory::IterateEntries<RealVfsDirectory, VfsDi
|
||||
|
||||
std::vector<VirtualDir> 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<std::string, VfsEntryType, std::less<>> RealVfsDirectory::GetEntries()
|
||||
|
||||
std::map<std::string, VfsEntryType, std::less<>> 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;
|
||||
};
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include "common/intrusive_list.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
@ -20,6 +21,8 @@ struct FileReference : public Common::IntrusiveListBaseNode<FileReference> {
|
||||
};
|
||||
|
||||
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<u64> 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<FileReference> reference,
|
||||
const std::string& path, Mode perms = Mode::Read);
|
||||
const std::string& path, Mode perms = Mode::Read, std::optional<u64> size = {});
|
||||
|
||||
RealVfsFilesystem& base;
|
||||
std::unique_ptr<FileReference> reference;
|
||||
std::string path;
|
||||
std::string parent_path;
|
||||
std::vector<std::string> path_components;
|
||||
std::optional<u64> size;
|
||||
Mode perms;
|
||||
};
|
||||
|
||||
|
@ -149,11 +149,15 @@ void EmulatedController::LoadDevices() {
|
||||
|
||||
camera_params[0] = right_joycon;
|
||||
camera_params[0].Set("camera", true);
|
||||
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"};
|
||||
nfc_params[1] = right_joycon;
|
||||
nfc_params[1].Set("nfc", true);
|
||||
}
|
||||
|
||||
output_params[LeftIndex] = left_joycon;
|
||||
output_params[RightIndex] = right_joycon;
|
||||
@ -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<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_virtual_output_device = output_devices[3];
|
||||
|
||||
return nfc_output_device->StartNfcPolling() == Common::Input::NfcState::Success ||
|
||||
nfc_virtual_output_device->StartNfcPolling() == Common::Input::NfcState::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::StopNfcPolling() {
|
||||
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_virtual_output_device = output_devices[3];
|
||||
|
||||
return nfc_output_device->StopNfcPolling() == Common::Input::NfcState::Success ||
|
||||
nfc_virtual_output_device->StopNfcPolling() == Common::Input::NfcState::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
|
||||
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_virtual_output_device = output_devices[3];
|
||||
|
||||
if (nfc_output_device->ReadAmiiboData(data) == Common::Input::NfcState::Success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return nfc_virtual_output_device->ReadAmiiboData(data) == Common::Input::NfcState::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request,
|
||||
Common::Input::MifareRequest& out_data) {
|
||||
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_virtual_output_device = output_devices[3];
|
||||
|
||||
if (nfc_output_device->ReadMifareData(request, out_data) == Common::Input::NfcState::Success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return nfc_virtual_output_device->ReadMifareData(request, out_data) ==
|
||||
Common::Input::NfcState::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) {
|
||||
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_virtual_output_device = output_devices[3];
|
||||
|
||||
if (nfc_output_device->WriteMifareData(request) == Common::Input::NfcState::Success) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return nfc_virtual_output_device->WriteMifareData(request) == Common::Input::NfcState::Success;
|
||||
}
|
||||
|
||||
bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
|
||||
auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
|
||||
auto& nfc_virtual_output_device = output_devices[3];
|
||||
|
@ -97,10 +97,7 @@ struct RingSensorForce {
|
||||
f32 force;
|
||||
};
|
||||
|
||||
struct NfcState {
|
||||
Common::Input::NfcState state{};
|
||||
std::vector<u8> 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<u8>& data);
|
||||
|
||||
/// Returns true if the nfc tag was written
|
||||
bool WriteNfc(const std::vector<u8>& data);
|
||||
|
||||
/// Returns true if the nfc tag was readable
|
||||
bool ReadMifareData(const Common::Input::MifareRequest& request,
|
||||
Common::Input::MifareRequest& out_data);
|
||||
|
||||
/// Returns true if the nfc tag was written
|
||||
bool WriteMifareData(const Common::Input::MifareRequest& request);
|
||||
|
||||
/// Returns the led pattern corresponding to this emulated controller
|
||||
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};
|
||||
|
@ -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;
|
||||
|
@ -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()) {
|
||||
|
@ -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<const u8> 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<u8>(allowed_protocols)) == 0) {
|
||||
LOG_ERROR(Service_NFC, "Protocol not supported {}", protocol);
|
||||
return false;
|
||||
}
|
||||
|
||||
real_tag_info = {
|
||||
.uuid = uuid,
|
||||
.uuid_length = uuid_length,
|
||||
.protocol = static_cast<NfcProtocol>(protocol),
|
||||
.tag_type = static_cast<TagType>(tag_type),
|
||||
};
|
||||
|
||||
device_state = DeviceState::TagFound;
|
||||
deactivate_event->GetReadableEvent().Clear();
|
||||
activate_event->Signal();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NfcDevice::LoadAmiiboData() {
|
||||
std::vector<u8> data{};
|
||||
|
||||
if (!npad_device->ReadAmiiboData(data)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.size() < sizeof(NFP::EncryptedNTAG215File)) {
|
||||
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<const u8> 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,40 +266,14 @@ 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};
|
||||
|
||||
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);
|
||||
tag_info = real_tag_info;
|
||||
|
||||
// Generate random UUID to bypass amiibo load limits
|
||||
if (Settings::values.random_amiibo_id) {
|
||||
if (real_tag_info.tag_type == TagType::Type2 && Settings::values.random_amiibo_id) {
|
||||
Common::TinyMT rng{};
|
||||
rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
|
||||
rng.GenerateRandomBytes(uuid.data(), uuid_length);
|
||||
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<const MifareReadBlockParameter> 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<const MifareReadBlockParameter> 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<const MifareReadBlockParameter> 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<u8>(parameters[i].sector_key.command);
|
||||
request.data[i].sector = parameters[i].sector_number;
|
||||
memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(),
|
||||
sizeof(KeyData));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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<MifareCmd>(out_data.data[i].command) == MifareCmd::None) {
|
||||
continue;
|
||||
}
|
||||
|
||||
read_block_data[i] = {
|
||||
.data = out_data.data[i].data,
|
||||
.sector_number = out_data.data[i].sector,
|
||||
};
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
@ -342,7 +346,7 @@ Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> 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<const MifareWriteBlockParameter> 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<u8>(parameters[i].sector_key.command);
|
||||
request.data[i].sector = parameters[i].sector_number;
|
||||
memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(),
|
||||
sizeof(KeyData));
|
||||
memcpy(request.data[i].data.data(), parameters[i].data.data(), sizeof(KeyData));
|
||||
}
|
||||
|
||||
if (!npad_device->WriteNfc(mifare_data)) {
|
||||
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<const u8> command_data,
|
||||
std::span<u8> 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<u8, sizeof(NFP::EncryptedNTAG215File)> 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),
|
||||
};
|
||||
|
@ -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<const MifareReadBlockParameter> parameters,
|
||||
std::span<MifareReadBlockData> read_block_data) const;
|
||||
Result ReadMifare(const MifareReadBlockParameter& parameter,
|
||||
MifareReadBlockData& read_block_data) const;
|
||||
|
||||
Result WriteMifare(std::span<const MifareWriteBlockParameter> parameters);
|
||||
Result WriteMifare(const MifareWriteBlockParameter& parameter);
|
||||
|
||||
Result SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout,
|
||||
std::span<const u8> command_data, std::span<u8> out_data);
|
||||
@ -105,7 +102,8 @@ public:
|
||||
|
||||
private:
|
||||
void NpadUpdate(Core::HID::ControllerTriggerType type);
|
||||
bool LoadNfcTag(std::span<const u8> data);
|
||||
bool LoadNfcTag(u8 protocol, u8 tag_type, u8 uuid_length, UniqueSerialNumber uuid);
|
||||
bool LoadAmiiboData();
|
||||
void CloseNfcTag();
|
||||
|
||||
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<u8> mifare_data{};
|
||||
NFP::EncryptedNTAG215File encrypted_tag_data{};
|
||||
};
|
||||
|
||||
|
@ -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<NfcDevice> 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<u8> 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<const u8> dat
|
||||
NFC::TagInfo tag_info{};
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
result = device->GetTagInfo(tag_info, false);
|
||||
result = device->GetTagInfo(tag_info);
|
||||
}
|
||||
|
||||
if (result.IsSuccess()) {
|
||||
|
@ -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,
|
||||
|
@ -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");
|
||||
|
@ -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<MifareReadBlockData> out_data(number_of_commands);
|
||||
auto result = GetManager()->ReadMifare(device_handle, read_commands, out_data);
|
||||
|
@ -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<u8>& 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<u8>& 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<u8>& out_data) {
|
||||
auto handle = GetHandle(identifier);
|
||||
if (handle == nullptr) {
|
||||
return Common::Input::NfcState::Unknown;
|
||||
}
|
||||
return TranslateDriverResult(handle->ReadAmiiboData(out_data));
|
||||
}
|
||||
|
||||
Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier,
|
||||
const std::vector<u8>& 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<Joycon::MifareCmd>(request.data[0].command);
|
||||
std::vector<Joycon::MifareReadChunk> read_request{};
|
||||
for (const auto& request_data : request.data) {
|
||||
if (request_data.command == 0) {
|
||||
continue;
|
||||
}
|
||||
Joycon::MifareReadChunk chunk = {
|
||||
.command = command,
|
||||
.sector_key = {},
|
||||
.sector = request_data.sector,
|
||||
};
|
||||
memcpy(chunk.sector_key.data(), request_data.key.data(),
|
||||
sizeof(Joycon::MifareReadChunk::sector_key));
|
||||
read_request.emplace_back(chunk);
|
||||
}
|
||||
|
||||
std::vector<Joycon::MifareReadData> read_data(read_request.size());
|
||||
const auto result = handle->ReadMifareData(read_request, read_data);
|
||||
if (result == Joycon::DriverResult::Success) {
|
||||
for (std::size_t i = 0; i < read_request.size(); i++) {
|
||||
data.data[i] = {
|
||||
.command = static_cast<u8>(command),
|
||||
.sector = read_data[i].sector,
|
||||
.key = {},
|
||||
.data = read_data[i].data,
|
||||
};
|
||||
}
|
||||
}
|
||||
return TranslateDriverResult(result);
|
||||
};
|
||||
|
||||
Common::Input::NfcState Joycons::WriteMifareData(const PadIdentifier& identifier,
|
||||
const Common::Input::MifareRequest& request) {
|
||||
auto handle = GetHandle(identifier);
|
||||
if (handle == nullptr) {
|
||||
return Common::Input::NfcState::Unknown;
|
||||
}
|
||||
|
||||
const auto command = static_cast<Joycon::MifareCmd>(request.data[0].command);
|
||||
std::vector<Joycon::MifareWriteChunk> write_request{};
|
||||
for (const auto& request_data : request.data) {
|
||||
if (request_data.command == 0) {
|
||||
continue;
|
||||
}
|
||||
Joycon::MifareWriteChunk chunk = {
|
||||
.command = command,
|
||||
.sector_key = {},
|
||||
.sector = request_data.sector,
|
||||
.data = {},
|
||||
};
|
||||
memcpy(chunk.sector_key.data(), request_data.key.data(),
|
||||
sizeof(Joycon::MifareReadChunk::sector_key));
|
||||
memcpy(chunk.data.data(), request_data.data.data(), sizeof(Joycon::MifareWriteChunk::data));
|
||||
write_request.emplace_back(chunk);
|
||||
}
|
||||
|
||||
return TranslateDriverResult(handle->WriteMifareData(write_request));
|
||||
};
|
||||
|
||||
Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier,
|
||||
@ -403,11 +495,20 @@ void Joycons::OnRingConUpdate(f32 ring_data) {
|
||||
}
|
||||
|
||||
void Joycons::OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type,
|
||||
const std::vector<u8>& amiibo_data) {
|
||||
const Joycon::TagInfo& tag_info) {
|
||||
const auto identifier = GetIdentifier(port, type);
|
||||
const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved
|
||||
const auto nfc_state = tag_info.uuid_length == 0 ? Common::Input::NfcState::AmiiboRemoved
|
||||
: Common::Input::NfcState::NewAmiibo;
|
||||
SetNfc(identifier, {nfc_state, amiibo_data});
|
||||
|
||||
const Common::Input::NfcStatus nfc_status{
|
||||
.state = nfc_state,
|
||||
.uuid_length = tag_info.uuid_length,
|
||||
.protocol = tag_info.protocol,
|
||||
.tag_type = tag_info.tag_type,
|
||||
.uuid = tag_info.uuid,
|
||||
};
|
||||
|
||||
SetNfc(identifier, nfc_status);
|
||||
}
|
||||
|
||||
void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
|
||||
@ -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
|
||||
|
@ -15,6 +15,7 @@ using SerialNumber = std::array<u8, 15>;
|
||||
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<u8>& out_data) override;
|
||||
Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier,
|
||||
const std::vector<u8>& data) override;
|
||||
Common::Input::NfcState ReadMifareData(const PadIdentifier& identifier,
|
||||
const Common::Input::MifareRequest& request,
|
||||
Common::Input::MifareRequest& out_data) override;
|
||||
Common::Input::NfcState WriteMifareData(const PadIdentifier& identifier,
|
||||
const Common::Input::MifareRequest& request) override;
|
||||
|
||||
Common::Input::DriverResult SetPollingMode(
|
||||
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<u8>& amiibo_data);
|
||||
const Joycon::TagInfo& amiibo_data);
|
||||
void OnCameraUpdate(std::size_t port, const std::vector<u8>& 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
|
||||
|
@ -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<float>();
|
||||
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<int>(x, y) - mouse_origin;
|
||||
const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f;
|
||||
SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * sensitivity);
|
||||
SetAxis(identifier, mouse_axis_y, static_cast<float>(-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<float>(mouse_move.x) * x_sensitivity * 0.0012f);
|
||||
SetAxis(identifier, mouse_axis_y,
|
||||
static_cast<float>(-mouse_move.y) * y_sensitivity * 0.0012f);
|
||||
|
||||
last_motion_change = {
|
||||
static_cast<float>(-mouse_move.y) / 50.0f,
|
||||
@ -241,10 +244,6 @@ void Mouse::ReleaseAllButtons() {
|
||||
button_pressed = false;
|
||||
}
|
||||
|
||||
void Mouse::StopPanning() {
|
||||
last_mouse_change = {};
|
||||
}
|
||||
|
||||
std::vector<Common::ParamPackage> Mouse::GetInputDevices() const {
|
||||
std::vector<Common::ParamPackage> devices;
|
||||
devices.emplace_back(Common::ParamPackage{
|
||||
|
@ -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<float> last_motion_change;
|
||||
Common::Vec2<int> wheel_position;
|
||||
bool button_pressed;
|
||||
int mouse_panning_timeout{};
|
||||
std::jthread update_thread;
|
||||
};
|
||||
|
||||
|
@ -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<u8>& out_data) {
|
||||
if (state != State::TagNearby) {
|
||||
return Common::Input::NfcState::WrongDeviceState;
|
||||
}
|
||||
|
||||
if (status.tag_type != 1U << 1) {
|
||||
return Common::Input::NfcState::InvalidTagType;
|
||||
}
|
||||
|
||||
out_data.resize(nfc_data.size());
|
||||
memcpy(out_data.data(), nfc_data.data(), nfc_data.size());
|
||||
return Common::Input::NfcState::Success;
|
||||
}
|
||||
|
||||
Common::Input::NfcState VirtualAmiibo::WriteNfcData(
|
||||
[[maybe_unused]] const PadIdentifier& identifier_, const std::vector<u8>& 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<u8> 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;
|
||||
}
|
||||
|
||||
|
@ -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<u8>& out_data) override;
|
||||
Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
|
||||
const std::vector<u8>& data) override;
|
||||
Common::Input::NfcState ReadMifareData(const PadIdentifier& identifier_,
|
||||
const Common::Input::MifareRequest& data,
|
||||
Common::Input::MifareRequest& out_data) override;
|
||||
Common::Input::NfcState WriteMifareData(const PadIdentifier& identifier_,
|
||||
const Common::Input::MifareRequest& data) override;
|
||||
|
||||
State GetCurrentState() const;
|
||||
|
||||
@ -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<u8> nfc_data;
|
||||
Common::Input::NfcStatus status;
|
||||
Common::Input::PollingMode polling_mode{Common::Input::PollingMode::Passive};
|
||||
};
|
||||
} // namespace InputCommon
|
||||
|
@ -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<JoyconPoller>(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<u8> 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<u8> buffer) {
|
||||
}
|
||||
|
||||
if (!amiibo_detected) {
|
||||
std::vector<u8> 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<u8> 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<const u8> 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<u8>& out_data) {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (!supported_features.nfc) {
|
||||
return DriverResult::NotSupported;
|
||||
@ -506,9 +533,72 @@ DriverResult JoyconDriver::WriteNfcData(std::span<const u8> 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<const u8> data) {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (!supported_features.nfc) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
if (!nfc_protocol->IsEnabled()) {
|
||||
return DriverResult::Disabled;
|
||||
}
|
||||
if (!amiibo_detected) {
|
||||
return DriverResult::ErrorWritingData;
|
||||
}
|
||||
|
||||
disable_input_thread = true;
|
||||
const auto result = nfc_protocol->WriteAmiibo(data);
|
||||
disable_input_thread = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::ReadMifareData(std::span<const MifareReadChunk> data,
|
||||
std::span<MifareReadData> out_data) {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (!supported_features.nfc) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
if (!nfc_protocol->IsEnabled()) {
|
||||
return DriverResult::Disabled;
|
||||
}
|
||||
if (!amiibo_detected) {
|
||||
return DriverResult::ErrorWritingData;
|
||||
}
|
||||
|
||||
disable_input_thread = true;
|
||||
const auto result = nfc_protocol->ReadMifare(data, out_data);
|
||||
disable_input_thread = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::WriteMifareData(std::span<const MifareWriteChunk> data) {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
if (!supported_features.nfc) {
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
if (!nfc_protocol->IsEnabled()) {
|
||||
return DriverResult::Disabled;
|
||||
}
|
||||
if (!amiibo_detected) {
|
||||
return DriverResult::ErrorWritingData;
|
||||
}
|
||||
|
||||
disable_input_thread = true;
|
||||
const auto result = nfc_protocol->WriteMifare(data);
|
||||
disable_input_thread = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -49,7 +49,13 @@ public:
|
||||
DriverResult SetIrMode();
|
||||
DriverResult SetNfcMode();
|
||||
DriverResult SetRingConMode();
|
||||
DriverResult StartNfcPolling();
|
||||
DriverResult StopNfcPolling();
|
||||
DriverResult ReadAmiiboData(std::vector<u8>& out_data);
|
||||
DriverResult WriteNfcData(std::span<const u8> data);
|
||||
DriverResult ReadMifareData(std::span<const MifareReadChunk> request,
|
||||
std::span<MifareReadData> out_data);
|
||||
DriverResult WriteMifareData(std::span<const MifareWriteChunk> request);
|
||||
|
||||
void SetCallbacks(const JoyconCallbacks& callbacks);
|
||||
|
||||
|
@ -24,6 +24,7 @@ constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x
|
||||
using MacAddress = std::array<u8, 6>;
|
||||
using SerialNumber = std::array<u8, 15>;
|
||||
using TagUUID = std::array<u8, 7>;
|
||||
using MifareUUID = std::array<u8, 4>;
|
||||
|
||||
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<NFCDataChunk, 4> data_chunks;
|
||||
};
|
||||
|
||||
struct MifareReadChunk {
|
||||
MifareCmd command;
|
||||
std::array<u8, 0x6> sector_key;
|
||||
u8 sector;
|
||||
};
|
||||
|
||||
struct MifareWriteChunk {
|
||||
MifareCmd command;
|
||||
std::array<u8, 0x6> sector_key;
|
||||
u8 sector;
|
||||
std::array<u8, 0x10> data;
|
||||
};
|
||||
|
||||
struct MifareReadData {
|
||||
u8 sector;
|
||||
std::array<u8, 0x10> data;
|
||||
};
|
||||
|
||||
struct MifareReadPackage {
|
||||
MifareCommandData command_data;
|
||||
std::array<MifareReadChunk, 0x10> data_chunks;
|
||||
};
|
||||
|
||||
struct MifareWritePackage {
|
||||
MifareCommandData command_data;
|
||||
std::array<MifareWriteChunk, 0x10> data_chunks;
|
||||
};
|
||||
|
||||
struct TagInfo {
|
||||
u8 uuid_length;
|
||||
u8 protocol;
|
||||
u8 tag_type;
|
||||
std::array<u8, 10> uuid;
|
||||
};
|
||||
|
||||
struct IrsConfigure {
|
||||
MCUCommand command;
|
||||
MCUSubCommand sub_command;
|
||||
@ -744,7 +801,7 @@ struct JoyconCallbacks {
|
||||
std::function<void(int, f32)> on_stick_data;
|
||||
std::function<void(int, const MotionData&)> on_motion_data;
|
||||
std::function<void(f32)> on_ring_data;
|
||||
std::function<void(const std::vector<u8>&)> on_amiibo_data;
|
||||
std::function<void(const Joycon::TagInfo&)> on_amiibo_data;
|
||||
std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data;
|
||||
};
|
||||
|
||||
|
@ -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<u8>& data) {
|
||||
DriverResult NfcProtocol::StopNFCPollingMode() {
|
||||
LOG_DEBUG(Input, "Stop NFC polling Mode");
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
MCUCommandResponse output{};
|
||||
result = SendStopPollingRequest(output);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitUntilNfcIs(NFCStatus::WriteReady);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
is_polling = false;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::GetTagInfo(Joycon::TagInfo& tag_info) {
|
||||
if (update_counter++ < AMIIBO_UPDATE_DELAY) {
|
||||
return DriverResult::Delayed;
|
||||
}
|
||||
@ -100,11 +123,41 @@ DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& 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<u8>& data) {
|
||||
LOG_DEBUG(Input, "Scan for amiibos");
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
TagFoundData tag_data{};
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = IsTagInRange(tag_data, 7);
|
||||
}
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = GetAmiiboData(data);
|
||||
}
|
||||
|
||||
@ -154,6 +207,69 @@ DriverResult NfcProtocol::WriteAmiibo(std::span<const u8> data) {
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::ReadMifare(std::span<const MifareReadChunk> read_request,
|
||||
std::span<MifareReadData> out_data) {
|
||||
LOG_DEBUG(Input, "Read mifare");
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
TagFoundData tag_data{};
|
||||
MifareUUID tag_uuid{};
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = IsTagInRange(tag_data, 7);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
memcpy(tag_uuid.data(), tag_data.uuid.data(), sizeof(MifareUUID));
|
||||
result = GetMifareData(tag_uuid, read_request, out_data);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
MCUCommandResponse output{};
|
||||
result = SendStopPollingRequest(output);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitUntilNfcIs(NFCStatus::Ready);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
MCUCommandResponse output{};
|
||||
result = SendStartPollingRequest(output, true);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitUntilNfcIs(NFCStatus::WriteReady);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::WriteMifare(std::span<const MifareWriteChunk> write_request) {
|
||||
LOG_DEBUG(Input, "Write mifare");
|
||||
ScopedSetBlocking sb(this);
|
||||
DriverResult result{DriverResult::Success};
|
||||
TagFoundData tag_data{};
|
||||
MifareUUID tag_uuid{};
|
||||
|
||||
if (result == DriverResult::Success) {
|
||||
result = IsTagInRange(tag_data, 7);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
memcpy(tag_uuid.data(), tag_data.uuid.data(), sizeof(MifareUUID));
|
||||
result = WriteMifareData(tag_uuid, write_request);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
MCUCommandResponse output{};
|
||||
result = SendStopPollingRequest(output);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitUntilNfcIs(NFCStatus::Ready);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
MCUCommandResponse output{};
|
||||
result = SendStartPollingRequest(output, true);
|
||||
}
|
||||
if (result == DriverResult::Success) {
|
||||
result = WaitUntilNfcIs(NFCStatus::WriteReady);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool NfcProtocol::HasAmiibo() {
|
||||
if (update_counter++ < AMIIBO_UPDATE_DELAY) {
|
||||
return true;
|
||||
@ -341,6 +457,158 @@ DriverResult NfcProtocol::WriteAmiiboData(const TagUUID& tag_uuid, std::span<con
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::GetMifareData(const MifareUUID& tag_uuid,
|
||||
std::span<const MifareReadChunk> read_request,
|
||||
std::span<MifareReadData> out_data) {
|
||||
constexpr std::size_t timeout_limit = 60;
|
||||
const auto nfc_data = MakeMifareReadPackage(tag_uuid, read_request);
|
||||
const std::vector<u8> nfc_buffer_data = SerializeMifareReadPackage(nfc_data);
|
||||
std::span<const u8> buffer(nfc_buffer_data);
|
||||
DriverResult result = DriverResult::Success;
|
||||
MCUCommandResponse output{};
|
||||
u8 block_id = 1;
|
||||
u8 package_index = 0;
|
||||
std::size_t tries = 0;
|
||||
std::size_t current_position = 0;
|
||||
|
||||
LOG_INFO(Input, "Reading Mifare data");
|
||||
|
||||
// Send data request. Nfc buffer size is 31, Send the data in smaller packages
|
||||
while (current_position < buffer.size() && tries++ < timeout_limit) {
|
||||
const std::size_t next_position =
|
||||
std::min(current_position + sizeof(NFCRequestState::raw_data), buffer.size());
|
||||
const std::size_t block_size = next_position - current_position;
|
||||
const bool is_last_packet = block_size < sizeof(NFCRequestState::raw_data);
|
||||
|
||||
SendReadDataMifareRequest(output, block_id, is_last_packet,
|
||||
buffer.subspan(current_position, block_size));
|
||||
|
||||
const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
|
||||
|
||||
if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
|
||||
return DriverResult::ErrorReadingData;
|
||||
}
|
||||
|
||||
// Increase position when data is confirmed by the joycon
|
||||
if (output.mcu_report == MCUReport::NFCState &&
|
||||
(output.mcu_data[1] << 8) + output.mcu_data[0] == 0x0500 &&
|
||||
output.mcu_data[3] == block_id) {
|
||||
block_id++;
|
||||
current_position = next_position;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Wait for reply and save the output data
|
||||
while (tries++ < timeout_limit) {
|
||||
result = SendNextPackageRequest(output, package_index);
|
||||
const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
|
||||
return DriverResult::ErrorReadingData;
|
||||
}
|
||||
|
||||
if (output.mcu_report == MCUReport::NFCState && output.mcu_data[1] == 0x10) {
|
||||
constexpr std::size_t DATA_LENGHT = 0x10 + 1;
|
||||
constexpr std::size_t DATA_START = 11;
|
||||
const u8 number_of_elements = output.mcu_data[10];
|
||||
for (std::size_t i = 0; i < number_of_elements; i++) {
|
||||
out_data[i].sector = output.mcu_data[DATA_START + (i * DATA_LENGHT)];
|
||||
memcpy(out_data[i].data.data(),
|
||||
output.mcu_data.data() + DATA_START + 1 + (i * DATA_LENGHT),
|
||||
sizeof(MifareReadData::data));
|
||||
}
|
||||
package_index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::MifareDone) {
|
||||
LOG_INFO(Input, "Finished reading mifare");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::WriteMifareData(const MifareUUID& tag_uuid,
|
||||
std::span<const MifareWriteChunk> write_request) {
|
||||
constexpr std::size_t timeout_limit = 60;
|
||||
const auto nfc_data = MakeMifareWritePackage(tag_uuid, write_request);
|
||||
const std::vector<u8> nfc_buffer_data = SerializeMifareWritePackage(nfc_data);
|
||||
std::span<const u8> buffer(nfc_buffer_data);
|
||||
DriverResult result = DriverResult::Success;
|
||||
MCUCommandResponse output{};
|
||||
u8 block_id = 1;
|
||||
u8 package_index = 0;
|
||||
std::size_t tries = 0;
|
||||
std::size_t current_position = 0;
|
||||
|
||||
LOG_INFO(Input, "Writing Mifare data");
|
||||
|
||||
// Send data request. Nfc buffer size is 31, Send the data in smaller packages
|
||||
while (current_position < buffer.size() && tries++ < timeout_limit) {
|
||||
const std::size_t next_position =
|
||||
std::min(current_position + sizeof(NFCRequestState::raw_data), buffer.size());
|
||||
const std::size_t block_size = next_position - current_position;
|
||||
const bool is_last_packet = block_size < sizeof(NFCRequestState::raw_data);
|
||||
|
||||
SendReadDataMifareRequest(output, block_id, is_last_packet,
|
||||
buffer.subspan(current_position, block_size));
|
||||
|
||||
const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
|
||||
|
||||
if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
|
||||
return DriverResult::ErrorReadingData;
|
||||
}
|
||||
|
||||
// Increase position when data is confirmed by the joycon
|
||||
if (output.mcu_report == MCUReport::NFCState &&
|
||||
(output.mcu_data[1] << 8) + output.mcu_data[0] == 0x0500 &&
|
||||
output.mcu_data[3] == block_id) {
|
||||
block_id++;
|
||||
current_position = next_position;
|
||||
}
|
||||
}
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// Wait for reply and ignore the output data
|
||||
while (tries++ < timeout_limit) {
|
||||
result = SendNextPackageRequest(output, package_index);
|
||||
const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
|
||||
|
||||
if (result != DriverResult::Success) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
|
||||
return DriverResult::ErrorReadingData;
|
||||
}
|
||||
|
||||
if (output.mcu_report == MCUReport::NFCState && output.mcu_data[1] == 0x10) {
|
||||
package_index++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::MifareDone) {
|
||||
LOG_INFO(Input, "Finished writing mifare");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output,
|
||||
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<const u8> data) {
|
||||
const auto data_size = std::min(data.size(), sizeof(NFCRequestState::raw_data));
|
||||
NFCRequestState request{
|
||||
.command_argument = NFCCommand::Mifare,
|
||||
.block_id = block_id,
|
||||
.packet_id = {},
|
||||
.packet_flag =
|
||||
is_last_packet ? MCUPacketFlag::LastCommandPacket : MCUPacketFlag::MorePacketsRemaining,
|
||||
.data_length = static_cast<u8>(data_size),
|
||||
.raw_data = {},
|
||||
.crc = {},
|
||||
};
|
||||
memcpy(request.raw_data.data(), data.data(), data_size);
|
||||
|
||||
std::array<u8, sizeof(NFCRequestState)> request_data{};
|
||||
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
|
||||
request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
|
||||
return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
|
||||
output);
|
||||
}
|
||||
|
||||
std::vector<u8> NfcProtocol::SerializeWritePackage(const NFCWritePackage& package) const {
|
||||
const std::size_t header_size =
|
||||
sizeof(NFCWriteCommandData) + sizeof(NFCWritePackage::number_of_chunks);
|
||||
@ -498,6 +788,48 @@ std::vector<u8> NfcProtocol::SerializeWritePackage(const NFCWritePackage& packag
|
||||
return serialized_data;
|
||||
}
|
||||
|
||||
std::vector<u8> NfcProtocol::SerializeMifareReadPackage(const MifareReadPackage& package) const {
|
||||
const std::size_t header_size = sizeof(MifareCommandData);
|
||||
std::vector<u8> serialized_data(header_size);
|
||||
std::size_t start_index = 0;
|
||||
|
||||
memcpy(serialized_data.data(), &package, header_size);
|
||||
start_index += header_size;
|
||||
|
||||
for (const auto& data_chunk : package.data_chunks) {
|
||||
const std::size_t chunk_size = sizeof(MifareReadChunk);
|
||||
if (data_chunk.command == MifareCmd::None) {
|
||||
continue;
|
||||
}
|
||||
serialized_data.resize(start_index + chunk_size);
|
||||
memcpy(serialized_data.data() + start_index, &data_chunk, chunk_size);
|
||||
start_index += chunk_size;
|
||||
}
|
||||
|
||||
return serialized_data;
|
||||
}
|
||||
|
||||
std::vector<u8> NfcProtocol::SerializeMifareWritePackage(const MifareWritePackage& package) const {
|
||||
const std::size_t header_size = sizeof(MifareCommandData);
|
||||
std::vector<u8> serialized_data(header_size);
|
||||
std::size_t start_index = 0;
|
||||
|
||||
memcpy(serialized_data.data(), &package, header_size);
|
||||
start_index += header_size;
|
||||
|
||||
for (const auto& data_chunk : package.data_chunks) {
|
||||
const std::size_t chunk_size = sizeof(MifareWriteChunk);
|
||||
if (data_chunk.command == MifareCmd::None) {
|
||||
continue;
|
||||
}
|
||||
serialized_data.resize(start_index + chunk_size);
|
||||
memcpy(serialized_data.data() + start_index, &data_chunk, chunk_size);
|
||||
start_index += chunk_size;
|
||||
}
|
||||
|
||||
return serialized_data;
|
||||
}
|
||||
|
||||
NFCWritePackage NfcProtocol::MakeAmiiboWritePackage(const TagUUID& tag_uuid,
|
||||
std::span<const u8> data) const {
|
||||
return {
|
||||
@ -527,6 +859,46 @@ NFCWritePackage NfcProtocol::MakeAmiiboWritePackage(const TagUUID& tag_uuid,
|
||||
};
|
||||
}
|
||||
|
||||
MifareReadPackage NfcProtocol::MakeMifareReadPackage(
|
||||
const MifareUUID& tag_uuid, std::span<const MifareReadChunk> read_request) const {
|
||||
MifareReadPackage package{
|
||||
.command_data{
|
||||
.unknown1 = 0xd0,
|
||||
.unknown2 = 0x07,
|
||||
.number_of_short_bytes = static_cast<u8>(
|
||||
((read_request.size() * sizeof(MifareReadChunk)) + sizeof(MifareUUID)) / 2),
|
||||
.uid = tag_uuid,
|
||||
},
|
||||
.data_chunks = {},
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < read_request.size() && i < package.data_chunks.size(); ++i) {
|
||||
package.data_chunks[i] = read_request[i];
|
||||
}
|
||||
|
||||
return package;
|
||||
}
|
||||
|
||||
MifareWritePackage NfcProtocol::MakeMifareWritePackage(
|
||||
const MifareUUID& tag_uuid, std::span<const MifareWriteChunk> read_request) const {
|
||||
MifareWritePackage package{
|
||||
.command_data{
|
||||
.unknown1 = 0xd0,
|
||||
.unknown2 = 0x07,
|
||||
.number_of_short_bytes = static_cast<u8>(
|
||||
((read_request.size() * sizeof(MifareReadChunk)) + sizeof(MifareUUID) + 2) / 2),
|
||||
.uid = tag_uuid,
|
||||
},
|
||||
.data_chunks = {},
|
||||
};
|
||||
|
||||
for (std::size_t i = 0; i < read_request.size() && i < package.data_chunks.size(); ++i) {
|
||||
package.data_chunks[i] = read_request[i];
|
||||
}
|
||||
|
||||
return package;
|
||||
}
|
||||
|
||||
NFCDataChunk NfcProtocol::MakeAmiiboChunk(u8 page, u8 size, std::span<const u8> data) const {
|
||||
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
|
||||
|
@ -25,14 +25,25 @@ public:
|
||||
|
||||
DriverResult StartNFCPollingMode();
|
||||
|
||||
DriverResult ScanAmiibo(std::vector<u8>& data);
|
||||
DriverResult StopNFCPollingMode();
|
||||
|
||||
DriverResult GetTagInfo(Joycon::TagInfo& tag_info);
|
||||
|
||||
DriverResult ReadAmiibo(std::vector<u8>& data);
|
||||
|
||||
DriverResult WriteAmiibo(std::span<const u8> data);
|
||||
|
||||
DriverResult ReadMifare(std::span<const MifareReadChunk> read_request,
|
||||
std::span<MifareReadData> out_data);
|
||||
|
||||
DriverResult WriteMifare(std::span<const MifareWriteChunk> 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<const u8> data);
|
||||
|
||||
DriverResult GetMifareData(const MifareUUID& tag_uuid,
|
||||
std::span<const MifareReadChunk> read_request,
|
||||
std::span<MifareReadData> out_data);
|
||||
|
||||
DriverResult WriteMifareData(const MifareUUID& tag_uuid,
|
||||
std::span<const MifareWriteChunk> write_request);
|
||||
|
||||
DriverResult SendStartPollingRequest(MCUCommandResponse& output,
|
||||
bool is_second_attempt = false);
|
||||
|
||||
@ -65,17 +83,31 @@ private:
|
||||
DriverResult SendWriteDataAmiiboRequest(MCUCommandResponse& output, u8 block_id,
|
||||
bool is_last_packet, std::span<const u8> data);
|
||||
|
||||
DriverResult SendReadDataMifareRequest(MCUCommandResponse& output, u8 block_id,
|
||||
bool is_last_packet, std::span<const u8> data);
|
||||
|
||||
std::vector<u8> SerializeWritePackage(const NFCWritePackage& package) const;
|
||||
|
||||
std::vector<u8> SerializeMifareReadPackage(const MifareReadPackage& package) const;
|
||||
|
||||
std::vector<u8> SerializeMifareWritePackage(const MifareWritePackage& package) const;
|
||||
|
||||
NFCWritePackage MakeAmiiboWritePackage(const TagUUID& tag_uuid, std::span<const u8> data) const;
|
||||
|
||||
NFCDataChunk MakeAmiiboChunk(u8 page, u8 size, std::span<const u8> data) const;
|
||||
|
||||
MifareReadPackage MakeMifareReadPackage(const MifareUUID& tag_uuid,
|
||||
std::span<const MifareReadChunk> read_request) const;
|
||||
|
||||
MifareWritePackage MakeMifareWritePackage(const MifareUUID& tag_uuid,
|
||||
std::span<const MifareWriteChunk> read_request) const;
|
||||
|
||||
NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const;
|
||||
|
||||
TagUUID GetTagUUID(std::span<const u8> data) const;
|
||||
|
||||
bool is_enabled{};
|
||||
bool is_polling{};
|
||||
std::size_t update_counter{};
|
||||
};
|
||||
|
||||
|
@ -70,8 +70,8 @@ void JoyconPoller::UpdateColor(const Color& color) {
|
||||
callbacks.on_color_data(color);
|
||||
}
|
||||
|
||||
void JoyconPoller::UpdateAmiibo(const std::vector<u8>& 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<u8>& camera_data, IrsResolution format) {
|
||||
|
@ -36,8 +36,8 @@ public:
|
||||
|
||||
void UpdateColor(const Color& color);
|
||||
void UpdateRing(s16 value, const RingStatus& ring_status);
|
||||
void UpdateAmiibo(const std::vector<u8>& amiibo_data);
|
||||
void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format);
|
||||
void UpdateAmiibo(const Joycon::TagInfo& tag_info);
|
||||
void UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format);
|
||||
|
||||
private:
|
||||
void UpdateActiveLeftPadInput(const InputReportActive& input,
|
||||
|
@ -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<u8>& 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<u8>& 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;
|
||||
|
||||
|
@ -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<u8>& out_data) {
|
||||
return input_engine->ReadAmiiboData(identifier, out_data);
|
||||
}
|
||||
|
||||
Common::Input::NfcState WriteNfcData(const std::vector<u8>& 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;
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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<float>(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);
|
||||
|
@ -2744,48 +2744,13 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0">
|
||||
<widget class="QCheckBox" name="mouse_panning">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>23</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Enable mouse panning</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="2">
|
||||
<widget class="QSpinBox" name="mouse_panning_sensitivity">
|
||||
<property name="toolTip">
|
||||
<string>Mouse sensitivity</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>100</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="motion_touch">
|
||||
<property name="text">
|
||||
<string>Motion / Touch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="2">
|
||||
<item row="8" column="2">
|
||||
<widget class="QPushButton" name="buttonMotionTouch">
|
||||
<property name="text">
|
||||
<string>Configure</string>
|
||||
|
@ -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); });
|
||||
|
@ -3048,6 +3048,102 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="mousePanningWidget" native="true">
|
||||
<layout class="QHBoxLayout" name="mousePanningHorizontalLayout">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="mousePanningHorizontalSpacerLeft">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="mousePanningGroup">
|
||||
<property name="title">
|
||||
<string>Mouse panning</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="mousePanningVerticalLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="mousePanningButton">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>68</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>68</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">min-width: 68px;</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Configure</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="mousePanningHorizontalSpacerRight">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
79
src/yuzu/configuration/configure_mouse_panning.cpp
Executable file
79
src/yuzu/configuration/configure_mouse_panning.cpp
Executable file
@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <QCloseEvent>
|
||||
|
||||
#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::ConfigureMousePanning>()) {
|
||||
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<int>(right_stick_deadzone * 100.0f)) + "% and " +
|
||||
std::to_string(static_cast<int>(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<float>(ui->x_sensitivity->value());
|
||||
Settings::values.mouse_panning_y_sensitivity = static_cast<float>(ui->y_sensitivity->value());
|
||||
Settings::values.mouse_panning_deadzone_x_counterweight =
|
||||
static_cast<float>(ui->deadzone_x_counterweight->value());
|
||||
Settings::values.mouse_panning_deadzone_y_counterweight =
|
||||
static_cast<float>(ui->deadzone_y_counterweight->value());
|
||||
Settings::values.mouse_panning_decay_strength = static_cast<float>(ui->decay_strength->value());
|
||||
Settings::values.mouse_panning_min_decay = static_cast<float>(ui->min_decay->value());
|
||||
|
||||
accept();
|
||||
}
|
35
src/yuzu/configuration/configure_mouse_panning.h
Executable file
35
src/yuzu/configuration/configure_mouse_panning.h
Executable file
@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <QDialog>
|
||||
|
||||
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::ConfigureMousePanning> ui;
|
||||
};
|
238
src/yuzu/configuration/configure_mouse_panning.ui
Executable file
238
src/yuzu/configuration/configure_mouse_panning.ui
Executable file
@ -0,0 +1,238 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ConfigureMousePanning</class>
|
||||
<widget class="QDialog" name="configure_mouse_panning">
|
||||
<property name="windowTitle">
|
||||
<string>Configure mouse panning</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout">
|
||||
<item>
|
||||
<widget class="QCheckBox" name="enable">
|
||||
<property name="text">
|
||||
<string>Enable</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Can be toggled via a hotkey</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="sensitivity_box">
|
||||
<property name="title">
|
||||
<string>Sensitivity</string>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="x_sensitivity_label">
|
||||
<property name="text">
|
||||
<string>Horizontal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="x_sensitivity">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="y_sensitivity_label">
|
||||
<property name="text">
|
||||
<string>Vertical</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="y_sensitivity">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>50</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="deadzone_counterweight_box">
|
||||
<property name="title">
|
||||
<string>Deadzone counterweight</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Counteracts a game's built-in deadzone</string>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="deadzone_x_counterweight_label">
|
||||
<property name="text">
|
||||
<string>Horizontal</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="deadzone_x_counterweight">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="deadzone_y_counterweight_label">
|
||||
<property name="text">
|
||||
<string>Vertical</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="deadzone_y_counterweight">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="decay_box">
|
||||
<property name="title">
|
||||
<string>Stick decay</string>
|
||||
</property>
|
||||
<layout class="QGridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="decay_strength_label">
|
||||
<property name="text">
|
||||
<string>Strength</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QSpinBox" name="decay_strength">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>22</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="min_decay_label">
|
||||
<property name="text">
|
||||
<string>Minimum</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="min_decay">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string>%</string>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>100</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>5</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="warning_label">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout">
|
||||
<item>
|
||||
<widget class="QPushButton" name="default_button">
|
||||
<property name="text">
|
||||
<string>Default</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="button_box">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -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;
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user