diff --git a/src/common/input.h b/src/common/input.h
index 66fb15f0a..ea30770ae 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -86,7 +86,7 @@ enum class NfcState {
     NewAmiibo,
     WaitingForAmiibo,
     AmiiboRemoved,
-    NotAnAmiibo,
+    InvalidTagType,
     NotSupported,
     WrongDeviceState,
     WriteFailed,
@@ -218,8 +218,22 @@ struct CameraStatus {
 };
 
 struct NfcStatus {
-    NfcState state{};
-    std::vector<u8> data{};
+    NfcState state{NfcState::Unknown};
+    u8 uuid_length;
+    u8 protocol;
+    u8 tag_type;
+    std::array<u8, 10> uuid;
+};
+
+struct MifareData {
+    u8 command;
+    u8 sector;
+    std::array<u8, 0x6> key;
+    std::array<u8, 0x10> data;
+};
+
+struct MifareRequest {
+    std::array<MifareData, 0x10> data;
 };
 
 // List of buttons to be passed to Qt that can be translated
@@ -294,7 +308,7 @@ struct CallbackStatus {
     BatteryStatus battery_status{};
     VibrationStatus vibration_status{};
     CameraFormat camera_status{CameraFormat::None};
-    NfcState nfc_status{NfcState::Unknown};
+    NfcStatus nfc_status{};
     std::vector<u8> raw_data{};
 };
 
@@ -356,9 +370,30 @@ public:
         return NfcState::NotSupported;
     }
 
+    virtual NfcState StartNfcPolling() {
+        return NfcState::NotSupported;
+    }
+
+    virtual NfcState StopNfcPolling() {
+        return NfcState::NotSupported;
+    }
+
+    virtual NfcState ReadAmiiboData([[maybe_unused]] std::vector<u8>& out_data) {
+        return NfcState::NotSupported;
+    }
+
     virtual NfcState WriteNfcData([[maybe_unused]] const std::vector<u8>& data) {
         return NfcState::NotSupported;
     }
+
+    virtual NfcState ReadMifareData([[maybe_unused]] const MifareRequest& request,
+                                    [[maybe_unused]] MifareRequest& out_data) {
+        return NfcState::NotSupported;
+    }
+
+    virtual NfcState WriteMifareData([[maybe_unused]] const MifareRequest& request) {
+        return NfcState::NotSupported;
+    }
 };
 
 /// An abstract class template for a factory that can create input devices.
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 0a7777732..c937495f9 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -149,12 +149,16 @@ void EmulatedController::LoadDevices() {
 
     camera_params[0] = right_joycon;
     camera_params[0].Set("camera", true);
-    camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
-    ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
-    nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
     nfc_params[1] = right_joycon;
     nfc_params[1].Set("nfc", true);
 
+    // Only map virtual devices to the first controller
+    if (npad_id_type == NpadIdType::Player1 || npad_id_type == NpadIdType::Handheld) {
+        camera_params[1] = Common::ParamPackage{"engine:camera,camera:1"};
+        ring_params[1] = Common::ParamPackage{"engine:joycon,axis_x:100,axis_y:101"};
+        nfc_params[0] = Common::ParamPackage{"engine:virtual_amiibo,nfc:1"};
+    }
+
     output_params[LeftIndex] = left_joycon;
     output_params[RightIndex] = right_joycon;
     output_params[2] = camera_params[1];
@@ -1176,10 +1180,7 @@ void EmulatedController::SetNfc(const Common::Input::CallbackStatus& callback) {
         return;
     }
 
-    controller.nfc_state = {
-        controller.nfc_values.state,
-        controller.nfc_values.data,
-    };
+    controller.nfc_state = controller.nfc_values;
 }
 
 bool EmulatedController::SetVibration(std::size_t device_index, VibrationValue vibration) {
@@ -1308,6 +1309,73 @@ bool EmulatedController::HasNfc() const {
     return is_connected && (has_virtual_nfc && is_virtual_nfc_supported);
 }
 
+bool EmulatedController::AddNfcHandle() {
+    nfc_handles++;
+    return SetPollingMode(EmulatedDeviceIndex::RightIndex, Common::Input::PollingMode::NFC) ==
+           Common::Input::DriverResult::Success;
+}
+
+bool EmulatedController::RemoveNfcHandle() {
+    nfc_handles--;
+    if (nfc_handles <= 0) {
+        return SetPollingMode(EmulatedDeviceIndex::RightIndex,
+                              Common::Input::PollingMode::Active) ==
+               Common::Input::DriverResult::Success;
+    }
+    return true;
+}
+
+bool EmulatedController::StartNfcPolling() {
+    auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
+    auto& nfc_virtual_output_device = output_devices[3];
+
+    return nfc_output_device->StartNfcPolling() == Common::Input::NfcState::Success ||
+           nfc_virtual_output_device->StartNfcPolling() == Common::Input::NfcState::Success;
+}
+
+bool EmulatedController::StopNfcPolling() {
+    auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
+    auto& nfc_virtual_output_device = output_devices[3];
+
+    return nfc_output_device->StopNfcPolling() == Common::Input::NfcState::Success ||
+           nfc_virtual_output_device->StopNfcPolling() == Common::Input::NfcState::Success;
+}
+
+bool EmulatedController::ReadAmiiboData(std::vector<u8>& data) {
+    auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
+    auto& nfc_virtual_output_device = output_devices[3];
+
+    if (nfc_output_device->ReadAmiiboData(data) == Common::Input::NfcState::Success) {
+        return true;
+    }
+
+    return nfc_virtual_output_device->ReadAmiiboData(data) == Common::Input::NfcState::Success;
+}
+
+bool EmulatedController::ReadMifareData(const Common::Input::MifareRequest& request,
+                                        Common::Input::MifareRequest& out_data) {
+    auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
+    auto& nfc_virtual_output_device = output_devices[3];
+
+    if (nfc_output_device->ReadMifareData(request, out_data) == Common::Input::NfcState::Success) {
+        return true;
+    }
+
+    return nfc_virtual_output_device->ReadMifareData(request, out_data) ==
+           Common::Input::NfcState::Success;
+}
+
+bool EmulatedController::WriteMifareData(const Common::Input::MifareRequest& request) {
+    auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
+    auto& nfc_virtual_output_device = output_devices[3];
+
+    if (nfc_output_device->WriteMifareData(request) == Common::Input::NfcState::Success) {
+        return true;
+    }
+
+    return nfc_virtual_output_device->WriteMifareData(request) == Common::Input::NfcState::Success;
+}
+
 bool EmulatedController::WriteNfc(const std::vector<u8>& data) {
     auto& nfc_output_device = output_devices[static_cast<std::size_t>(DeviceIndex::Right)];
     auto& nfc_virtual_output_device = output_devices[3];
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index 09fe1a0ab..d511e5fac 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -97,10 +97,7 @@ struct RingSensorForce {
     f32 force;
 };
 
-struct NfcState {
-    Common::Input::NfcState state{};
-    std::vector<u8> data{};
-};
+using NfcState = Common::Input::NfcStatus;
 
 struct ControllerMotion {
     Common::Vec3f accel{};
@@ -393,9 +390,31 @@ public:
     /// Returns true if the device has nfc support
     bool HasNfc() const;
 
+    /// Sets the joycon in nfc mode and increments the handle count
+    bool AddNfcHandle();
+
+    /// Decrements the handle count if zero sets the joycon in active mode
+    bool RemoveNfcHandle();
+
+    /// Start searching for nfc tags
+    bool StartNfcPolling();
+
+    /// Stop searching for nfc tags
+    bool StopNfcPolling();
+
+    /// Returns true if the nfc tag was readable
+    bool ReadAmiiboData(std::vector<u8>& data);
+
     /// Returns true if the nfc tag was written
     bool WriteNfc(const std::vector<u8>& data);
 
+    /// Returns true if the nfc tag was readable
+    bool ReadMifareData(const Common::Input::MifareRequest& request,
+                        Common::Input::MifareRequest& out_data);
+
+    /// Returns true if the nfc tag was written
+    bool WriteMifareData(const Common::Input::MifareRequest& request);
+
     /// Returns the led pattern corresponding to this emulated controller
     LedPattern GetLedPattern() const;
 
@@ -532,6 +551,7 @@ private:
     bool system_buttons_enabled{true};
     f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
     u32 turbo_button_state{0};
+    std::size_t nfc_handles{0};
 
     // Temporary values to avoid doing changes while the controller is in configuring mode
     NpadStyleIndex tmp_npad_type{NpadStyleIndex::None};
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index 4ccb1c596..a05716fd8 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -299,11 +299,7 @@ Common::Input::NfcStatus TransformToNfc(const Common::Input::CallbackStatus& cal
     Common::Input::NfcStatus nfc{};
     switch (callback.type) {
     case Common::Input::InputType::Nfc:
-        nfc = {
-            .state = callback.nfc_status,
-            .data = callback.raw_data,
-        };
-        break;
+        return callback.nfc_status;
     default:
         LOG_ERROR(Input, "Conversion from type {} to NFC not implemented", callback.type);
         break;
diff --git a/src/core/hle/service/am/applets/applet_cabinet.cpp b/src/core/hle/service/am/applets/applet_cabinet.cpp
index 8b754e9d4..19ed184e8 100644
--- a/src/core/hle/service/am/applets/applet_cabinet.cpp
+++ b/src/core/hle/service/am/applets/applet_cabinet.cpp
@@ -141,7 +141,7 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
     applet_output.device_handle = applet_input_common.device_handle;
     applet_output.result = CabinetResult::Cancel;
     const auto reg_result = nfp_device->GetRegisterInfo(applet_output.register_info);
-    const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info, false);
+    const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info);
     nfp_device->Finalize();
 
     if (reg_result.IsSuccess()) {
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index f4b180b06..5bf289818 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -93,7 +93,8 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
     const auto nfc_status = npad_device->GetNfc();
     switch (nfc_status.state) {
     case Common::Input::NfcState::NewAmiibo:
-        LoadNfcTag(nfc_status.data);
+        LoadNfcTag(nfc_status.protocol, nfc_status.tag_type, nfc_status.uuid_length,
+                   nfc_status.uuid);
         break;
     case Common::Input::NfcState::AmiiboRemoved:
         if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
@@ -108,28 +109,46 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
     }
 }
 
-bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
+bool NfcDevice::LoadNfcTag(u8 protocol, u8 tag_type, u8 uuid_length, UniqueSerialNumber uuid) {
     if (device_state != DeviceState::SearchingForTag) {
         LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state);
         return false;
     }
 
+    if ((protocol & static_cast<u8>(allowed_protocols)) == 0) {
+        LOG_ERROR(Service_NFC, "Protocol not supported {}", protocol);
+        return false;
+    }
+
+    real_tag_info = {
+        .uuid = uuid,
+        .uuid_length = uuid_length,
+        .protocol = static_cast<NfcProtocol>(protocol),
+        .tag_type = static_cast<TagType>(tag_type),
+    };
+
+    device_state = DeviceState::TagFound;
+    deactivate_event->GetReadableEvent().Clear();
+    activate_event->Signal();
+    return true;
+}
+
+bool NfcDevice::LoadAmiiboData() {
+    std::vector<u8> data{};
+
+    if (!npad_device->ReadAmiiboData(data)) {
+        return false;
+    }
+
     if (data.size() < sizeof(NFP::EncryptedNTAG215File)) {
         LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size());
         return false;
     }
 
-    mifare_data.resize(data.size());
-    memcpy(mifare_data.data(), data.data(), data.size());
-
     memcpy(&tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
     is_plain_amiibo = NFP::AmiiboCrypto::IsAmiiboValid(tag_data);
     is_write_protected = false;
 
-    device_state = DeviceState::TagFound;
-    deactivate_event->GetReadableEvent().Clear();
-    activate_event->Signal();
-
     // Fallback for plain amiibos
     if (is_plain_amiibo) {
         LOG_INFO(Service_NFP, "Using plain amiibo");
@@ -147,6 +166,7 @@ bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
         return true;
     }
 
+    LOG_INFO(Service_NFP, "Using encrypted amiibo");
     tag_data = {};
     memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
     return true;
@@ -162,7 +182,6 @@ void NfcDevice::CloseNfcTag() {
     device_state = DeviceState::TagRemoved;
     encrypted_tag_data = {};
     tag_data = {};
-    mifare_data = {};
     activate_event->GetReadableEvent().Clear();
     deactivate_event->Signal();
 }
@@ -179,8 +198,12 @@ void NfcDevice::Initialize() {
     device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable;
     encrypted_tag_data = {};
     tag_data = {};
-    mifare_data = {};
-    is_initalized = true;
+
+    if (device_state != DeviceState::Initialized) {
+        return;
+    }
+
+    is_initalized = npad_device->AddNfcHandle();
 }
 
 void NfcDevice::Finalize() {
@@ -190,6 +213,11 @@ void NfcDevice::Finalize() {
     if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
         StopDetection();
     }
+
+    if (device_state != DeviceState::Unavailable) {
+        npad_device->RemoveNfcHandle();
+    }
+
     device_state = DeviceState::Unavailable;
     is_initalized = false;
 }
@@ -200,10 +228,8 @@ Result NfcDevice::StartDetection(NfcProtocol allowed_protocol) {
         return ResultWrongDeviceState;
     }
 
-    if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
-                                    Common::Input::PollingMode::NFC) !=
-        Common::Input::DriverResult::Success) {
-        LOG_ERROR(Service_NFC, "Nfc not supported");
+    if (!npad_device->StartNfcPolling()) {
+        LOG_ERROR(Service_NFC, "Nfc polling not supported");
         return ResultNfcDisabled;
     }
 
@@ -213,9 +239,6 @@ Result NfcDevice::StartDetection(NfcProtocol allowed_protocol) {
 }
 
 Result NfcDevice::StopDetection() {
-    npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
-                                Common::Input::PollingMode::Active);
-
     if (device_state == DeviceState::Initialized) {
         return ResultSuccess;
     }
@@ -225,6 +248,7 @@ Result NfcDevice::StopDetection() {
     }
 
     if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
+        npad_device->StopNfcPolling();
         device_state = DeviceState::Initialized;
         return ResultSuccess;
     }
@@ -233,7 +257,7 @@ Result NfcDevice::StopDetection() {
     return ResultWrongDeviceState;
 }
 
-Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const {
+Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info) const {
     if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
         LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
         if (device_state == DeviceState::TagRemoved) {
@@ -242,41 +266,15 @@ Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const {
         return ResultWrongDeviceState;
     }
 
-    UniqueSerialNumber uuid{};
-    u8 uuid_length{};
-    NfcProtocol protocol{NfcProtocol::TypeA};
-    TagType tag_type{TagType::Type2};
+    tag_info = real_tag_info;
 
-    if (is_mifare) {
-        tag_type = TagType::Mifare;
-        uuid_length = sizeof(NFP::NtagTagUuid);
-        memcpy(uuid.data(), mifare_data.data(), uuid_length);
-    } else {
-        tag_type = TagType::Type2;
-        uuid_length = sizeof(NFP::NtagTagUuid);
-        NFP::NtagTagUuid nUuid{
-            .part1 = encrypted_tag_data.uuid.part1,
-            .part2 = encrypted_tag_data.uuid.part2,
-            .nintendo_id = encrypted_tag_data.uuid.nintendo_id,
-        };
-        memcpy(uuid.data(), &nUuid, uuid_length);
-
-        // Generate random UUID to bypass amiibo load limits
-        if (Settings::values.random_amiibo_id) {
-            Common::TinyMT rng{};
-            rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
-            rng.GenerateRandomBytes(uuid.data(), uuid_length);
-        }
+    // Generate random UUID to bypass amiibo load limits
+    if (real_tag_info.tag_type == TagType::Type2 && Settings::values.random_amiibo_id) {
+        Common::TinyMT rng{};
+        rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
+        rng.GenerateRandomBytes(tag_info.uuid.data(), tag_info.uuid_length);
     }
 
-    // Protocol and tag type may change here
-    tag_info = {
-        .uuid = uuid,
-        .uuid_length = uuid_length,
-        .protocol = protocol,
-        .tag_type = tag_type,
-    };
-
     return ResultSuccess;
 }
 
@@ -293,7 +291,7 @@ Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameter
     Result result = ResultSuccess;
 
     TagInfo tag_info{};
-    result = GetTagInfo(tag_info, true);
+    result = GetTagInfo(tag_info);
 
     if (result.IsError()) {
         return result;
@@ -307,6 +305,8 @@ Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameter
         return ResultInvalidArgument;
     }
 
+    Common::Input::MifareRequest request{};
+    Common::Input::MifareRequest out_data{};
     const auto unknown = parameters[0].sector_key.unknown;
     for (std::size_t i = 0; i < parameters.size(); i++) {
         if (unknown != parameters[i].sector_key.unknown) {
@@ -315,25 +315,29 @@ Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameter
     }
 
     for (std::size_t i = 0; i < parameters.size(); i++) {
-        result = ReadMifare(parameters[i], read_block_data[i]);
-        if (result.IsError()) {
-            break;
+        if (parameters[i].sector_key.command == MifareCmd::None) {
+            continue;
         }
+        request.data[i].command = static_cast<u8>(parameters[i].sector_key.command);
+        request.data[i].sector = parameters[i].sector_number;
+        memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(),
+               sizeof(KeyData));
     }
 
-    return result;
-}
-
-Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter,
-                             MifareReadBlockData& read_block_data) const {
-    const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock);
-    read_block_data.sector_number = parameter.sector_number;
-    if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
+    if (!npad_device->ReadMifareData(request, out_data)) {
         return ResultMifareError288;
     }
 
-    // TODO: Use parameter.sector_key to read encrypted data
-    memcpy(read_block_data.data.data(), mifare_data.data() + sector_index, sizeof(DataBlock));
+    for (std::size_t i = 0; i < read_block_data.size(); i++) {
+        if (static_cast<MifareCmd>(out_data.data[i].command) == MifareCmd::None) {
+            continue;
+        }
+
+        read_block_data[i] = {
+            .data = out_data.data[i].data,
+            .sector_number = out_data.data[i].sector,
+        };
+    }
 
     return ResultSuccess;
 }
@@ -342,7 +346,7 @@ Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> paramet
     Result result = ResultSuccess;
 
     TagInfo tag_info{};
-    result = GetTagInfo(tag_info, true);
+    result = GetTagInfo(tag_info);
 
     if (result.IsError()) {
         return result;
@@ -363,42 +367,25 @@ Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> paramet
         }
     }
 
