Browse Source
Merge pull request #8955 from german77/amiibo-rewrite
Merge pull request #8955 from german77/amiibo-rewrite
core: nfp: Rewrite implementation to remove direct access from the frontendnce_cpp
committed by
GitHub
29 changed files with 2303 additions and 1333 deletions
-
27src/common/input.h
-
5src/core/CMakeLists.txt
-
70src/core/hid/emulated_controller.cpp
-
34src/core/hid/emulated_controller.h
-
14src/core/hid/input_converter.cpp
-
8src/core/hid/input_converter.h
-
161src/core/hle/service/mii/mii_manager.cpp
-
4src/core/hle/service/mii/mii_manager.h
-
8src/core/hle/service/nfc/nfc.cpp
-
77src/core/hle/service/nfp/amiibo_crypto.cpp
-
10src/core/hle/service/nfp/amiibo_crypto.h
-
1085src/core/hle/service/nfp/nfp.cpp
-
161src/core/hle/service/nfp/nfp.h
-
676src/core/hle/service/nfp/nfp_device.cpp
-
101src/core/hle/service/nfp/nfp_device.h
-
22src/core/hle/service/nfp/nfp_result.h
-
116src/core/hle/service/nfp/nfp_types.h
-
634src/core/hle/service/nfp/nfp_user.cpp
-
44src/core/hle/service/nfp/nfp_user.h
-
2src/input_common/CMakeLists.txt
-
101src/input_common/drivers/virtual_amiibo.cpp
-
61src/input_common/drivers/virtual_amiibo.h
-
37src/input_common/input_engine.cpp
-
17src/input_common/input_engine.h
-
64src/input_common/input_poller.cpp
-
10src/input_common/input_poller.h
-
21src/input_common/main.cpp
-
7src/input_common/main.h
-
51src/yuzu/main.cpp
1085
src/core/hle/service/nfp/nfp.cpp
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,676 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
|||
#include <array>
|
|||
#include <atomic>
|
|||
|
|||
#include "common/fs/file.h"
|
|||
#include "common/fs/path_util.h"
|
|||
#include "common/input.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "common/string_util.h"
|
|||
#include "common/tiny_mt.h"
|
|||
#include "core/core.h"
|
|||
#include "core/hid/emulated_controller.h"
|
|||
#include "core/hid/hid_core.h"
|
|||
#include "core/hid/hid_types.h"
|
|||
#include "core/hle/ipc_helpers.h"
|
|||
#include "core/hle/kernel/k_event.h"
|
|||
#include "core/hle/service/mii/mii_manager.h"
|
|||
#include "core/hle/service/nfp/amiibo_crypto.h"
|
|||
#include "core/hle/service/nfp/nfp.h"
|
|||
#include "core/hle/service/nfp/nfp_device.h"
|
|||
#include "core/hle/service/nfp/nfp_result.h"
|
|||
#include "core/hle/service/nfp/nfp_user.h"
|
|||
#include "core/hle/service/time/time_manager.h"
|
|||
#include "core/hle/service/time/time_zone_content_manager.h"
|
|||
#include "core/hle/service/time/time_zone_types.h"
|
|||
|
|||
namespace Service::NFP { |
|||
NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, |
|||
KernelHelpers::ServiceContext& service_context_, |
|||
Kernel::KEvent* availability_change_event_) |
|||
: npad_id{npad_id_}, system{system_}, service_context{service_context_}, |
|||
availability_change_event{availability_change_event_} { |
|||
activate_event = service_context.CreateEvent("IUser:NFPActivateEvent"); |
|||
deactivate_event = service_context.CreateEvent("IUser:NFPDeactivateEvent"); |
|||
npad_device = system.HIDCore().GetEmulatedController(npad_id); |
|||
|
|||
Core::HID::ControllerUpdateCallback engine_callback{ |
|||
.on_change = [this](Core::HID::ControllerTriggerType type) { NpadUpdate(type); }, |
|||
.is_npad_service = false, |
|||
}; |
|||
is_controller_set = true; |
|||
callback_key = npad_device->SetCallback(engine_callback); |
|||
|
|||
auto& standard_steady_clock{system.GetTimeManager().GetStandardSteadyClockCore()}; |
|||
current_posix_time = standard_steady_clock.GetCurrentTimePoint(system).time_point; |
|||
} |
|||
|
|||
NfpDevice::~NfpDevice() { |
|||
if (!is_controller_set) { |
|||
return; |
|||
} |
|||
npad_device->DeleteCallback(callback_key); |
|||
is_controller_set = false; |
|||
}; |
|||
|
|||
void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { |
|||
if (type == Core::HID::ControllerTriggerType::Connected || |
|||
type == Core::HID::ControllerTriggerType::Disconnected) { |
|||
availability_change_event->GetWritableEvent().Signal(); |
|||
return; |
|||
} |
|||
|
|||
if (type != Core::HID::ControllerTriggerType::Nfc) { |
|||
return; |
|||
} |
|||
|
|||
if (!npad_device->IsConnected()) { |
|||
return; |
|||
} |
|||
|
|||
const auto nfc_status = npad_device->GetNfc(); |
|||
switch (nfc_status.state) { |
|||
case Common::Input::NfcState::NewAmiibo: |
|||
LoadAmiibo(nfc_status.data); |
|||
break; |
|||
case Common::Input::NfcState::AmiiboRemoved: |
|||
if (device_state != DeviceState::SearchingForTag) { |
|||
CloseAmiibo(); |
|||
} |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
|
|||
bool NfpDevice::LoadAmiibo(std::span<const u8> data) { |
|||
if (device_state != DeviceState::SearchingForTag) { |
|||
LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state); |
|||
return false; |
|||
} |
|||
|
|||
if (data.size() != sizeof(EncryptedNTAG215File)) { |
|||
LOG_ERROR(Service_NFP, "Not an amiibo, size={}", data.size()); |
|||
return false; |
|||
} |
|||
|
|||
memcpy(&encrypted_tag_data, data.data(), sizeof(EncryptedNTAG215File)); |
|||
|
|||
if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) { |
|||
LOG_INFO(Service_NFP, "Invalid amiibo"); |
|||
return false; |
|||
} |
|||
|
|||
device_state = DeviceState::TagFound; |
|||
deactivate_event->GetReadableEvent().Clear(); |
|||
activate_event->GetWritableEvent().Signal(); |
|||
return true; |
|||
} |
|||
|
|||
void NfpDevice::CloseAmiibo() { |
|||
LOG_INFO(Service_NFP, "Remove amiibo"); |
|||
|
|||
if (device_state == DeviceState::TagMounted) { |
|||
Unmount(); |
|||
} |
|||
|
|||
device_state = DeviceState::TagRemoved; |
|||
encrypted_tag_data = {}; |
|||
tag_data = {}; |
|||
activate_event->GetReadableEvent().Clear(); |
|||
deactivate_event->GetWritableEvent().Signal(); |
|||
} |
|||
|
|||
Kernel::KReadableEvent& NfpDevice::GetActivateEvent() const { |
|||
return activate_event->GetReadableEvent(); |
|||
} |
|||
|
|||
Kernel::KReadableEvent& NfpDevice::GetDeactivateEvent() const { |
|||
return deactivate_event->GetReadableEvent(); |
|||
} |
|||
|
|||
void NfpDevice::Initialize() { |
|||
device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable; |
|||
encrypted_tag_data = {}; |
|||
tag_data = {}; |
|||
} |
|||
|
|||
void NfpDevice::Finalize() { |
|||
if (device_state == DeviceState::TagMounted) { |
|||
Unmount(); |
|||
} |
|||
if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { |
|||
StopDetection(); |
|||
} |
|||
device_state = DeviceState::Unavailable; |
|||
} |
|||
|
|||
Result NfpDevice::StartDetection(s32 protocol_) { |
|||
if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) { |
|||
npad_device->SetPollingMode(Common::Input::PollingMode::NFC); |
|||
device_state = DeviceState::SearchingForTag; |
|||
protocol = protocol_; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
Result NfpDevice::StopDetection() { |
|||
npad_device->SetPollingMode(Common::Input::PollingMode::Active); |
|||
|
|||
if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) { |
|||
CloseAmiibo(); |
|||
return ResultSuccess; |
|||
} |
|||
if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { |
|||
device_state = DeviceState::Initialized; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
Result NfpDevice::Flush() { |
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
if (device_state == DeviceState::TagRemoved) { |
|||
return TagRemoved; |
|||
} |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { |
|||
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
auto& settings = tag_data.settings; |
|||
|
|||
const auto& current_date = GetAmiiboDate(current_posix_time); |
|||
if (settings.write_date.raw_date != current_date.raw_date) { |
|||
settings.write_date = current_date; |
|||
settings.crc_counter++; |
|||
// TODO: Find how to calculate the crc check
|
|||
// settings.crc = CalculateCRC(settings);
|
|||
} |
|||
|
|||
tag_data.write_counter++; |
|||
|
|||
if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) { |
|||
LOG_ERROR(Service_NFP, "Failed to encode data"); |
|||
return WriteAmiiboFailed; |
|||
} |
|||
|
|||
std::vector<u8> data(sizeof(encrypted_tag_data)); |
|||
memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data)); |
|||
|
|||
if (!npad_device->WriteNfc(data)) { |
|||
LOG_ERROR(Service_NFP, "Error writing to file"); |
|||
return WriteAmiiboFailed; |
|||
} |
|||
|
|||
is_data_moddified = false; |
|||
|
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result NfpDevice::Mount(MountTarget mount_target_) { |
|||
if (device_state != DeviceState::TagFound) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) { |
|||
LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state); |
|||
return CorruptedData; |
|||
} |
|||
|
|||
device_state = DeviceState::TagMounted; |
|||
mount_target = mount_target_; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result NfpDevice::Unmount() { |
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
// Save data before unloading the amiibo
|
|||
if (is_data_moddified) { |
|||
Flush(); |
|||
} |
|||
|
|||
device_state = DeviceState::TagFound; |
|||
mount_target = MountTarget::None; |
|||
is_app_area_open = false; |
|||
|
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result NfpDevice::GetTagInfo(TagInfo& tag_info) const { |
|||
if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
tag_info = { |
|||
.uuid = encrypted_tag_data.uuid.uid, |
|||
.uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()), |
|||
.protocol = TagProtocol::TypeA, |
|||
.tag_type = TagType::Type2, |
|||
}; |
|||
|
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const { |
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
if (device_state == DeviceState::TagRemoved) { |
|||
return TagRemoved; |
|||
} |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { |
|||
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
const auto& settings = tag_data.settings; |
|||
|
|||
// TODO: Validate this data
|
|||
common_info = { |
|||
.last_write_date = |
|||
{ |
|||
settings.write_date.GetYear(), |
|||
settings.write_date.GetMonth(), |
|||
settings.write_date.GetDay(), |
|||
}, |
|||
.write_counter = tag_data.write_counter, |
|||
.version = 0, |
|||
.application_area_size = sizeof(ApplicationArea), |
|||
}; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result NfpDevice::GetModelInfo(ModelInfo& model_info) const { |
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
const auto& model_info_data = encrypted_tag_data.user_memory.model_info; |
|||
model_info = { |
|||
.character_id = model_info_data.character_id, |
|||
.character_variant = model_info_data.character_variant, |
|||
.amiibo_type = model_info_data.amiibo_type, |
|||
.model_number = model_info_data.model_number, |
|||
.series = model_info_data.series, |
|||
}; |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const { |
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
if (device_state == DeviceState::TagRemoved) { |
|||
return TagRemoved; |
|||
} |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { |
|||
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (tag_data.settings.settings.amiibo_initialized == 0) { |
|||
return RegistrationIsNotInitialized; |
|||
} |
|||
|
|||
Service::Mii::MiiManager manager; |
|||
const auto& settings = tag_data.settings; |
|||
|
|||
// TODO: Validate this data
|
|||
register_info = { |
|||
.mii_char_info = manager.ConvertV3ToCharInfo(tag_data.owner_mii), |
|||
.creation_date = |
|||
{ |
|||
settings.init_date.GetYear(), |
|||
settings.init_date.GetMonth(), |
|||
settings.init_date.GetDay(), |
|||
}, |
|||
.amiibo_name = GetAmiiboName(settings), |
|||
.font_region = {}, |
|||
}; |
|||
|
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result NfpDevice::SetNicknameAndOwner(const AmiiboName& amiibo_name) { |
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
if (device_state == DeviceState::TagRemoved) { |
|||
return TagRemoved; |
|||
} |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { |
|||
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
Service::Mii::MiiManager manager; |
|||
auto& settings = tag_data.settings; |
|||
|
|||
settings.init_date = GetAmiiboDate(current_posix_time); |
|||
settings.write_date = GetAmiiboDate(current_posix_time); |
|||
settings.crc_counter++; |
|||
// TODO: Find how to calculate the crc check
|
|||
// settings.crc = CalculateCRC(settings);
|
|||
|
|||
SetAmiiboName(settings, amiibo_name); |
|||
tag_data.owner_mii = manager.ConvertCharInfoToV3(manager.BuildDefault(0)); |
|||
settings.settings.amiibo_initialized.Assign(1); |
|||
|
|||
return Flush(); |
|||
} |
|||
|
|||
Result NfpDevice::RestoreAmiibo() { |
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
if (device_state == DeviceState::TagRemoved) { |
|||
return TagRemoved; |
|||
} |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { |
|||
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
// TODO: Load amiibo from backup on system
|
|||
LOG_ERROR(Service_NFP, "Not Implemented"); |
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result NfpDevice::DeleteAllData() { |
|||
const auto result = DeleteApplicationArea(); |
|||
if (result.IsError()) { |
|||
return result; |
|||
} |
|||
|
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
if (device_state == DeviceState::TagRemoved) { |
|||
return TagRemoved; |
|||
} |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
Common::TinyMT rng{}; |
|||
rng.GenerateRandomBytes(&tag_data.owner_mii, sizeof(tag_data.owner_mii)); |
|||
tag_data.settings.settings.amiibo_initialized.Assign(0); |
|||
|
|||
return Flush(); |
|||
} |
|||
|
|||
Result NfpDevice::OpenApplicationArea(u32 access_id) { |
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
if (device_state == DeviceState::TagRemoved) { |
|||
return TagRemoved; |
|||
} |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { |
|||
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (tag_data.settings.settings.appdata_initialized.Value() == 0) { |
|||
LOG_WARNING(Service_NFP, "Application area is not initialized"); |
|||
return ApplicationAreaIsNotInitialized; |
|||
} |
|||
|
|||
if (tag_data.application_area_id != access_id) { |
|||
LOG_WARNING(Service_NFP, "Wrong application area id"); |
|||
return WrongApplicationAreaId; |
|||
} |
|||
|
|||
is_app_area_open = true; |
|||
|
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const { |
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
if (device_state == DeviceState::TagRemoved) { |
|||
return TagRemoved; |
|||
} |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { |
|||
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (!is_app_area_open) { |
|||
LOG_ERROR(Service_NFP, "Application area is not open"); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (tag_data.settings.settings.appdata_initialized.Value() == 0) { |
|||
LOG_ERROR(Service_NFP, "Application area is not initialized"); |
|||
return ApplicationAreaIsNotInitialized; |
|||
} |
|||
|
|||
if (data.size() > sizeof(ApplicationArea)) { |
|||
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size()); |
|||
return ResultUnknown; |
|||
} |
|||
|
|||
memcpy(data.data(), tag_data.application_area.data(), data.size()); |
|||
|
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result NfpDevice::SetApplicationArea(std::span<const u8> data) { |
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
if (device_state == DeviceState::TagRemoved) { |
|||
return TagRemoved; |
|||
} |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { |
|||
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (!is_app_area_open) { |
|||
LOG_ERROR(Service_NFP, "Application area is not open"); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (tag_data.settings.settings.appdata_initialized.Value() == 0) { |
|||
LOG_ERROR(Service_NFP, "Application area is not initialized"); |
|||
return ApplicationAreaIsNotInitialized; |
|||
} |
|||
|
|||
if (data.size() > sizeof(ApplicationArea)) { |
|||
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size()); |
|||
return ResultUnknown; |
|||
} |
|||
|
|||
Common::TinyMT rng{}; |
|||
std::memcpy(tag_data.application_area.data(), data.data(), data.size()); |
|||
// HW seems to fill excess data with garbage
|
|||
rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(), |
|||
sizeof(ApplicationArea) - data.size()); |
|||
|
|||
tag_data.applicaton_write_counter++; |
|||
is_data_moddified = true; |
|||
|
|||
return ResultSuccess; |
|||
} |
|||
|
|||
Result NfpDevice::CreateApplicationArea(u32 access_id, std::span<const u8> data) { |
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
if (device_state == DeviceState::TagRemoved) { |
|||
return TagRemoved; |
|||
} |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (tag_data.settings.settings.appdata_initialized.Value() != 0) { |
|||
LOG_ERROR(Service_NFP, "Application area already exist"); |
|||
return ApplicationAreaExist; |
|||
} |
|||
|
|||
return RecreateApplicationArea(access_id, data); |
|||
} |
|||
|
|||
Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> data) { |
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
if (device_state == DeviceState::TagRemoved) { |
|||
return TagRemoved; |
|||
} |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { |
|||
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (data.size() > sizeof(ApplicationArea)) { |
|||
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size()); |
|||
return ResultUnknown; |
|||
} |
|||
|
|||
Common::TinyMT rng{}; |
|||
std::memcpy(tag_data.application_area.data(), data.data(), data.size()); |
|||
// HW seems to fill excess data with garbage
|
|||
rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(), |
|||
sizeof(ApplicationArea) - data.size()); |
|||
|
|||
// TODO: Investigate why the title id needs to be moddified
|
|||
tag_data.title_id = system.GetCurrentProcessProgramID(); |
|||
tag_data.title_id = tag_data.title_id | 0x30000000ULL; |
|||
tag_data.settings.settings.appdata_initialized.Assign(1); |
|||
tag_data.application_area_id = access_id; |
|||
tag_data.applicaton_write_counter++; |
|||
tag_data.unknown = {}; |
|||
|
|||
return Flush(); |
|||
} |
|||
|
|||
Result NfpDevice::DeleteApplicationArea() { |
|||
if (device_state != DeviceState::TagMounted) { |
|||
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state); |
|||
if (device_state == DeviceState::TagRemoved) { |
|||
return TagRemoved; |
|||
} |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) { |
|||
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state); |
|||
return WrongDeviceState; |
|||
} |
|||
|
|||
Common::TinyMT rng{}; |
|||
rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(ApplicationArea)); |
|||
rng.GenerateRandomBytes(&tag_data.title_id, sizeof(u64)); |
|||
rng.GenerateRandomBytes(&tag_data.application_area_id, sizeof(u32)); |
|||
tag_data.settings.settings.appdata_initialized.Assign(0); |
|||
tag_data.applicaton_write_counter++; |
|||
tag_data.unknown = {}; |
|||
|
|||
return Flush(); |
|||
} |
|||
|
|||
u64 NfpDevice::GetHandle() const { |
|||
// Generate a handle based of the npad id
|
|||
return static_cast<u64>(npad_id); |
|||
} |
|||
|
|||
u32 NfpDevice::GetApplicationAreaSize() const { |
|||
// Investigate if this value is really constant
|
|||
return sizeof(ApplicationArea); |
|||
} |
|||
|
|||
DeviceState NfpDevice::GetCurrentState() const { |
|||
return device_state; |
|||
} |
|||
|
|||
Core::HID::NpadIdType NfpDevice::GetNpadId() const { |
|||
return npad_id; |
|||
} |
|||
|
|||
AmiiboName NfpDevice::GetAmiiboName(const AmiiboSettings& settings) const { |
|||
std::array<char16_t, amiibo_name_length> settings_amiibo_name{}; |
|||
AmiiboName amiibo_name{}; |
|||
|
|||
// Convert from big endian to little endian
|
|||
for (std::size_t i = 0; i < amiibo_name_length; i++) { |
|||
settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]); |
|||
} |
|||
|
|||
// Convert from utf16 to utf8
|
|||
const auto amiibo_name_utf8 = Common::UTF16ToUTF8(settings_amiibo_name.data()); |
|||
memcpy(amiibo_name.data(), amiibo_name_utf8.data(), amiibo_name_utf8.size()); |
|||
|
|||
return amiibo_name; |
|||
} |
|||
|
|||
void NfpDevice::SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name) { |
|||
std::array<char16_t, amiibo_name_length> settings_amiibo_name{}; |
|||
|
|||
// Convert from utf8 to utf16
|
|||
const auto amiibo_name_utf16 = Common::UTF8ToUTF16(amiibo_name.data()); |
|||
memcpy(settings_amiibo_name.data(), amiibo_name_utf16.data(), |
|||
amiibo_name_utf16.size() * sizeof(char16_t)); |
|||
|
|||
// Convert from little endian to big endian
|
|||
for (std::size_t i = 0; i < amiibo_name_length; i++) { |
|||
settings.amiibo_name[i] = static_cast<u16_be>(settings_amiibo_name[i]); |
|||
} |
|||
} |
|||
|
|||
AmiiboDate NfpDevice::GetAmiiboDate(s64 posix_time) const { |
|||
const auto& time_zone_manager = |
|||
system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager(); |
|||
Time::TimeZone::CalendarInfo calendar_info{}; |
|||
AmiiboDate amiibo_date{}; |
|||
|
|||
amiibo_date.SetYear(2000); |
|||
amiibo_date.SetMonth(1); |
|||
amiibo_date.SetDay(1); |
|||
|
|||
if (time_zone_manager.ToCalendarTime({}, posix_time, calendar_info) == ResultSuccess) { |
|||
amiibo_date.SetYear(calendar_info.time.year); |
|||
amiibo_date.SetMonth(calendar_info.time.month); |
|||
amiibo_date.SetDay(calendar_info.time.day); |
|||
} |
|||
|
|||
return amiibo_date; |
|||
} |
|||
|
|||
} // namespace Service::NFP
|
|||
@ -0,0 +1,101 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-2.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
#include <vector> |
|||
|
|||
#include "common/common_funcs.h" |
|||
#include "core/hle/service/kernel_helpers.h" |
|||
#include "core/hle/service/mii/types.h" |
|||
#include "core/hle/service/nfp/nfp_types.h" |
|||
#include "core/hle/service/service.h" |
|||
|
|||
namespace Kernel { |
|||
class KEvent; |
|||
class KReadableEvent; |
|||
} // namespace Kernel |
|||
|
|||
namespace Core { |
|||
class System; |
|||
} // namespace Core |
|||
|
|||
namespace Core::HID { |
|||
class EmulatedController; |
|||
enum class ControllerTriggerType; |
|||
enum class NpadIdType : u32; |
|||
} // namespace Core::HID |
|||
|
|||
namespace Service::NFP { |
|||
class NfpDevice { |
|||
public: |
|||
NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, |
|||
KernelHelpers::ServiceContext& service_context_, |
|||
Kernel::KEvent* availability_change_event_); |
|||
~NfpDevice(); |
|||
|
|||
void Initialize(); |
|||
void Finalize(); |
|||
|
|||
Result StartDetection(s32 protocol_); |
|||
Result StopDetection(); |
|||
Result Mount(MountTarget mount_target); |
|||
Result Unmount(); |
|||
Result Flush(); |
|||
|
|||
Result GetTagInfo(TagInfo& tag_info) const; |
|||
Result GetCommonInfo(CommonInfo& common_info) const; |
|||
Result GetModelInfo(ModelInfo& model_info) const; |
|||
Result GetRegisterInfo(RegisterInfo& register_info) const; |
|||
|
|||
Result SetNicknameAndOwner(const AmiiboName& amiibo_name); |
|||
Result RestoreAmiibo(); |
|||
Result DeleteAllData(); |
|||
|
|||
Result OpenApplicationArea(u32 access_id); |
|||
Result GetApplicationArea(std::vector<u8>& data) const; |
|||
Result SetApplicationArea(std::span<const u8> data); |
|||
Result CreateApplicationArea(u32 access_id, std::span<const u8> data); |
|||
Result RecreateApplicationArea(u32 access_id, std::span<const u8> data); |
|||
Result DeleteApplicationArea(); |
|||
|
|||
u64 GetHandle() const; |
|||
u32 GetApplicationAreaSize() const; |
|||
DeviceState GetCurrentState() const; |
|||
Core::HID::NpadIdType GetNpadId() const; |
|||
|
|||
Kernel::KReadableEvent& GetActivateEvent() const; |
|||
Kernel::KReadableEvent& GetDeactivateEvent() const; |
|||
|
|||
private: |
|||
void NpadUpdate(Core::HID::ControllerTriggerType type); |
|||
bool LoadAmiibo(std::span<const u8> data); |
|||
void CloseAmiibo(); |
|||
|
|||
AmiiboName GetAmiiboName(const AmiiboSettings& settings) const; |
|||
void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name); |
|||
AmiiboDate GetAmiiboDate(s64 posix_time) const; |
|||
|
|||
bool is_controller_set{}; |
|||
int callback_key; |
|||
const Core::HID::NpadIdType npad_id; |
|||
Core::System& system; |
|||
Core::HID::EmulatedController* npad_device = nullptr; |
|||
KernelHelpers::ServiceContext& service_context; |
|||
Kernel::KEvent* activate_event = nullptr; |
|||
Kernel::KEvent* deactivate_event = nullptr; |
|||
Kernel::KEvent* availability_change_event = nullptr; |
|||
|
|||
bool is_data_moddified{}; |
|||
bool is_app_area_open{}; |
|||
s32 protocol{}; |
|||
s64 current_posix_time{}; |
|||
MountTarget mount_target{MountTarget::None}; |
|||
DeviceState device_state{DeviceState::Unavailable}; |
|||
|
|||
NTAG215File tag_data{}; |
|||
EncryptedNTAG215File encrypted_tag_data{}; |
|||
}; |
|||
|
|||
} // namespace Service::NFP |
|||
@ -0,0 +1,22 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include "core/hle/result.h" |
|||
|
|||
namespace Service::NFP { |
|||
|
|||
constexpr Result DeviceNotFound(ErrorModule::NFP, 64); |
|||
constexpr Result WrongDeviceState(ErrorModule::NFP, 73); |
|||
constexpr Result NfcDisabled(ErrorModule::NFP, 80); |
|||
constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88); |
|||
constexpr Result TagRemoved(ErrorModule::NFP, 97); |
|||
constexpr Result RegistrationIsNotInitialized(ErrorModule::NFP, 120); |
|||
constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128); |
|||
constexpr Result CorruptedData(ErrorModule::NFP, 144); |
|||
constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152); |
|||
constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168); |
|||
constexpr Result NotAnAmiibo(ErrorModule::NFP, 178); |
|||
|
|||
} // namespace Service::NFP |
|||
@ -1,18 +1,644 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|||
|
|||
#include <array>
|
|||
#include <atomic>
|
|||
|
|||
#include "common/logging/log.h"
|
|||
#include "core/core.h"
|
|||
#include "core/hid/emulated_controller.h"
|
|||
#include "core/hid/hid_core.h"
|
|||
#include "core/hid/hid_types.h"
|
|||
#include "core/hle/ipc_helpers.h"
|
|||
#include "core/hle/kernel/k_event.h"
|
|||
#include "core/hle/service/mii/mii_manager.h"
|
|||
#include "core/hle/service/nfp/nfp_device.h"
|
|||
#include "core/hle/service/nfp/nfp_result.h"
|
|||
#include "core/hle/service/nfp/nfp_user.h"
|
|||
|
|||
namespace Service::NFP { |
|||
|
|||
NFP_User::NFP_User(std::shared_ptr<Module> module_, Core::System& system_) |
|||
: Interface(std::move(module_), system_, "nfp:user") { |
|||
IUser::IUser(Core::System& system_) |
|||
: ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name} { |
|||
static const FunctionInfo functions[] = { |
|||
{0, &NFP_User::CreateUserInterface, "CreateUserInterface"}, |
|||
{0, &IUser::Initialize, "Initialize"}, |
|||
{1, &IUser::Finalize, "Finalize"}, |
|||
{2, &IUser::ListDevices, "ListDevices"}, |
|||
{3, &IUser::StartDetection, "StartDetection"}, |
|||
{4, &IUser::StopDetection, "StopDetection"}, |
|||
{5, &IUser::Mount, "Mount"}, |
|||
{6, &IUser::Unmount, "Unmount"}, |
|||
{7, &IUser::OpenApplicationArea, "OpenApplicationArea"}, |
|||
{8, &IUser::GetApplicationArea, "GetApplicationArea"}, |
|||
{9, &IUser::SetApplicationArea, "SetApplicationArea"}, |
|||
{10, &IUser::Flush, "Flush"}, |
|||
{11, &IUser::Restore, "Restore"}, |
|||
{12, &IUser::CreateApplicationArea, "CreateApplicationArea"}, |
|||
{13, &IUser::GetTagInfo, "GetTagInfo"}, |
|||
{14, &IUser::GetRegisterInfo, "GetRegisterInfo"}, |
|||
{15, &IUser::GetCommonInfo, "GetCommonInfo"}, |
|||
{16, &IUser::GetModelInfo, "GetModelInfo"}, |
|||
{17, &IUser::AttachActivateEvent, "AttachActivateEvent"}, |
|||
{18, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"}, |
|||
{19, &IUser::GetState, "GetState"}, |
|||
{20, &IUser::GetDeviceState, "GetDeviceState"}, |
|||
{21, &IUser::GetNpadId, "GetNpadId"}, |
|||
{22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"}, |
|||
{23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"}, |
|||
{24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"}, |
|||
}; |
|||
RegisterHandlers(functions); |
|||
|
|||
availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent"); |
|||
|
|||
for (u32 device_index = 0; device_index < 10; device_index++) { |
|||
devices[device_index] = |
|||
std::make_shared<NfpDevice>(Core::HID::IndexToNpadIdType(device_index), system, |
|||
service_context, availability_change_event); |
|||
} |
|||
} |
|||
|
|||
void IUser::Initialize(Kernel::HLERequestContext& ctx) { |
|||
LOG_INFO(Service_NFC, "called"); |
|||
|
|||
state = State::Initialized; |
|||
|
|||
for (auto& device : devices) { |
|||
device->Initialize(); |
|||
} |
|||
|
|||
IPC::ResponseBuilder rb{ctx, 2, 0}; |
|||
rb.Push(ResultSuccess); |
|||
} |
|||
|
|||
void IUser::Finalize(Kernel::HLERequestContext& ctx) { |
|||
LOG_INFO(Service_NFP, "called"); |
|||
|
|||
state = State::NonInitialized; |
|||
|
|||
for (auto& device : devices) { |
|||
device->Finalize(); |
|||
} |
|||
|
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(ResultSuccess); |
|||
} |
|||
|
|||
void IUser::ListDevices(Kernel::HLERequestContext& ctx) { |
|||
LOG_INFO(Service_NFP, "called"); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
std::vector<u64> nfp_devices; |
|||
const std::size_t max_allowed_devices = ctx.GetWriteBufferSize() / sizeof(u64); |
|||
|
|||
for (auto& device : devices) { |
|||
if (nfp_devices.size() >= max_allowed_devices) { |
|||
continue; |
|||
} |
|||
if (device->GetCurrentState() != DeviceState::Unavailable) { |
|||
nfp_devices.push_back(device->GetHandle()); |
|||
} |
|||
} |
|||
|
|||
if (nfp_devices.size() == 0) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
ctx.WriteBuffer(nfp_devices); |
|||
|
|||
IPC::ResponseBuilder rb{ctx, 3}; |
|||
rb.Push(ResultSuccess); |
|||
rb.Push(static_cast<s32>(nfp_devices.size())); |
|||
} |
|||
|
|||
void IUser::StartDetection(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
const auto nfp_protocol{rp.Pop<s32>()}; |
|||
LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
const auto result = device.value()->StartDetection(nfp_protocol); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
void IUser::StopDetection(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
const auto result = device.value()->StopDetection(); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
void IUser::Mount(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
const auto model_type{rp.PopEnum<ModelType>()}; |
|||
const auto mount_target{rp.PopEnum<MountTarget>()}; |
|||
LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle, |
|||
model_type, mount_target); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
const auto result = device.value()->Mount(mount_target); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
void IUser::Unmount(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
const auto result = device.value()->Unmount(); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
void IUser::OpenApplicationArea(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
const auto access_id{rp.Pop<u32>()}; |
|||
LOG_INFO(Service_NFP, "called, device_handle={}, access_id={}", device_handle, access_id); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
const auto result = device.value()->OpenApplicationArea(access_id); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
void IUser::GetApplicationArea(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
const auto data_size = ctx.GetWriteBufferSize(); |
|||
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
std::vector<u8> data(data_size); |
|||
const auto result = device.value()->GetApplicationArea(data); |
|||
ctx.WriteBuffer(data); |
|||
IPC::ResponseBuilder rb{ctx, 3}; |
|||
rb.Push(result); |
|||
rb.Push(static_cast<u32>(data_size)); |
|||
} |
|||
|
|||
void IUser::SetApplicationArea(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
const auto data{ctx.ReadBuffer()}; |
|||
LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}", device_handle, data.size()); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
const auto result = device.value()->SetApplicationArea(data); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
void IUser::Flush(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
const auto result = device.value()->Flush(); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
void IUser::Restore(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
const auto result = device.value()->RestoreAmiibo(); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
void IUser::CreateApplicationArea(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
const auto access_id{rp.Pop<u32>()}; |
|||
const auto data{ctx.ReadBuffer()}; |
|||
LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle, |
|||
access_id, data.size()); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
const auto result = device.value()->CreateApplicationArea(access_id, data); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
void IUser::GetTagInfo(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
TagInfo tag_info{}; |
|||
const auto result = device.value()->GetTagInfo(tag_info); |
|||
ctx.WriteBuffer(tag_info); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
void IUser::GetRegisterInfo(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
RegisterInfo register_info{}; |
|||
const auto result = device.value()->GetRegisterInfo(register_info); |
|||
ctx.WriteBuffer(register_info); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
void IUser::GetCommonInfo(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
CommonInfo common_info{}; |
|||
const auto result = device.value()->GetCommonInfo(common_info); |
|||
ctx.WriteBuffer(common_info); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
void IUser::GetModelInfo(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
ModelInfo model_info{}; |
|||
const auto result = device.value()->GetModelInfo(model_info); |
|||
ctx.WriteBuffer(model_info); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
void IUser::AttachActivateEvent(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
NFP_User::~NFP_User() = default; |
|||
IPC::ResponseBuilder rb{ctx, 2, 1}; |
|||
rb.Push(ResultSuccess); |
|||
rb.PushCopyObjects(device.value()->GetActivateEvent()); |
|||
} |
|||
|
|||
void IUser::AttachDeactivateEvent(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
IPC::ResponseBuilder rb{ctx, 2, 1}; |
|||
rb.Push(ResultSuccess); |
|||
rb.PushCopyObjects(device.value()->GetDeactivateEvent()); |
|||
} |
|||
|
|||
void IUser::GetState(Kernel::HLERequestContext& ctx) { |
|||
LOG_DEBUG(Service_NFC, "called"); |
|||
|
|||
IPC::ResponseBuilder rb{ctx, 3, 0}; |
|||
rb.Push(ResultSuccess); |
|||
rb.PushEnum(state); |
|||
} |
|||
|
|||
void IUser::GetDeviceState(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
IPC::ResponseBuilder rb{ctx, 3}; |
|||
rb.Push(ResultSuccess); |
|||
rb.PushEnum(device.value()->GetCurrentState()); |
|||
} |
|||
|
|||
void IUser::GetNpadId(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
IPC::ResponseBuilder rb{ctx, 3}; |
|||
rb.Push(ResultSuccess); |
|||
rb.PushEnum(device.value()->GetNpadId()); |
|||
} |
|||
|
|||
void IUser::GetApplicationAreaSize(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle); |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
IPC::ResponseBuilder rb{ctx, 3}; |
|||
rb.Push(ResultSuccess); |
|||
rb.Push(device.value()->GetApplicationAreaSize()); |
|||
} |
|||
|
|||
void IUser::AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx) { |
|||
LOG_INFO(Service_NFP, "called"); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
IPC::ResponseBuilder rb{ctx, 2, 1}; |
|||
rb.Push(ResultSuccess); |
|||
rb.PushCopyObjects(availability_change_event->GetReadableEvent()); |
|||
} |
|||
|
|||
void IUser::RecreateApplicationArea(Kernel::HLERequestContext& ctx) { |
|||
IPC::RequestParser rp{ctx}; |
|||
const auto device_handle{rp.Pop<u64>()}; |
|||
const auto access_id{rp.Pop<u32>()}; |
|||
const auto data{ctx.ReadBuffer()}; |
|||
LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle, |
|||
access_id, data.size()); |
|||
|
|||
if (state == State::NonInitialized) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(NfcDisabled); |
|||
return; |
|||
} |
|||
|
|||
auto device = GetNfpDevice(device_handle); |
|||
|
|||
if (!device.has_value()) { |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(DeviceNotFound); |
|||
return; |
|||
} |
|||
|
|||
const auto result = device.value()->RecreateApplicationArea(access_id, data); |
|||
IPC::ResponseBuilder rb{ctx, 2}; |
|||
rb.Push(result); |
|||
} |
|||
|
|||
std::optional<std::shared_ptr<NfpDevice>> IUser::GetNfpDevice(u64 handle) { |
|||
for (auto& device : devices) { |
|||
if (device->GetHandle() == handle) { |
|||
return device; |
|||
} |
|||
} |
|||
return std::nullopt; |
|||
} |
|||
|
|||
} // namespace Service::NFP
|
|||
@ -0,0 +1,101 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
|||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|||
|
|||
#include <cstring>
|
|||
#include <fmt/format.h>
|
|||
|
|||
#include "common/fs/file.h"
|
|||
#include "common/fs/fs.h"
|
|||
#include "common/fs/path_util.h"
|
|||
#include "common/logging/log.h"
|
|||
#include "common/settings.h"
|
|||
#include "input_common/drivers/virtual_amiibo.h"
|
|||
|
|||
namespace InputCommon { |
|||
constexpr PadIdentifier identifier = { |
|||
.guid = Common::UUID{}, |
|||
.port = 0, |
|||
.pad = 0, |
|||
}; |
|||
|
|||
VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move(input_engine_)) {} |
|||
|
|||
VirtualAmiibo::~VirtualAmiibo() = default; |
|||
|
|||
Common::Input::PollingError VirtualAmiibo::SetPollingMode( |
|||
[[maybe_unused]] const PadIdentifier& identifier_, |
|||
const Common::Input::PollingMode polling_mode_) { |
|||
polling_mode = polling_mode_; |
|||
|
|||
if (polling_mode == Common::Input::PollingMode::NFC) { |
|||
if (state == State::Initialized) { |
|||
state = State::WaitingForAmiibo; |
|||
} |
|||
} else { |
|||
if (state == State::AmiiboIsOpen) { |
|||
CloseAmiibo(); |
|||
} |
|||
} |
|||
|
|||
return Common::Input::PollingError::None; |
|||
} |
|||
|
|||
Common::Input::NfcState VirtualAmiibo::SupportsNfc( |
|||
[[maybe_unused]] const PadIdentifier& identifier_) const { |
|||
return Common::Input::NfcState::Success; |
|||
} |
|||
|
|||
Common::Input::NfcState VirtualAmiibo::WriteNfcData( |
|||
[[maybe_unused]] const PadIdentifier& identifier_, const std::vector<u8>& data) { |
|||
const Common::FS::IOFile amiibo_file{file_path, Common::FS::FileAccessMode::ReadWrite, |
|||
Common::FS::FileType::BinaryFile}; |
|||
|
|||
if (!amiibo_file.IsOpen()) { |
|||
LOG_ERROR(Core, "Amiibo is already on use"); |
|||
return Common::Input::NfcState::WriteFailed; |
|||
} |
|||
|
|||
if (!amiibo_file.Write(data)) { |
|||
LOG_ERROR(Service_NFP, "Error writting to file"); |
|||
return Common::Input::NfcState::WriteFailed; |
|||
} |
|||
|
|||
return Common::Input::NfcState::Success; |
|||
} |
|||
|
|||
VirtualAmiibo::State VirtualAmiibo::GetCurrentState() const { |
|||
return state; |
|||
} |
|||
|
|||
VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) { |
|||
const Common::FS::IOFile amiibo_file{filename, Common::FS::FileAccessMode::Read, |
|||
Common::FS::FileType::BinaryFile}; |
|||
|
|||
if (state != State::WaitingForAmiibo) { |
|||
return Info::WrongDeviceState; |
|||
} |
|||
|
|||
if (!amiibo_file.IsOpen()) { |
|||
return Info::UnableToLoad; |
|||
} |
|||
|
|||
amiibo_data.resize(amiibo_size); |
|||
|
|||
if (amiibo_file.Read(amiibo_data) < amiibo_size_without_password) { |
|||
return Info::NotAnAmiibo; |
|||
} |
|||
|
|||
file_path = filename; |
|||
state = State::AmiiboIsOpen; |
|||
SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data}); |
|||
return Info::Success; |
|||
} |
|||
|
|||
VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() { |
|||
state = polling_mode == Common::Input::PollingMode::NFC ? State::WaitingForAmiibo |
|||
: State::Initialized; |
|||
SetNfc(identifier, {Common::Input::NfcState::AmiiboRemoved, {}}); |
|||
return Info::Success; |
|||
} |
|||
|
|||
} // namespace InputCommon
|
|||
@ -0,0 +1,61 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <array> |
|||
#include <string> |
|||
#include <vector> |
|||
|
|||
#include "common/common_types.h" |
|||
#include "input_common/input_engine.h" |
|||
|
|||
namespace Common::FS { |
|||
class IOFile; |
|||
} |
|||
|
|||
namespace InputCommon { |
|||
|
|||
class VirtualAmiibo final : public InputEngine { |
|||
public: |
|||
enum class State { |
|||
Initialized, |
|||
WaitingForAmiibo, |
|||
AmiiboIsOpen, |
|||
}; |
|||
|
|||
enum class Info { |
|||
Success, |
|||
UnableToLoad, |
|||
NotAnAmiibo, |
|||
WrongDeviceState, |
|||
Unknown, |
|||
}; |
|||
|
|||
explicit VirtualAmiibo(std::string input_engine_); |
|||
~VirtualAmiibo() override; |
|||
|
|||
// Sets polling mode to a controller |
|||
Common::Input::PollingError SetPollingMode( |
|||
const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override; |
|||
|
|||
Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override; |
|||
|
|||
Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_, |
|||
const std::vector<u8>& data) override; |
|||
|
|||
State GetCurrentState() const; |
|||
|
|||
Info LoadAmiibo(const std::string& amiibo_file); |
|||
Info CloseAmiibo(); |
|||
|
|||
private: |
|||
static constexpr std::size_t amiibo_size = 0x21C; |
|||
static constexpr std::size_t amiibo_size_without_password = amiibo_size - 0x8; |
|||
|
|||
std::string file_path{}; |
|||
State state{State::Initialized}; |
|||
std::vector<u8> amiibo_data; |
|||
Common::Input::PollingMode polling_mode{Common::Input::PollingMode::Pasive}; |
|||
}; |
|||
} // namespace InputCommon |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue