1
0
Fork 0
forked from suyu/suyu

service: nfp: Rewrite and implement applet calls

This commit is contained in:
german77 2022-09-24 21:28:06 -05:00
parent 8a3d22c4bd
commit afea5c163f
13 changed files with 1544 additions and 1265 deletions

View file

@ -523,9 +523,12 @@ add_library(core STATIC
hle/service/nfc/nfc.h hle/service/nfc/nfc.h
hle/service/nfp/amiibo_crypto.cpp hle/service/nfp/amiibo_crypto.cpp
hle/service/nfp/amiibo_crypto.h hle/service/nfp/amiibo_crypto.h
hle/service/nfp/amiibo_types.h
hle/service/nfp/nfp.cpp hle/service/nfp/nfp.cpp
hle/service/nfp/nfp.h hle/service/nfp/nfp.h
hle/service/nfp/nfp_device.cpp
hle/service/nfp/nfp_device.h
hle/service/nfp/nfp_result.h
hle/service/nfp/nfp_types.h
hle/service/nfp/nfp_user.cpp hle/service/nfp/nfp_user.cpp
hle/service/nfp/nfp_user.h hle/service/nfp/nfp_user.h
hle/service/ngct/ngct.cpp hle/service/ngct/ngct.cpp

View file

@ -427,12 +427,12 @@ CharInfo MiiManager::BuildDefault(std::size_t index) {
return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id)); return ConvertStoreDataToInfo(BuildDefaultStoreData(RawData::DefaultMii.at(index), user_id));
} }
CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const { CharInfo MiiManager::ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const {
Service::Mii::MiiManager manager; Service::Mii::MiiManager manager;
auto mii = manager.BuildDefault(0); auto mii = manager.BuildDefault(0);
// Check if mii data exist // Check if mii data exist
if (mii_v3.mii_name[0] == 0) { if (mii_v3.version == 0) {
return mii; return mii;
} }
@ -443,8 +443,8 @@ CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
mii.height = mii_v3.height; mii.height = mii_v3.height;
mii.build = mii_v3.build; mii.build = mii_v3.build;
memset(mii.name.data(), 0, sizeof(mii.name)); memset(mii.name.data(), 0, mii.name.size());
memcpy(mii.name.data(), mii_v3.mii_name.data(), sizeof(mii_v3.mii_name)); memcpy(mii.name.data(), mii_v3.mii_name.data(), mii_v3.mii_name.size());
mii.font_region = mii_v3.region_information.character_set; mii.font_region = mii_v3.region_information.character_set;
mii.faceline_type = mii_v3.appearance_bits1.face_shape; mii.faceline_type = mii_v3.appearance_bits1.face_shape;
@ -504,6 +504,78 @@ CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
return mii; return mii;
} }
Ver3StoreData MiiManager::ConvertCharInfoToV3(const CharInfo& mii) const {
Service::Mii::MiiManager manager;
Ver3StoreData mii_v3{};
// TODO: We are ignoring a bunch of data from the mii_v3
mii_v3.version = 1;
mii_v3.mii_information.gender.Assign(mii.gender);
mii_v3.mii_information.favorite_color.Assign(mii.favorite_color);
mii_v3.height = mii.height;
mii_v3.build = mii.build;
memcpy(mii_v3.mii_name.data(), mii.name.data(), mii.name.size());
mii_v3.region_information.character_set.Assign(mii.font_region);
mii_v3.appearance_bits1.face_shape.Assign(mii.faceline_type);
mii_v3.appearance_bits1.skin_color.Assign(mii.faceline_color);
mii_v3.appearance_bits2.wrinkles.Assign(mii.faceline_wrinkle);
mii_v3.appearance_bits2.makeup.Assign(mii.faceline_make);
mii_v3.hair_style = mii.hair_type;
mii_v3.appearance_bits3.hair_color.Assign(mii.hair_color);
mii_v3.appearance_bits3.flip_hair.Assign(mii.hair_flip);
mii_v3.appearance_bits4.eye_type.Assign(mii.eye_type);
mii_v3.appearance_bits4.eye_color.Assign(mii.eye_color);
mii_v3.appearance_bits4.eye_scale.Assign(mii.eye_scale);
mii_v3.appearance_bits4.eye_vertical_stretch.Assign(mii.eye_aspect);
mii_v3.appearance_bits4.eye_rotation.Assign(mii.eye_rotate);
mii_v3.appearance_bits4.eye_spacing.Assign(mii.eye_x);
mii_v3.appearance_bits4.eye_y_position.Assign(mii.eye_y);
mii_v3.appearance_bits5.eyebrow_style.Assign(mii.eyebrow_type);
mii_v3.appearance_bits5.eyebrow_color.Assign(mii.eyebrow_color);
mii_v3.appearance_bits5.eyebrow_scale.Assign(mii.eyebrow_scale);
mii_v3.appearance_bits5.eyebrow_yscale.Assign(mii.eyebrow_aspect);
mii_v3.appearance_bits5.eyebrow_rotation.Assign(mii.eyebrow_rotate);
mii_v3.appearance_bits5.eyebrow_spacing.Assign(mii.eyebrow_x);
mii_v3.appearance_bits5.eyebrow_y_position.Assign(mii.eyebrow_y);
mii_v3.appearance_bits6.nose_type.Assign(mii.nose_type);
mii_v3.appearance_bits6.nose_scale.Assign(mii.nose_scale);
mii_v3.appearance_bits6.nose_y_position.Assign(mii.nose_y);
mii_v3.appearance_bits7.mouth_type.Assign(mii.mouth_type);
mii_v3.appearance_bits7.mouth_color.Assign(mii.mouth_color);
mii_v3.appearance_bits7.mouth_scale.Assign(mii.mouth_scale);
mii_v3.appearance_bits7.mouth_horizontal_stretch.Assign(mii.mouth_aspect);
mii_v3.appearance_bits8.mouth_y_position.Assign(mii.mouth_y);
mii_v3.appearance_bits8.mustache_type.Assign(mii.mustache_type);
mii_v3.appearance_bits9.mustache_scale.Assign(mii.mustache_scale);
mii_v3.appearance_bits9.mustache_y_position.Assign(mii.mustache_y);
mii_v3.appearance_bits9.bear_type.Assign(mii.beard_type);
mii_v3.appearance_bits9.facial_hair_color.Assign(mii.beard_color);
mii_v3.appearance_bits10.glasses_type.Assign(mii.glasses_type);
mii_v3.appearance_bits10.glasses_color.Assign(mii.glasses_color);
mii_v3.appearance_bits10.glasses_scale.Assign(mii.glasses_scale);
mii_v3.appearance_bits10.glasses_y_position.Assign(mii.glasses_y);
mii_v3.appearance_bits11.mole_enabled.Assign(mii.mole_type);
mii_v3.appearance_bits11.mole_scale.Assign(mii.mole_scale);
mii_v3.appearance_bits11.mole_x_position.Assign(mii.mole_x);
mii_v3.appearance_bits11.mole_y_position.Assign(mii.mole_y);
// TODO: Validate mii_v3 data
return mii_v3;
}
ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) { ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
std::vector<MiiInfoElement> result; std::vector<MiiInfoElement> result;

View file

@ -22,7 +22,8 @@ public:
ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag); ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag);
CharInfo BuildRandom(Age age, Gender gender, Race race); CharInfo BuildRandom(Age age, Gender gender, Race race);
CharInfo BuildDefault(std::size_t index); CharInfo BuildDefault(std::size_t index);
CharInfo ConvertV3ToCharInfo(Ver3StoreData mii_v3) const; CharInfo ConvertV3ToCharInfo(const Ver3StoreData& mii_v3) const;
Ver3StoreData ConvertCharInfoToV3(const CharInfo& mii) const;
ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag); ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag);
Result GetIndex(const CharInfo& info, u32& index); Result GetIndex(const CharInfo& info, u32& index);

