Merge pull request #8955 from german77/amiibo-rewrite

core: nfp: Rewrite implementation to remove direct access from the frontend
This commit is contained in:
bunnei 2022-10-02 14:28:43 -07:00 committed by GitHub
commit 61399de5db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 2304 additions and 1334 deletions

View file

@ -76,6 +76,19 @@ enum class PollingError {
Unknown,
};
// Nfc reply from the controller
enum class NfcState {
Success,
NewAmiibo,
WaitingForAmiibo,
AmiiboRemoved,
NotAnAmiibo,
NotSupported,
WrongDeviceState,
WriteFailed,
Unknown,
};
// Ir camera reply from the controller
enum class CameraError {
None,
@ -202,6 +215,11 @@ struct CameraStatus {
std::vector<u8> data{};
};
struct NfcStatus {
NfcState state{};
std::vector<u8> data{};
};
// List of buttons to be passed to Qt that can be translated
enum class ButtonNames {
Undefined,
@ -260,6 +278,7 @@ struct CallbackStatus {
BatteryStatus battery_status{};
VibrationStatus vibration_status{};
CameraStatus camera_status{};
NfcStatus nfc_status{};
};
// Triggered once every input change
@ -312,6 +331,14 @@ public:
virtual CameraError SetCameraFormat([[maybe_unused]] CameraFormat camera_format) {
return CameraError::NotSupported;
}
virtual NfcState SupportsNfc() const {
return NfcState::NotSupported;
}
virtual NfcState WriteNfcData([[maybe_unused]] const std::vector<u8>& data) {
return NfcState::NotSupported;
}
};
/// An abstract class template for a factory that can create input devices.

View file

@ -525,9 +525,12 @@ add_library(core STATIC
hle/service/nfc/nfc.h
hle/service/nfp/amiibo_crypto.cpp
hle/service/nfp/amiibo_crypto.h
hle/service/nfp/amiibo_types.h
hle/service/nfp/nfp.cpp
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.h
hle/service/ngct/ngct.cpp

View file

@ -131,13 +131,16 @@ void EmulatedController::LoadDevices() {
battery_params[RightIndex].Set("battery", true);
camera_params = Common::ParamPackage{"engine:camera,camera:1"};
nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
output_params[LeftIndex] = left_joycon;
output_params[RightIndex] = right_joycon;
output_params[2] = camera_params;
output_params[3] = nfc_params;
output_params[LeftIndex].Set("output", true);
output_params[RightIndex].Set("output", true);
output_params[2].Set("output", true);
output_params[3].Set("output", true);
LoadTASParams();
@ -155,6 +158,7 @@ void EmulatedController::LoadDevices() {
std::transform(battery_params.begin(), battery_params.end(), battery_devices.begin(),
Common::Input::CreateDevice<Common::Input::InputDevice>);
camera_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(camera_params);
nfc_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(nfc_params);
std::transform(output_params.begin(), output_params.end(), output_devices.begin(),
Common::Input::CreateDevice<Common::Input::OutputDevice>);
@ -284,6 +288,16 @@ void EmulatedController::ReloadInput() {
camera_devices->ForceUpdate();
}
if (nfc_devices) {
if (npad_id_type == NpadIdType::Handheld || npad_id_type == NpadIdType::Player1) {
nfc_devices->SetCallback({
.on_change =
[this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); },
});
nfc_devices->ForceUpdate();
}
}
// Use a common UUID for TAS
static constexpr Common::UUID TAS_UUID = Common::UUID{
{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
@ -339,6 +353,8 @@ void EmulatedController::UnloadInput() {
for (auto& stick : tas_stick_devices) {
stick.reset();
}
camera_devices.reset();
nfc_devices.reset();
}
void EmulatedController::EnableConfiguration() {
@ -903,6 +919,25 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback
TriggerOnChange(ControllerTriggerType::IrSensor, true);
}
void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
std::unique_lock lock{mutex};
controller.nfc_values = TransformToNfc(callback);
if (is_configuring) {
lock.unlock();
TriggerOnChange(ControllerTriggerType::Nfc, false);
return;
}
controller.nfc_state = {
controller.nfc_values.state,
controller.nfc_values.data,
};
lock.unlock();
TriggerOnChange(ControllerTriggerType::Nfc, true);
}
bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
if (device_index >= output_devices.size()) {
return false;
@ -980,6 +1015,10 @@ bool EmulatedController::TestVibration(std::size_t device_index) {
bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) {
LOG_INFO(Service_HID, "Set polling mode {}", polling_mode);
auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
auto& nfc_output_device = output_devices[3];
nfc_output_device->SetPollingMode(polling_mode);
return output_device->SetPollingMode(polling_mode) == Common::Input::PollingError::None;
}
@ -1000,6 +1039,32 @@ bool EmulatedController::SetCameraFormat(
camera_format)) == Common::Input::CameraError::None;
}
bool EmulatedController::HasNfc() const {
const auto& nfc_output_device = output_devices[3];
switch (npad_type) {
case NpadStyleIndex::JoyconRight:
case NpadStyleIndex::JoyconDual:
case NpadStyleIndex::ProController:
break;
default:
return false;
}
const bool has_virtual_nfc =
npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld;
const bool is_virtual_nfc_supported =
nfc_output_device->SupportsNfc() != Common::Input::NfcState::NotSupported;
return is_connected && (has_virtual_nfc && is_virtual_nfc_supported);
}
bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
auto& nfc_output_device = output_devices[3];
return nfc_output_device->WriteNfcData(data) == Common::Input::NfcState::Success;
}
void EmulatedController::SetLedPattern() {
for (auto& device : output_devices) {
if (!device) {
@ -1363,6 +1428,11 @@ const CameraState& EmulatedController::GetCamera() const {
return controller.camera_state;
}
const NfcState& EmulatedController::GetNfc() const {
std::scoped_lock lock{mutex};
return controller.nfc_state;
}
NpadColor EmulatedController::GetNpadColor(u32 color) {
return {
.r = static_cast<u8>((color >> 16) & 0xFF),

View file

@ -20,7 +20,7 @@
namespace Core::HID {
const std::size_t max_emulated_controllers = 2;
const std::size_t output_devices = 3;
const std::size_t output_devices_size = 4;
struct ControllerMotionInfo {
Common::Input::MotionStatus raw_status{};
MotionInput emulated{};
@ -37,7 +37,8 @@ using TriggerDevices =
using BatteryDevices =
std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>;
using CameraDevices = std::unique_ptr<Common::Input::InputDevice>;
using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices>;
using NfcDevices = std::unique_ptr<Common::Input::InputDevice>;
using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>;
using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>;
using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>;
@ -45,7 +46,8 @@ using ControllerMotionParams = std::array<Common::ParamPackage, Settings::Native
using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>;
using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>;
using CameraParams = Common::ParamPackage;
using OutputParams = std::array<Common::ParamPackage, output_devices>;
using NfcParams = Common::ParamPackage;
using OutputParams = std::array<Common::ParamPackage, output_devices_size>;
using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>;
using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>;
@ -55,6 +57,7 @@ using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::Native
using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>;
using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>;
using CameraValues = Common::Input::CameraStatus;
using NfcValues = Common::Input::NfcStatus;
using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>;
struct AnalogSticks {
@ -80,6 +83,11 @@ struct CameraState {
std::size_t sample{};
};
struct NfcState {
Common::Input::NfcState state{};
std::vector<u8> data{};
};
struct ControllerMotion {
Common::Vec3f accel{};
Common::Vec3f gyro{};
@ -107,6 +115,7 @@ struct ControllerStatus {
BatteryValues battery_values{};
VibrationValues vibration_values{};
CameraValues camera_values{};
NfcValues nfc_values{};
// Data for HID serices
HomeButtonState home_button_state{};
@ -119,6 +128,7 @@ struct ControllerStatus {
ControllerColors colors_state{};
BatteryLevelState battery_state{};
CameraState camera_state{};
NfcState nfc_state{};
};
enum class ControllerTriggerType {
@ -130,6 +140,7 @@ enum class ControllerTriggerType {
Battery,
Vibration,
IrSensor,
Nfc,
Connected,
Disconnected,
Type,
@ -315,6 +326,9 @@ public:
/// Returns the latest camera status from the controller
const CameraState& GetCamera() const;
/// Returns the latest ntag status from the controller
const NfcState& GetNfc() const;
/**
* Sends a specific vibration to the output device
* @return true if vibration had no errors
@ -341,6 +355,12 @@ public:
*/
bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format);
/// Returns true if the device has nfc support
bool HasNfc() const;
/// Returns true if the nfc tag was written
bool WriteNfc(const std::vector<u8>& data);
/// Returns the led pattern corresponding to this emulated controller
LedPattern GetLedPattern() const;
@ -424,6 +444,12 @@ private:
*/
void SetCamera(const Common::Input::CallbackStatus& callback);
/**
* Updates the nfc status of the controller
* @param callback A CallbackStatus containing the nfc status
*/
void SetNfc(const Common::Input::CallbackStatus& callback);
/**
* Converts a color format from bgra to rgba
* @param color in bgra format
@ -458,6 +484,7 @@ private:
TriggerParams trigger_params;
BatteryParams battery_params;
CameraParams camera_params;
NfcParams nfc_params;
OutputParams output_params;
ButtonDevices button_devices;
@ -466,6 +493,7 @@ private:
TriggerDevices trigger_devices;
BatteryDevices battery_devices;
CameraDevices camera_devices;
NfcDevices nfc_devices;
OutputDevices output_devices;
// TAS related variables

View file

@ -287,6 +287,20 @@ Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatu
return camera;
}
Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback) {
Common::Input::NfcStatus nfc{};
switch (callback.type) {
case Common::Input::InputType::Nfc:
return callback.nfc_status;
break;
default:
LOG_ERROR(Input, "Conversion from type {} to NFC not implemented", callback.type);
break;
}
return nfc;
}
void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) {
const auto& properties = analog.properties;
float& raw_value = analog.raw_value;

View file

@ -84,6 +84,14 @@ Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatu
*/
Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback);
/**
* Converts raw input data into a valid nfc status.
*
* @param callback Supported callbacks: Nfc.
* @return A valid CameraObject object.
*/
Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback);
/**
* Converts raw analog data into a valid analog value
* @param analog An analog object containing raw data and properties

View file

@ -427,12 +427,11 @@ CharInfo MiiManager::BuildDefault(std::size_t index) {
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;
auto mii = manager.BuildDefault(0);
// Check if mii data exist
if (mii_v3.mii_name[0] == 0) {
if (!ValidateV3Info(mii_v3)) {
return mii;
}
@ -443,8 +442,15 @@ CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
mii.height = mii_v3.height;
mii.build = mii_v3.build;
memset(mii.name.data(), 0, sizeof(mii.name));
memcpy(mii.name.data(), mii_v3.mii_name.data(), sizeof(mii_v3.mii_name));
// Copy name until string terminator
mii.name = {};
for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
mii.name[index] = mii_v3.mii_name[index];
if (mii.name[index] == 0) {
break;
}
}
mii.font_region = mii_v3.region_information.character_set;
mii.faceline_type = mii_v3.appearance_bits1.face_shape;
@ -504,6 +510,151 @@ CharInfo MiiManager::ConvertV3ToCharInfo(Ver3StoreData mii_v3) const {
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;
// Copy name until string terminator
mii_v3.mii_name = {};
for (std::size_t index = 0; index < mii.name.size() - 1; index++) {
mii_v3.mii_name[index] = mii.name[index];
if (mii_v3.mii_name[index] == 0) {
break;
}
}
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;
}
bool MiiManager::ValidateV3Info(const Ver3StoreData& mii_v3) const {
bool is_valid = mii_v3.version == 0 || mii_v3.version == 3;
is_valid = is_valid && (mii_v3.mii_name[0] != 0);
is_valid = is_valid && (mii_v3.mii_information.birth_month < 13);
is_valid = is_valid && (mii_v3.mii_information.birth_day < 32);
is_valid = is_valid && (mii_v3.mii_information.favorite_color < 12);
is_valid = is_valid && (mii_v3.height < 128);
is_valid = is_valid && (mii_v3.build < 128);
is_valid = is_valid && (mii_v3.appearance_bits1.face_shape < 12);
is_valid = is_valid && (mii_v3.appearance_bits1.skin_color < 7);
is_valid = is_valid && (mii_v3.appearance_bits2.wrinkles < 12);
is_valid = is_valid && (mii_v3.appearance_bits2.makeup < 12);
is_valid = is_valid && (mii_v3.hair_style < 132);
is_valid = is_valid && (mii_v3.appearance_bits3.hair_color < 8);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_type < 60);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_color < 6);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_scale < 8);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_vertical_stretch < 7);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_rotation < 8);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_spacing < 13);
is_valid = is_valid && (mii_v3.appearance_bits4.eye_y_position < 19);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_style < 25);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_color < 8);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_scale < 9);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_yscale < 7);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_rotation < 12);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_spacing < 12);
is_valid = is_valid && (mii_v3.appearance_bits5.eyebrow_y_position < 19);
is_valid = is_valid && (mii_v3.appearance_bits6.nose_type < 18);
is_valid = is_valid && (mii_v3.appearance_bits6.nose_scale < 9);
is_valid = is_valid && (mii_v3.appearance_bits6.nose_y_position < 19);
is_valid = is_valid && (mii_v3.appearance_bits7.mouth_type < 36);
is_valid = is_valid && (mii_v3.appearance_bits7.mouth_color < 5);
is_valid = is_valid && (mii_v3.appearance_bits7.mouth_scale < 9);
is_valid = is_valid && (mii_v3.appearance_bits7.mouth_horizontal_stretch < 7);
is_valid = is_valid && (mii_v3.appearance_bits8.mouth_y_position < 19);
is_valid = is_valid && (mii_v3.appearance_bits8.mustache_type < 6);
is_valid = is_valid && (mii_v3.appearance_bits9.mustache_scale < 7);
is_valid = is_valid && (mii_v3.appearance_bits9.mustache_y_position < 17);
is_valid = is_valid && (mii_v3.appearance_bits9.bear_type < 6);
is_valid = is_valid && (mii_v3.appearance_bits9.facial_hair_color < 8);
is_valid = is_valid && (mii_v3.appearance_bits10.glasses_type < 9);
is_valid = is_valid && (mii_v3.appearance_bits10.glasses_color < 6);
is_valid = is_valid && (mii_v3.appearance_bits10.glasses_scale < 8);
is_valid = is_valid && (mii_v3.appearance_bits10.glasses_y_position < 21);
is_valid = is_valid && (mii_v3.appearance_bits11.mole_enabled < 2);
is_valid = is_valid && (mii_v3.appearance_bits11.mole_scale < 9);
is_valid = is_valid && (mii_v3.appearance_bits11.mole_x_position < 17);
is_valid = is_valid && (mii_v3.appearance_bits11.mole_y_position < 31);
return is_valid;
}
ResultVal<std::vector<MiiInfoElement>> MiiManager::GetDefault(SourceFlag source_flag) {
std::vector<MiiInfoElement> result;

View file

@ -22,7 +22,9 @@ public:
ResultVal<CharInfo> UpdateLatest(const CharInfo& info, SourceFlag source_flag);
CharInfo BuildRandom(Age age, Gender gender, Race race);
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;
bool ValidateV3Info(const Ver3StoreData& mii_v3) const;
ResultVal<std::vector<MiiInfoElement>> GetDefault(SourceFlag source_flag);
Result GetIndex(const CharInfo& info, u32& index);

View file

@ -106,10 +106,10 @@ public:
{1, &IUser::FinalizeOld, "FinalizeOld"},
{2, &IUser::GetStateOld, "GetStateOld"},
{3, &IUser::IsNfcEnabledOld, "IsNfcEnabledOld"},
{400, nullptr, "Initialize"},
{401, nullptr, "Finalize"},
{402, nullptr, "GetState"},
{403, nullptr, "IsNfcEnabled"},
{400, &IUser::InitializeOld, "Initialize"},
{401, &IUser::FinalizeOld, "Finalize"},
{402, &IUser::GetStateOld, "GetState"},
{403, &IUser::IsNfcEnabledOld, "IsNfcEnabled"},
{404, nullptr, "ListDevices"},
{405, nullptr, "GetDeviceState"},
{406, nullptr, "GetNpadId"},

View file

@ -20,13 +20,14 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
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, "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_INFO(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
LOG_INFO(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_INFO(Service_NFP, "series={}", amiibo_data.model_info.series);
LOG_DEBUG(Service_NFP, "character_id=0x{0:x}", amiibo_data.model_info.character_id);
LOG_DEBUG(Service_NFP, "character_variant={}", amiibo_data.model_info.character_variant);
LOG_DEBUG(Service_NFP, "amiibo_type={}", amiibo_data.model_info.amiibo_type);
LOG_DEBUG(Service_NFP, "model_number=0x{0:x}",
static_cast<u16>(amiibo_data.model_info.model_number));
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, "tag_dynamic_lock=0x{0:x}", ntag_file.dynamic_lock);
@ -35,11 +36,12 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
// Validate UUID
constexpr u8 CT = 0x88; // As defined in `ISO / IEC 14443 - 3`
if ((CT ^ ntag_file.uuid[0] ^ ntag_file.uuid[1] ^ ntag_file.uuid[2]) != ntag_file.uuid[3]) {
if ((CT ^ ntag_file.uuid.uid[0] ^ ntag_file.uuid.uid[1] ^ ntag_file.uuid.uid[2]) !=
ntag_file.uuid.uid[3]) {
return false;
}
if ((ntag_file.uuid[4] ^ ntag_file.uuid[5] ^ ntag_file.uuid[6] ^ ntag_file.uuid[7]) !=
ntag_file.uuid[8]) {
if ((ntag_file.uuid.uid[4] ^ ntag_file.uuid.uid[5] ^ ntag_file.uuid.uid[6] ^
ntag_file.uuid.nintendo_id) != ntag_file.uuid.lock_bytes[0]) {
return false;
}
@ -56,8 +58,9 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
if (amiibo_data.model_info.constant_value != 0x02) {
return false;
}
// dynamic_lock value apparently is not constant
// ntag_file.dynamic_lock == 0x0F0001
if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001U) {
return false;
}
if (ntag_file.CFG0 != 0x04000000U) {
return false;
}
@ -70,7 +73,8 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
NTAG215File encoded_data{};
memcpy(encoded_data.uuid2.data(), nfc_data.uuid.data() + 0x8, sizeof(encoded_data.uuid2));
encoded_data.uid = nfc_data.uuid.uid;
encoded_data.nintendo_id = nfc_data.uuid.nintendo_id;
encoded_data.static_lock = nfc_data.static_lock;
encoded_data.compability_container = nfc_data.compability_container;
encoded_data.hmac_data = nfc_data.user_memory.hmac_data;
@ -82,10 +86,10 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
encoded_data.applicaton_write_counter = nfc_data.user_memory.applicaton_write_counter;
encoded_data.application_area_id = nfc_data.user_memory.application_area_id;
encoded_data.unknown = nfc_data.user_memory.unknown;
encoded_data.hash = nfc_data.user_memory.hash;
encoded_data.unknown2 = nfc_data.user_memory.unknown2;
encoded_data.application_area = nfc_data.user_memory.application_area;
encoded_data.hmac_tag = nfc_data.user_memory.hmac_tag;
memcpy(encoded_data.uuid.data(), nfc_data.uuid.data(), sizeof(encoded_data.uuid));
encoded_data.lock_bytes = nfc_data.uuid.lock_bytes;
encoded_data.model_info = nfc_data.user_memory.model_info;
encoded_data.keygen_salt = nfc_data.user_memory.keygen_salt;
encoded_data.dynamic_lock = nfc_data.dynamic_lock;
@ -99,8 +103,9 @@ NTAG215File NfcDataToEncodedData(const EncryptedNTAG215File& nfc_data) {
EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
EncryptedNTAG215File nfc_data{};
memcpy(nfc_data.uuid.data() + 0x8, encoded_data.uuid2.data(), sizeof(encoded_data.uuid2));
memcpy(nfc_data.uuid.data(), encoded_data.uuid.data(), sizeof(encoded_data.uuid));
nfc_data.uuid.uid = encoded_data.uid;
nfc_data.uuid.nintendo_id = encoded_data.nintendo_id;
nfc_data.uuid.lock_bytes = encoded_data.lock_bytes;
nfc_data.static_lock = encoded_data.static_lock;
nfc_data.compability_container = encoded_data.compability_container;
nfc_data.user_memory.hmac_data = encoded_data.hmac_data;
@ -112,7 +117,7 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
nfc_data.user_memory.applicaton_write_counter = encoded_data.applicaton_write_counter;
nfc_data.user_memory.application_area_id = encoded_data.application_area_id;
nfc_data.user_memory.unknown = encoded_data.unknown;
nfc_data.user_memory.hash = encoded_data.hash;
nfc_data.user_memory.unknown2 = encoded_data.unknown2;
nfc_data.user_memory.application_area = encoded_data.application_area;
nfc_data.user_memory.hmac_tag = encoded_data.hmac_tag;
nfc_data.user_memory.model_info = encoded_data.model_info;
@ -127,10 +132,10 @@ EncryptedNTAG215File EncodedDataToNfcData(const NTAG215File& encoded_data) {
u32 GetTagPassword(const TagUuid& uuid) {
// Verifiy that the generated password is correct
u32 password = 0xAA ^ (uuid[1] ^ uuid[3]);
password &= (0x55 ^ (uuid[2] ^ uuid[4])) << 8;
password &= (0xAA ^ (uuid[3] ^ uuid[5])) << 16;
password &= (0x55 ^ (uuid[4] ^ uuid[6])) << 24;
u32 password = 0xAA ^ (uuid.uid[1] ^ uuid.uid[3]);
password &= (0x55 ^ (uuid.uid[2] ^ uuid.uid[4])) << 8;
password &= (0xAA ^ (uuid.uid[3] ^ uuid.uid[5])) << 16;
password &= (0x55 ^ (uuid.uid[4] ^ uuid.uid[6])) << 24;
return password;
}
@ -138,15 +143,13 @@ HashSeed GetSeed(const NTAG215File& data) {
HashSeed seed{
.magic = data.write_counter,
.padding = {},
.uuid1 = {},
.uuid2 = {},
.uid_1 = data.uid,
.nintendo_id_1 = data.nintendo_id,
.uid_2 = data.uid,
.nintendo_id_2 = data.nintendo_id,
.keygen_salt = data.keygen_salt,
};
// Copy the first 8 bytes of uuid
memcpy(seed.uuid1.data(), data.uuid.data(), sizeof(seed.uuid1));
memcpy(seed.uuid2.data(), data.uuid.data(), sizeof(seed.uuid2));
return seed;
}
@ -165,8 +168,10 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed
output.insert(output.end(), key.magic_bytes.begin(),
key.magic_bytes.begin() + key.magic_length);
output.insert(output.end(), seed.uuid1.begin(), seed.uuid1.end());
output.insert(output.end(), seed.uuid2.begin(), seed.uuid2.end());
output.insert(output.end(), seed.uid_1.begin(), seed.uid_1.end());
output.emplace_back(seed.nintendo_id_1);
output.insert(output.end(), seed.uid_2.begin(), seed.uid_2.end());
output.emplace_back(seed.nintendo_id_2);
for (std::size_t i = 0; i < sizeof(seed.keygen_salt); i++) {
output.emplace_back(static_cast<u8>(seed.keygen_salt[i] ^ key.xor_pad[i]));
@ -177,7 +182,6 @@ std::vector<u8> GenerateInternalKey(const InternalKey& key, const HashSeed& seed
void CryptoInit(CryptoCtx& ctx, mbedtls_md_context_t& hmac_ctx, const HmacKey& hmac_key,
const std::vector<u8>& seed) {
// Initialize context
ctx.used = false;
ctx.counter = 0;
@ -250,14 +254,15 @@ void Cipher(const DerivedKeys& keys, const NTAG215File& in_data, NTAG215File& ou
reinterpret_cast<unsigned char*>(&out_data.settings));
// Copy the rest of the data directly
out_data.uuid2 = in_data.uuid2;
out_data.uid = in_data.uid;
out_data.nintendo_id = in_data.nintendo_id;
out_data.lock_bytes = in_data.lock_bytes;
out_data.static_lock = in_data.static_lock;
out_data.compability_container = in_data.compability_container;
out_data.constant_value = in_data.constant_value;
out_data.write_counter = in_data.write_counter;
out_data.uuid = in_data.uuid;
out_data.model_info = in_data.model_info;
out_data.keygen_salt = in_data.keygen_salt;
out_data.dynamic_lock = in_data.dynamic_lock;
@ -309,7 +314,7 @@ bool DecodeAmiibo(const EncryptedNTAG215File& encrypted_tag_data, NTAG215File& t
// Regenerate tag HMAC. Note: order matters, data HMAC depends on tag HMAC!
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid),
sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
input_length, reinterpret_cast<unsigned char*>(&tag_data.hmac_tag));
// Regenerate data HMAC
@ -350,7 +355,7 @@ bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_t
constexpr std::size_t input_length = DYNAMIC_LOCK_START - UUID_START;
constexpr std::size_t input_length2 = HMAC_TAG_START - WRITE_COUNTER_START;
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), tag_keys.hmac_key.data(),
sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uuid),
sizeof(HmacKey), reinterpret_cast<const unsigned char*>(&tag_data.uid),
input_length, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag));
// Init mbedtls HMAC context
@ -364,7 +369,7 @@ bool EncodeAmiibo(const NTAG215File& tag_data, EncryptedNTAG215File& encrypted_t
input_length2); // Data
mbedtls_md_hmac_update(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_tag),
sizeof(HashData)); // Tag HMAC
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uuid),
mbedtls_md_hmac_update(&ctx, reinterpret_cast<const unsigned char*>(&tag_data.uid),
input_length);
mbedtls_md_hmac_finish(&ctx, reinterpret_cast<unsigned char*>(&encoded_tag_data.hmac_data));

View file

@ -5,7 +5,7 @@
#include <array>
#include "core/hle/service/nfp/amiibo_types.h"
#include "core/hle/service/nfp/nfp_types.h"
struct mbedtls_md_context_t;
@ -22,10 +22,12 @@ using HmacKey = std::array<u8, 0x10>;
using DrgbOutput = std::array<u8, 0x20>;
struct HashSeed {
u16 magic;
u16_be magic;
std::array<u8, 0xE> padding;
std::array<u8, 0x8> uuid1;
std::array<u8, 0x8> uuid2;
UniqueSerialNumber uid_1;
u8 nintendo_id_1;
UniqueSerialNumber uid_2;
u8 nintendo_id_2;
std::array<u8, 0x20> keygen_salt;
};
static_assert(sizeof(HashSeed) == 0x40, "HashSeed is an invalid size");

File diff suppressed because it is too large Load diff

View file

@ -3,170 +3,9 @@
#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"
namespace Kernel {
class KEvent;
class KReadableEvent;
} // namespace Kernel
namespace Core::HID {
enum class NpadIdType : u32;
} // namespace Core::HID
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);

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

View file

@ -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

View file

@ -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

View file

@ -5,6 +5,7 @@
#include <array>
#include "common/swap.h"
#include "core/hle/service/mii/types.h"
namespace Service::NFP {
@ -27,7 +28,7 @@ enum class DeviceState : u32 {
TagFound,
TagRemoved,
TagMounted,
Unaviable,
Unavailable,
Finalized,
};
@ -36,6 +37,7 @@ enum class ModelType : u32 {
};
enum class MountTarget : u32 {
None,
Rom,
Ram,
All,
@ -73,21 +75,63 @@ enum class AmiiboSeries : u8 {
Diablo,
};
using TagUuid = std::array<u8, 10>;
enum class TagType : u32 {
None,
Type1, // ISO14443A RW 96-2k bytes 106kbit/s
Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
};
enum class TagProtocol : u32 {
None,
TypeA, // ISO14443A
TypeB, // ISO14443B
TypeF, // Sony Felica
};
using UniqueSerialNumber = std::array<u8, 7>;
using LockBytes = std::array<u8, 2>;
using HashData = std::array<u8, 0x20>;
using ApplicationArea = std::array<u8, 0xD8>;
using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
struct TagUuid {
UniqueSerialNumber uid;
u8 nintendo_id;
LockBytes lock_bytes;
};
static_assert(sizeof(TagUuid) == 10, "TagUuid is an invalid size");
struct AmiiboDate {
u16 raw_date{};
u16 GetValue() const {
return Common::swap16(raw_date);
}
u16 GetYear() const {
return static_cast<u16>(((raw_date & 0xFE00) >> 9) + 2000);
return static_cast<u16>(((GetValue() & 0xFE00) >> 9) + 2000);
}
u8 GetMonth() const {
return static_cast<u8>(((raw_date & 0x01E0) >> 5) - 1);
return static_cast<u8>((GetValue() & 0x01E0) >> 5);
}
u8 GetDay() const {
return static_cast<u8>(raw_date & 0x001F);
return static_cast<u8>(GetValue() & 0x001F);
}
void SetYear(u16 year) {
const u16 year_converted = static_cast<u16>((year - 2000) << 9);
raw_date = Common::swap16((GetValue() & ~0xFE00) | year_converted);
}
void SetMonth(u8 month) {
const u16 month_converted = static_cast<u16>(month << 5);
raw_date = Common::swap16((GetValue() & ~0x01E0) | month_converted);
}
void SetDay(u8 day) {
const u16 day_converted = static_cast<u16>(day);
raw_date = Common::swap16((GetValue() & ~0x001F) | day_converted);
}
};
static_assert(sizeof(AmiiboDate) == 2, "AmiiboDate is an invalid size");
@ -117,7 +161,7 @@ struct AmiiboModelInfo {
u16 character_id;
u8 character_variant;
AmiiboType amiibo_type;
u16 model_number;
u16_be model_number;
AmiiboSeries series;
u8 constant_value; // Must be 02
INSERT_PADDING_BYTES(0x4); // Unknown
@ -134,7 +178,7 @@ static_assert(sizeof(NTAG215Password) == 0x8, "NTAG215Password is an invalid siz
#pragma pack(1)
struct EncryptedAmiiboFile {
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
AmiiboSettings settings; // Encrypted amiibo settings
HashData hmac_tag; // Hash
@ -146,18 +190,18 @@ struct EncryptedAmiiboFile {
u16_be applicaton_write_counter; // Encrypted Counter
u32_be application_area_id; // Encrypted Game id
std::array<u8, 0x2> unknown;
HashData hash; // Probably a SHA256-HMAC hash?
std::array<u32, 0x8> unknown2;
ApplicationArea application_area; // Encrypted Game data
};
static_assert(sizeof(EncryptedAmiiboFile) == 0x1F8, "AmiiboFile is an invalid size");
struct NTAG215File {
std::array<u8, 0x2> uuid2;
LockBytes lock_bytes; // Tag UUID
u16 static_lock; // Set defined pages as read only
u32 compability_container; // Defines available memory
HashData hmac_data; // Hash
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
AmiiboSettings settings;
Service::Mii::Ver3StoreData owner_mii; // Encrypted Mii data
@ -165,10 +209,11 @@ struct NTAG215File {
u16_be applicaton_write_counter; // Encrypted Counter
u32_be application_area_id;
std::array<u8, 0x2> unknown;
HashData hash; // Probably a SHA256-HMAC hash?
std::array<u32, 0x8> unknown2;
ApplicationArea application_area; // Encrypted Game data
HashData hmac_tag; // Hash
std::array<u8, 0x8> uuid;
UniqueSerialNumber uid; // Unique serial number
u8 nintendo_id; // Tag UUID
AmiiboModelInfo model_info;
HashData keygen_salt; // Salt
u32 dynamic_lock; // Dynamic lock
@ -194,4 +239,51 @@ static_assert(sizeof(EncryptedNTAG215File) == 0x21C, "EncryptedNTAG215File is an
static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
"EncryptedNTAG215File must be trivially copyable.");
struct TagInfo {
UniqueSerialNumber uuid;
INSERT_PADDING_BYTES(0x3);
u8 uuid_length;
INSERT_PADDING_BYTES(0x15);
TagProtocol protocol;
TagType 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

View file

@ -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);
}
}
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(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;
}
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

View file

@ -3,14 +3,52 @@
#pragma once
#include "core/hle/service/kernel_helpers.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/nfp/nfp_types.h"
namespace Service::NFP {
class NfpDevice;
class NFP_User final : public Module::Interface {
class IUser final : public ServiceFramework<IUser> {
public:
explicit NFP_User(std::shared_ptr<Module> module_, Core::System& system_);
~NFP_User() override;
explicit IUser(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 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

View file

@ -18,6 +18,8 @@ add_library(input_common STATIC
drivers/touch_screen.h
drivers/udp_client.cpp
drivers/udp_client.h
drivers/virtual_amiibo.cpp
drivers/virtual_amiibo.h
helpers/stick_from_buttons.cpp
helpers/stick_from_buttons.h
helpers/touch_from_buttons.cpp

View file

@ -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

View file

@ -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

View file

@ -102,6 +102,17 @@ void InputEngine::SetCamera(const PadIdentifier& identifier,
TriggerOnCameraChange(identifier, value);
}
void InputEngine::SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value) {
{
std::scoped_lock lock{mutex};
ControllerData& controller = controller_list.at(identifier);
if (!configuring) {
controller.nfc = value;
}
}
TriggerOnNfcChange(identifier, value);
}
bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const {
std::scoped_lock lock{mutex};
const auto controller_iter = controller_list.find(identifier);
@ -189,6 +200,18 @@ Common::Input::CameraStatus InputEngine::GetCamera(const PadIdentifier& identifi
return controller.camera;
}
Common::Input::NfcStatus InputEngine::GetNfc(const PadIdentifier& identifier) const {
std::scoped_lock lock{mutex};
const auto controller_iter = controller_list.find(identifier);
if (controller_iter == controller_list.cend()) {
LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.RawString(),
identifier.pad, identifier.port);
return {};
}
const ControllerData& controller = controller_iter->second;
return controller.nfc;
}
void InputEngine::ResetButtonState() {
for (const auto& controller : controller_list) {
for (const auto& button : controller.second.buttons) {
@ -355,6 +378,20 @@ void InputEngine::TriggerOnCameraChange(const PadIdentifier& identifier,
}
}
void InputEngine::TriggerOnNfcChange(const PadIdentifier& identifier,
[[maybe_unused]] const Common::Input::NfcStatus& value) {
std::scoped_lock lock{mutex_callback};
for (const auto& poller_pair : callback_list) {
const InputIdentifier& poller = poller_pair.second;
if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Nfc, 0)) {
continue;
}
if (poller.callback.on_change) {
poller.callback.on_change();
}
}
}
bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier,
const PadIdentifier& identifier, EngineInputType type,
int index) const {

View file

@ -42,6 +42,7 @@ enum class EngineInputType {
Camera,
HatButton,
Motion,
Nfc,
};
namespace std {
@ -127,6 +128,18 @@ public:
return Common::Input::CameraError::NotSupported;
}
// Request nfc data from a controller
virtual Common::Input::NfcState SupportsNfc(
[[maybe_unused]] const PadIdentifier& identifier) const {
return Common::Input::NfcState::NotSupported;
}
// Writes data to an nfc tag
virtual Common::Input::NfcState WriteNfcData([[maybe_unused]] const PadIdentifier& identifier,
[[maybe_unused]] const std::vector<u8>& data) {
return Common::Input::NfcState::NotSupported;
}
// Returns the engine name
[[nodiscard]] const std::string& GetEngineName() const;
@ -183,6 +196,7 @@ public:
Common::Input::BatteryLevel GetBattery(const PadIdentifier& identifier) const;
BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const;
Common::Input::CameraStatus GetCamera(const PadIdentifier& identifier) const;
Common::Input::NfcStatus GetNfc(const PadIdentifier& identifier) const;
int SetCallback(InputIdentifier input_identifier);
void SetMappingCallback(MappingCallback callback);
@ -195,6 +209,7 @@ protected:
void SetBattery(const PadIdentifier& identifier, Common::Input::BatteryLevel value);
void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value);
void SetCamera(const PadIdentifier& identifier, const Common::Input::CameraStatus& value);
void SetNfc(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);
virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const {
return "Unknown";
@ -208,6 +223,7 @@ private:
std::unordered_map<int, BasicMotion> motions;
Common::Input::BatteryLevel battery{};
Common::Input::CameraStatus camera{};
Common::Input::NfcStatus nfc{};
};
void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value);
@ -218,6 +234,7 @@ private:
const BasicMotion& value);
void TriggerOnCameraChange(const PadIdentifier& identifier,
const Common::Input::CameraStatus& value);
void TriggerOnNfcChange(const PadIdentifier& identifier, const Common::Input::NfcStatus& value);
bool IsInputIdentifierEqual(const InputIdentifier& input_identifier,
const PadIdentifier& identifier, EngineInputType type,

View file

@ -705,6 +705,47 @@ private:
InputEngine* input_engine;
};
class InputFromNfc final : public Common::Input::InputDevice {
public:
explicit InputFromNfc(PadIdentifier identifier_, InputEngine* input_engine_)
: identifier(identifier_), input_engine(input_engine_) {
UpdateCallback engine_callback{[this]() { OnChange(); }};
const InputIdentifier input_identifier{
.identifier = identifier,
.type = EngineInputType::Nfc,
.index = 0,
.callback = engine_callback,
};
callback_key = input_engine->SetCallback(input_identifier);
}
~InputFromNfc() override {
input_engine->DeleteCallback(callback_key);
}
Common::Input::NfcStatus GetStatus() const {
return input_engine->GetNfc(identifier);
}
void ForceUpdate() override {
OnChange();
}
void OnChange() {
const Common::Input::CallbackStatus status{
.type = Common::Input::InputType::Nfc,
.nfc_status = GetStatus(),
};
TriggerOnChange(status);
}
private:
const PadIdentifier identifier;
int callback_key;
InputEngine* input_engine;
};
class OutputFromIdentifier final : public Common::Input::OutputDevice {
public:
explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_)
@ -727,6 +768,14 @@ public:
return input_engine->SetCameraFormat(identifier, camera_format);
}
Common::Input::NfcState SupportsNfc() const override {
return input_engine->SupportsNfc(identifier);
}
Common::Input::NfcState WriteNfcData(const std::vector<u8>& data) override {
return input_engine->WriteNfcData(identifier, data);
}
private:
const PadIdentifier identifier;
InputEngine* input_engine;
@ -978,6 +1027,18 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateCameraDevice(
return std::make_unique<InputFromCamera>(identifier, input_engine.get());
}
std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateNfcDevice(
const Common::ParamPackage& params) {
const PadIdentifier identifier = {
.guid = Common::UUID{params.Get("guid", "")},
.port = static_cast<std::size_t>(params.Get("port", 0)),
.pad = static_cast<std::size_t>(params.Get("pad", 0)),
};
input_engine->PreSetController(identifier);
return std::make_unique<InputFromNfc>(identifier, input_engine.get());
}
InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_)
: input_engine(std::move(input_engine_)) {}
@ -989,6 +1050,9 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::Create(
if (params.Has("camera")) {
return CreateCameraDevice(params);
}
if (params.Has("nfc")) {
return CreateNfcDevice(params);
}
if (params.Has("button") && params.Has("axis")) {
return CreateTriggerDevice(params);
}

View file

@ -222,6 +222,16 @@ private:
std::unique_ptr<Common::Input::InputDevice> CreateCameraDevice(
const Common::ParamPackage& params);
/**
* Creates a nfc device from the parameters given.
* @param params contains parameters for creating the device:
* - "guid": text string for identifying controllers
* - "port": port of the connected device
* - "pad": slot of the connected controller
* @returns a unique input device with the parameters specified
*/
std::unique_ptr<Common::Input::InputDevice> CreateNfcDevice(const Common::ParamPackage& params);
std::shared_ptr<InputEngine> input_engine;
};
} // namespace InputCommon

View file

@ -11,6 +11,7 @@
#include "input_common/drivers/tas_input.h"
#include "input_common/drivers/touch_screen.h"
#include "input_common/drivers/udp_client.h"
#include "input_common/drivers/virtual_amiibo.h"
#include "input_common/helpers/stick_from_buttons.h"
#include "input_common/helpers/touch_from_buttons.h"
#include "input_common/input_engine.h"
@ -87,6 +88,15 @@ struct InputSubsystem::Impl {
Common::Input::RegisterFactory<Common::Input::OutputDevice>(camera->GetEngineName(),
camera_output_factory);
virtual_amiibo = std::make_shared<VirtualAmiibo>("virtual_amiibo");
virtual_amiibo->SetMappingCallback(mapping_callback);
virtual_amiibo_input_factory = std::make_shared<InputFactory>(virtual_amiibo);
virtual_amiibo_output_factory = std::make_shared<OutputFactory>(virtual_amiibo);
Common::Input::RegisterFactory<Common::Input::InputDevice>(virtual_amiibo->GetEngineName(),
virtual_amiibo_input_factory);
Common::Input::RegisterFactory<Common::Input::OutputDevice>(virtual_amiibo->GetEngineName(),
virtual_amiibo_output_factory);
#ifdef HAVE_SDL2
sdl = std::make_shared<SDLDriver>("sdl");
sdl->SetMappingCallback(mapping_callback);
@ -327,6 +337,7 @@ struct InputSubsystem::Impl {
std::shared_ptr<TasInput::Tas> tas_input;
std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
std::shared_ptr<Camera> camera;
std::shared_ptr<VirtualAmiibo> virtual_amiibo;
std::shared_ptr<InputFactory> keyboard_factory;
std::shared_ptr<InputFactory> mouse_factory;
@ -335,6 +346,7 @@ struct InputSubsystem::Impl {
std::shared_ptr<InputFactory> udp_client_input_factory;
std::shared_ptr<InputFactory> tas_input_factory;
std::shared_ptr<InputFactory> camera_input_factory;
std::shared_ptr<InputFactory> virtual_amiibo_input_factory;
std::shared_ptr<OutputFactory> keyboard_output_factory;
std::shared_ptr<OutputFactory> mouse_output_factory;
@ -342,6 +354,7 @@ struct InputSubsystem::Impl {
std::shared_ptr<OutputFactory> udp_client_output_factory;
std::shared_ptr<OutputFactory> tas_output_factory;
std::shared_ptr<OutputFactory> camera_output_factory;
std::shared_ptr<OutputFactory> virtual_amiibo_output_factory;
#ifdef HAVE_SDL2
std::shared_ptr<SDLDriver> sdl;
@ -402,6 +415,14 @@ const Camera* InputSubsystem::GetCamera() const {
return impl->camera.get();
}
VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() {
return impl->virtual_amiibo.get();
}
const VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() const {
return impl->virtual_amiibo.get();
}
std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
return impl->GetInputDevices();
}

View file

@ -33,6 +33,7 @@ class Camera;
class Keyboard;
class Mouse;
class TouchScreen;
class VirtualAmiibo;
struct MappingData;
} // namespace InputCommon
@ -101,6 +102,12 @@ public:
/// Retrieves the underlying camera input device.
[[nodiscard]] const Camera* GetCamera() const;
/// Retrieves the underlying virtual amiibo input device.
[[nodiscard]] VirtualAmiibo* GetVirtualAmiibo();
/// Retrieves the underlying virtual amiibo input device.
[[nodiscard]] const VirtualAmiibo* GetVirtualAmiibo() const;
/**
* Returns all available input devices that this Factory can create a new device with.
* Each returned ParamPackage should have a `display` field used for display, a `engine` field

View file

@ -105,12 +105,12 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
#include "core/hle/kernel/k_process.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/nfp/nfp.h"
#include "core/hle/service/sm/sm.h"
#include "core/loader/loader.h"
#include "core/perf_stats.h"
#include "core/telemetry_session.h"
#include "input_common/drivers/tas_input.h"
#include "input_common/drivers/virtual_amiibo.h"
#include "input_common/main.h"
#include "ui_main.h"
#include "util/overlay_dialog.h"
@ -3219,21 +3219,16 @@ void GMainWindow::OnLoadAmiibo() {
return;
}
Service::SM::ServiceManager& sm = system->ServiceManager();
auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user");
if (nfc == nullptr) {
QMessageBox::warning(this, tr("Error"), tr("The current game is not looking for amiibos"));
return;
}
const auto nfc_state = nfc->GetCurrentState();
if (nfc_state == Service::NFP::DeviceState::TagFound ||
nfc_state == Service::NFP::DeviceState::TagMounted) {
nfc->CloseAmiibo();
auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
// Remove amiibo if one is connected
if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) {
virtual_amiibo->CloseAmiibo();
QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
return;
}
if (nfc_state != Service::NFP::DeviceState::SearchingForTag) {
if (virtual_amiibo->GetCurrentState() != InputCommon::VirtualAmiibo::State::WaitingForAmiibo) {
QMessageBox::warning(this, tr("Error"), tr("The current game is not looking for amiibos"));
return;
}
@ -3252,24 +3247,30 @@ void GMainWindow::OnLoadAmiibo() {
}
void GMainWindow::LoadAmiibo(const QString& filename) {
Service::SM::ServiceManager& sm = system->ServiceManager();
auto nfc = sm.GetService<Service::NFP::Module::Interface>("nfp:user");
if (nfc == nullptr) {
return;
}
auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
const QString title = tr("Error loading Amiibo data");
// Remove amiibo if one is connected
const auto nfc_state = nfc->GetCurrentState();
if (nfc_state == Service::NFP::DeviceState::TagFound ||
nfc_state == Service::NFP::DeviceState::TagMounted) {
nfc->CloseAmiibo();
if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) {
virtual_amiibo->CloseAmiibo();
QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
return;
}
if (!nfc->LoadAmiibo(filename.toStdString())) {
QMessageBox::warning(this, tr("Error loading Amiibo data"),
tr("Unable to load Amiibo data."));
switch (virtual_amiibo->LoadAmiibo(filename.toStdString())) {
case InputCommon::VirtualAmiibo::Info::NotAnAmiibo:
QMessageBox::warning(this, title, tr("The selected file is not a valid amiibo"));
break;
case InputCommon::VirtualAmiibo::Info::UnableToLoad:
QMessageBox::warning(this, title, tr("The selected file is already on use"));
break;
case InputCommon::VirtualAmiibo::Info::WrongDeviceState:
QMessageBox::warning(this, title, tr("The current game is not looking for amiibos"));
break;
case InputCommon::VirtualAmiibo::Info::Unknown:
QMessageBox::warning(this, title, tr("An unkown error occured"));
break;
default:
break;
}
}