+    Common::Input::MifareRequest request{};
     for (std::size_t i = 0; i < parameters.size(); i++) {
-        result = WriteMifare(parameters[i]);
-        if (result.IsError()) {
-            break;
+        if (parameters[i].sector_key.command == MifareCmd::None) {
+            continue;
         }
+        request.data[i].command = static_cast<u8>(parameters[i].sector_key.command);
+        request.data[i].sector = parameters[i].sector_number;
+        memcpy(request.data[i].key.data(), parameters[i].sector_key.sector_key.data(),
+               sizeof(KeyData));
+        memcpy(request.data[i].data.data(), parameters[i].data.data(), sizeof(KeyData));
     }
 
-    if (!npad_device->WriteNfc(mifare_data)) {
-        LOG_ERROR(Service_NFP, "Error writing to file");
+    if (!npad_device->WriteMifareData(request)) {
         return ResultMifareError288;
     }
 
     return result;
 }
 
-Result NfcDevice::WriteMifare(const MifareWriteBlockParameter& parameter) {
-    const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock);
-
-    if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
-        LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
-        if (device_state == DeviceState::TagRemoved) {
-            return ResultTagRemoved;
-        }
-        return ResultWrongDeviceState;
-    }
-
-    if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
-        return ResultMifareError288;
-    }
-
-    // TODO: Use parameter.sector_key to encrypt the data
-    memcpy(mifare_data.data() + sector_index, parameter.data.data(), sizeof(DataBlock));
-
-    return ResultSuccess;
-}
-
 Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout,
                                            std::span<const u8> command_data,
                                            std::span<u8> out_data) {
@@ -412,6 +399,11 @@ Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target
         return ResultWrongDeviceState;
     }
 
+    if (!LoadAmiiboData()) {
+        LOG_ERROR(Service_NFP, "Not an amiibo");
+        return ResultInvalidTagType;
+    }
+
     if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
         LOG_ERROR(Service_NFP, "Not an amiibo");
         return ResultInvalidTagType;