View file

@ -20,13 +20,13 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
const auto& amiibo_data = ntag_file.user_memory; const auto& amiibo_data = ntag_file.user_memory;
LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock); LOG_DEBUG(Service_NFP, "uuid_lock=0x{0:x}", ntag_file.static_lock);
LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", ntag_file.compability_container); LOG_DEBUG(Service_NFP, "compability_container=0x{0:x}", ntag_file.compability_container);
LOG_INFO(Service_NFP, "write_count={}", amiibo_data.write_counter); LOG_DEBUG(Service_NFP, "write_count={}", static_cast<u16>(amiibo_data.write_counter));
LOG_INFO(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id); LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
LOG_INFO(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant); LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
LOG_INFO(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type); LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
LOG_INFO(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number); LOG_DEBUG(Service_NFP, "model_number=0x{0:x}", amiibo_data.model_info.model_number);
LOG_INFO(Service_NFP, "series={}", amiibo_data.model_info.series); LOG_DEBUG(Service_NFP, "series={}", amiibo_data.model_info.series);
LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.constant_value); LOG_DEBUG(Service_NFP, "fixed_value=0x{0:x}", amiibo_data.model_info.constant_value);
LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock); LOG_DEBUG(Service_NFP, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock);

View file

@ -5,7 +5,7 @@
#include <array> #include <array>
#include "core/hle/service/nfp/amiibo_types.h" #include "core/hle/service/nfp/nfp_types.h"
struct mbedtls_md_context_t; struct mbedtls_md_context_t;
@ -22,7 +22,7 @@ using HmacKey = std::array<u8, 0x10>;
using DrgbOutput = std::array<u8, 0x20>; using DrgbOutput = std::array<u8, 0x20>;
struct HashSeed { struct HashSeed {
u16 magic; u16_be magic;
std::array<u8, 0xE> padding; std::array<u8, 0xE> padding;
std::array<u8, 0x8> uuid1; std::array<u8, 0x8> uuid1;
std::array<u8, 0x8> uuid2; std::array<u8, 0x8> uuid2;

File diff suppressed because it is too large Load diff

View file

@ -3,170 +3,9 @@
#pragma once #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/amiibo_types.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace Core::HID {
enum class NpadIdType : u32;
} // namespace Core::HID
namespace Service::NFP { namespace Service::NFP {
using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
struct TagInfo {
TagUuid uuid;
u8 uuid_length;
INSERT_PADDING_BYTES(0x15);
s32 protocol;
u32 tag_type;
INSERT_PADDING_BYTES(0x30);
};
static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
struct CommonInfo {
u16 last_write_year;
u8 last_write_month;
u8 last_write_day;
u16 write_counter;
u16 version;
u32 application_area_size;
INSERT_PADDING_BYTES(0x34);
};
static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
struct ModelInfo {
u16 character_id;
u8 character_variant;
AmiiboType amiibo_type;
u16 model_number;
AmiiboSeries series;
u8 constant_value; // Must be 02
INSERT_PADDING_BYTES(0x38); // Unknown
};
static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
struct RegisterInfo {
Service::Mii::CharInfo mii_char_info;
u16 first_write_year;
u8 first_write_month;
u8 first_write_day;
AmiiboName amiibo_name;
u8 font_region;
INSERT_PADDING_BYTES(0x7A);
};
static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
class Module final {
public:
class Interface : public ServiceFramework<Interface> {
public:
explicit Interface(std::shared_ptr<Module> module_, Core::System& system_,
const char* name);
~Interface() override;
void CreateUserInterface(Kernel::HLERequestContext& ctx);
bool LoadAmiibo(const std::string& filename);
bool LoadAmiiboFile(const std::string& filename);
void CloseAmiibo();
void Initialize();
void Finalize();
Result StartDetection(s32 protocol_);
Result StopDetection();
Result Mount();
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 OpenApplicationArea(u32 access_id);
Result GetApplicationArea(ApplicationArea& data) const;
Result SetApplicationArea(const std::vector<u8>& data);
Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data);
Result RecreateApplicationArea(u32 access_id, const std::vector<u8>& data);
u64 GetHandle() const;
DeviceState GetCurrentState() const;
Core::HID::NpadIdType GetNpadId() const;
Kernel::KReadableEvent& GetActivateEvent() const;
Kernel::KReadableEvent& GetDeactivateEvent() const;
protected:
std::shared_ptr<Module> module;
private:
AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
const Core::HID::NpadIdType npad_id;
bool is_data_decoded{};
bool is_application_area_initialized{};
s32 protocol;
std::string file_path{};
Kernel::KEvent* activate_event;
Kernel::KEvent* deactivate_event;
DeviceState device_state{DeviceState::Unaviable};
KernelHelpers::ServiceContext service_context;
NTAG215File tag_data{};
EncryptedNTAG215File encrypted_tag_data{};
};
};
class IUser final : public ServiceFramework<IUser> {
public:
explicit IUser(Module::Interface& nfp_interface_, Core::System& system_);
private:
void Initialize(Kernel::HLERequestContext& ctx);
void Finalize(Kernel::HLERequestContext& ctx);
void ListDevices(Kernel::HLERequestContext& ctx);
void StartDetection(Kernel::HLERequestContext& ctx);
void StopDetection(Kernel::HLERequestContext& ctx);
void Mount(Kernel::HLERequestContext& ctx);
void Unmount(Kernel::HLERequestContext& ctx);
void OpenApplicationArea(Kernel::HLERequestContext& ctx);
void GetApplicationArea(Kernel::HLERequestContext& ctx);
void SetApplicationArea(Kernel::HLERequestContext& ctx);
void Flush(Kernel::HLERequestContext& ctx);
void CreateApplicationArea(Kernel::HLERequestContext& ctx);
void GetTagInfo(Kernel::HLERequestContext& ctx);
void GetRegisterInfo(Kernel::HLERequestContext& ctx);
void GetCommonInfo(Kernel::HLERequestContext& ctx);
void GetModelInfo(Kernel::HLERequestContext& ctx);
void AttachActivateEvent(Kernel::HLERequestContext& ctx);
void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
void GetState(Kernel::HLERequestContext& ctx);
void GetDeviceState(Kernel::HLERequestContext& ctx);
void GetNpadId(Kernel::HLERequestContext& ctx);
void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
void RecreateApplicationArea(Kernel::HLERequestContext& ctx);
KernelHelpers::ServiceContext service_context;
// TODO(german77): We should have a vector of interfaces
Module::Interface& nfp_interface;
State state{State::NonInitialized};
Kernel::KEvent* availability_change_event;
};
void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system); void InstallInterfaces(SM::ServiceManager& service_manager, Core::System& system);

View file

@ -0,0 +1,572 @@
// 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"
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);
}
NfpDevice::~NfpDevice() {
if (is_controller_set) {
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(const std::vector<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;
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 = {};
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_) {
// TODO(german77): Add callback for when nfc data is available
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() {
auto& settings = tag_data.settings;
if (settings.write_date.raw_date != settings.write_date.raw_date) {
// TODO: Read current system date
settings.write_date.SetYear(2022);
settings.write_date.SetMonth(9);
settings.write_date.SetDay(9);
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() {
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;
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;
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,
.uuid_length = static_cast<u8>(encrypted_tag_data.uuid.size()),
.protocol = protocol,
.tag_type = static_cast<u32>(encrypted_tag_data.user_memory.model_info.amiibo_type),
};
return ResultSuccess;
}
Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
return WrongDeviceState;
}
const auto& settings = tag_data.settings;
const u32 application_area_size =
tag_data.settings.settings.appdata_initialized == 0 ? 0 : sizeof(ApplicationArea);
// 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 = 1,
.application_area_size = application_area_size,
};
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 (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;
}
Service::Mii::MiiManager manager;
auto& settings = tag_data.settings;
// TODO: Read current system date
settings.init_date.SetYear(2022);
settings.init_date.SetMonth(9);
settings.init_date.SetDay(9);
settings.write_date.SetYear(2022);
settings.write_date.SetMonth(9);
settings.write_date.SetDay(9);
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() {
// 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 (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;
}
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 (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(const std::vector<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 is not initialized");
return ApplicationAreaIsNotInitialized;
}
if (data.size() > sizeof(ApplicationArea)) {
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
return ResultUnknown;
}
Common::TinyMT rng{};
rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(ApplicationArea));
std::memcpy(tag_data.application_area.data(), data.data(), data.size());
tag_data.applicaton_write_counter++;
is_data_moddified = true;
return ResultSuccess;
}
Result NfpDevice::CreateApplicationArea(u32 access_id, const std::vector<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, const std::vector<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 (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;
}
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]);
}
}
} // namespace Service::NFP

