diff options
| author | 2022-09-24 20:36:40 -0500 | |
|---|---|---|
| committer | 2022-10-02 12:32:26 -0500 | |
| commit | 8a3d22c4bd54c18edb51fe6513761ec2189e3369 (patch) | |
| tree | 013e5121310205ba5e4c43f6760fd54be8de1ce0 | |
| parent | yuzu: Use virtual amiibo driver instead of nfp service (diff) | |
| download | yuzu-8a3d22c4bd54c18edb51fe6513761ec2189e3369.tar.gz yuzu-8a3d22c4bd54c18edb51fe6513761ec2189e3369.tar.xz yuzu-8a3d22c4bd54c18edb51fe6513761ec2189e3369.zip | |
core: hid: Add nfc support to emulated controller
| -rw-r--r-- | src/core/hid/emulated_controller.cpp | 70 | ||||
| -rw-r--r-- | src/core/hid/emulated_controller.h | 34 | ||||
| -rw-r--r-- | src/core/hid/input_converter.cpp | 14 | ||||
| -rw-r--r-- | src/core/hid/input_converter.h | 8 |
4 files changed, 123 insertions, 3 deletions
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 01c43be93..142c39003 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp | |||
| @@ -131,13 +131,16 @@ void EmulatedController::LoadDevices() { | |||
| 131 | battery_params[RightIndex].Set("battery", true); | 131 | battery_params[RightIndex].Set("battery", true); |
| 132 | 132 | ||
| 133 | camera_params = Common::ParamPackage{"engine:camera,camera:1"}; | 133 | camera_params = Common::ParamPackage{"engine:camera,camera:1"}; |
| 134 | nfc_params = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"}; | ||
| 134 | 135 | ||
| 135 | output_params[LeftIndex] = left_joycon; | 136 | output_params[LeftIndex] = left_joycon; |
| 136 | output_params[RightIndex] = right_joycon; | 137 | output_params[RightIndex] = right_joycon; |
| 137 | output_params[2] = camera_params; | 138 | output_params[2] = camera_params; |
| 139 | output_params[3] = nfc_params; | ||
| 138 | output_params[LeftIndex].Set("output", true); | 140 | output_params[LeftIndex].Set("output", true); |
| 139 | output_params[RightIndex].Set("output", true); | 141 | output_params[RightIndex].Set("output", true); |
| 140 | output_params[2].Set("output", true); | 142 | output_params[2].Set("output", true); |
| 143 | output_params[3].Set("output", true); | ||
| 141 | 144 | ||
| 142 | LoadTASParams(); | 145 | LoadTASParams(); |
| 143 | 146 | ||
| @@ -155,6 +158,7 @@ void EmulatedController::LoadDevices() { | |||
| 155 | std::transform(battery_params.begin(), battery_params.end(), battery_devices.begin(), | 158 | std::transform(battery_params.begin(), battery_params.end(), battery_devices.begin(), |
| 156 | Common::Input::CreateDevice<Common::Input::InputDevice>); | 159 | Common::Input::CreateDevice<Common::Input::InputDevice>); |
| 157 | camera_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(camera_params); | 160 | camera_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(camera_params); |
| 161 | nfc_devices = Common::Input::CreateDevice<Common::Input::InputDevice>(nfc_params); | ||
| 158 | std::transform(output_params.begin(), output_params.end(), output_devices.begin(), | 162 | std::transform(output_params.begin(), output_params.end(), output_devices.begin(), |
| 159 | Common::Input::CreateDevice<Common::Input::OutputDevice>); | 163 | Common::Input::CreateDevice<Common::Input::OutputDevice>); |
| 160 | 164 | ||
| @@ -284,6 +288,16 @@ void EmulatedController::ReloadInput() { | |||
| 284 | camera_devices->ForceUpdate(); | 288 | camera_devices->ForceUpdate(); |
| 285 | } | 289 | } |
| 286 | 290 | ||
| 291 | if (nfc_devices) { | ||
| 292 | if (npad_id_type == NpadIdType::Handheld || npad_id_type == NpadIdType::Player1) { | ||
| 293 | nfc_devices->SetCallback({ | ||
| 294 | .on_change = | ||
| 295 | [this](const Common::Input::CallbackStatus& callback) { SetNfc(callback); }, | ||
| 296 | }); | ||
| 297 | nfc_devices->ForceUpdate(); | ||
| 298 | } | ||
| 299 | } | ||
| 300 | |||
| 287 | // Use a common UUID for TAS | 301 | // Use a common UUID for TAS |
| 288 | static constexpr Common::UUID TAS_UUID = Common::UUID{ | 302 | static constexpr Common::UUID TAS_UUID = Common::UUID{ |
| 289 | {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; | 303 | {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xA5, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}}; |
| @@ -339,6 +353,8 @@ void EmulatedController::UnloadInput() { | |||
| 339 | for (auto& stick : tas_stick_devices) { | 353 | for (auto& stick : tas_stick_devices) { |
| 340 | stick.reset(); | 354 | stick.reset(); |
| 341 | } | 355 | } |
| 356 | camera_devices.reset(); | ||
| 357 | nfc_devices.reset(); | ||
| 342 | } | 358 | } |
| 343 | 359 | ||
| 344 | void EmulatedController::EnableConfiguration() { | 360 | void EmulatedController::EnableConfiguration() { |
| @@ -903,6 +919,25 @@ void EmulatedController::SetCamera(const Common::Input::CallbackStatus& callback | |||
| 903 | TriggerOnChange(ControllerTriggerType::IrSensor, true); | 919 | TriggerOnChange(ControllerTriggerType::IrSensor, true); |
| 904 | } | 920 | } |
| 905 | 921 | ||
| 922 | void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) { | ||
| 923 | std::unique_lock lock{mutex}; | ||
| 924 | controller.nfc_values = TransformToNfc(callback); | ||
| 925 | |||
| 926 | if (is_configuring) { | ||
| 927 | lock.unlock(); | ||
| 928 | TriggerOnChange(ControllerTriggerType::Nfc, false); | ||
| 929 | return; | ||
| 930 | } | ||
| 931 | |||
| 932 | controller.nfc_state = { | ||
| 933 | controller.nfc_values.state, | ||
| 934 | controller.nfc_values.data, | ||
| 935 | }; | ||
| 936 | |||
| 937 | lock.unlock(); | ||
| 938 | TriggerOnChange(ControllerTriggerType::Nfc, true); | ||
| 939 | } | ||
| 940 | |||
| 906 | bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { | 941 | bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) { |
| 907 | if (device_index >= output_devices.size()) { | 942 | if (device_index >= output_devices.size()) { |
| 908 | return false; | 943 | return false; |
| @@ -980,6 +1015,10 @@ bool EmulatedController::TestVibration(std::size_t device_index) { | |||
| 980 | bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) { | 1015 | bool EmulatedController::SetPollingMode(Common::Input::PollingMode polling_mode) { |
| 981 | LOG_INFO(Service_HID, "Set polling mode {}", polling_mode); | 1016 | LOG_INFO(Service_HID, "Set polling mode {}", polling_mode); |
| 982 | auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; | 1017 | auto& output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)]; |
| 1018 | auto& nfc_output_device = output_devices[3]; | ||
| 1019 | |||
| 1020 | nfc_output_device->SetPollingMode(polling_mode); | ||
| 1021 | |||
| 983 | return output_device->SetPollingMode(polling_mode) == Common::Input::PollingError::None; | 1022 | return output_device->SetPollingMode(polling_mode) == Common::Input::PollingError::None; |
| 984 | } | 1023 | } |
| 985 | 1024 | ||
| @@ -1000,6 +1039,32 @@ bool EmulatedController::SetCameraFormat( | |||
| 1000 | camera_format)) == Common::Input::CameraError::None; | 1039 | camera_format)) == Common::Input::CameraError::None; |
| 1001 | } | 1040 | } |
| 1002 | 1041 | ||
| 1042 | bool EmulatedController::HasNfc() const { | ||
| 1043 | const auto& nfc_output_device = output_devices[3]; | ||
| 1044 | |||
| 1045 | switch (npad_type) { | ||
| 1046 | case NpadStyleIndex::JoyconRight: | ||
| 1047 | case NpadStyleIndex::JoyconDual: | ||
| 1048 | case NpadStyleIndex::ProController: | ||
| 1049 | break; | ||
| 1050 | default: | ||
| 1051 | return false; | ||
| 1052 | } | ||
| 1053 | |||
| 1054 | const bool has_virtual_nfc = | ||
| 1055 | npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld; | ||
| 1056 | const bool is_virtual_nfc_supported = | ||
| 1057 | nfc_output_device->SupportsNfc() != Common::Input::NfcState::NotSupported; | ||
| 1058 | |||
| 1059 | return is_connected && (has_virtual_nfc && is_virtual_nfc_supported); | ||
| 1060 | } | ||
| 1061 | |||
| 1062 | bool EmulatedController::WriteNfc(const std::vector<u8>& data) { | ||
| 1063 | auto& nfc_output_device = output_devices[3]; | ||
| 1064 | |||
| 1065 | return nfc_output_device->WriteNfcData(data) == Common::Input::NfcState::Success; | ||
| 1066 | } | ||
| 1067 | |||
| 1003 | void EmulatedController::SetLedPattern() { | 1068 | void EmulatedController::SetLedPattern() { |
| 1004 | for (auto& device : output_devices) { | 1069 | for (auto& device : output_devices) { |
| 1005 | if (!device) { | 1070 | if (!device) { |
| @@ -1363,6 +1428,11 @@ const CameraState& EmulatedController::GetCamera() const { | |||
| 1363 | return controller.camera_state; | 1428 | return controller.camera_state; |
| 1364 | } | 1429 | } |
| 1365 | 1430 | ||
| 1431 | const NfcState& EmulatedController::GetNfc() const { | ||
| 1432 | std::scoped_lock lock{mutex}; | ||
| 1433 | return controller.nfc_state; | ||
| 1434 | } | ||
| 1435 | |||
| 1366 | NpadColor EmulatedController::GetNpadColor(u32 color) { | 1436 | NpadColor EmulatedController::GetNpadColor(u32 color) { |
| 1367 | return { | 1437 | return { |
| 1368 | .r = static_cast<u8>((color >> 16) & 0xFF), | 1438 | .r = static_cast<u8>((color >> 16) & 0xFF), |
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h index c3aa8f9d3..319226bf8 100644 --- a/src/core/hid/emulated_controller.h +++ b/src/core/hid/emulated_controller.h | |||
| @@ -20,7 +20,7 @@ | |||
| 20 | 20 | ||
| 21 | namespace Core::HID { | 21 | namespace Core::HID { |
| 22 | const std::size_t max_emulated_controllers = 2; | 22 | const std::size_t max_emulated_controllers = 2; |
| 23 | const std::size_t output_devices = 3; | 23 | const std::size_t output_devices_size = 4; |
| 24 | struct ControllerMotionInfo { | 24 | struct ControllerMotionInfo { |
| 25 | Common::Input::MotionStatus raw_status{}; | 25 | Common::Input::MotionStatus raw_status{}; |
| 26 | MotionInput emulated{}; | 26 | MotionInput emulated{}; |
| @@ -37,7 +37,8 @@ using TriggerDevices = | |||
| 37 | using BatteryDevices = | 37 | using BatteryDevices = |
| 38 | std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; | 38 | std::array<std::unique_ptr<Common::Input::InputDevice>, max_emulated_controllers>; |
| 39 | using CameraDevices = std::unique_ptr<Common::Input::InputDevice>; | 39 | using CameraDevices = std::unique_ptr<Common::Input::InputDevice>; |
| 40 | using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices>; | 40 | using NfcDevices = std::unique_ptr<Common::Input::InputDevice>; |
| 41 | using OutputDevices = std::array<std::unique_ptr<Common::Input::OutputDevice>, output_devices_size>; | ||
| 41 | 42 | ||
| 42 | using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>; | 43 | using ButtonParams = std::array<Common::ParamPackage, Settings::NativeButton::NumButtons>; |
| 43 | using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>; | 44 | using StickParams = std::array<Common::ParamPackage, Settings::NativeAnalog::NumAnalogs>; |
| @@ -45,7 +46,8 @@ using ControllerMotionParams = std::array<Common::ParamPackage, Settings::Native | |||
| 45 | using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>; | 46 | using TriggerParams = std::array<Common::ParamPackage, Settings::NativeTrigger::NumTriggers>; |
| 46 | using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>; | 47 | using BatteryParams = std::array<Common::ParamPackage, max_emulated_controllers>; |
| 47 | using CameraParams = Common::ParamPackage; | 48 | using CameraParams = Common::ParamPackage; |
| 48 | using OutputParams = std::array<Common::ParamPackage, output_devices>; | 49 | using NfcParams = Common::ParamPackage; |
| 50 | using OutputParams = std::array<Common::ParamPackage, output_devices_size>; | ||
| 49 | 51 | ||
| 50 | using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>; | 52 | using ButtonValues = std::array<Common::Input::ButtonStatus, Settings::NativeButton::NumButtons>; |
| 51 | using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>; | 53 | using SticksValues = std::array<Common::Input::StickStatus, Settings::NativeAnalog::NumAnalogs>; |
| @@ -55,6 +57,7 @@ using ControllerMotionValues = std::array<ControllerMotionInfo, Settings::Native | |||
| 55 | using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>; | 57 | using ColorValues = std::array<Common::Input::BodyColorStatus, max_emulated_controllers>; |
| 56 | using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>; | 58 | using BatteryValues = std::array<Common::Input::BatteryStatus, max_emulated_controllers>; |
| 57 | using CameraValues = Common::Input::CameraStatus; | 59 | using CameraValues = Common::Input::CameraStatus; |
| 60 | using NfcValues = Common::Input::NfcStatus; | ||
| 58 | using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>; | 61 | using VibrationValues = std::array<Common::Input::VibrationStatus, max_emulated_controllers>; |
| 59 | 62 | ||
| 60 | struct AnalogSticks { | 63 | struct AnalogSticks { |
| @@ -80,6 +83,11 @@ struct CameraState { | |||
| 80 | std::size_t sample{}; | 83 | std::size_t sample{}; |
| 81 | }; | 84 | }; |
| 82 | 85 | ||
| 86 | struct NfcState { | ||
| 87 | Common::Input::NfcState state{}; | ||
| 88 | std::vector<u8> data{}; | ||
| 89 | }; | ||
| 90 | |||
| 83 | struct ControllerMotion { | 91 | struct ControllerMotion { |
| 84 | Common::Vec3f accel{}; | 92 | Common::Vec3f accel{}; |
| 85 | Common::Vec3f gyro{}; | 93 | Common::Vec3f gyro{}; |
| @@ -107,6 +115,7 @@ struct ControllerStatus { | |||
| 107 | BatteryValues battery_values{}; | 115 | BatteryValues battery_values{}; |
| 108 | VibrationValues vibration_values{}; | 116 | VibrationValues vibration_values{}; |
| 109 | CameraValues camera_values{}; | 117 | CameraValues camera_values{}; |
| 118 | NfcValues nfc_values{}; | ||
| 110 | 119 | ||
| 111 | // Data for HID serices | 120 | // Data for HID serices |
| 112 | HomeButtonState home_button_state{}; | 121 | HomeButtonState home_button_state{}; |
| @@ -119,6 +128,7 @@ struct ControllerStatus { | |||
| 119 | ControllerColors colors_state{}; | 128 | ControllerColors colors_state{}; |
| 120 | BatteryLevelState battery_state{}; | 129 | BatteryLevelState battery_state{}; |
| 121 | CameraState camera_state{}; | 130 | CameraState camera_state{}; |
| 131 | NfcState nfc_state{}; | ||
| 122 | }; | 132 | }; |
| 123 | 133 | ||
| 124 | enum class ControllerTriggerType { | 134 | enum class ControllerTriggerType { |
| @@ -130,6 +140,7 @@ enum class ControllerTriggerType { | |||
| 130 | Battery, | 140 | Battery, |
| 131 | Vibration, | 141 | Vibration, |
| 132 | IrSensor, | 142 | IrSensor, |
| 143 | Nfc, | ||
| 133 | Connected, | 144 | Connected, |
| 134 | Disconnected, | 145 | Disconnected, |
| 135 | Type, | 146 | Type, |
| @@ -315,6 +326,9 @@ public: | |||
| 315 | /// Returns the latest camera status from the controller | 326 | /// Returns the latest camera status from the controller |
| 316 | const CameraState& GetCamera() const; | 327 | const CameraState& GetCamera() const; |
| 317 | 328 | ||
| 329 | /// Returns the latest ntag status from the controller | ||
| 330 | const NfcState& GetNfc() const; | ||
| 331 | |||
| 318 | /** | 332 | /** |
| 319 | * Sends a specific vibration to the output device | 333 | * Sends a specific vibration to the output device |
| 320 | * @return true if vibration had no errors | 334 | * @return true if vibration had no errors |
| @@ -341,6 +355,12 @@ public: | |||
| 341 | */ | 355 | */ |
| 342 | bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format); | 356 | bool SetCameraFormat(Core::IrSensor::ImageTransferProcessorFormat camera_format); |
| 343 | 357 | ||
| 358 | /// Returns true if the device has nfc support | ||
| 359 | bool HasNfc() const; | ||
| 360 | |||
| 361 | /// Returns true if the nfc tag was written | ||
| 362 | bool WriteNfc(const std::vector<u8>& data); | ||
| 363 | |||
| 344 | /// Returns the led pattern corresponding to this emulated controller | 364 | /// Returns the led pattern corresponding to this emulated controller |
| 345 | LedPattern GetLedPattern() const; | 365 | LedPattern GetLedPattern() const; |
| 346 | 366 | ||
| @@ -425,6 +445,12 @@ private: | |||
| 425 | void SetCamera(const Common::Input::CallbackStatus& callback); | 445 | void SetCamera(const Common::Input::CallbackStatus& callback); |
| 426 | 446 | ||
| 427 | /** | 447 | /** |
| 448 | * Updates the nfc status of the controller | ||
| 449 | * @param callback A CallbackStatus containing the nfc status | ||
| 450 | */ | ||
| 451 | void SetNfc(const Common::Input::CallbackStatus& callback); | ||
| 452 | |||
| 453 | /** | ||
| 428 | * Converts a color format from bgra to rgba | 454 | * Converts a color format from bgra to rgba |
| 429 | * @param color in bgra format | 455 | * @param color in bgra format |
| 430 | * @return NpadColor in rgba format | 456 | * @return NpadColor in rgba format |
| @@ -458,6 +484,7 @@ private: | |||
| 458 | TriggerParams trigger_params; | 484 | TriggerParams trigger_params; |
| 459 | BatteryParams battery_params; | 485 | BatteryParams battery_params; |
| 460 | CameraParams camera_params; | 486 | CameraParams camera_params; |
| 487 | NfcParams nfc_params; | ||
| 461 | OutputParams output_params; | 488 | OutputParams output_params; |
| 462 | 489 | ||
| 463 | ButtonDevices button_devices; | 490 | ButtonDevices button_devices; |
| @@ -466,6 +493,7 @@ private: | |||
| 466 | TriggerDevices trigger_devices; | 493 | TriggerDevices trigger_devices; |
| 467 | BatteryDevices battery_devices; | 494 | BatteryDevices battery_devices; |
| 468 | CameraDevices camera_devices; | 495 | CameraDevices camera_devices; |
| 496 | NfcDevices nfc_devices; | ||
| 469 | OutputDevices output_devices; | 497 | OutputDevices output_devices; |
| 470 | 498 | ||
| 471 | // TAS related variables | 499 | // TAS related variables |
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index 52fb69e9c..e7b871acc 100644 --- a/src/core/hid/input_converter.cpp +++ b/src/core/hid/input_converter.cpp | |||
| @@ -287,6 +287,20 @@ Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatu | |||
| 287 | return camera; | 287 | return camera; |
| 288 | } | 288 | } |
| 289 | 289 | ||
| 290 | Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback) { | ||
| 291 | Common::Input::NfcStatus nfc{}; | ||
| 292 | switch (callback.type) { | ||
| 293 | case Common::Input::InputType::Nfc: | ||
| 294 | nfc = callback.nfc_status; | ||
| 295 | break; | ||
| 296 | default: | ||
| 297 | LOG_ERROR(Input, "Conversion from type {} to NFC not implemented", callback.type); | ||
| 298 | break; | ||
| 299 | } | ||
| 300 | |||
| 301 | return nfc; | ||
| 302 | } | ||
| 303 | |||
| 290 | void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) { | 304 | void SanitizeAnalog(Common::Input::AnalogStatus& analog, bool clamp_value) { |
| 291 | const auto& properties = analog.properties; | 305 | const auto& properties = analog.properties; |
| 292 | float& raw_value = analog.raw_value; | 306 | float& raw_value = analog.raw_value; |
diff --git a/src/core/hid/input_converter.h b/src/core/hid/input_converter.h index 143c50cc0..b7eb6e660 100644 --- a/src/core/hid/input_converter.h +++ b/src/core/hid/input_converter.h | |||
| @@ -85,6 +85,14 @@ Common::Input::AnalogStatus TransformToAnalog(const Common::Input::CallbackStatu | |||
| 85 | Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback); | 85 | Common::Input::CameraStatus TransformToCamera(const Common::Input::CallbackStatus& callback); |
| 86 | 86 | ||
| 87 | /** | 87 | /** |
| 88 | * Converts raw input data into a valid nfc status. | ||
| 89 | * | ||
| 90 | * @param callback Supported callbacks: Nfc. | ||
| 91 | * @return A valid CameraObject object. | ||
| 92 | */ | ||
| 93 | Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& callback); | ||
| 94 | |||
| 95 | /** | ||
| 88 | * Converts raw analog data into a valid analog value | 96 | * Converts raw analog data into a valid analog value |
| 89 | * @param analog An analog object containing raw data and properties | 97 | * @param analog An analog object containing raw data and properties |
| 90 | * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f. | 98 | * @param clamp_value determines if the value needs to be clamped between -1.0f and 1.0f. |