@@ -562,7 +554,7 @@ Result NfcDevice::Restore() {
 
     NFC::TagInfo tag_info{};
     std::array<u8, sizeof(NFP::EncryptedNTAG215File)> data{};
-    Result result = GetTagInfo(tag_info, false);
+    Result result = GetTagInfo(tag_info);
 
     if (result.IsError()) {
         return result;
@@ -635,7 +627,7 @@ Result NfcDevice::GetCommonInfo(NFP::CommonInfo& common_info) const {
     // TODO: Validate this data
     common_info = {
         .last_write_date = settings.write_date.GetWriteDate(),
-        .write_counter = tag_data.write_counter,
+        .write_counter = tag_data.application_write_counter,
         .version = tag_data.amiibo_version,
         .application_area_size = sizeof(NFP::ApplicationArea),
     };
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h
index 7560210d6..0ed1ff34c 100644
--- a/src/core/hle/service/nfc/common/device.h
+++ b/src/core/hle/service/nfc/common/device.h
@@ -42,15 +42,12 @@ public:
     Result StartDetection(NfcProtocol allowed_protocol);
     Result StopDetection();
 
-    Result GetTagInfo(TagInfo& tag_info, bool is_mifare) const;
+    Result GetTagInfo(TagInfo& tag_info) const;
 
     Result ReadMifare(std::span<const MifareReadBlockParameter> parameters,
                       std::span<MifareReadBlockData> read_block_data) const;
-    Result ReadMifare(const MifareReadBlockParameter& parameter,
-                      MifareReadBlockData& read_block_data) const;
 
     Result WriteMifare(std::span<const MifareWriteBlockParameter> parameters);
-    Result WriteMifare(const MifareWriteBlockParameter& parameter);
 
     Result SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout,
                                     std::span<const u8> command_data, std::span<u8> out_data);
@@ -105,7 +102,8 @@ public:
 
 private:
     void NpadUpdate(Core::HID::ControllerTriggerType type);
-    bool LoadNfcTag(std::span<const u8> data);
+    bool LoadNfcTag(u8 protocol, u8 tag_type, u8 uuid_length, UniqueSerialNumber uuid);
+    bool LoadAmiiboData();
     void CloseNfcTag();
 
     NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const;
@@ -140,8 +138,8 @@ private:
     bool is_write_protected{};
     NFP::MountTarget mount_target{NFP::MountTarget::None};
 
+    TagInfo real_tag_info{};
     NFP::NTAG215File tag_data{};
-    std::vector<u8> mifare_data{};
     NFP::EncryptedNTAG215File encrypted_tag_data{};
 };
 
diff --git a/src/core/hle/service/nfc/common/device_manager.cpp b/src/core/hle/service/nfc/common/device_manager.cpp
index b0456508e..562f3a28e 100644
--- a/src/core/hle/service/nfc/common/device_manager.cpp
+++ b/src/core/hle/service/nfc/common/device_manager.cpp
@@ -29,6 +29,9 @@ DeviceManager::DeviceManager(Core::System& system_, KernelHelpers::ServiceContex
 }
 
 DeviceManager ::~DeviceManager() {
+    if (is_initialized) {
+        Finalize();
+    }
     service_context.CloseEvent(availability_change_event);
 }
 
@@ -125,14 +128,14 @@ Result DeviceManager::StopDetection(u64 device_handle) {
     return result;
 }
 
-Result DeviceManager::GetTagInfo(u64 device_handle, TagInfo& tag_info, bool is_mifare) const {
+Result DeviceManager::GetTagInfo(u64 device_handle, TagInfo& tag_info) const {
     std::scoped_lock lock{mutex};
 
     std::shared_ptr<NfcDevice> device = nullptr;
     auto result = GetDeviceHandle(device_handle, device);
 
     if (result.IsSuccess()) {
-        result = device->GetTagInfo(tag_info, is_mifare);
+        result = device->GetTagInfo(tag_info);
         result = VerifyDeviceResult(device, result);
     }
 
@@ -546,7 +549,7 @@ Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) cons
     NFC::TagInfo tag_info{};
 
     if (result.IsSuccess()) {
-        result = device->GetTagInfo(tag_info, false);
+        result = device->GetTagInfo(tag_info);
     }
 
     if (result.IsSuccess()) {
@@ -565,7 +568,7 @@ Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> dat
     NFC::TagInfo tag_info{};
 
     if (result.IsSuccess()) {
-        result = device->GetTagInfo(tag_info, false);
+        result = device->GetTagInfo(tag_info);
     }
 
     if (result.IsSuccess()) {
diff --git a/src/core/hle/service/nfc/common/device_manager.h b/src/core/hle/service/nfc/common/device_manager.h
index 2971e280f..c61ba0cf3 100644
--- a/src/core/hle/service/nfc/common/device_manager.h
+++ b/src/core/hle/service/nfc/common/device_manager.h
@@ -33,7 +33,7 @@ public:
     Kernel::KReadableEvent& AttachAvailabilityChangeEvent() const;
     Result StartDetection(u64 device_handle, NfcProtocol tag_protocol);
     Result StopDetection(u64 device_handle);
-    Result GetTagInfo(u64 device_handle, NFP::TagInfo& tag_info, bool is_mifare) const;
+    Result GetTagInfo(u64 device_handle, NFP::TagInfo& tag_info) const;
     Kernel::KReadableEvent& AttachActivateEvent(u64 device_handle) const;
     Kernel::KReadableEvent& AttachDeactivateEvent(u64 device_handle) const;
     Result ReadMifare(u64 device_handle,
diff --git a/src/core/hle/service/nfc/mifare_types.h b/src/core/hle/service/nfc/mifare_types.h
index 75b59f021..467937399 100644
--- a/src/core/hle/service/nfc/mifare_types.h
+++ b/src/core/hle/service/nfc/mifare_types.h
@@ -11,9 +11,10 @@
 namespace Service::NFC {
 
 enum class MifareCmd : u8 {
+    None = 0x00,
+    Read = 0x30,
     AuthA = 0x60,
     AuthB = 0x61,
-    Read = 0x30,
     Write = 0xA0,
     Transfer = 0xB0,
     Decrement = 0xC0,
@@ -35,17 +36,17 @@ static_assert(sizeof(SectorKey) == 0x10, "SectorKey is an invalid size");
 
 // This is nn::nfc::MifareReadBlockParameter
 struct MifareReadBlockParameter {
-    u8 sector_number;
+    u8 sector_number{};
     INSERT_PADDING_BYTES(0x7);
-    SectorKey sector_key;
+    SectorKey sector_key{};
 };
 static_assert(sizeof(MifareReadBlockParameter) == 0x18,
               "MifareReadBlockParameter is an invalid size");
 
 // This is nn::nfc::MifareReadBlockData
 struct MifareReadBlockData {
-    DataBlock data;
-    u8 sector_number;
+    DataBlock data{};
+    u8 sector_number{};
     INSERT_PADDING_BYTES(0x7);
 };
 static_assert(sizeof(MifareReadBlockData) == 0x18, "MifareReadBlockData is an invalid size");
diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp
index 130fb7f78..e7ca7582e 100644
--- a/src/core/hle/service/nfc/nfc_interface.cpp
+++ b/src/core/hle/service/nfc/nfc_interface.cpp
@@ -174,8 +174,7 @@ void NfcInterface::GetTagInfo(HLERequestContext& ctx) {
     LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
 
     TagInfo tag_info{};
-    auto result =
-        GetManager()->GetTagInfo(device_handle, tag_info, backend_type == BackendType::Mifare);
+    auto result = GetManager()->GetTagInfo(device_handle, tag_info);
     result = TranslateResultToServiceError(result);
 
     if (result.IsSuccess()) {
@@ -216,8 +215,8 @@ void NfcInterface::ReadMifare(HLERequestContext& ctx) {
     memcpy(read_commands.data(), buffer.data(),
            number_of_commands * sizeof(MifareReadBlockParameter));
 
-    LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, read_commands_size={}",
-             device_handle, number_of_commands);
+    LOG_INFO(Service_NFC, "called, device_handle={}, read_commands_size={}", device_handle,
+             number_of_commands);
 
     std::vector<MifareReadBlockData> out_data(number_of_commands);
     auto result = GetManager()->ReadMifare(device_handle, read_commands, out_data);
diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp
index b2b5677c8..52494e0d9 100644
--- a/src/input_common/drivers/joycon.cpp
+++ b/src/input_common/drivers/joycon.cpp
@@ -195,8 +195,8 @@ void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
                 OnMotionUpdate(port, type, id, value);
             }},
             .on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }},
-            .on_amiibo_data = {[this, port, type](const std::vector<u8>& amiibo_data) {
-                OnAmiiboUpdate(port, type, amiibo_data);
+            .on_amiibo_data = {[this, port, type](const Joycon::TagInfo& tag_info) {
+                OnAmiiboUpdate(port, type, tag_info);
             }},
             .on_camera_data = {[this, port](const std::vector<u8>& camera_data,
                                             Joycon::IrsResolution format) {
@@ -291,13 +291,105 @@ Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) c
     return Common::Input::NfcState::Success;
 };
 
+Common::Input::NfcState Joycons::StartNfcPolling(const PadIdentifier& identifier) {
+    auto handle = GetHandle(identifier);
+    if (handle == nullptr) {
+        return Common::Input::NfcState::Unknown;
+    }
+    return TranslateDriverResult(handle->StartNfcPolling());
+};
+
+Common::Input::NfcState Joycons::StopNfcPolling(const PadIdentifier& identifier) {
+    auto handle = GetHandle(identifier);
+    if (handle == nullptr) {
+        return Common::Input::NfcState::Unknown;
+    }
+    return TranslateDriverResult(handle->StopNfcPolling());
+};
+
+Common::Input::NfcState Joycons::ReadAmiiboData(const PadIdentifier& identifier,
+                                                std::vector<u8>& out_data) {
+    auto handle = GetHandle(identifier);
+    if (handle == nullptr) {
+        return Common::Input::NfcState::Unknown;
+    }
+    return TranslateDriverResult(handle->ReadAmiiboData(out_data));
+}
+
 Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier,
                                               const std::vector<u8>& data) {
     auto handle = GetHandle(identifier);
-    if (handle->WriteNfcData(data) != Joycon::DriverResult::Success) {
-        return Common::Input::NfcState::WriteFailed;
+    if (handle == nullptr) {
+        return Common::Input::NfcState::Unknown;
     }
-    return Common::Input::NfcState::Success;
+    return TranslateDriverResult(handle->WriteNfcData(data));
+};
+
+Common::Input::NfcState Joycons::ReadMifareData(const PadIdentifier& identifier,
+                                                const Common::Input::MifareRequest& request,
+                                                Common::Input::MifareRequest& data) {
+    auto handle = GetHandle(identifier);
+    if (handle == nullptr) {
+        return Common::Input::NfcState::Unknown;
+    }
+
+    const auto command = static_cast<Joycon::MifareCmd>(request.data[0].command);
+    std::vector<Joycon::MifareReadChunk> read_request{};
+    for (const auto& request_data : request.data) {
+        if (request_data.command == 0) {
+            continue;
+        }
+        Joycon::MifareReadChunk chunk = {
+            .command = command,
+            .sector_key = {},
+            .sector = request_data.sector,
+        };
+        memcpy(chunk.sector_key.data(), request_data.key.data(),
+               sizeof(Joycon::MifareReadChunk::sector_key));
+        read_request.emplace_back(chunk);
+    }
+
+    std::vector<Joycon::MifareReadData> read_data(read_request.size());
+    const auto result = handle->ReadMifareData(read_request, read_data);
+    if (result == Joycon::DriverResult::Success) {
+        for (std::size_t i = 0; i < read_request.size(); i++) {
+            data.data[i] = {
+                .command = static_cast<u8>(command),
+                .sector = read_data[i].sector,
+                .key = {},
+                .data = read_data[i].data,
+            };
+        }
+    }
+    return TranslateDriverResult(result);
+};
+
+Common::Input::NfcState Joycons::WriteMifareData(const PadIdentifier& identifier,
+                                                 const Common::Input::MifareRequest& request) {
+    auto handle = GetHandle(identifier);
+    if (handle == nullptr) {
+        return Common::Input::NfcState::Unknown;
+    }
+
+    const auto command = static_cast<Joycon::MifareCmd>(request.data[0].command);
+    std::vector<Joycon::MifareWriteChunk> write_request{};
+    for (const auto& request_data : request.data) {
+        if (request_data.command == 0) {
+            continue;
+        }
+        Joycon::MifareWriteChunk chunk = {
+            .command = command,
+            .sector_key = {},
+            .sector = request_data.sector,
+            .data = {},
+        };
+        memcpy(chunk.sector_key.data(), request_data.key.data(),
+               sizeof(Joycon::MifareReadChunk::sector_key));
+        memcpy(chunk.data.data(), request_data.data.data(), sizeof(Joycon::MifareWriteChunk::data));
+        write_request.emplace_back(chunk);
+    }
+
+    return TranslateDriverResult(handle->WriteMifareData(write_request));
 };
 
 Common::Input::DriverResult Joycons::SetPollingMode(const PadIdentifier& identifier,
@@ -403,11 +495,20 @@ void Joycons::OnRingConUpdate(f32 ring_data) {
 }
 
 void Joycons::OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type,
-                             const std::vector<u8>& amiibo_data) {
+                             const Joycon::TagInfo& tag_info) {
     const auto identifier = GetIdentifier(port, type);
-    const auto nfc_state = amiibo_data.empty() ? Common::Input::NfcState::AmiiboRemoved
-                                               : Common::Input::NfcState::NewAmiibo;
-    SetNfc(identifier, {nfc_state, amiibo_data});
+    const auto nfc_state = tag_info.uuid_length == 0 ? Common::Input::NfcState::AmiiboRemoved
+                                                     : Common::Input::NfcState::NewAmiibo;
+
+    const Common::Input::NfcStatus nfc_status{
+        .state = nfc_state,
+        .uuid_length = tag_info.uuid_length,
+        .protocol = tag_info.protocol,
+        .tag_type = tag_info.tag_type,
+        .uuid = tag_info.uuid,
+    };
+
+    SetNfc(identifier, nfc_status);
 }
 
 void Joycons::OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
@@ -726,4 +827,18 @@ std::string Joycons::JoyconName(Joycon::ControllerType type) const {
         return "Unknown Switch Controller";
     }
 }
+
+Common::Input::NfcState Joycons::TranslateDriverResult(Joycon::DriverResult result) const {
+    switch (result) {
+    case Joycon::DriverResult::Success:
+        return Common::Input::NfcState::Success;
+    case Joycon::DriverResult::Disabled:
+        return Common::Input::NfcState::WrongDeviceState;
+    case Joycon::DriverResult::NotSupported:
+        return Common::Input::NfcState::NotSupported;
+    default:
+        return Common::Input::NfcState::Unknown;
+    }
+}
+
 } // namespace InputCommon
diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h
index e3f0ad78f..4c323d7d6 100644
--- a/src/input_common/drivers/joycon.h
+++ b/src/input_common/drivers/joycon.h
@@ -15,6 +15,7 @@ using SerialNumber = std::array<u8, 15>;
 struct Battery;
 struct Color;
 struct MotionData;
+struct TagInfo;
 enum class ControllerType : u8;
 enum class DriverResult;
 enum class IrsResolution;
@@ -39,9 +40,18 @@ public:
     Common::Input::DriverResult SetCameraFormat(const PadIdentifier& identifier,
                                                 Common::Input::CameraFormat camera_format) override;
 
-    Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
-    Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
+    Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier) const override;
+    Common::Input::NfcState StartNfcPolling(const PadIdentifier& identifier) override;
+    Common::Input::NfcState StopNfcPolling(const PadIdentifier& identifier) override;
+    Common::Input::NfcState ReadAmiiboData(const PadIdentifier& identifier,
+                                           std::vector<u8>& out_data) override;
+    Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier,
                                          const std::vector<u8>& data) override;
+    Common::Input::NfcState ReadMifareData(const PadIdentifier& identifier,
+                                           const Common::Input::MifareRequest& request,
+                                           Common::Input::MifareRequest& out_data) override;
+    Common::Input::NfcState WriteMifareData(const PadIdentifier& identifier,
+                                            const Common::Input::MifareRequest& request) override;
 
     Common::Input::DriverResult SetPollingMode(
         const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override;
@@ -82,7 +92,7 @@ private:
                         const Joycon::MotionData& value);
     void OnRingConUpdate(f32 ring_data);
     void OnAmiiboUpdate(std::size_t port, Joycon::ControllerType type,
-                        const std::vector<u8>& amiibo_data);
+                        const Joycon::TagInfo& amiibo_data);
     void OnCameraUpdate(std::size_t port, const std::vector<u8>& camera_data,
                         Joycon::IrsResolution format);
 
@@ -102,6 +112,8 @@ private:
     /// Returns the name of the device in text format
     std::string JoyconName(Joycon::ControllerType type) const;
 
+    Common::Input::NfcState TranslateDriverResult(Joycon::DriverResult result) const;
+
     std::jthread scan_thread;
 
     // Joycon types are split by type to ease supporting dualjoycon configurations
diff --git a/src/input_common/drivers/virtual_amiibo.cpp b/src/input_common/drivers/virtual_amiibo.cpp
index 6435b8af8..180eb53ef 100644
--- a/src/input_common/drivers/virtual_amiibo.cpp
+++ b/src/input_common/drivers/virtual_amiibo.cpp
@@ -29,14 +29,13 @@ Common::Input::DriverResult VirtualAmiibo::SetPollingMode(
 
     switch (polling_mode) {
     case Common::Input::PollingMode::NFC:
-        if (state == State::Initialized) {
-            state = State::WaitingForAmiibo;
-        }
+        state = State::Initialized;
         return Common::Input::DriverResult::Success;
     default:
-        if (state == State::AmiiboIsOpen) {
+        if (state == State::TagNearby) {
             CloseAmiibo();
         }
+        state = State::Disabled;
         return Common::Input::DriverResult::NotSupported;
     }
 }
@@ -45,6 +44,39 @@ Common::Input::NfcState VirtualAmiibo::SupportsNfc(
     [[maybe_unused]] const PadIdentifier& identifier_) const {
     return Common::Input::NfcState::Success;
 }
+Common::Input::NfcState VirtualAmiibo::StartNfcPolling(const PadIdentifier& identifier_) {
+    if (state != State::Initialized) {
+        return Common::Input::NfcState::WrongDeviceState;
+    }
+    state = State::WaitingForAmiibo;
+    return Common::Input::NfcState::Success;
+}
+
+Common::Input::NfcState VirtualAmiibo::StopNfcPolling(const PadIdentifier& identifier_) {
+    if (state == State::Disabled) {
+        return Common::Input::NfcState::WrongDeviceState;
+    }
+    if (state == State::TagNearby) {
+        CloseAmiibo();
+    }
+    state = State::Initialized;
+    return Common::Input::NfcState::Success;
+}
+
+Common::Input::NfcState VirtualAmiibo::ReadAmiiboData(const PadIdentifier& identifier_,
+                                                      std::vector<u8>& out_data) {
+    if (state != State::TagNearby) {
+        return Common::Input::NfcState::WrongDeviceState;
+    }
+
+    if (status.tag_type != 1U << 1) {
+        return Common::Input::NfcState::InvalidTagType;
+    }
+
+    out_data.resize(nfc_data.size());
+    memcpy(out_data.data(), nfc_data.data(), nfc_data.size());
+    return Common::Input::NfcState::Success;
+}
 
 Common::Input::NfcState VirtualAmiibo::WriteNfcData(
     [[maybe_unused]] const PadIdentifier& identifier_, const std::vector<u8>& data) {
@@ -66,6 +98,69 @@ Common::Input::NfcState VirtualAmiibo::WriteNfcData(
     return Common::Input::NfcState::Success;
 }
 
+Common::Input::NfcState VirtualAmiibo::ReadMifareData(const PadIdentifier& identifier_,
+                                                      const Common::Input::MifareRequest& request,
+                                                      Common::Input::MifareRequest& out_data) {
+    if (state != State::TagNearby) {
+        return Common::Input::NfcState::WrongDeviceState;
+    }
+
+    if (status.tag_type != 1U << 6) {
+        return Common::Input::NfcState::InvalidTagType;
+    }
+
+    for (std::size_t i = 0; i < request.data.size(); i++) {
+        if (request.data[i].command == 0) {
+            continue;
+        }
+        out_data.data[i].command = request.data[i].command;
+        out_data.data[i].sector = request.data[i].sector;
+
+        const std::size_t sector_index =
+            request.data[i].sector * sizeof(Common::Input::MifareData::data);
+
+        if (nfc_data.size() < sector_index + sizeof(Common::Input::MifareData::data)) {
+            return Common::Input::NfcState::WriteFailed;
+        }
+
+        // Ignore the sector key as we don't support it
+        memcpy(out_data.data[i].data.data(), nfc_data.data() + sector_index,
+               sizeof(Common::Input::MifareData::data));
+    }
+
+    return Common::Input::NfcState::Success;
+}
+
+Common::Input::NfcState VirtualAmiibo::WriteMifareData(
+    const PadIdentifier& identifier_, const Common::Input::MifareRequest& request) {
+    if (state != State::TagNearby) {
+        return Common::Input::NfcState::WrongDeviceState;
+    }
+
+    if (status.tag_type != 1U << 6) {
+        return Common::Input::NfcState::InvalidTagType;
+    }
+
+    for (std::size_t i = 0; i < request.data.size(); i++) {
+        if (request.data[i].command == 0) {
+            continue;
+        }
+
+        const std::size_t sector_index =
+            request.data[i].sector * sizeof(Common::Input::MifareData::data);
+
+        if (nfc_data.size() < sector_index + sizeof(Common::Input::MifareData::data)) {
+            return Common::Input::NfcState::WriteFailed;
+        }
+
+        // Ignore the sector key as we don't support it
+        memcpy(nfc_data.data() + sector_index, request.data[i].data.data(),
+               sizeof(Common::Input::MifareData::data));
+    }
+
+    return Common::Input::NfcState::Success;
+}
+
 VirtualAmiibo::State VirtualAmiibo::GetCurrentState() const {
     return state;
 }
@@ -112,23 +207,31 @@ VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(std::span<u8> data) {
     case AmiiboSizeWithoutPassword:
     case AmiiboSizeWithSignature:
         nfc_data.resize(AmiiboSize);
+        status.tag_type = 1U << 1;
+        status.uuid_length = 7;
         break;
     case MifareSize:
         nfc_data.resize(MifareSize);
+        status.tag_type = 1U << 6;
+        status.uuid_length = 4;
         break;
     default:
         return Info::NotAnAmiibo;
     }
 
-    state = State::AmiiboIsOpen;
+    status.uuid = {};
+    status.protocol = 1;
+    state = State::TagNearby;
+    status.state = Common::Input::NfcState::NewAmiibo,
     memcpy(nfc_data.data(), data.data(), data.size_bytes());
-    SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, nfc_data});
+    memcpy(status.uuid.data(), nfc_data.data(), status.uuid_length);
+    SetNfc(identifier, status);
     return Info::Success;
 }
 
 VirtualAmiibo::Info VirtualAmiibo::ReloadAmiibo() {
-    if (state == State::AmiiboIsOpen) {
-        SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, nfc_data});
+    if (state == State::TagNearby) {
+        SetNfc(identifier, status);
         return Info::Success;
     }
 
@@ -136,9 +239,14 @@ VirtualAmiibo::Info VirtualAmiibo::ReloadAmiibo() {
 }
 
 VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() {
-    state = polling_mode == Common::Input::PollingMode::NFC ? State::WaitingForAmiibo
-                                                            : State::Initialized;
-    SetNfc(identifier, {Common::Input::NfcState::AmiiboRemoved, {}});
+    if (state != State::TagNearby) {
+        return Info::Success;
+    }
+
+    state = State::WaitingForAmiibo;
+    status.state = Common::Input::NfcState::AmiiboRemoved;
+    SetNfc(identifier, status);
+    status.tag_type = 0;
     return Info::Success;
 }
 
diff --git a/src/input_common/drivers/virtual_amiibo.h b/src/input_common/drivers/virtual_amiibo.h
index 09ca09e68..490f38e05 100644
--- a/src/input_common/drivers/virtual_amiibo.h
+++ b/src/input_common/drivers/virtual_amiibo.h
@@ -20,9 +20,10 @@ namespace InputCommon {
 class VirtualAmiibo final : public InputEngine {
 public:
     enum class State {
+        Disabled,
         Initialized,
         WaitingForAmiibo,
-        AmiiboIsOpen,
+        TagNearby,
     };
 
     enum class Info {
@@ -41,9 +42,17 @@ public:
         const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;
 
     Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
-
+    Common::Input::NfcState StartNfcPolling(const PadIdentifier& identifier_) override;
+    Common::Input::NfcState StopNfcPolling(const PadIdentifier& identifier_) override;
+    Common::Input::NfcState ReadAmiiboData(const PadIdentifier& identifier_,
+                                           std::vector<u8>& out_data) override;
     Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
                                          const std::vector<u8>& data) override;
+    Common::Input::NfcState ReadMifareData(const PadIdentifier& identifier_,
+                                           const Common::Input::MifareRequest& data,
+                                           Common::Input::MifareRequest& out_data) override;
+    Common::Input::NfcState WriteMifareData(const PadIdentifier& identifier_,
+                                            const Common::Input::MifareRequest& data) override;
 
     State GetCurrentState() const;
 
@@ -61,8 +70,9 @@ private:
     static constexpr std::size_t MifareSize = 0x400;
 
     std::string file_path{};
-    State state{State::Initialized};
+    State state{State::Disabled};
     std::vector<u8> nfc_data;
+    Common::Input::NfcStatus status;
     Common::Input::PollingMode polling_mode{Common::Input::PollingMode::Passive};
 };
 } // namespace InputCommon
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
index 95106f16d..2c8c66951 100644
--- a/src/input_common/helpers/joycon_driver.cpp
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -2,6 +2,7 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
 
 #include "common/logging/log.h"
+#include "common/scope_exit.h"
 #include "common/swap.h"
 #include "common/thread.h"
 #include "input_common/helpers/joycon_driver.h"
@@ -112,7 +113,7 @@ DriverResult JoyconDriver::InitializeDevice() {
     joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration,
                                                    right_stick_calibration, motion_calibration);
 
-    // Start pooling for data
+    // Start polling for data
     is_connected = true;
     if (!input_thread_running) {
         input_thread =
@@ -208,7 +209,7 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
         joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat());
     }
 