View file

@ -0,0 +1,97 @@
// 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();
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(const std::vector<u8>& data);
Result CreateApplicationArea(u32 access_id, const std::vector<u8>& data);
Result RecreateApplicationArea(u32 access_id, const std::vector<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(const std::vector<u8>& data);
void CloseAmiibo();
AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name);
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{};
s32 protocol{};
DeviceState device_state{DeviceState::Unavailable};
NTAG215File tag_data{};
EncryptedNTAG215File encrypted_tag_data{};
};
} // namespace Service::NFP

View file

@ -0,0 +1,21 @@
// 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);
} // namespace Service::NFP

View file

@ -27,7 +27,7 @@ enum class DeviceState : u32 {
TagFound, TagFound,
TagRemoved, TagRemoved,
TagMounted, TagMounted,
Unaviable, Unavailable,
Finalized, Finalized,
}; };
@ -36,6 +36,7 @@ enum class ModelType : u32 {
}; };
enum class MountTarget : u32 { enum class MountTarget : u32 {
None,
Rom, Rom,
Ram, Ram,
All, All,
@ -76,18 +77,36 @@ enum class AmiiboSeries : u8 {
using TagUuid = std::array<u8, 10>; using TagUuid = std::array<u8, 10>;
using HashData = std::array<u8, 0x20>; using HashData = std::array<u8, 0x20>;
using ApplicationArea = std::array<u8, 0xD8>; using ApplicationArea = std::array<u8, 0xD8>;
using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
struct AmiiboDate { struct AmiiboDate {
u16 raw_date{}; u16_be raw_date{};
u16 DateRaw() const {
return static_cast<u16>(raw_date);
}
u16 GetYear() const { u16 GetYear() const {
return static_cast<u16>(((raw_date & 0xFE00) >> 9) + 2000); return static_cast<u16>(((DateRaw() & 0xFE00) >> 9) + 2000);
} }
u8 GetMonth() const { u8 GetMonth() const {
return static_cast<u8>(((raw_date & 0x01E0) >> 5) - 1); return static_cast<u8>(((DateRaw() & 0x01E0) >> 5) - 1);
} }
u8 GetDay() const { u8 GetDay() const {
return static_cast<u8>(raw_date & 0x001F); return static_cast<u8>(DateRaw() & 0x001F);
}
void SetYear(u16 year) {
raw_date = DateRaw() & ~0xFE00;
raw_date |= static_cast<u16_be>((year - 2000) << 9);
}
void SetMonth(u8 month) {
raw_date = DateRaw() & ~0x01E0;
raw_date |= static_cast<u16_be>((month + 1) << 5);
}
void SetDay(u8 day) {
raw_date = DateRaw() & ~0x001F;
raw_date |= static_cast<u16_be>(day);
} }
}; };
static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size"); static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
@ -134,7 +153,7 @@ static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid siz
#pragma pack(1) #pragma pack(1)
struct EncryptedAmiiboFile { struct EncryptedAmiiboFile {
u8 constant_value; // Must be A5 u8 constant_value; // Must be A5
u16 write_counter; // Number of times the amiibo has been written? u16_be write_counter; // Number of times the amiibo has been written?
INSERT_PADDING_BYTES(0x1); // Unknown 1 INSERT_PADDING_BYTES(0x1); // Unknown 1
AmiiboSettings settings; // Encrypted amiibo settings AmiiboSettings settings; // Encrypted amiibo settings
HashData hmac_tag; // Hash HashData hmac_tag; // Hash
@ -157,7 +176,7 @@ struct NTAG215File {
u32 compability_container; // Defines available memory u32 compability_container; // Defines available memory
HashData hmac_data; // Hash HashData hmac_data; // Hash
u8 constant_value; // Must be A5 u8 constant_value; // Must be A5
u16 write_counter; // Number of times the amiibo has been written? u16_be write_counter; // Number of times the amiibo has been written?
INSERT_PADDING_BYTES(0x1); // Unknown 1 INSERT_PADDING_BYTES(0x1); // Unknown 1
AmiiboSettings settings; AmiiboSettings settings;
Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
@ -194,4 +213,50 @@ static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an
static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>, static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
"EncryptedNTAG215File must be trivially copyable."); "EncryptedNTAG215File must be trivially copyable.");
struct TagInfo {
TagUuid uuid;
u8 uuid_length;
INSERT_PADDING_BYTES(0x15);
s32 protocol;
u32 tag_type;
INSERT_PADDING_BYTES(0x30);
};
static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
struct WriteDate {
u16 year;
u8 month;
u8 day;
};
static_assert(sizeof(WriteDate) == 0x4, "WriteDate is an invalid size");
struct CommonInfo {
WriteDate last_write_date;
u16 write_counter;
u8 version;
INSERT_PADDING_BYTES(0x1);
u32 application_area_size;
INSERT_PADDING_BYTES(0x34);
};
static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
struct ModelInfo {
u16 character_id;
u8 character_variant;
AmiiboType amiibo_type;
u16 model_number;
AmiiboSeries series;
INSERT_PADDING_BYTES(0x39); // Unknown
};
static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
struct RegisterInfo {
Service::Mii::CharInfo mii_char_info;
WriteDate creation_date;
AmiiboName amiibo_name;
u8 font_region;
INSERT_PADDING_BYTES(0x7A);
};
static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
} // namespace Service::NFP } // namespace Service::NFP

View file

@ -1,18 +1,644 @@
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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" #include "core/hle/service/nfp/nfp_user.h"
namespace Service::NFP { namespace Service::NFP {
NFP_User::NFP_User(std::shared_ptr<Module> module_, Core::System& system_) IUser::IUser(Core::System& system_)
: Interface(std::move(module_), system_, "nfp:user") { : ServiceFramework{system_, "NFP::IUser"}, service_context{system_, service_name} {
static const FunctionInfo functions[] = { 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); 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);
}
} }
NFP_User::~NFP_User() = default; 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();
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;
}
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 } // namespace Service::NFP

View file

@ -3,14 +3,52 @@
#pragma once #pragma once
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nfp/nfp.h" #include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_types.h"
namespace Service::NFP { namespace Service::NFP {
class NfpDevice;
class NFP_User final : public Module::Interface { class IUser final : public ServiceFramework<IUser> {
public: public:
explicit NFP_User(std::shared_ptr<Module> module_, Core::System& system_); explicit IUser(Core::System& system_);
~NFP_User() override;
private:
void Initialize(Kernel::HLERequestContext& ctx);
void Finalize(Kernel::HLERequestContext& ctx);
void ListDevices(Kernel::HLERequestContext& ctx);
void StartDetection(Kernel::HLERequestContext& ctx);
void StopDetection(Kernel::HLERequestContext& ctx);
void Mount(Kernel::HLERequestContext& ctx);
void Unmount(Kernel::HLERequestContext& ctx);
void OpenApplicationArea(Kernel::HLERequestContext& ctx);
void GetApplicationArea(Kernel::HLERequestContext& ctx);
void SetApplicationArea(Kernel::HLERequestContext& ctx);
void Flush(Kernel::HLERequestContext& ctx);
void Restore(Kernel::HLERequestContext& ctx);
void CreateApplicationArea(Kernel::HLERequestContext& ctx);
void GetTagInfo(Kernel::HLERequestContext& ctx);
void GetRegisterInfo(Kernel::HLERequestContext& ctx);
void GetCommonInfo(Kernel::HLERequestContext& ctx);
void GetModelInfo(Kernel::HLERequestContext& ctx);
void AttachActivateEvent(Kernel::HLERequestContext& ctx);
void AttachDeactivateEvent(Kernel::HLERequestContext& ctx);
void GetState(Kernel::HLERequestContext& ctx);
void GetDeviceState(Kernel::HLERequestContext& ctx);
void GetNpadId(Kernel::HLERequestContext& ctx);
void GetApplicationAreaSize(Kernel::HLERequestContext& ctx);
void AttachAvailabilityChangeEvent(Kernel::HLERequestContext& ctx);
void RecreateApplicationArea(Kernel::HLERequestContext& ctx);
std::optional<std::shared_ptr<NfpDevice>> GetNfpDevice(u64 handle);
KernelHelpers::ServiceContext service_context;
std::array<std::shared_ptr<NfpDevice>, 10> devices{};
State state{State::NonInitialized};
Kernel::KEvent* availability_change_event;
}; };
} // namespace Service::NFP } // namespace Service::NFP