-    if (nfc_protocol->IsEnabled()) {
+    if (nfc_protocol->IsPolling()) {
         if (amiibo_detected) {
             if (!nfc_protocol->HasAmiibo()) {
                 joycon_poller->UpdateAmiibo({});
@@ -218,10 +219,10 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
         }
 
         if (!amiibo_detected) {
-            std::vector<u8> data(0x21C);
-            const auto result = nfc_protocol->ScanAmiibo(data);
+            Joycon::TagInfo tag_info;
+            const auto result = nfc_protocol->GetTagInfo(tag_info);
             if (result == DriverResult::Success) {
-                joycon_poller->UpdateAmiibo(data);
+                joycon_poller->UpdateAmiibo(tag_info);
                 amiibo_detected = true;
             }
         }
@@ -247,6 +248,7 @@ void JoyconDriver::OnNewData(std::span<u8> buffer) {
 }
 
 DriverResult JoyconDriver::SetPollingMode() {
+    SCOPE_EXIT({ disable_input_thread = false; });
     disable_input_thread = true;
 
     rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration);
@@ -276,7 +278,6 @@ DriverResult JoyconDriver::SetPollingMode() {
     if (irs_enabled && supported_features.irs) {
         auto result = irs_protocol->EnableIrs();
         if (result == DriverResult::Success) {
-            disable_input_thread = false;
             return result;
         }
         irs_protocol->DisableIrs();
@@ -286,10 +287,6 @@ DriverResult JoyconDriver::SetPollingMode() {
     if (nfc_enabled && supported_features.nfc) {
         auto result = nfc_protocol->EnableNfc();
         if (result == DriverResult::Success) {
-            result = nfc_protocol->StartNFCPollingMode();
-        }
-        if (result == DriverResult::Success) {
-            disable_input_thread = false;
             return result;
         }
         nfc_protocol->DisableNfc();
@@ -303,7 +300,6 @@ DriverResult JoyconDriver::SetPollingMode() {
         }
         if (result == DriverResult::Success) {
             ring_connected = true;
-            disable_input_thread = false;
             return result;
         }
         ring_connected = false;
@@ -314,7 +310,6 @@ DriverResult JoyconDriver::SetPollingMode() {
     if (passive_enabled && supported_features.passive) {
         const auto result = generic_protocol->EnablePassiveMode();
         if (result == DriverResult::Success) {
-            disable_input_thread = false;
             return result;
         }
         LOG_ERROR(Input, "Error enabling passive mode");
@@ -328,7 +323,6 @@ DriverResult JoyconDriver::SetPollingMode() {
     // Switch calls this function after enabling active mode
     generic_protocol->TriggersElapsed();
 
-    disable_input_thread = false;
     return result;
 }
 
@@ -492,9 +486,42 @@ DriverResult JoyconDriver::SetRingConMode() {
     return result;
 }
 
-DriverResult JoyconDriver::WriteNfcData(std::span<const u8> data) {
+DriverResult JoyconDriver::StartNfcPolling() {
     std::scoped_lock lock{mutex};
+
+    if (!supported_features.nfc) {
+        return DriverResult::NotSupported;
+    }
+    if (!nfc_protocol->IsEnabled()) {
+        return DriverResult::Disabled;
+    }
+
     disable_input_thread = true;
+    const auto result = nfc_protocol->StartNFCPollingMode();
+    disable_input_thread = false;
+
+    return result;
+}
+
+DriverResult JoyconDriver::StopNfcPolling() {
+    std::scoped_lock lock{mutex};
+
+    if (!supported_features.nfc) {
+        return DriverResult::NotSupported;
+    }
+    if (!nfc_protocol->IsEnabled()) {
+        return DriverResult::Disabled;
+    }
+
+    disable_input_thread = true;
+    const auto result = nfc_protocol->StopNFCPollingMode();
+    disable_input_thread = false;
+
+    return result;
+}
+
+DriverResult JoyconDriver::ReadAmiiboData(std::vector<u8>& out_data) {
+    std::scoped_lock lock{mutex};
 
     if (!supported_features.nfc) {
         return DriverResult::NotSupported;
@@ -506,9 +533,72 @@ DriverResult JoyconDriver::WriteNfcData(std::span<const u8> data) {
         return DriverResult::ErrorWritingData;
     }
 
-    const auto result = nfc_protocol->WriteAmiibo(data);
-
+    out_data.resize(0x21C);
+    disable_input_thread = true;
+    const auto result = nfc_protocol->ReadAmiibo(out_data);
     disable_input_thread = false;
+
+    return result;
+}
+
+DriverResult JoyconDriver::WriteNfcData(std::span<const u8> data) {
+    std::scoped_lock lock{mutex};
+
+    if (!supported_features.nfc) {
+        return DriverResult::NotSupported;
+    }
+    if (!nfc_protocol->IsEnabled()) {
+        return DriverResult::Disabled;
+    }
+    if (!amiibo_detected) {
+        return DriverResult::ErrorWritingData;
+    }
+
+    disable_input_thread = true;
+    const auto result = nfc_protocol->WriteAmiibo(data);
+    disable_input_thread = false;
+
+    return result;
+}
+
+DriverResult JoyconDriver::ReadMifareData(std::span<const MifareReadChunk> data,
+                                          std::span<MifareReadData> out_data) {
+    std::scoped_lock lock{mutex};
+
+    if (!supported_features.nfc) {
+        return DriverResult::NotSupported;
+    }
+    if (!nfc_protocol->IsEnabled()) {
+        return DriverResult::Disabled;
+    }
+    if (!amiibo_detected) {
+        return DriverResult::ErrorWritingData;
+    }
+
+    disable_input_thread = true;
+    const auto result = nfc_protocol->ReadMifare(data, out_data);
+    disable_input_thread = false;
+
+    return result;
+}
+
+DriverResult JoyconDriver::WriteMifareData(std::span<const MifareWriteChunk> data) {
+    std::scoped_lock lock{mutex};
+
+    if (!supported_features.nfc) {
+        return DriverResult::NotSupported;
+    }
+    if (!nfc_protocol->IsEnabled()) {
+        return DriverResult::Disabled;
+    }
+    if (!amiibo_detected) {
+        return DriverResult::ErrorWritingData;
+    }
+
+    disable_input_thread = true;
+    const auto result = nfc_protocol->WriteMifare(data);
+    disable_input_thread = false;
+
     return result;
 }
 
diff --git a/src/input_common/helpers/joycon_driver.h b/src/input_common/helpers/joycon_driver.h
index e9b2fccbb..bc7025a21 100644
--- a/src/input_common/helpers/joycon_driver.h
+++ b/src/input_common/helpers/joycon_driver.h
@@ -49,7 +49,13 @@ public:
     DriverResult SetIrMode();
     DriverResult SetNfcMode();
     DriverResult SetRingConMode();
+    DriverResult StartNfcPolling();
+    DriverResult StopNfcPolling();
+    DriverResult ReadAmiiboData(std::vector<u8>& out_data);
     DriverResult WriteNfcData(std::span<const u8> data);
+    DriverResult ReadMifareData(std::span<const MifareReadChunk> request,
+                                std::span<MifareReadData> out_data);
+    DriverResult WriteMifareData(std::span<const MifareWriteChunk> request);
 
     void SetCallbacks(const JoyconCallbacks& callbacks);
 
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h
index 5007b0e18..e0e431156 100644
--- a/src/input_common/helpers/joycon_protocol/joycon_types.h
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -24,6 +24,7 @@ constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x
 using MacAddress = std::array<u8, 6>;
 using SerialNumber = std::array<u8, 15>;
 using TagUUID = std::array<u8, 7>;
+using MifareUUID = std::array<u8, 4>;
 
 enum class ControllerType : u8 {
     None = 0x00,
@@ -307,6 +308,19 @@ enum class NFCStatus : u8 {
     WriteDone = 0x05,
     TagLost = 0x07,
     WriteReady = 0x09,
+    MifareDone = 0x10,
+};
+
+enum class MifareCmd : u8 {
+    None = 0x00,
+    Read = 0x30,
+    AuthA = 0x60,
+    AuthB = 0x61,
+    Write = 0xA0,
+    Transfer = 0xB0,
+    Decrement = 0xC0,
+    Increment = 0xC1,
+    Store = 0xC2
 };
 
 enum class IrsMode : u8 {
@@ -592,6 +606,14 @@ struct NFCWriteCommandData {
 static_assert(sizeof(NFCWriteCommandData) == 0x15, "NFCWriteCommandData is an invalid size");
 #pragma pack(pop)
 
+struct MifareCommandData {
+    u8 unknown1;
+    u8 unknown2;
+    u8 number_of_short_bytes;
+    MifareUUID uid;
+};
+static_assert(sizeof(MifareCommandData) == 0x7, "MifareCommandData is an invalid size");
+
 struct NFCPollingCommandData {
     u8 enable_mifare;
     u8 unknown_1;
@@ -629,6 +651,41 @@ struct NFCWritePackage {
     std::array<NFCDataChunk, 4> data_chunks;
 };
 
+struct MifareReadChunk {
+    MifareCmd command;
+    std::array<u8, 0x6> sector_key;
+    u8 sector;
+};
+
+struct MifareWriteChunk {
+    MifareCmd command;
+    std::array<u8, 0x6> sector_key;
+    u8 sector;
+    std::array<u8, 0x10> data;
+};
+
+struct MifareReadData {
+    u8 sector;
+    std::array<u8, 0x10> data;
+};
+
+struct MifareReadPackage {
+    MifareCommandData command_data;
+    std::array<MifareReadChunk, 0x10> data_chunks;
+};
+
+struct MifareWritePackage {
+    MifareCommandData command_data;
+    std::array<MifareWriteChunk, 0x10> data_chunks;
+};
+
+struct TagInfo {
+    u8 uuid_length;
+    u8 protocol;
+    u8 tag_type;
+    std::array<u8, 10> uuid;
+};
+
 struct IrsConfigure {
     MCUCommand command;
     MCUSubCommand sub_command;
@@ -744,7 +801,7 @@ struct JoyconCallbacks {
     std::function<void(int, f32)> on_stick_data;
     std::function<void(int, const MotionData&)> on_motion_data;
     std::function<void(f32)> on_ring_data;
-    std::function<void(const std::vector<u8>&)> on_amiibo_data;
+    std::function<void(const Joycon::TagInfo&)> on_amiibo_data;
     std::function<void(const std::vector<u8>&, IrsResolution)> on_camera_data;
 };
 
diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp
index f7058c4a7..261f46255 100644
--- a/src/input_common/helpers/joycon_protocol/nfc.cpp
+++ b/src/input_common/helpers/joycon_protocol/nfc.cpp
@@ -40,6 +40,16 @@ DriverResult NfcProtocol::EnableNfc() {
     if (result == DriverResult::Success) {
         result = WaitUntilNfcIs(NFCStatus::Ready);
     }
+    if (result == DriverResult::Success) {
+        MCUCommandResponse output{};
+        result = SendStopPollingRequest(output);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitUntilNfcIs(NFCStatus::Ready);
+    }
+    if (result == DriverResult::Success) {
+        is_enabled = true;
+    }
 
     return result;
 }
@@ -54,22 +64,16 @@ DriverResult NfcProtocol::DisableNfc() {
     }
 
     is_enabled = false;
+    is_polling = false;
 
     return result;
 }
 
 DriverResult NfcProtocol::StartNFCPollingMode() {
-    LOG_DEBUG(Input, "Start NFC pooling Mode");
+    LOG_DEBUG(Input, "Start NFC polling Mode");
     ScopedSetBlocking sb(this);
     DriverResult result{DriverResult::Success};
 
-    if (result == DriverResult::Success) {
-        MCUCommandResponse output{};
-        result = SendStopPollingRequest(output);
-    }
-    if (result == DriverResult::Success) {
-        result = WaitUntilNfcIs(NFCStatus::Ready);
-    }
     if (result == DriverResult::Success) {
         MCUCommandResponse output{};
         result = SendStartPollingRequest(output);
@@ -78,13 +82,32 @@ DriverResult NfcProtocol::StartNFCPollingMode() {
         result = WaitUntilNfcIs(NFCStatus::Polling);
     }
     if (result == DriverResult::Success) {
-        is_enabled = true;
+        is_polling = true;
     }
 
     return result;
 }
 
-DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
+DriverResult NfcProtocol::StopNFCPollingMode() {
+    LOG_DEBUG(Input, "Stop NFC polling Mode");
+    ScopedSetBlocking sb(this);
+    DriverResult result{DriverResult::Success};
+
+    if (result == DriverResult::Success) {
+        MCUCommandResponse output{};
+        result = SendStopPollingRequest(output);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitUntilNfcIs(NFCStatus::WriteReady);
+    }
+    if (result == DriverResult::Success) {
+        is_polling = false;
+    }
+
+    return result;
+}
+
+DriverResult NfcProtocol::GetTagInfo(Joycon::TagInfo& tag_info) {
     if (update_counter++ < AMIIBO_UPDATE_DELAY) {
         return DriverResult::Delayed;
     }
@@ -100,11 +123,41 @@ DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
     }
 
     if (result == DriverResult::Success) {
+        tag_info = {
+            .uuid_length = tag_data.uuid_size,
+            .protocol = 1,
+            .tag_type = tag_data.type,
+            .uuid = {},
+        };
+
+        memcpy(tag_info.uuid.data(), tag_data.uuid.data(), tag_data.uuid_size);
+
+        // Investigate why mifare type is not correct
+        if (tag_info.tag_type == 144) {
+            tag_info.tag_type = 1U << 6;
+        }
+
         std::string uuid_string;
         for (auto& content : tag_data.uuid) {
             uuid_string += fmt::format(" {:02x}", content);
         }
         LOG_INFO(Input, "Tag detected, type={}, uuid={}", tag_data.type, uuid_string);
+    }
+
+    return result;
+}
+
+DriverResult NfcProtocol::ReadAmiibo(std::vector<u8>& data) {
+    LOG_DEBUG(Input, "Scan for amiibos");
+    ScopedSetBlocking sb(this);
+    DriverResult result{DriverResult::Success};
+    TagFoundData tag_data{};
+
+    if (result == DriverResult::Success) {
+        result = IsTagInRange(tag_data, 7);
+    }
+
+    if (result == DriverResult::Success) {
         result = GetAmiiboData(data);
     }
 
@@ -154,6 +207,69 @@ DriverResult NfcProtocol::WriteAmiibo(std::span<const u8> data) {
     return result;
 }
 
+DriverResult NfcProtocol::ReadMifare(std::span<const MifareReadChunk> read_request,
+                                     std::span<MifareReadData> out_data) {
+    LOG_DEBUG(Input, "Read mifare");
+    ScopedSetBlocking sb(this);
+    DriverResult result{DriverResult::Success};
+    TagFoundData tag_data{};
+    MifareUUID tag_uuid{};
+
+    if (result == DriverResult::Success) {
+        result = IsTagInRange(tag_data, 7);
+    }
+    if (result == DriverResult::Success) {
+        memcpy(tag_uuid.data(), tag_data.uuid.data(), sizeof(MifareUUID));
+        result = GetMifareData(tag_uuid, read_request, out_data);
+    }
+    if (result == DriverResult::Success) {
+        MCUCommandResponse output{};
+        result = SendStopPollingRequest(output);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitUntilNfcIs(NFCStatus::Ready);
+    }
+    if (result == DriverResult::Success) {
+        MCUCommandResponse output{};
+        result = SendStartPollingRequest(output, true);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitUntilNfcIs(NFCStatus::WriteReady);
+    }
+    return result;
+}
+
+DriverResult NfcProtocol::WriteMifare(std::span<const MifareWriteChunk> write_request) {
+    LOG_DEBUG(Input, "Write mifare");
+    ScopedSetBlocking sb(this);
+    DriverResult result{DriverResult::Success};
+    TagFoundData tag_data{};
+    MifareUUID tag_uuid{};
+
+    if (result == DriverResult::Success) {
+        result = IsTagInRange(tag_data, 7);
+    }
+    if (result == DriverResult::Success) {
+        memcpy(tag_uuid.data(), tag_data.uuid.data(), sizeof(MifareUUID));
+        result = WriteMifareData(tag_uuid, write_request);
+    }
+    if (result == DriverResult::Success) {
+        MCUCommandResponse output{};
+        result = SendStopPollingRequest(output);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitUntilNfcIs(NFCStatus::Ready);
+    }
+    if (result == DriverResult::Success) {
+        MCUCommandResponse output{};
+        result = SendStartPollingRequest(output, true);
+    }
+    if (result == DriverResult::Success) {
+        result = WaitUntilNfcIs(NFCStatus::WriteReady);
+    }
+    return result;
+}
+
 bool NfcProtocol::HasAmiibo() {
     if (update_counter++ < AMIIBO_UPDATE_DELAY) {
         return true;
@@ -341,6 +457,158 @@ DriverResult NfcProtocol::WriteAmiiboData(const TagUUID& tag_uuid, std::span<con
     return result;
 }
 
+DriverResult NfcProtocol::GetMifareData(const MifareUUID& tag_uuid,
+                                        std::span<const MifareReadChunk> read_request,
+                                        std::span<MifareReadData> out_data) {
+    constexpr std::size_t timeout_limit = 60;
+    const auto nfc_data = MakeMifareReadPackage(tag_uuid, read_request);
+    const std::vector<u8> nfc_buffer_data = SerializeMifareReadPackage(nfc_data);
+    std::span<const u8> buffer(nfc_buffer_data);
+    DriverResult result = DriverResult::Success;
+    MCUCommandResponse output{};
+    u8 block_id = 1;
+    u8 package_index = 0;
+    std::size_t tries = 0;
+    std::size_t current_position = 0;
+
+    LOG_INFO(Input, "Reading Mifare data");
+
+    // Send data request. Nfc buffer size is 31, Send the data in smaller packages
+    while (current_position < buffer.size() && tries++ < timeout_limit) {
+        const std::size_t next_position =
+            std::min(current_position + sizeof(NFCRequestState::raw_data), buffer.size());
+        const std::size_t block_size = next_position - current_position;
+        const bool is_last_packet = block_size < sizeof(NFCRequestState::raw_data);
+
+        SendReadDataMifareRequest(output, block_id, is_last_packet,
+                                  buffer.subspan(current_position, block_size));
+
+        const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
+
+        if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
+            return DriverResult::ErrorReadingData;
+        }
+
+        // Increase position when data is confirmed by the joycon
+        if (output.mcu_report == MCUReport::NFCState &&
+            (output.mcu_data[1] << 8) + output.mcu_data[0] == 0x0500 &&
+            output.mcu_data[3] == block_id) {
+            block_id++;
+            current_position = next_position;
+        }
+    }
+
+    if (result != DriverResult::Success) {
+        return result;
+    }
+
+    // Wait for reply and save the output data
+    while (tries++ < timeout_limit) {
+        result = SendNextPackageRequest(output, package_index);
+        const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+
+        if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
+            return DriverResult::ErrorReadingData;
+        }
+
+        if (output.mcu_report == MCUReport::NFCState && output.mcu_data[1] == 0x10) {
+            constexpr std::size_t DATA_LENGHT = 0x10 + 1;
+            constexpr std::size_t DATA_START = 11;
+            const u8 number_of_elements = output.mcu_data[10];
+            for (std::size_t i = 0; i < number_of_elements; i++) {
+                out_data[i].sector = output.mcu_data[DATA_START + (i * DATA_LENGHT)];
+                memcpy(out_data[i].data.data(),
+                       output.mcu_data.data() + DATA_START + 1 + (i * DATA_LENGHT),
+                       sizeof(MifareReadData::data));
+            }
+            package_index++;
+            continue;
+        }
+
+        if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::MifareDone) {
+            LOG_INFO(Input, "Finished reading mifare");
+            break;
+        }
+    }
+
+    return result;
+}
+
+DriverResult NfcProtocol::WriteMifareData(const MifareUUID& tag_uuid,
+                                          std::span<const MifareWriteChunk> write_request) {
+    constexpr std::size_t timeout_limit = 60;
+    const auto nfc_data = MakeMifareWritePackage(tag_uuid, write_request);
+    const std::vector<u8> nfc_buffer_data = SerializeMifareWritePackage(nfc_data);
+    std::span<const u8> buffer(nfc_buffer_data);
+    DriverResult result = DriverResult::Success;
+    MCUCommandResponse output{};
+    u8 block_id = 1;
+    u8 package_index = 0;
+    std::size_t tries = 0;
+    std::size_t current_position = 0;
+
+    LOG_INFO(Input, "Writing Mifare data");
+
+    // Send data request. Nfc buffer size is 31, Send the data in smaller packages
+    while (current_position < buffer.size() && tries++ < timeout_limit) {
+        const std::size_t next_position =
+            std::min(current_position + sizeof(NFCRequestState::raw_data), buffer.size());
+        const std::size_t block_size = next_position - current_position;
+        const bool is_last_packet = block_size < sizeof(NFCRequestState::raw_data);
+
+        SendReadDataMifareRequest(output, block_id, is_last_packet,
+                                  buffer.subspan(current_position, block_size));
+
+        const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
+
+        if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
+            return DriverResult::ErrorReadingData;
+        }
+
+        // Increase position when data is confirmed by the joycon
+        if (output.mcu_report == MCUReport::NFCState &&
+            (output.mcu_data[1] << 8) + output.mcu_data[0] == 0x0500 &&
+            output.mcu_data[3] == block_id) {
+            block_id++;
+            current_position = next_position;
+        }
+    }
+
+    if (result != DriverResult::Success) {
+        return result;
+    }
+
+    // Wait for reply and ignore the output data
+    while (tries++ < timeout_limit) {
+        result = SendNextPackageRequest(output, package_index);
+        const auto nfc_status = static_cast<NFCStatus>(output.mcu_data[6]);
+
+        if (result != DriverResult::Success) {
+            return result;
+        }
+
+        if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::TagLost) {
+            return DriverResult::ErrorReadingData;
+        }
+
+        if (output.mcu_report == MCUReport::NFCState && output.mcu_data[1] == 0x10) {
+            package_index++;
+            continue;
+        }
+
+        if (output.mcu_report == MCUReport::NFCState && nfc_status == NFCStatus::MifareDone) {
+            LOG_INFO(Input, "Finished writing mifare");
+            break;
+        }
+    }
+
+    return result;
+}
+
 DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output,
                                                   bool is_second_attempt) {
     NFCRequestState request{
@@ -477,6 +745,28 @@ DriverResult NfcProtocol::SendWriteDataAmiiboRequest(MCUCommandResponse& output,
                        output);
 }
 
+DriverResult NfcProtocol::SendReadDataMifareRequest(MCUCommandResponse& output, u8 block_id,
+                                                    bool is_last_packet, std::span<const u8> data) {
+    const auto data_size = std::min(data.size(), sizeof(NFCRequestState::raw_data));
+    NFCRequestState request{
+        .command_argument = NFCCommand::Mifare,
+        .block_id = block_id,
+        .packet_id = {},
+        .packet_flag =
+            is_last_packet ? MCUPacketFlag::LastCommandPacket : MCUPacketFlag::MorePacketsRemaining,
+        .data_length = static_cast<u8>(data_size),
+        .raw_data = {},
+        .crc = {},
+    };
+    memcpy(request.raw_data.data(), data.data(), data_size);
+
+    std::array<u8, sizeof(NFCRequestState)> request_data{};
+    memcpy(request_data.data(), &request, sizeof(NFCRequestState));
+    request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
+    return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
+                       output);
+}
+
 std::vector<u8> NfcProtocol::SerializeWritePackage(const NFCWritePackage& package) const {
     const std::size_t header_size =
         sizeof(NFCWriteCommandData) + sizeof(NFCWritePackage::number_of_chunks);
@@ -498,6 +788,48 @@ std::vector<u8> NfcProtocol::SerializeWritePackage(const NFCWritePackage& packag
     return serialized_data;
 }
 
+std::vector<u8> NfcProtocol::SerializeMifareReadPackage(const MifareReadPackage& package) const {
+    const std::size_t header_size = sizeof(MifareCommandData);
+    std::vector<u8> serialized_data(header_size);
+    std::size_t start_index = 0;
+
+    memcpy(serialized_data.data(), &package, header_size);
+    start_index += header_size;
+
+    for (const auto& data_chunk : package.data_chunks) {
+        const std::size_t chunk_size = sizeof(MifareReadChunk);
+        if (data_chunk.command == MifareCmd::None) {
+            continue;
+        }
+        serialized_data.resize(start_index + chunk_size);
+        memcpy(serialized_data.data() + start_index, &data_chunk, chunk_size);
+        start_index += chunk_size;
+    }
+
+    return serialized_data;
+}
+
+std::vector<u8> NfcProtocol::SerializeMifareWritePackage(const MifareWritePackage& package) const {
+    const std::size_t header_size = sizeof(MifareCommandData);
+    std::vector<u8> serialized_data(header_size);
+    std::size_t start_index = 0;
+
+    memcpy(serialized_data.data(), &package, header_size);
+    start_index += header_size;
+
+    for (const auto& data_chunk : package.data_chunks) {
+        const std::size_t chunk_size = sizeof(MifareWriteChunk);
+        if (data_chunk.command == MifareCmd::None) {
+            continue;
+        }
+        serialized_data.resize(start_index + chunk_size);
+        memcpy(serialized_data.data() + start_index, &data_chunk, chunk_size);
+        start_index += chunk_size;
+    }
+
+    return serialized_data;
+}
+
 NFCWritePackage NfcProtocol::MakeAmiiboWritePackage(const TagUUID& tag_uuid,
                                                     std::span<const u8> data) const {
     return {
@@ -527,6 +859,46 @@ NFCWritePackage NfcProtocol::MakeAmiiboWritePackage(const TagUUID& tag_uuid,
     };
 }
 
+MifareReadPackage NfcProtocol::MakeMifareReadPackage(
+    const MifareUUID& tag_uuid, std::span<const MifareReadChunk> read_request) const {
+    MifareReadPackage package{
+        .command_data{
+            .unknown1 = 0xd0,
+            .unknown2 = 0x07,
+            .number_of_short_bytes = static_cast<u8>(
+                ((read_request.size() * sizeof(MifareReadChunk)) + sizeof(MifareUUID)) / 2),
+            .uid = tag_uuid,
+        },
+        .data_chunks = {},
+    };
+
+    for (std::size_t i = 0; i < read_request.size() && i < package.data_chunks.size(); ++i) {
+        package.data_chunks[i] = read_request[i];
+    }
+
+    return package;
+}
+
+MifareWritePackage NfcProtocol::MakeMifareWritePackage(
+    const MifareUUID& tag_uuid, std::span<const MifareWriteChunk> read_request) const {
+    MifareWritePackage package{
+        .command_data{
+            .unknown1 = 0xd0,
+            .unknown2 = 0x07,
+            .number_of_short_bytes = static_cast<u8>(
+                ((read_request.size() * sizeof(MifareReadChunk)) + sizeof(MifareUUID) + 2) / 2),
+            .uid = tag_uuid,
+        },
+        .data_chunks = {},
+    };
+
+    for (std::size_t i = 0; i < read_request.size() && i < package.data_chunks.size(); ++i) {
+        package.data_chunks[i] = read_request[i];
+    }
+
+    return package;
+}
+
 NFCDataChunk NfcProtocol::MakeAmiiboChunk(u8 page, u8 size, std::span<const u8> data) const {
     constexpr u8 NFC_PAGE_SIZE = 4;
 
@@ -606,4 +978,8 @@ bool NfcProtocol::IsEnabled() const {
     return is_enabled;
 }
 
+bool NfcProtocol::IsPolling() const {
+    return is_polling;
+}
+
 } // namespace InputCommon::Joycon
diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h
index eb58c427d..0be95e40e 100644
--- a/src/input_common/helpers/joycon_protocol/nfc.h
+++ b/src/input_common/helpers/joycon_protocol/nfc.h
@@ -25,14 +25,25 @@ public:
 
     DriverResult StartNFCPollingMode();
 
-    DriverResult ScanAmiibo(std::vector<u8>& data);
+    DriverResult StopNFCPollingMode();
+
+    DriverResult GetTagInfo(Joycon::TagInfo& tag_info);
+
+    DriverResult ReadAmiibo(std::vector<u8>& data);
 
     DriverResult WriteAmiibo(std::span<const u8> data);
 
+    DriverResult ReadMifare(std::span<const MifareReadChunk> read_request,
+                            std::span<MifareReadData> out_data);
+
+    DriverResult WriteMifare(std::span<const MifareWriteChunk> write_request);
+
     bool HasAmiibo();
 
     bool IsEnabled() const;
 
+    bool IsPolling() const;
+
 private:
     // Number of times the function will be delayed until it outputs valid data
     static constexpr std::size_t AMIIBO_UPDATE_DELAY = 15;
@@ -51,6 +62,13 @@ private:
 
     DriverResult WriteAmiiboData(const TagUUID& tag_uuid, std::span<const u8> data);
 
+    DriverResult GetMifareData(const MifareUUID& tag_uuid,
+                               std::span<const MifareReadChunk> read_request,
+                               std::span<MifareReadData> out_data);
+
+    DriverResult WriteMifareData(const MifareUUID& tag_uuid,
+                                 std::span<const MifareWriteChunk> write_request);
+
     DriverResult SendStartPollingRequest(MCUCommandResponse& output,
                                          bool is_second_attempt = false);
 
@@ -65,17 +83,31 @@ private:
     DriverResult SendWriteDataAmiiboRequest(MCUCommandResponse& output, u8 block_id,
                                             bool is_last_packet, std::span<const u8> data);
 
+    DriverResult SendReadDataMifareRequest(MCUCommandResponse& output, u8 block_id,
+                                           bool is_last_packet, std::span<const u8> data);
+
     std::vector<u8> SerializeWritePackage(const NFCWritePackage& package) const;
 
+    std::vector<u8> SerializeMifareReadPackage(const MifareReadPackage& package) const;
+
+    std::vector<u8> SerializeMifareWritePackage(const MifareWritePackage& package) const;
+
     NFCWritePackage MakeAmiiboWritePackage(const TagUUID& tag_uuid, std::span<const u8> data) const;
 
     NFCDataChunk MakeAmiiboChunk(u8 page, u8 size, std::span<const u8> data) const;
 
+    MifareReadPackage MakeMifareReadPackage(const MifareUUID& tag_uuid,
+                                            std::span<const MifareReadChunk> read_request) const;
+
+    MifareWritePackage MakeMifareWritePackage(const MifareUUID& tag_uuid,
+                                              std::span<const MifareWriteChunk> read_request) const;
+
     NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const;
 
     TagUUID GetTagUUID(std::span<const u8> data) const;
 
     bool is_enabled{};
+    bool is_polling{};
     std::size_t update_counter{};
 };
 
diff --git a/src/input_common/helpers/joycon_protocol/poller.cpp b/src/input_common/helpers/joycon_protocol/poller.cpp
index dca797f7a..1aab9e12a 100644
--- a/src/input_common/helpers/joycon_protocol/poller.cpp
+++ b/src/input_common/helpers/joycon_protocol/poller.cpp
@@ -70,8 +70,8 @@ void JoyconPoller::UpdateColor(const Color& color) {
     callbacks.on_color_data(color);
 }
 
-void JoyconPoller::UpdateAmiibo(const std::vector<u8>& amiibo_data) {
-    callbacks.on_amiibo_data(amiibo_data);
+void JoyconPoller::UpdateAmiibo(const Joycon::TagInfo& tag_info) {
+    callbacks.on_amiibo_data(tag_info);
 }
 
 void JoyconPoller::UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format) {
diff --git a/src/input_common/helpers/joycon_protocol/poller.h b/src/input_common/helpers/joycon_protocol/poller.h
index 0fa72c6db..3746abe5d 100644
--- a/src/input_common/helpers/joycon_protocol/poller.h
+++ b/src/input_common/helpers/joycon_protocol/poller.h
@@ -36,8 +36,8 @@ public:
 
     void UpdateColor(const Color& color);
     void UpdateRing(s16 value, const RingStatus& ring_status);
-    void UpdateAmiibo(const std::vector<u8>& amiibo_data);
-    void UpdateCamera(const std::vector<u8>& amiibo_data, IrsResolution format);
+    void UpdateAmiibo(const Joycon::TagInfo& tag_info);
+    void UpdateCamera(const std::vector<u8>& camera_data, IrsResolution format);
 
 private:
     void UpdateActiveLeftPadInput(const InputReportActive& input,
diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h
index 50b5a3dc8..c2d0cbb34 100644
--- a/src/input_common/input_engine.h
+++ b/src/input_common/input_engine.h
@@ -143,12 +143,46 @@ public:
         return Common::Input::NfcState::NotSupported;
     }
 
+    // Start scanning for nfc tags
+    virtual Common::Input::NfcState StartNfcPolling(
+        [[maybe_unused]] const PadIdentifier& identifier_) {
+        return Common::Input::NfcState::NotSupported;
+    }
+
+    // Start scanning for nfc tags
+    virtual Common::Input::NfcState StopNfcPolling(
+        [[maybe_unused]] const PadIdentifier& identifier_) {
+        return Common::Input::NfcState::NotSupported;
+    }
+
+    // Reads data from amiibo tag
+    virtual Common::Input::NfcState ReadAmiiboData(
+        [[maybe_unused]] const PadIdentifier& identifier_,
+        [[maybe_unused]] std::vector<u8>& out_data) {
+        return Common::Input::NfcState::NotSupported;
+    }
+
     // Writes data to an nfc tag
     virtual Common::Input::NfcState WriteNfcData([[maybe_unused]] const PadIdentifier& identifier,
                                                  [[maybe_unused]] const std::vector<u8>& data) {
         return Common::Input::NfcState::NotSupported;
     }
 
+    // Reads data from mifare tag
+    virtual Common::Input::NfcState ReadMifareData(
+        [[maybe_unused]] const PadIdentifier& identifier_,
+        [[maybe_unused]] const Common::Input::MifareRequest& request,
+        [[maybe_unused]] Common::Input::MifareRequest& out_data) {
+        return Common::Input::NfcState::NotSupported;
+    }
+
+    // Write data to mifare tag
+    virtual Common::Input::NfcState WriteMifareData(
+        [[maybe_unused]] const PadIdentifier& identifier_,
+        [[maybe_unused]] const Common::Input::MifareRequest& request) {
+        return Common::Input::NfcState::NotSupported;
+    }
+
     // Returns the engine name
     [[nodiscard]] const std::string& GetEngineName() const;
 
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index 380a01542..870e76ab0 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -792,8 +792,7 @@ public:
 
         const Common::Input::CallbackStatus status{
             .type = Common::Input::InputType::Nfc,
-            .nfc_status = nfc_status.state,
-            .raw_data = nfc_status.data,
+            .nfc_status = nfc_status,
         };
 
         TriggerOnChange(status);
@@ -836,10 +835,31 @@ public:
         return input_engine->SupportsNfc(identifier);
     }
 
+    Common::Input::NfcState StartNfcPolling() {
+        return input_engine->StartNfcPolling(identifier);
+    }
+
+    Common::Input::NfcState StopNfcPolling() {
+        return input_engine->StopNfcPolling(identifier);
+    }
+
+    Common::Input::NfcState ReadAmiiboData(std::vector<u8>& out_data) {
+        return input_engine->ReadAmiiboData(identifier, out_data);
+    }
+
     Common::Input::NfcState WriteNfcData(const std::vector<u8>& data) override {
         return input_engine->WriteNfcData(identifier, data);
     }
 
+    Common::Input::NfcState ReadMifareData(const Common::Input::MifareRequest& request,
+                                           Common::Input::MifareRequest& out_data) {
+        return input_engine->ReadMifareData(identifier, request, out_data);
+    }
+
+    Common::Input::NfcState WriteMifareData(const Common::Input::MifareRequest& request) {
+        return input_engine->WriteMifareData(identifier, request);
+    }
+
 private:
     const PadIdentifier identifier;
     InputEngine* input_engine;
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 8768a7c3c..45a39451d 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -3840,7 +3840,7 @@ void GMainWindow::OnLoadAmiibo() {
     auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
 
     // Remove amiibo if one is connected
-    if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) {
+    if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::TagNearby) {
         virtual_amiibo->CloseAmiibo();
         QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
         return;
@@ -3868,7 +3868,7 @@ void GMainWindow::LoadAmiibo(const QString& filename) {
     auto* virtual_amiibo = input_subsystem->GetVirtualAmiibo();
     const QString title = tr("Error loading Amiibo data");
     // Remove amiibo if one is connected
-    if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::AmiiboIsOpen) {
+    if (virtual_amiibo->GetCurrentState() == InputCommon::VirtualAmiibo::State::TagNearby) {
         virtual_amiibo->CloseAmiibo();
         QMessageBox::warning(this, tr("Amiibo"), tr("The current amiibo has been removed"));
         return;