diff --git a/applications/main/ibutton/scenes/ibutton_scene_config.h b/applications/main/ibutton/scenes/ibutton_scene_config.h index f3fd0fff7..79f6791b3 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_config.h +++ b/applications/main/ibutton/scenes/ibutton_scene_config.h @@ -17,6 +17,5 @@ ADD_SCENE(ibutton, delete_confirm, DeleteConfirm) ADD_SCENE(ibutton, delete_success, DeleteSuccess) ADD_SCENE(ibutton, retry_confirm, RetryConfirm) ADD_SCENE(ibutton, exit_confirm, ExitConfirm) -ADD_SCENE(ibutton, read_exit_confirm, ReadExitConfirm) ADD_SCENE(ibutton, view_data, ViewData) ADD_SCENE(ibutton, rpc, Rpc) diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c deleted file mode 100644 index 4077de9a6..000000000 --- a/applications/main/ibutton/scenes/ibutton_scene_read_exit_confirm.c +++ /dev/null @@ -1,59 +0,0 @@ -#include "../ibutton_i.h" - -static void ibutton_scene_read_exit_confirm_widget_callback( - GuiButtonType result, - InputType type, - void* context) { - iButton* ibutton = context; - if(type == InputTypeShort) { - view_dispatcher_send_custom_event(ibutton->view_dispatcher, result); - } -} - -void ibutton_scene_read_exit_confirm_on_enter(void* context) { - iButton* ibutton = context; - Widget* widget = ibutton->widget; - - widget_add_button_element( - widget, - GuiButtonTypeLeft, - "Exit", - ibutton_scene_read_exit_confirm_widget_callback, - ibutton); - widget_add_button_element( - widget, - GuiButtonTypeRight, - "Stay", - ibutton_scene_read_exit_confirm_widget_callback, - ibutton); - widget_add_string_element( - widget, 64, 19, AlignCenter, AlignBottom, FontPrimary, "Retry Reading?"); - widget_add_string_element( - widget, 64, 31, AlignCenter, AlignBottom, FontSecondary, "All unsaved data will be lost!"); - - view_dispatcher_switch_to_view(ibutton->view_dispatcher, iButtonViewWidget); -} - -bool ibutton_scene_read_exit_confirm_on_event(void* context, SceneManagerEvent event) { - iButton* ibutton = context; - SceneManager* scene_manager = ibutton->scene_manager; - bool consumed = false; - - if(event.type == SceneManagerEventTypeBack) { - consumed = true; // Ignore Back button presses - } else if(event.type == SceneManagerEventTypeCustom) { - consumed = true; - if(event.event == GuiButtonTypeLeft) { - scene_manager_search_and_switch_to_previous_scene(scene_manager, iButtonSceneRead); - } else if(event.event == GuiButtonTypeRight) { - scene_manager_previous_scene(scene_manager); - } - } - - return consumed; -} - -void ibutton_scene_read_exit_confirm_on_exit(void* context) { - iButton* ibutton = context; - widget_reset(ibutton->widget); -} diff --git a/applications/main/ibutton/scenes/ibutton_scene_read_success.c b/applications/main/ibutton/scenes/ibutton_scene_read_success.c index 6dd06ca52..a6c4db12a 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_read_success.c +++ b/applications/main/ibutton/scenes/ibutton_scene_read_success.c @@ -44,7 +44,7 @@ bool ibutton_scene_read_success_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeBack) { consumed = true; - scene_manager_next_scene(scene_manager, iButtonSceneReadExitConfirm); + scene_manager_next_scene(scene_manager, iButtonSceneExitConfirm); } else if(event.type == SceneManagerEventTypeCustom) { consumed = true; if(event.event == GuiButtonTypeRight) { diff --git a/applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c b/applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c index 34de5b877..75ae84a14 100644 --- a/applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c +++ b/applications/main/ibutton/scenes/ibutton_scene_retry_confirm.c @@ -15,7 +15,7 @@ void ibutton_scene_retry_confirm_on_enter(void* context) { Widget* widget = ibutton->widget; widget_add_button_element( - widget, GuiButtonTypeLeft, "Exit", ibutton_scene_retry_confirm_widget_callback, ibutton); + widget, GuiButtonTypeLeft, "Retry", ibutton_scene_retry_confirm_widget_callback, ibutton); widget_add_button_element( widget, GuiButtonTypeRight, "Stay", ibutton_scene_retry_confirm_widget_callback, ibutton); widget_add_string_element( diff --git a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c index 8844ee4af..3138d790b 100644 --- a/applications/main/nfc/api/mosgortrans/mosgortrans_util.c +++ b/applications/main/nfc/api/mosgortrans/mosgortrans_util.c @@ -506,7 +506,7 @@ bool mosgortrans_parse_transport_block(const MfClassicBlock* block, FuriString* if(data_block.valid_from_date == 0 || data_block.valid_to_date == 0) { furi_string_cat(result, "\e#No ticket"); - return true; + return false; } //remaining_trips furi_string_cat_printf(result, "Trips: %d\n", data_block.total_trips); diff --git a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c index f8eacd51a..23a1a3b69 100644 --- a/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c +++ b/applications/main/nfc/helpers/protocol_support/mf_desfire/mf_desfire_render.c @@ -2,6 +2,8 @@ #include "../iso14443_4a/iso14443_4a_render.h" +#define MF_DESFIRE_RENDER_MAX_RECORD_SIZE (256U) + void nfc_render_mf_desfire_info( const MfDesfireData* data, NfcProtocolFormatType format_type, @@ -212,8 +214,6 @@ void nfc_render_mf_desfire_file_settings_data( uint32_t record_count = 1; uint32_t record_size = 0; - const uint32_t total_size = simple_array_get_count(data->data); - switch(settings->type) { case MfDesfireFileTypeStandard: case MfDesfireFileTypeBackup: @@ -257,17 +257,14 @@ void nfc_render_mf_desfire_file_settings_data( return; } - for(uint32_t rec = 0; rec < record_count; rec++) { - const uint32_t size_offset = rec * record_size; - const uint32_t size_remaining = total_size > size_offset ? total_size - size_offset : 0; + // Limit record size + bool trim_data = record_size > MF_DESFIRE_RENDER_MAX_RECORD_SIZE; + if(trim_data) { + record_size = MF_DESFIRE_RENDER_MAX_RECORD_SIZE; + } - if(size_remaining < record_size) { - furi_string_cat_printf( - str, "record %lu (partial %lu of %lu)\n", rec, size_remaining, record_size); - record_size = size_remaining; - } else { - furi_string_cat_printf(str, "record %lu\n", rec); - } + for(uint32_t rec = 0; rec < record_count; rec++) { + furi_string_cat_printf(str, "record %lu\n", rec); for(uint32_t ch = 0; ch < record_size; ch += 4) { furi_string_cat_printf(str, "%03lx|", ch); @@ -296,6 +293,9 @@ void nfc_render_mf_desfire_file_settings_data( furi_string_push_back(str, '\n'); } + if(trim_data) { + furi_string_cat_str(str, "..."); + } furi_string_push_back(str, '\n'); } diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c new file mode 100644 index 000000000..a020c9bbd --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.c @@ -0,0 +1,121 @@ +#include "mf_plus.h" +#include "mf_plus_render.h" + +#include + +#include "nfc/nfc_app_i.h" + +#include "../nfc_protocol_support_common.h" +#include "../nfc_protocol_support_gui_common.h" +#include "../iso14443_4a/iso14443_4a_i.h" + +static void nfc_scene_info_on_enter_mf_plus(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfPlusData* data = nfc_device_get_data(device, NfcProtocolMfPlus); + + FuriString* temp_str = furi_string_alloc(); + nfc_append_filename_string_when_present(instance, temp_str); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + furi_string_replace(temp_str, "Mifare", "MIFARE"); + nfc_render_mf_plus_info(data, NfcProtocolFormatTypeFull, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} +static NfcCommand nfc_scene_read_poller_callback_mf_plus(NfcGenericEvent event, void* context) { + furi_assert(event.protocol == NfcProtocolMfPlus); + + NfcApp* instance = context; + const MfPlusPollerEvent* mf_plus_event = event.event_data; + + if(mf_plus_event->type == MfPlusPollerEventTypeReadSuccess) { + nfc_device_set_data( + instance->nfc_device, NfcProtocolMfPlus, nfc_poller_get_data(instance->poller)); + FURI_LOG_D( + "MFP", + "Read success: %s", + nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull)); + view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess); + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static void nfc_scene_read_on_enter_mf_plus(NfcApp* instance) { + nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_plus, instance); +} + +static void nfc_scene_read_success_on_enter_mf_plus(NfcApp* instance) { + const NfcDevice* device = instance->nfc_device; + const MfPlusData* data = nfc_device_get_data(device, NfcProtocolMfPlus); + + FuriString* temp_str = furi_string_alloc(); + furi_string_cat_printf( + temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull)); + furi_string_replace(temp_str, "Mifare", "MIFARE"); + nfc_render_mf_plus_info(data, NfcProtocolFormatTypeShort, temp_str); + + widget_add_text_scroll_element( + instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str)); + + furi_string_free(temp_str); +} + +static void nfc_scene_emulate_on_enter_mf_plus(NfcApp* instance) { + const Iso14443_4aData* iso14443_4a_data = + nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a); + + instance->listener = + nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, iso14443_4a_data); + nfc_listener_start( + instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance); +} + +const NfcProtocolSupportBase nfc_protocol_support_mf_plus = { + .features = NfcProtocolFeatureMoreInfo, + + .scene_info = + { + .on_enter = nfc_scene_info_on_enter_mf_plus, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_more_info = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read = + { + .on_enter = nfc_scene_read_on_enter_mf_plus, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_read_success = + { + .on_enter = nfc_scene_read_success_on_enter_mf_plus, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_saved_menu = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_save_name = + { + .on_enter = nfc_protocol_support_common_on_enter_empty, + .on_event = nfc_protocol_support_common_on_event_empty, + }, + .scene_emulate = + { + .on_enter = nfc_scene_emulate_on_enter_mf_plus, + .on_event = nfc_protocol_support_common_on_event_empty, + }, +}; \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.h b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.h new file mode 100644 index 000000000..7f2e63dd1 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus.h @@ -0,0 +1,5 @@ +#pragma once + +#include "../nfc_protocol_support_base.h" + +extern const NfcProtocolSupportBase nfc_protocol_support_mf_plus; \ No newline at end of file diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c new file mode 100644 index 000000000..8640fa16d --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.c @@ -0,0 +1,67 @@ +#include "mf_plus_render.h" + +#include "../iso14443_4a/iso14443_4a_render.h" + +void nfc_render_mf_plus_info( + const MfPlusData* data, + NfcProtocolFormatType format_type, + FuriString* str) { + nfc_render_iso14443_4a_brief(mf_plus_get_base_data(data), str); + + if(format_type != NfcProtocolFormatTypeFull) return; + + furi_string_cat(str, "\n\e#ISO14443-4 data"); + nfc_render_iso14443_4a_extra(mf_plus_get_base_data(data), str); +} + +void nfc_render_mf_plus_data(const MfPlusData* data, FuriString* str) { + nfc_render_mf_plus_version(&data->version, str); +} + +void nfc_render_mf_plus_version(const MfPlusVersion* data, FuriString* str) { + furi_string_cat_printf( + str, + "%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", + data->uid[0], + data->uid[1], + data->uid[2], + data->uid[3], + data->uid[4], + data->uid[5], + data->uid[6]); + furi_string_cat_printf( + str, + "hw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + data->hw_vendor, + data->hw_type, + data->hw_subtype, + data->hw_major, + data->hw_minor, + data->hw_storage, + data->hw_proto); + furi_string_cat_printf( + str, + "sw %02x type %02x sub %02x\n" + " maj %02x min %02x\n" + " size %02x proto %02x\n", + data->sw_vendor, + data->sw_type, + data->sw_subtype, + data->sw_major, + data->sw_minor, + data->sw_storage, + data->sw_proto); + furi_string_cat_printf( + str, + "batch %02x:%02x:%02x:%02x:%02x\n" + "week %d year %d\n", + data->batch[0], + data->batch[1], + data->batch[2], + data->batch[3], + data->batch[4], + data->prod_week, + data->prod_year); +} diff --git a/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.h b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.h new file mode 100644 index 000000000..5aa8436a9 --- /dev/null +++ b/applications/main/nfc/helpers/protocol_support/mf_plus/mf_plus_render.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "../nfc_protocol_support_render_common.h" + +void nfc_render_mf_plus_info( + const MfPlusData* data, + NfcProtocolFormatType format_type, + FuriString* str); + +void nfc_render_mf_plus_data(const MfPlusData* data, FuriString* str); + +void nfc_render_mf_plus_version(const MfPlusVersion* data, FuriString* str); diff --git a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c index 6b42a1660..a80cd6cc0 100644 --- a/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c +++ b/applications/main/nfc/helpers/protocol_support/nfc_protocol_support_defs.c @@ -17,6 +17,7 @@ #include "felica/felica.h" #include "mf_ultralight/mf_ultralight.h" #include "mf_classic/mf_classic.h" +#include "mf_plus/mf_plus.h" #include "mf_desfire/mf_desfire.h" #include "emv/emv.h" #include "slix/slix.h" @@ -39,6 +40,7 @@ const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = { [NfcProtocolFelica] = &nfc_protocol_support_felica, [NfcProtocolMfUltralight] = &nfc_protocol_support_mf_ultralight, [NfcProtocolMfClassic] = &nfc_protocol_support_mf_classic, + [NfcProtocolMfPlus] = &nfc_protocol_support_mf_plus, [NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire, [NfcProtocolSlix] = &nfc_protocol_support_slix, [NfcProtocolSt25tb] = &nfc_protocol_support_st25tb, diff --git a/applications/main/nfc/plugins/supported_cards/troika.c b/applications/main/nfc/plugins/supported_cards/troika.c index cccee769f..64a9ac5dc 100644 --- a/applications/main/nfc/plugins/supported_cards/troika.c +++ b/applications/main/nfc/plugins/supported_cards/troika.c @@ -83,6 +83,20 @@ static const MfClassicKeyPair troika_4k_keys[] = { {.a = 0xBB52F8CCE07F, .b = 0x6B6119752C70}, //40 }; +static void troika_render_section_header( + FuriString* str, + const char* name, + uint8_t prefix_separator_cnt, + uint8_t suffix_separator_cnt) { + for(uint8_t i = 0; i < prefix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } + furi_string_cat_printf(str, "[ %s ]", name); + for(uint8_t i = 0; i < suffix_separator_cnt; i++) { + furi_string_cat_printf(str, ":"); + } +} + static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) { bool success = true; @@ -204,18 +218,19 @@ static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) { bool result3 = mosgortrans_parse_transport_block(&data->block[16], tat_result); furi_string_cat_printf(parsed_data, "\e#Troyka card\n"); - if(result1) { - furi_string_cat_printf( - parsed_data, "\e#Metro\n%s\n", furi_string_get_cstr(metro_result)); + if(result1 && !furi_string_empty(metro_result)) { + troika_render_section_header(parsed_data, "Metro", 22, 21); + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(metro_result)); } - if(result2) { - furi_string_cat_printf( - parsed_data, "\n\e#Ediniy\n%s\n", furi_string_get_cstr(ground_result)); + if(result2 && !furi_string_empty(ground_result)) { + troika_render_section_header(parsed_data, "Ediny", 22, 22); + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(ground_result)); } - if(result3) { - furi_string_cat_printf(parsed_data, "\n\e#TAT\n%s", furi_string_get_cstr(tat_result)); + if(result3 && !furi_string_empty(tat_result)) { + troika_render_section_header(parsed_data, "TAT", 24, 23); + furi_string_cat_printf(parsed_data, "%s\n", furi_string_get_cstr(tat_result)); } furi_string_free(tat_result); diff --git a/applications/services/desktop/desktop.c b/applications/services/desktop/desktop.c index a0f64990f..9ade1cba5 100644 --- a/applications/services/desktop/desktop.c +++ b/applications/services/desktop/desktop.c @@ -299,7 +299,7 @@ Desktop* desktop_alloc(void) { desktop->lock_menu = desktop_lock_menu_alloc(); desktop->debug_view = desktop_debug_alloc(); - desktop->hw_mismatch_popup = popup_alloc(); + desktop->popup = popup_alloc(); desktop->locked_view = desktop_view_locked_alloc(); desktop->pin_input_view = desktop_view_pin_input_alloc(); desktop->pin_timeout_view = desktop_view_pin_timeout_alloc(); @@ -335,9 +335,7 @@ Desktop* desktop_alloc(void) { view_dispatcher_add_view( desktop->view_dispatcher, DesktopViewIdDebug, desktop_debug_get_view(desktop->debug_view)); view_dispatcher_add_view( - desktop->view_dispatcher, - DesktopViewIdHwMismatch, - popup_get_view(desktop->hw_mismatch_popup)); + desktop->view_dispatcher, DesktopViewIdPopup, popup_get_view(desktop->popup)); view_dispatcher_add_view( desktop->view_dispatcher, DesktopViewIdPinTimeout, @@ -477,6 +475,17 @@ int32_t desktop_srv(void* p) { scene_manager_next_scene(desktop->scene_manager, DesktopSceneFault); } + uint8_t keys_total, keys_valid; + if(!furi_hal_crypto_enclave_verify(&keys_total, &keys_valid)) { + FURI_LOG_E( + TAG, + "Secure Enclave verification failed: total %hhu, valid %hhu", + keys_total, + keys_valid); + + scene_manager_next_scene(desktop->scene_manager, DesktopSceneSecureEnclave); + } + // Special case: autostart application is already running if(loader_is_locked(desktop->loader) && animation_manager_is_animation_loaded(desktop->animation_manager)) { diff --git a/applications/services/desktop/desktop_i.h b/applications/services/desktop/desktop_i.h index c0b29f922..b694e05f8 100644 --- a/applications/services/desktop/desktop_i.h +++ b/applications/services/desktop/desktop_i.h @@ -28,7 +28,7 @@ typedef enum { DesktopViewIdLockMenu, DesktopViewIdLocked, DesktopViewIdDebug, - DesktopViewIdHwMismatch, + DesktopViewIdPopup, DesktopViewIdPinInput, DesktopViewIdPinTimeout, DesktopViewIdSlideshow, @@ -43,7 +43,7 @@ struct Desktop { ViewDispatcher* view_dispatcher; SceneManager* scene_manager; - Popup* hw_mismatch_popup; + Popup* popup; DesktopLockMenuView* lock_menu; DesktopDebugView* debug_view; DesktopViewLocked* locked_view; diff --git a/applications/services/desktop/scenes/desktop_scene_config.h b/applications/services/desktop/scenes/desktop_scene_config.h index c153972b2..34d000543 100644 --- a/applications/services/desktop/scenes/desktop_scene_config.h +++ b/applications/services/desktop/scenes/desktop_scene_config.h @@ -7,3 +7,4 @@ ADD_SCENE(desktop, locked, Locked) ADD_SCENE(desktop, pin_input, PinInput) ADD_SCENE(desktop, pin_timeout, PinTimeout) ADD_SCENE(desktop, slideshow, Slideshow) +ADD_SCENE(desktop, secure_enclave, SecureEnclave) \ No newline at end of file diff --git a/applications/services/desktop/scenes/desktop_scene_fault.c b/applications/services/desktop/scenes/desktop_scene_fault.c index 36c958af5..16683ba74 100644 --- a/applications/services/desktop/scenes/desktop_scene_fault.c +++ b/applications/services/desktop/scenes/desktop_scene_fault.c @@ -12,20 +12,21 @@ void desktop_scene_fault_callback(void* context) { void desktop_scene_fault_on_enter(void* context) { Desktop* desktop = (Desktop*)context; - Popup* popup = desktop->hw_mismatch_popup; + Popup* popup = desktop->popup; popup_set_context(popup, desktop); popup_set_header( popup, "Flipper crashed\n and was rebooted", - 60, + 64, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); char* message = (char*)furi_hal_rtc_get_fault_data(); - popup_set_text(popup, message, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); + popup_set_text(popup, message, 64, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); popup_set_callback(popup, desktop_scene_fault_callback); - view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdHwMismatch); + + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdPopup); } bool desktop_scene_fault_on_event(void* context, SceneManagerEvent event) { @@ -47,6 +48,11 @@ bool desktop_scene_fault_on_event(void* context, SceneManagerEvent event) { } void desktop_scene_fault_on_exit(void* context) { - UNUSED(context); + Desktop* desktop = (Desktop*)context; + furi_assert(desktop); + + Popup* popup = desktop->popup; + popup_reset(popup); + furi_hal_rtc_set_fault_data(0); } diff --git a/applications/services/desktop/scenes/desktop_scene_hw_mismatch.c b/applications/services/desktop/scenes/desktop_scene_hw_mismatch.c index 35c506103..4624b589c 100644 --- a/applications/services/desktop/scenes/desktop_scene_hw_mismatch.c +++ b/applications/services/desktop/scenes/desktop_scene_hw_mismatch.c @@ -4,17 +4,15 @@ #include "desktop_scene.h" #include "../desktop_i.h" -#define HW_MISMATCH_BACK_EVENT (0UL) - void desktop_scene_hw_mismatch_callback(void* context) { Desktop* desktop = (Desktop*)context; - view_dispatcher_send_custom_event(desktop->view_dispatcher, HW_MISMATCH_BACK_EVENT); + view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopHwMismatchExit); } void desktop_scene_hw_mismatch_on_enter(void* context) { Desktop* desktop = (Desktop*)context; furi_assert(desktop); - Popup* popup = desktop->hw_mismatch_popup; + Popup* popup = desktop->popup; char* text_buffer = malloc(256); scene_manager_set_scene_state( @@ -28,10 +26,10 @@ void desktop_scene_hw_mismatch_on_enter(void* context) { version_get_target(NULL)); popup_set_context(popup, desktop); popup_set_header( - popup, "!!!! HW Mismatch !!!!", 60, 14 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); - popup_set_text(popup, text_buffer, 60, 37 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); + popup, "!!!! HW Mismatch !!!!", 64, 12 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignBottom); + popup_set_text(popup, text_buffer, 64, 33 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignCenter); popup_set_callback(popup, desktop_scene_hw_mismatch_callback); - view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdHwMismatch); + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdPopup); } bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) { @@ -40,11 +38,10 @@ bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) if(event.type == SceneManagerEventTypeCustom) { switch(event.event) { - case HW_MISMATCH_BACK_EVENT: + case DesktopHwMismatchExit: scene_manager_previous_scene(desktop->scene_manager); consumed = true; break; - default: break; } @@ -55,11 +52,10 @@ bool desktop_scene_hw_mismatch_on_event(void* context, SceneManagerEvent event) void desktop_scene_hw_mismatch_on_exit(void* context) { Desktop* desktop = (Desktop*)context; furi_assert(desktop); - Popup* popup = desktop->hw_mismatch_popup; - popup_set_header(popup, NULL, 0, 0, AlignCenter, AlignBottom); - popup_set_text(popup, NULL, 0, 0, AlignCenter, AlignTop); - popup_set_callback(popup, NULL); - popup_set_context(popup, NULL); + + Popup* popup = desktop->popup; + popup_reset(popup); + char* text_buffer = (char*)scene_manager_get_scene_state(desktop->scene_manager, DesktopSceneHwMismatch); free(text_buffer); diff --git a/applications/services/desktop/scenes/desktop_scene_secure_enclave.c b/applications/services/desktop/scenes/desktop_scene_secure_enclave.c new file mode 100644 index 000000000..c08125c70 --- /dev/null +++ b/applications/services/desktop/scenes/desktop_scene_secure_enclave.c @@ -0,0 +1,57 @@ +#include +#include + +#include "desktop_scene.h" +#include "../desktop_i.h" + +void desktop_scene_secure_enclave_callback(void* context) { + Desktop* desktop = (Desktop*)context; + view_dispatcher_send_custom_event(desktop->view_dispatcher, DesktopEnclaveExit); +} + +void desktop_scene_secure_enclave_on_enter(void* context) { + Desktop* desktop = (Desktop*)context; + furi_assert(desktop); + + Popup* popup = desktop->popup; + popup_set_context(popup, desktop); + popup_set_header( + popup, "No Factory Keys Found", 64, 12 + STATUS_BAR_Y_SHIFT, AlignCenter, AlignBottom); + popup_set_text( + popup, + "Secure Enclave is damaged.\n" + "Some apps will not work.", + 64, + 33 + STATUS_BAR_Y_SHIFT, + AlignCenter, + AlignCenter); + popup_set_callback(popup, desktop_scene_secure_enclave_callback); + + view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdPopup); +} + +bool desktop_scene_secure_enclave_on_event(void* context, SceneManagerEvent event) { + Desktop* desktop = (Desktop*)context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + switch(event.event) { + case DesktopEnclaveExit: + scene_manager_previous_scene(desktop->scene_manager); + consumed = true; + break; + + default: + break; + } + } + return consumed; +} + +void desktop_scene_secure_enclave_on_exit(void* context) { + Desktop* desktop = (Desktop*)context; + furi_assert(desktop); + + Popup* popup = desktop->popup; + popup_reset(popup); +} diff --git a/applications/services/desktop/views/desktop_events.h b/applications/services/desktop/views/desktop_events.h index 4b157f9c6..c22b19acc 100644 --- a/applications/services/desktop/views/desktop_events.h +++ b/applications/services/desktop/views/desktop_events.h @@ -51,6 +51,10 @@ typedef enum { DesktopSlideshowCompleted, DesktopSlideshowPoweroff, + DesktopHwMismatchExit, + + DesktopEnclaveExit, + // Global events DesktopGlobalBeforeAppStarted, DesktopGlobalAfterAppFinished, diff --git a/applications/system/js_app/application.fam b/applications/system/js_app/application.fam index 8088f232f..c3c19b3e3 100644 --- a/applications/system/js_app/application.fam +++ b/applications/system/js_app/application.fam @@ -117,3 +117,11 @@ App( requires=["js_app"], sources=["modules/js_widget.c"], ) + +App( + appid="js_vgm", + apptype=FlipperAppType.PLUGIN, + entry_point="js_vgm_ep", + requires=["js_app"], + sources=["modules/js_vgm/*.c", "modules/js_vgm/ICM42688P/*.c"], +) diff --git a/applications/system/js_app/modules/js_vgm/ICM42688P/ICM42688P.c b/applications/system/js_app/modules/js_vgm/ICM42688P/ICM42688P.c new file mode 100644 index 000000000..9f8e9a0aa --- /dev/null +++ b/applications/system/js_app/modules/js_vgm/ICM42688P/ICM42688P.c @@ -0,0 +1,297 @@ +#include "ICM42688P_regs.h" +#include "ICM42688P.h" + +#define TAG "ICM42688P" + +#define ICM42688P_TIMEOUT 100 + +struct ICM42688P { + FuriHalSpiBusHandle* spi_bus; + const GpioPin* irq_pin; + float accel_scale; + float gyro_scale; +}; + +static const struct AccelFullScale { + float value; + uint8_t reg_mask; +} accel_fs_modes[] = { + [AccelFullScale16G] = {16.f, ICM42688_AFS_16G}, + [AccelFullScale8G] = {8.f, ICM42688_AFS_8G}, + [AccelFullScale4G] = {4.f, ICM42688_AFS_4G}, + [AccelFullScale2G] = {2.f, ICM42688_AFS_2G}, +}; + +static const struct GyroFullScale { + float value; + uint8_t reg_mask; +} gyro_fs_modes[] = { + [GyroFullScale2000DPS] = {2000.f, ICM42688_GFS_2000DPS}, + [GyroFullScale1000DPS] = {1000.f, ICM42688_GFS_1000DPS}, + [GyroFullScale500DPS] = {500.f, ICM42688_GFS_500DPS}, + [GyroFullScale250DPS] = {250.f, ICM42688_GFS_250DPS}, + [GyroFullScale125DPS] = {125.f, ICM42688_GFS_125DPS}, + [GyroFullScale62_5DPS] = {62.5f, ICM42688_GFS_62_5DPS}, + [GyroFullScale31_25DPS] = {31.25f, ICM42688_GFS_31_25DPS}, + [GyroFullScale15_625DPS] = {15.625f, ICM42688_GFS_15_625DPS}, +}; + +static bool icm42688p_write_reg(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t value) { + bool res = false; + furi_hal_spi_acquire(spi_bus); + do { + uint8_t cmd_data[2] = {addr & 0x7F, value}; + if(!furi_hal_spi_bus_tx(spi_bus, cmd_data, 2, ICM42688P_TIMEOUT)) break; + res = true; + } while(0); + furi_hal_spi_release(spi_bus); + return res; +} + +static bool icm42688p_read_reg(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t* value) { + bool res = false; + furi_hal_spi_acquire(spi_bus); + do { + uint8_t cmd_byte = addr | (1 << 7); + if(!furi_hal_spi_bus_tx(spi_bus, &cmd_byte, 1, ICM42688P_TIMEOUT)) break; + if(!furi_hal_spi_bus_rx(spi_bus, value, 1, ICM42688P_TIMEOUT)) break; + res = true; + } while(0); + furi_hal_spi_release(spi_bus); + return res; +} + +static bool + icm42688p_read_mem(FuriHalSpiBusHandle* spi_bus, uint8_t addr, uint8_t* data, uint8_t len) { + bool res = false; + furi_hal_spi_acquire(spi_bus); + do { + uint8_t cmd_byte = addr | (1 << 7); + if(!furi_hal_spi_bus_tx(spi_bus, &cmd_byte, 1, ICM42688P_TIMEOUT)) break; + if(!furi_hal_spi_bus_rx(spi_bus, data, len, ICM42688P_TIMEOUT)) break; + res = true; + } while(0); + furi_hal_spi_release(spi_bus); + return res; +} + +bool icm42688p_accel_config( + ICM42688P* icm42688p, + ICM42688PAccelFullScale full_scale, + ICM42688PDataRate rate) { + icm42688p->accel_scale = accel_fs_modes[full_scale].value; + uint8_t reg_value = accel_fs_modes[full_scale].reg_mask | rate; + return icm42688p_write_reg(icm42688p->spi_bus, ICM42688_ACCEL_CONFIG0, reg_value); +} + +float icm42688p_accel_get_full_scale(ICM42688P* icm42688p) { + return icm42688p->accel_scale; +} + +bool icm42688p_gyro_config( + ICM42688P* icm42688p, + ICM42688PGyroFullScale full_scale, + ICM42688PDataRate rate) { + icm42688p->gyro_scale = gyro_fs_modes[full_scale].value; + uint8_t reg_value = gyro_fs_modes[full_scale].reg_mask | rate; + return icm42688p_write_reg(icm42688p->spi_bus, ICM42688_GYRO_CONFIG0, reg_value); +} + +float icm42688p_gyro_get_full_scale(ICM42688P* icm42688p) { + return icm42688p->gyro_scale; +} + +bool icm42688p_read_accel_raw(ICM42688P* icm42688p, ICM42688PRawData* data) { + bool ret = icm42688p_read_mem( + icm42688p->spi_bus, ICM42688_ACCEL_DATA_X1, (uint8_t*)data, sizeof(ICM42688PRawData)); + return ret; +} + +bool icm42688p_read_gyro_raw(ICM42688P* icm42688p, ICM42688PRawData* data) { + bool ret = icm42688p_read_mem( + icm42688p->spi_bus, ICM42688_GYRO_DATA_X1, (uint8_t*)data, sizeof(ICM42688PRawData)); + return ret; +} + +bool icm42688p_write_gyro_offset(ICM42688P* icm42688p, ICM42688PScaledData* scaled_data) { + if((fabsf(scaled_data->x) > 64.f) || (fabsf(scaled_data->y) > 64.f) || + (fabsf(scaled_data->z) > 64.f)) { + return false; + } + + uint16_t offset_x = (uint16_t)(-(int16_t)(scaled_data->x * 32.f) * 16) >> 4; + uint16_t offset_y = (uint16_t)(-(int16_t)(scaled_data->y * 32.f) * 16) >> 4; + uint16_t offset_z = (uint16_t)(-(int16_t)(scaled_data->z * 32.f) * 16) >> 4; + + uint8_t offset_regs[9]; + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 4); + icm42688p_read_mem(icm42688p->spi_bus, ICM42688_OFFSET_USER0, offset_regs, 5); + + offset_regs[0] = offset_x & 0xFF; + offset_regs[1] = (offset_x & 0xF00) >> 8; + offset_regs[1] |= (offset_y & 0xF00) >> 4; + offset_regs[2] = offset_y & 0xFF; + offset_regs[3] = offset_z & 0xFF; + offset_regs[4] &= 0xF0; + offset_regs[4] |= (offset_z & 0x0F00) >> 8; + + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER0, offset_regs[0]); + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER1, offset_regs[1]); + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER2, offset_regs[2]); + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER3, offset_regs[3]); + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_OFFSET_USER4, offset_regs[4]); + + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 0); + return true; +} + +void icm42688p_apply_scale(ICM42688PRawData* raw_data, float full_scale, ICM42688PScaledData* data) { + data->x = ((float)(raw_data->x)) / 32768.f * full_scale; + data->y = ((float)(raw_data->y)) / 32768.f * full_scale; + data->z = ((float)(raw_data->z)) / 32768.f * full_scale; +} + +void icm42688p_apply_scale_fifo( + ICM42688P* icm42688p, + ICM42688PFifoPacket* fifo_data, + ICM42688PScaledData* accel_data, + ICM42688PScaledData* gyro_data) { + float full_scale = icm42688p->accel_scale; + accel_data->x = ((float)(fifo_data->a_x)) / 32768.f * full_scale; + accel_data->y = ((float)(fifo_data->a_y)) / 32768.f * full_scale; + accel_data->z = ((float)(fifo_data->a_z)) / 32768.f * full_scale; + + full_scale = icm42688p->gyro_scale; + gyro_data->x = ((float)(fifo_data->g_x)) / 32768.f * full_scale; + gyro_data->y = ((float)(fifo_data->g_y)) / 32768.f * full_scale; + gyro_data->z = ((float)(fifo_data->g_z)) / 32768.f * full_scale; +} + +float icm42688p_read_temp(ICM42688P* icm42688p) { + uint8_t reg_val[2]; + + icm42688p_read_mem(icm42688p->spi_bus, ICM42688_TEMP_DATA1, reg_val, 2); + int16_t temp_int = (reg_val[0] << 8) | reg_val[1]; + return ((float)temp_int / 132.48f) + 25.f; +} + +void icm42688_fifo_enable( + ICM42688P* icm42688p, + ICM42688PIrqCallback irq_callback, + void* irq_context) { + // FIFO mode: stream + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_FIFO_CONFIG, (1 << 6)); + // Little-endian data, FIFO count in records + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INTF_CONFIG0, (1 << 7) | (1 << 6)); + // FIFO partial read, FIFO packet: gyro + accel TODO: 20bit + icm42688p_write_reg( + icm42688p->spi_bus, ICM42688_FIFO_CONFIG1, (1 << 6) | (1 << 5) | (1 << 1) | (1 << 0)); + // FIFO irq watermark + uint16_t fifo_watermark = 1; + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_FIFO_CONFIG2, fifo_watermark & 0xFF); + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_FIFO_CONFIG3, fifo_watermark >> 8); + + // IRQ1: push-pull, active high + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_CONFIG, (1 << 1) | (1 << 0)); + // Clear IRQ on status read + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_CONFIG0, 0); + // IRQ pulse duration + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_CONFIG1, (1 << 6) | (1 << 5)); + + uint8_t reg_data = 0; + icm42688p_read_reg(icm42688p->spi_bus, ICM42688_INT_STATUS, ®_data); + + furi_hal_gpio_init(icm42688p->irq_pin, GpioModeInterruptRise, GpioPullDown, GpioSpeedVeryHigh); + furi_hal_gpio_remove_int_callback(icm42688p->irq_pin); + furi_hal_gpio_add_int_callback(icm42688p->irq_pin, irq_callback, irq_context); + + // IRQ1 source: FIFO threshold + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE0, (1 << 2)); +} + +void icm42688_fifo_disable(ICM42688P* icm42688p) { + furi_hal_gpio_remove_int_callback(icm42688p->irq_pin); + furi_hal_gpio_init(icm42688p->irq_pin, GpioModeAnalog, GpioPullNo, GpioSpeedLow); + + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE0, 0); + + // FIFO mode: bypass + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_FIFO_CONFIG, 0); +} + +uint16_t icm42688_fifo_get_count(ICM42688P* icm42688p) { + uint16_t reg_val = 0; + icm42688p_read_mem(icm42688p->spi_bus, ICM42688_FIFO_COUNTH, (uint8_t*)®_val, 2); + return reg_val; +} + +bool icm42688_fifo_read(ICM42688P* icm42688p, ICM42688PFifoPacket* data) { + icm42688p_read_mem( + icm42688p->spi_bus, ICM42688_FIFO_DATA, (uint8_t*)data, sizeof(ICM42688PFifoPacket)); + return (data->header) & (1 << 7); +} + +ICM42688P* icm42688p_alloc(FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin) { + ICM42688P* icm42688p = malloc(sizeof(ICM42688P)); + icm42688p->spi_bus = spi_bus; + icm42688p->irq_pin = irq_pin; + return icm42688p; +} + +void icm42688p_free(ICM42688P* icm42688p) { + free(icm42688p); +} + +bool icm42688p_init(ICM42688P* icm42688p) { + furi_hal_spi_bus_handle_init(icm42688p->spi_bus); + + // Software reset + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 0); // Set reg bank to 0 + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_DEVICE_CONFIG, 0x01); // SPI Mode 0, SW reset + furi_delay_ms(1); + + uint8_t reg_value = 0; + bool read_ok = icm42688p_read_reg(icm42688p->spi_bus, ICM42688_WHO_AM_I, ®_value); + if(!read_ok) { + FURI_LOG_E(TAG, "Chip ID read failed"); + return false; + } else if(reg_value != ICM42688_WHOAMI) { + FURI_LOG_E( + TAG, "Sensor returned wrong ID 0x%02X, expected 0x%02X", reg_value, ICM42688_WHOAMI); + return false; + } + + // Disable all interrupts + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE0, 0); + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE1, 0); + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE3, 0); + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE4, 0); + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 4); + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE6, 0); + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INT_SOURCE7, 0); + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 0); + + // Data format: little endian + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_INTF_CONFIG0, 0); + + // Enable all sensors + icm42688p_write_reg( + icm42688p->spi_bus, + ICM42688_PWR_MGMT0, + ICM42688_PWR_TEMP_ON | ICM42688_PWR_GYRO_MODE_LN | ICM42688_PWR_ACCEL_MODE_LN); + furi_delay_ms(45); + + icm42688p_accel_config(icm42688p, AccelFullScale16G, DataRate1kHz); + icm42688p_gyro_config(icm42688p, GyroFullScale2000DPS, DataRate1kHz); + + return true; +} + +bool icm42688p_deinit(ICM42688P* icm42688p) { + // Software reset + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_REG_BANK_SEL, 0); // Set reg bank to 0 + icm42688p_write_reg(icm42688p->spi_bus, ICM42688_DEVICE_CONFIG, 0x01); // SPI Mode 0, SW reset + + furi_hal_spi_bus_handle_deinit(icm42688p->spi_bus); + return true; +} diff --git a/applications/system/js_app/modules/js_vgm/ICM42688P/ICM42688P.h b/applications/system/js_app/modules/js_vgm/ICM42688P/ICM42688P.h new file mode 100644 index 000000000..b04fb9809 --- /dev/null +++ b/applications/system/js_app/modules/js_vgm/ICM42688P/ICM42688P.h @@ -0,0 +1,127 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + DataRate32kHz = 0x01, + DataRate16kHz = 0x02, + DataRate8kHz = 0x03, + DataRate4kHz = 0x04, + DataRate2kHz = 0x05, + DataRate1kHz = 0x06, + DataRate200Hz = 0x07, + DataRate100Hz = 0x08, + DataRate50Hz = 0x09, + DataRate25Hz = 0x0A, + DataRate12_5Hz = 0x0B, + DataRate6_25Hz = 0x0C, // Accelerometer only + DataRate3_125Hz = 0x0D, // Accelerometer only + DataRate1_5625Hz = 0x0E, // Accelerometer only + DataRate500Hz = 0x0F, +} ICM42688PDataRate; + +typedef enum { + AccelFullScale16G = 0, + AccelFullScale8G, + AccelFullScale4G, + AccelFullScale2G, + AccelFullScaleTotal, +} ICM42688PAccelFullScale; + +typedef enum { + GyroFullScale2000DPS = 0, + GyroFullScale1000DPS, + GyroFullScale500DPS, + GyroFullScale250DPS, + GyroFullScale125DPS, + GyroFullScale62_5DPS, + GyroFullScale31_25DPS, + GyroFullScale15_625DPS, + GyroFullScaleTotal, +} ICM42688PGyroFullScale; + +typedef struct { + int16_t x; + int16_t y; + int16_t z; +} __attribute__((packed)) ICM42688PRawData; + +typedef struct { + uint8_t header; + int16_t a_x; + int16_t a_y; + int16_t a_z; + int16_t g_x; + int16_t g_y; + int16_t g_z; + uint8_t temp; + uint16_t ts; +} __attribute__((packed)) ICM42688PFifoPacket; + +typedef struct { + float x; + float y; + float z; +} ICM42688PScaledData; + +typedef struct ICM42688P ICM42688P; + +typedef void (*ICM42688PIrqCallback)(void* ctx); + +ICM42688P* icm42688p_alloc(FuriHalSpiBusHandle* spi_bus, const GpioPin* irq_pin); + +bool icm42688p_init(ICM42688P* icm42688p); + +bool icm42688p_deinit(ICM42688P* icm42688p); + +void icm42688p_free(ICM42688P* icm42688p); + +bool icm42688p_accel_config( + ICM42688P* icm42688p, + ICM42688PAccelFullScale full_scale, + ICM42688PDataRate rate); + +float icm42688p_accel_get_full_scale(ICM42688P* icm42688p); + +bool icm42688p_gyro_config( + ICM42688P* icm42688p, + ICM42688PGyroFullScale full_scale, + ICM42688PDataRate rate); + +float icm42688p_gyro_get_full_scale(ICM42688P* icm42688p); + +bool icm42688p_read_accel_raw(ICM42688P* icm42688p, ICM42688PRawData* data); + +bool icm42688p_read_gyro_raw(ICM42688P* icm42688p, ICM42688PRawData* data); + +bool icm42688p_write_gyro_offset(ICM42688P* icm42688p, ICM42688PScaledData* scaled_data); + +void icm42688p_apply_scale(ICM42688PRawData* raw_data, float full_scale, ICM42688PScaledData* data); + +void icm42688p_apply_scale_fifo( + ICM42688P* icm42688p, + ICM42688PFifoPacket* fifo_data, + ICM42688PScaledData* accel_data, + ICM42688PScaledData* gyro_data); + +float icm42688p_read_temp(ICM42688P* icm42688p); + +void icm42688_fifo_enable( + ICM42688P* icm42688p, + ICM42688PIrqCallback irq_callback, + void* irq_context); + +void icm42688_fifo_disable(ICM42688P* icm42688p); + +uint16_t icm42688_fifo_get_count(ICM42688P* icm42688p); + +bool icm42688_fifo_read(ICM42688P* icm42688p, ICM42688PFifoPacket* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/applications/system/js_app/modules/js_vgm/ICM42688P/ICM42688P_regs.h b/applications/system/js_app/modules/js_vgm/ICM42688P/ICM42688P_regs.h new file mode 100644 index 000000000..1967534df --- /dev/null +++ b/applications/system/js_app/modules/js_vgm/ICM42688P/ICM42688P_regs.h @@ -0,0 +1,176 @@ +#pragma once + +#define ICM42688_WHOAMI 0x47 + +// Bank 0 +#define ICM42688_DEVICE_CONFIG 0x11 +#define ICM42688_DRIVE_CONFIG 0x13 +#define ICM42688_INT_CONFIG 0x14 +#define ICM42688_FIFO_CONFIG 0x16 +#define ICM42688_TEMP_DATA1 0x1D +#define ICM42688_TEMP_DATA0 0x1E +#define ICM42688_ACCEL_DATA_X1 0x1F +#define ICM42688_ACCEL_DATA_X0 0x20 +#define ICM42688_ACCEL_DATA_Y1 0x21 +#define ICM42688_ACCEL_DATA_Y0 0x22 +#define ICM42688_ACCEL_DATA_Z1 0x23 +#define ICM42688_ACCEL_DATA_Z0 0x24 +#define ICM42688_GYRO_DATA_X1 0x25 +#define ICM42688_GYRO_DATA_X0 0x26 +#define ICM42688_GYRO_DATA_Y1 0x27 +#define ICM42688_GYRO_DATA_Y0 0x28 +#define ICM42688_GYRO_DATA_Z1 0x29 +#define ICM42688_GYRO_DATA_Z0 0x2A +#define ICM42688_TMST_FSYNCH 0x2B +#define ICM42688_TMST_FSYNCL 0x2C +#define ICM42688_INT_STATUS 0x2D +#define ICM42688_FIFO_COUNTH 0x2E +#define ICM42688_FIFO_COUNTL 0x2F +#define ICM42688_FIFO_DATA 0x30 +#define ICM42688_APEX_DATA0 0x31 +#define ICM42688_APEX_DATA1 0x32 +#define ICM42688_APEX_DATA2 0x33 +#define ICM42688_APEX_DATA3 0x34 +#define ICM42688_APEX_DATA4 0x35 +#define ICM42688_APEX_DATA5 0x36 +#define ICM42688_INT_STATUS2 0x37 +#define ICM42688_INT_STATUS3 0x38 +#define ICM42688_SIGNAL_PATH_RESET 0x4B +#define ICM42688_INTF_CONFIG0 0x4C +#define ICM42688_INTF_CONFIG1 0x4D +#define ICM42688_PWR_MGMT0 0x4E +#define ICM42688_GYRO_CONFIG0 0x4F +#define ICM42688_ACCEL_CONFIG0 0x50 +#define ICM42688_GYRO_CONFIG1 0x51 +#define ICM42688_GYRO_ACCEL_CONFIG0 0x52 +#define ICM42688_ACCEL_CONFIG1 0x53 +#define ICM42688_TMST_CONFIG 0x54 +#define ICM42688_APEX_CONFIG0 0x56 +#define ICM42688_SMD_CONFIG 0x57 +#define ICM42688_FIFO_CONFIG1 0x5F +#define ICM42688_FIFO_CONFIG2 0x60 +#define ICM42688_FIFO_CONFIG3 0x61 +#define ICM42688_FSYNC_CONFIG 0x62 +#define ICM42688_INT_CONFIG0 0x63 +#define ICM42688_INT_CONFIG1 0x64 +#define ICM42688_INT_SOURCE0 0x65 +#define ICM42688_INT_SOURCE1 0x66 +#define ICM42688_INT_SOURCE3 0x68 +#define ICM42688_INT_SOURCE4 0x69 +#define ICM42688_FIFO_LOST_PKT0 0x6C +#define ICM42688_FIFO_LOST_PKT1 0x6D +#define ICM42688_SELF_TEST_CONFIG 0x70 +#define ICM42688_WHO_AM_I 0x75 +#define ICM42688_REG_BANK_SEL 0x76 + +// Bank 1 +#define ICM42688_SENSOR_CONFIG0 0x03 +#define ICM42688_GYRO_CONFIG_STATIC2 0x0B +#define ICM42688_GYRO_CONFIG_STATIC3 0x0C +#define ICM42688_GYRO_CONFIG_STATIC4 0x0D +#define ICM42688_GYRO_CONFIG_STATIC5 0x0E +#define ICM42688_GYRO_CONFIG_STATIC6 0x0F +#define ICM42688_GYRO_CONFIG_STATIC7 0x10 +#define ICM42688_GYRO_CONFIG_STATIC8 0x11 +#define ICM42688_GYRO_CONFIG_STATIC9 0x12 +#define ICM42688_GYRO_CONFIG_STATIC10 0x13 +#define ICM42688_XG_ST_DATA 0x5F +#define ICM42688_YG_ST_DATA 0x60 +#define ICM42688_ZG_ST_DATA 0x61 +#define ICM42688_TMSTVAL0 0x62 +#define ICM42688_TMSTVAL1 0x63 +#define ICM42688_TMSTVAL2 0x64 +#define ICM42688_INTF_CONFIG4 0x7A +#define ICM42688_INTF_CONFIG5 0x7B +#define ICM42688_INTF_CONFIG6 0x7C + +// Bank 2 +#define ICM42688_ACCEL_CONFIG_STATIC2 0x03 +#define ICM42688_ACCEL_CONFIG_STATIC3 0x04 +#define ICM42688_ACCEL_CONFIG_STATIC4 0x05 +#define ICM42688_XA_ST_DATA 0x3B +#define ICM42688_YA_ST_DATA 0x3C +#define ICM42688_ZA_ST_DATA 0x3D + +// Bank 4 +#define ICM42688_APEX_CONFIG1 0x40 +#define ICM42688_APEX_CONFIG2 0x41 +#define ICM42688_APEX_CONFIG3 0x42 +#define ICM42688_APEX_CONFIG4 0x43 +#define ICM42688_APEX_CONFIG5 0x44 +#define ICM42688_APEX_CONFIG6 0x45 +#define ICM42688_APEX_CONFIG7 0x46 +#define ICM42688_APEX_CONFIG8 0x47 +#define ICM42688_APEX_CONFIG9 0x48 +#define ICM42688_ACCEL_WOM_X_THR 0x4A +#define ICM42688_ACCEL_WOM_Y_THR 0x4B +#define ICM42688_ACCEL_WOM_Z_THR 0x4C +#define ICM42688_INT_SOURCE6 0x4D +#define ICM42688_INT_SOURCE7 0x4E +#define ICM42688_INT_SOURCE8 0x4F +#define ICM42688_INT_SOURCE9 0x50 +#define ICM42688_INT_SOURCE10 0x51 +#define ICM42688_OFFSET_USER0 0x77 +#define ICM42688_OFFSET_USER1 0x78 +#define ICM42688_OFFSET_USER2 0x79 +#define ICM42688_OFFSET_USER3 0x7A +#define ICM42688_OFFSET_USER4 0x7B +#define ICM42688_OFFSET_USER5 0x7C +#define ICM42688_OFFSET_USER6 0x7D +#define ICM42688_OFFSET_USER7 0x7E +#define ICM42688_OFFSET_USER8 0x7F + +// PWR_MGMT0 +#define ICM42688_PWR_TEMP_ON (0 << 5) +#define ICM42688_PWR_TEMP_OFF (1 << 5) +#define ICM42688_PWR_IDLE (1 << 4) +#define ICM42688_PWR_GYRO_MODE_OFF (0 << 2) +#define ICM42688_PWR_GYRO_MODE_LN (3 << 2) +#define ICM42688_PWR_ACCEL_MODE_OFF (0 << 0) +#define ICM42688_PWR_ACCEL_MODE_LP (2 << 0) +#define ICM42688_PWR_ACCEL_MODE_LN (3 << 0) + +// GYRO_CONFIG0 +#define ICM42688_GFS_2000DPS (0x00 << 5) +#define ICM42688_GFS_1000DPS (0x01 << 5) +#define ICM42688_GFS_500DPS (0x02 << 5) +#define ICM42688_GFS_250DPS (0x03 << 5) +#define ICM42688_GFS_125DPS (0x04 << 5) +#define ICM42688_GFS_62_5DPS (0x05 << 5) +#define ICM42688_GFS_31_25DPS (0x06 << 5) +#define ICM42688_GFS_15_625DPS (0x07 << 5) + +#define ICM42688_GODR_32kHz 0x01 +#define ICM42688_GODR_16kHz 0x02 +#define ICM42688_GODR_8kHz 0x03 +#define ICM42688_GODR_4kHz 0x04 +#define ICM42688_GODR_2kHz 0x05 +#define ICM42688_GODR_1kHz 0x06 +#define ICM42688_GODR_200Hz 0x07 +#define ICM42688_GODR_100Hz 0x08 +#define ICM42688_GODR_50Hz 0x09 +#define ICM42688_GODR_25Hz 0x0A +#define ICM42688_GODR_12_5Hz 0x0B +#define ICM42688_GODR_500Hz 0x0F + +// ACCEL_CONFIG0 +#define ICM42688_AFS_16G (0x00 << 5) +#define ICM42688_AFS_8G (0x01 << 5) +#define ICM42688_AFS_4G (0x02 << 5) +#define ICM42688_AFS_2G (0x03 << 5) + +#define ICM42688_AODR_32kHz 0x01 +#define ICM42688_AODR_16kHz 0x02 +#define ICM42688_AODR_8kHz 0x03 +#define ICM42688_AODR_4kHz 0x04 +#define ICM42688_AODR_2kHz 0x05 +#define ICM42688_AODR_1kHz 0x06 +#define ICM42688_AODR_200Hz 0x07 +#define ICM42688_AODR_100Hz 0x08 +#define ICM42688_AODR_50Hz 0x09 +#define ICM42688_AODR_25Hz 0x0A +#define ICM42688_AODR_12_5Hz 0x0B +#define ICM42688_AODR_6_25Hz 0x0C +#define ICM42688_AODR_3_125Hz 0x0D +#define ICM42688_AODR_1_5625Hz 0x0E +#define ICM42688_AODR_500Hz 0x0F diff --git a/applications/system/js_app/modules/js_vgm/README.md b/applications/system/js_app/modules/js_vgm/README.md new file mode 100644 index 000000000..c9b7e75c1 --- /dev/null +++ b/applications/system/js_app/modules/js_vgm/README.md @@ -0,0 +1,3 @@ +The files imu.c, imu.h, and ICM42688P are from https://github.com/flipperdevices/flipperzero-game-engine.git + +Please see that file for the license. \ No newline at end of file diff --git a/applications/system/js_app/modules/js_vgm/imu.c b/applications/system/js_app/modules/js_vgm/imu.c new file mode 100644 index 000000000..2b4cd98b8 --- /dev/null +++ b/applications/system/js_app/modules/js_vgm/imu.c @@ -0,0 +1,328 @@ +#include +#include "imu.h" +#include "ICM42688P/ICM42688P.h" + +#define TAG "IMU" + +#define ACCEL_GYRO_RATE DataRate100Hz + +#define FILTER_SAMPLE_FREQ 100.f +#define FILTER_BETA 0.08f + +#define SAMPLE_RATE_DIV 5 + +#define SENSITIVITY_K 30.f +#define EXP_RATE 1.1f + +#define IMU_CALI_AVG 64 + +typedef enum { + ImuStop = (1 << 0), + ImuNewData = (1 << 1), +} ImuThreadFlags; + +#define FLAGS_ALL (ImuStop | ImuNewData) + +typedef struct { + float q0; + float q1; + float q2; + float q3; + float roll; + float pitch; + float yaw; +} ImuProcessedData; + +typedef struct { + FuriThread* thread; + ICM42688P* icm42688p; + ImuProcessedData processed_data; + bool lefty; +} ImuThread; + +static void imu_madgwick_filter( + ImuProcessedData* out, + ICM42688PScaledData* accel, + ICM42688PScaledData* gyro); + +static void imu_irq_callback(void* context) { + furi_assert(context); + ImuThread* imu = context; + furi_thread_flags_set(furi_thread_get_id(imu->thread), ImuNewData); +} + +static void imu_process_data(ImuThread* imu, ICM42688PFifoPacket* in_data) { + ICM42688PScaledData accel_data; + ICM42688PScaledData gyro_data; + + // Get accel and gyro data in g and degrees/s + icm42688p_apply_scale_fifo(imu->icm42688p, in_data, &accel_data, &gyro_data); + + // Gyro: degrees/s to rads/s + gyro_data.x = gyro_data.x / 180.f * M_PI; + gyro_data.y = gyro_data.y / 180.f * M_PI; + gyro_data.z = gyro_data.z / 180.f * M_PI; + + // Sensor Fusion algorithm + ImuProcessedData* out = &imu->processed_data; + imu_madgwick_filter(out, &accel_data, &gyro_data); + + // Quaternion to euler angles + float roll = atan2f( + out->q0 * out->q1 + out->q2 * out->q3, 0.5f - out->q1 * out->q1 - out->q2 * out->q2); + float pitch = asinf(-2.0f * (out->q1 * out->q3 - out->q0 * out->q2)); + float yaw = atan2f( + out->q1 * out->q2 + out->q0 * out->q3, 0.5f - out->q2 * out->q2 - out->q3 * out->q3); + + // Euler angles: rads to degrees + out->roll = roll / M_PI * 180.f; + out->pitch = pitch / M_PI * 180.f; + out->yaw = yaw / M_PI * 180.f; +} + +static void calibrate_gyro(ImuThread* imu) { + ICM42688PRawData data; + ICM42688PScaledData offset_scaled = {.x = 0.f, .y = 0.f, .z = 0.f}; + + icm42688p_write_gyro_offset(imu->icm42688p, &offset_scaled); + furi_delay_ms(10); + + int32_t avg_x = 0; + int32_t avg_y = 0; + int32_t avg_z = 0; + + for(uint8_t i = 0; i < IMU_CALI_AVG; i++) { + icm42688p_read_gyro_raw(imu->icm42688p, &data); + avg_x += data.x; + avg_y += data.y; + avg_z += data.z; + furi_delay_ms(2); + } + + data.x = avg_x / IMU_CALI_AVG; + data.y = avg_y / IMU_CALI_AVG; + data.z = avg_z / IMU_CALI_AVG; + + icm42688p_apply_scale(&data, icm42688p_gyro_get_full_scale(imu->icm42688p), &offset_scaled); + FURI_LOG_I( + TAG, + "Offsets: x %f, y %f, z %f", + (double)offset_scaled.x, + (double)offset_scaled.y, + (double)offset_scaled.z); + icm42688p_write_gyro_offset(imu->icm42688p, &offset_scaled); +} + +// static float imu_angle_diff(float a, float b) { +// float diff = a - b; +// if(diff > 180.f) +// diff -= 360.f; +// else if(diff < -180.f) +// diff += 360.f; + +// return diff; +// } + +static int32_t imu_thread(void* context) { + furi_assert(context); + ImuThread* imu = context; + + // float yaw_last = 0.f; + // float pitch_last = 0.f; + // float diff_x = 0.f; + // float diff_y = 0.f; + + calibrate_gyro(imu); + + icm42688p_accel_config(imu->icm42688p, AccelFullScale16G, ACCEL_GYRO_RATE); + icm42688p_gyro_config(imu->icm42688p, GyroFullScale2000DPS, ACCEL_GYRO_RATE); + + imu->processed_data.q0 = 1.f; + imu->processed_data.q1 = 0.f; + imu->processed_data.q2 = 0.f; + imu->processed_data.q3 = 0.f; + icm42688_fifo_enable(imu->icm42688p, imu_irq_callback, imu); + + while(1) { + uint32_t events = furi_thread_flags_wait(FLAGS_ALL, FuriFlagWaitAny, FuriWaitForever); + + if(events & ImuStop) { + break; + } + + if(events & ImuNewData) { + uint16_t data_pending = icm42688_fifo_get_count(imu->icm42688p); + ICM42688PFifoPacket data; + while(data_pending--) { + icm42688_fifo_read(imu->icm42688p, &data); + imu_process_data(imu, &data); + } + } + } + + icm42688_fifo_disable(imu->icm42688p); + + return 0; +} + +ImuThread* imu_start(ICM42688P* icm42688p) { + ImuThread* imu = malloc(sizeof(ImuThread)); + imu->icm42688p = icm42688p; + imu->thread = furi_thread_alloc_ex("ImuThread", 4096, imu_thread, imu); + imu->lefty = furi_hal_rtc_is_flag_set(FuriHalRtcFlagHandOrient); + furi_thread_start(imu->thread); + + return imu; +} + +void imu_stop(ImuThread* imu) { + furi_assert(imu); + + furi_thread_flags_set(furi_thread_get_id(imu->thread), ImuStop); + + furi_thread_join(imu->thread); + furi_thread_free(imu->thread); + + free(imu); +} + +static float imu_inv_sqrt(float number) { + union { + float f; + uint32_t i; + } conv = {.f = number}; + conv.i = 0x5F3759Df - (conv.i >> 1); + conv.f *= 1.5f - (number * 0.5f * conv.f * conv.f); + return conv.f; +} + +/* Simple madgwik filter, based on: https://github.com/arduino-libraries/MadgwickAHRS/ */ + +static void imu_madgwick_filter( + ImuProcessedData* out, + ICM42688PScaledData* accel, + ICM42688PScaledData* gyro) { + float recipNorm; + float s0, s1, s2, s3; + float qDot1, qDot2, qDot3, qDot4; + float _2q0, _2q1, _2q2, _2q3, _4q0, _4q1, _4q2, _8q1, _8q2, q0q0, q1q1, q2q2, q3q3; + + // Rate of change of quaternion from gyroscope + qDot1 = 0.5f * (-out->q1 * gyro->x - out->q2 * gyro->y - out->q3 * gyro->z); + qDot2 = 0.5f * (out->q0 * gyro->x + out->q2 * gyro->z - out->q3 * gyro->y); + qDot3 = 0.5f * (out->q0 * gyro->y - out->q1 * gyro->z + out->q3 * gyro->x); + qDot4 = 0.5f * (out->q0 * gyro->z + out->q1 * gyro->y - out->q2 * gyro->x); + + // Compute feedback only if accelerometer measurement valid (avoids NaN in accelerometer normalisation) + if(!((accel->x == 0.0f) && (accel->y == 0.0f) && (accel->z == 0.0f))) { + // Normalise accelerometer measurement + recipNorm = imu_inv_sqrt(accel->x * accel->x + accel->y * accel->y + accel->z * accel->z); + accel->x *= recipNorm; + accel->y *= recipNorm; + accel->z *= recipNorm; + + // Auxiliary variables to avoid repeated arithmetic + _2q0 = 2.0f * out->q0; + _2q1 = 2.0f * out->q1; + _2q2 = 2.0f * out->q2; + _2q3 = 2.0f * out->q3; + _4q0 = 4.0f * out->q0; + _4q1 = 4.0f * out->q1; + _4q2 = 4.0f * out->q2; + _8q1 = 8.0f * out->q1; + _8q2 = 8.0f * out->q2; + q0q0 = out->q0 * out->q0; + q1q1 = out->q1 * out->q1; + q2q2 = out->q2 * out->q2; + q3q3 = out->q3 * out->q3; + + // Gradient decent algorithm corrective step + s0 = _4q0 * q2q2 + _2q2 * accel->x + _4q0 * q1q1 - _2q1 * accel->y; + s1 = _4q1 * q3q3 - _2q3 * accel->x + 4.0f * q0q0 * out->q1 - _2q0 * accel->y - _4q1 + + _8q1 * q1q1 + _8q1 * q2q2 + _4q1 * accel->z; + s2 = 4.0f * q0q0 * out->q2 + _2q0 * accel->x + _4q2 * q3q3 - _2q3 * accel->y - _4q2 + + _8q2 * q1q1 + _8q2 * q2q2 + _4q2 * accel->z; + s3 = 4.0f * q1q1 * out->q3 - _2q1 * accel->x + 4.0f * q2q2 * out->q3 - _2q2 * accel->y; + recipNorm = + imu_inv_sqrt(s0 * s0 + s1 * s1 + s2 * s2 + s3 * s3); // normalise step magnitude + s0 *= recipNorm; + s1 *= recipNorm; + s2 *= recipNorm; + s3 *= recipNorm; + + // Apply feedback step + qDot1 -= FILTER_BETA * s0; + qDot2 -= FILTER_BETA * s1; + qDot3 -= FILTER_BETA * s2; + qDot4 -= FILTER_BETA * s3; + } + + // Integrate rate of change of quaternion to yield quaternion + out->q0 += qDot1 * (1.0f / FILTER_SAMPLE_FREQ); + out->q1 += qDot2 * (1.0f / FILTER_SAMPLE_FREQ); + out->q2 += qDot3 * (1.0f / FILTER_SAMPLE_FREQ); + out->q3 += qDot4 * (1.0f / FILTER_SAMPLE_FREQ); + + // Normalise quaternion + recipNorm = imu_inv_sqrt( + out->q0 * out->q0 + out->q1 * out->q1 + out->q2 * out->q2 + out->q3 * out->q3); + out->q0 *= recipNorm; + out->q1 *= recipNorm; + out->q2 *= recipNorm; + out->q3 *= recipNorm; +} + +/* IMU API */ + +struct Imu { + FuriHalSpiBusHandle* icm42688p_device; + ICM42688P* icm42688p; + ImuThread* thread; + bool present; +}; + +Imu* imu_alloc(void) { + Imu* imu = malloc(sizeof(Imu)); + imu->icm42688p_device = malloc(sizeof(FuriHalSpiBusHandle)); + memcpy(imu->icm42688p_device, &furi_hal_spi_bus_handle_external, sizeof(FuriHalSpiBusHandle)); + imu->icm42688p_device->cs = &gpio_ext_pc3; + + imu->icm42688p = icm42688p_alloc(imu->icm42688p_device, &gpio_ext_pb2); + imu->present = icm42688p_init(imu->icm42688p); + + if(imu->present) { + imu->thread = imu_start(imu->icm42688p); + } + + return imu; +} + +void imu_free(Imu* imu) { + if(imu->present) { + imu_stop(imu->thread); + } + icm42688p_deinit(imu->icm42688p); + icm42688p_free(imu->icm42688p); + free(imu->icm42688p_device); + free(imu); +} + +bool imu_present(Imu* imu) { + return imu->present; +} + +float imu_pitch_get(Imu* imu) { + // we pretend that reading a float is an atomic operation + return imu->thread->lefty ? -imu->thread->processed_data.pitch : + imu->thread->processed_data.pitch; +} + +float imu_roll_get(Imu* imu) { + // we pretend that reading a float is an atomic operation + return imu->thread->processed_data.roll; +} + +float imu_yaw_get(Imu* imu) { + // we pretend that reading a float is an atomic operation + return imu->thread->lefty ? -imu->thread->processed_data.yaw : imu->thread->processed_data.yaw; +} \ No newline at end of file diff --git a/applications/system/js_app/modules/js_vgm/imu.h b/applications/system/js_app/modules/js_vgm/imu.h new file mode 100644 index 000000000..42a08fe0c --- /dev/null +++ b/applications/system/js_app/modules/js_vgm/imu.h @@ -0,0 +1,15 @@ +#pragma once + +typedef struct Imu Imu; + +Imu* imu_alloc(void); + +void imu_free(Imu* imu); + +bool imu_present(Imu* imu); + +float imu_pitch_get(Imu* imu); + +float imu_roll_get(Imu* imu); + +float imu_yaw_get(Imu* imu); \ No newline at end of file diff --git a/applications/system/js_app/modules/js_vgm/js_vgm.c b/applications/system/js_app/modules/js_vgm/js_vgm.c new file mode 100644 index 000000000..f7c0d6fcd --- /dev/null +++ b/applications/system/js_app/modules/js_vgm/js_vgm.c @@ -0,0 +1,159 @@ +#include "../../js_modules.h" +#include +#include +#include "imu.h" + +#define TAG "JsVgm" + +typedef struct { + Imu* imu; + bool present; +} JsVgmInst; + +static void js_vgm_get_pitch(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsVgmInst* vgm = mjs_get_ptr(mjs, obj_inst); + furi_assert(vgm); + + if(vgm->present) { + mjs_return(mjs, mjs_mk_number(mjs, imu_pitch_get(vgm->imu))); + } else { + FURI_LOG_T(TAG, "VGM not present."); + mjs_return(mjs, mjs_mk_undefined()); + } +} + +static void js_vgm_get_roll(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsVgmInst* vgm = mjs_get_ptr(mjs, obj_inst); + furi_assert(vgm); + + if(vgm->present) { + mjs_return(mjs, mjs_mk_number(mjs, imu_roll_get(vgm->imu))); + } else { + FURI_LOG_T(TAG, "VGM not present."); + mjs_return(mjs, mjs_mk_undefined()); + } +} + +static void js_vgm_get_yaw(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsVgmInst* vgm = mjs_get_ptr(mjs, obj_inst); + furi_assert(vgm); + + if(vgm->present) { + mjs_return(mjs, mjs_mk_number(mjs, imu_yaw_get(vgm->imu))); + } else { + FURI_LOG_T(TAG, "VGM not present."); + mjs_return(mjs, mjs_mk_undefined()); + } +} + +static float distance(float current, float start, float angle) { + // make 0 to 359.999... + current = (current < 0) ? current + 360 : current; + start = (start < 0) ? start + 360 : start; + + // get max and min + bool max_is_current = current > start; + float max = (max_is_current) ? current : start; + float min = (max_is_current) ? start : current; + + // get diff, check if it's greater than 180, and adjust + float diff = max - min; + bool diff_gt_180 = diff > 180; + if(diff_gt_180) { + diff = 360 - diff; + } + + // if diff is less than angle return 0 + if(diff < angle) { + FURI_LOG_T(TAG, "diff: %f, angle: %f", (double)diff, (double)angle); + return 0; + } + + // return diff with sign + return (max_is_current ^ diff_gt_180) ? -diff : diff; +} + +static void js_vgm_delta_yaw(struct mjs* mjs) { + mjs_val_t obj_inst = mjs_get(mjs, mjs_get_this(mjs), INST_PROP_NAME, ~0); + JsVgmInst* vgm = mjs_get_ptr(mjs, obj_inst); + furi_assert(vgm); + size_t num_args = mjs_nargs(mjs); + if(num_args < 1 || num_args > 2) { + mjs_prepend_errorf( + mjs, + MJS_BAD_ARGS_ERROR, + "Invalid args. Pass (angle [, timeout]). Got %d args.", + num_args); + mjs_return(mjs, mjs_mk_undefined()); + return; + } + + if(!vgm->present) { + FURI_LOG_T(TAG, "VGM not present."); + mjs_return(mjs, MJS_UNDEFINED); + return; + } + + double angle = mjs_get_double(mjs, mjs_arg(mjs, 0)); + if(isnan(angle)) { + mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "Invalid arg (angle)."); + mjs_return(mjs, mjs_mk_undefined()); + return; + } + uint32_t ms = (num_args == 2) ? mjs_get_int(mjs, mjs_arg(mjs, 1)) : 3000; + uint32_t timeout = furi_get_tick() + ms; + float initial_yaw = imu_yaw_get(vgm->imu); + do { + float current_yaw = imu_yaw_get(vgm->imu); + double diff = distance(current_yaw, initial_yaw, angle); + if(diff != 0) { + mjs_return(mjs, mjs_mk_number(mjs, diff)); + return; + } + furi_delay_ms(100); + } while(furi_get_tick() < timeout); + + mjs_return(mjs, mjs_mk_number(mjs, 0)); +} + +static void* js_vgm_create(struct mjs* mjs, mjs_val_t* object) { + JsVgmInst* vgm = malloc(sizeof(JsVgmInst)); + vgm->imu = imu_alloc(); + vgm->present = imu_present(vgm->imu); + if(!vgm->present) FURI_LOG_W(TAG, "VGM not present."); + mjs_val_t vgm_obj = mjs_mk_object(mjs); + mjs_set(mjs, vgm_obj, INST_PROP_NAME, ~0, mjs_mk_foreign(mjs, vgm)); + mjs_set(mjs, vgm_obj, "getPitch", ~0, MJS_MK_FN(js_vgm_get_pitch)); + mjs_set(mjs, vgm_obj, "getRoll", ~0, MJS_MK_FN(js_vgm_get_roll)); + mjs_set(mjs, vgm_obj, "getYaw", ~0, MJS_MK_FN(js_vgm_get_yaw)); + mjs_set(mjs, vgm_obj, "deltaYaw", ~0, MJS_MK_FN(js_vgm_delta_yaw)); + *object = vgm_obj; + return vgm; +} + +static void js_vgm_destroy(void* inst) { + JsVgmInst* vgm = inst; + furi_assert(vgm); + imu_free(vgm->imu); + vgm->imu = NULL; + free(vgm); +} + +static const JsModuleDescriptor js_vgm_desc = { + name: "vgm", + create: js_vgm_create, + destroy: js_vgm_destroy, +}; + +static const FlipperAppPluginDescriptor plugin_descriptor = { + .appid = PLUGIN_APP_ID, + .ep_api_version = PLUGIN_API_VERSION, + .entry_point = &js_vgm_desc, +}; + +const FlipperAppPluginDescriptor* js_vgm_ep(void) { + return &plugin_descriptor; +} diff --git a/applications/system/js_app/modules/js_widget.c b/applications/system/js_app/modules/js_widget.c index 382177bbd..69279bb7c 100644 --- a/applications/system/js_app/modules/js_widget.c +++ b/applications/system/js_app/modules/js_widget.c @@ -851,9 +851,8 @@ static void widget_remove_view(void* context) { ComponentArray_it(it, model->component); while(!ComponentArray_end_p(it)) { WidgetComponent* component = *ComponentArray_ref(it); - if(component->free) { + if(component && component->free) { component->free(component); - component->free = NULL; } ComponentArray_next(it); } diff --git a/lib/ibutton/protocols/dallas/protocol_ds1420.c b/lib/ibutton/protocols/dallas/protocol_ds1420.c index 85e0145f4..42af9f0b1 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1420.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1420.c @@ -27,6 +27,7 @@ static bool dallas_ds1420_write_blank(OneWireHost*, iButtonProtocolData*); static void dallas_ds1420_emulate(OneWireSlave*, iButtonProtocolData*); static bool dallas_ds1420_load(FlipperFormat*, uint32_t, iButtonProtocolData*); static bool dallas_ds1420_save(FlipperFormat*, const iButtonProtocolData*); +static void dallas_ds1420_render_uid(FuriString*, const iButtonProtocolData*); static void dallas_ds1420_render_brief_data(FuriString*, const iButtonProtocolData*); static void dallas_ds1420_render_error(FuriString*, const iButtonProtocolData*); static bool dallas_ds1420_is_data_valid(const iButtonProtocolData*); @@ -46,6 +47,7 @@ const iButtonProtocolDallasBase ibutton_protocol_ds1420 = { .emulate = dallas_ds1420_emulate, .save = dallas_ds1420_save, .load = dallas_ds1420_load, + .render_uid = dallas_ds1420_render_uid, .render_data = NULL, /* No data to render */ .render_brief_data = dallas_ds1420_render_brief_data, .render_error = dallas_ds1420_render_error, @@ -117,12 +119,20 @@ bool dallas_ds1420_load( return dallas_common_load_rom_data(ff, format_version, &data->rom_data); } +void dallas_ds1420_render_uid(FuriString* result, const iButtonProtocolData* protocol_data) { + const DS1420ProtocolData* data = protocol_data; + + dallas_common_render_uid(result, &data->rom_data); +} + void dallas_ds1420_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1420ProtocolData* data = protocol_data; + furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < sizeof(DallasCommonRomData); ++i) { furi_string_cat_printf(result, "%02X ", data->rom_data.bytes[i]); } + furi_string_cat_printf(result, "\nFamily Code: %02X\n", data->rom_data.bytes[0]); } void dallas_ds1420_render_error(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1971.c b/lib/ibutton/protocols/dallas/protocol_ds1971.c index d60803fc6..6d147d28d 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1971.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1971.c @@ -218,19 +218,15 @@ void dallas_ds1971_render_uid(FuriString* result, const iButtonProtocolData* pro void dallas_ds1971_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1971ProtocolData* data = protocol_data; - FuriString* data_string = furi_string_alloc(); + + furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); pretty_format_bytes_hex_canonical( - data_string, + result, DS1971_DATA_BYTE_COUNT, PRETTY_FORMAT_FONT_MONOSPACE, data->eeprom_data, DS1971_EEPROM_DATA_SIZE); - - furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); - furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string)); - - furi_string_free(data_string); } void dallas_ds1971_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1992.c b/lib/ibutton/protocols/dallas/protocol_ds1992.c index 5ddd8ef2c..483d9827f 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1992.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1992.c @@ -191,19 +191,15 @@ void dallas_ds1992_render_uid(FuriString* result, const iButtonProtocolData* pro void dallas_ds1992_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1992ProtocolData* data = protocol_data; - FuriString* data_string = furi_string_alloc(); + + furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); pretty_format_bytes_hex_canonical( - data_string, + result, DS1992_DATA_BYTE_COUNT, PRETTY_FORMAT_FONT_MONOSPACE, data->sram_data, DS1992_SRAM_DATA_SIZE); - - furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); - furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string)); - - furi_string_free(data_string); } void dallas_ds1992_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/dallas/protocol_ds1996.c b/lib/ibutton/protocols/dallas/protocol_ds1996.c index 6af61f355..157dc601a 100644 --- a/lib/ibutton/protocols/dallas/protocol_ds1996.c +++ b/lib/ibutton/protocols/dallas/protocol_ds1996.c @@ -217,19 +217,14 @@ void dallas_ds1996_render_uid(FuriString* result, const iButtonProtocolData* pro void dallas_ds1996_render_data(FuriString* result, const iButtonProtocolData* protocol_data) { const DS1996ProtocolData* data = protocol_data; - FuriString* data_string = furi_string_alloc(); + furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); pretty_format_bytes_hex_canonical( - data_string, + result, DS1996_DATA_BYTE_COUNT, PRETTY_FORMAT_FONT_MONOSPACE, data->sram_data, DS1996_SRAM_DATA_SIZE); - - furi_string_cat_printf(result, "\e#Memory Data\n--------------------\n"); - furi_string_cat_printf(result, "%s", furi_string_get_cstr(data_string)); - - furi_string_free(data_string); } void dallas_ds1996_render_brief_data(FuriString* result, const iButtonProtocolData* protocol_data) { diff --git a/lib/ibutton/protocols/misc/protocol_cyfral.c b/lib/ibutton/protocols/misc/protocol_cyfral.c index d38865bae..e43d0c6ad 100644 --- a/lib/ibutton/protocols/misc/protocol_cyfral.c +++ b/lib/ibutton/protocols/misc/protocol_cyfral.c @@ -325,7 +325,7 @@ static LevelDuration protocol_cyfral_encoder_yield(ProtocolCyfral* proto) { return result; } -static void protocol_cyfral_render_uid(FuriString* result, ProtocolCyfral* proto) { +static void protocol_cyfral_render_uid(ProtocolCyfral* proto, FuriString* result) { furi_string_cat_printf(result, "ID: "); for(size_t i = 0; i < CYFRAL_DATA_SIZE; ++i) { furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]); @@ -333,10 +333,7 @@ static void protocol_cyfral_render_uid(FuriString* result, ProtocolCyfral* proto } static void protocol_cyfral_render_brief_data(ProtocolCyfral* proto, FuriString* result) { - furi_string_cat_printf(result, "ID: "); - for(size_t i = 0; i < CYFRAL_DATA_SIZE; ++i) { - furi_string_cat_printf(result, "%02X ", ((uint8_t*)&proto->data)[i]); - } + protocol_cyfral_render_uid(proto, result); } const ProtocolBase ibutton_protocol_misc_cyfral = { diff --git a/lib/ibutton/protocols/misc/protocol_group_misc.c b/lib/ibutton/protocols/misc/protocol_group_misc.c index 20534602d..ddbbf6bd8 100644 --- a/lib/ibutton/protocols/misc/protocol_group_misc.c +++ b/lib/ibutton/protocols/misc/protocol_group_misc.c @@ -200,6 +200,16 @@ static bool ibutton_protocol_group_misc_load( } } +static void ibutton_protocol_group_misc_render_uid( + iButtonProtocolGroupMisc* group, + const iButtonProtocolData* data, + iButtonProtocolLocalId id, + FuriString* result) { + const size_t data_size = protocol_dict_get_data_size(group->dict, id); + protocol_dict_set_data(group->dict, id, data, data_size); + protocol_dict_render_uid(group->dict, result, id); +} + static void ibutton_protocol_group_misc_render_data( iButtonProtocolGroupMisc* group, const iButtonProtocolData* data, @@ -283,6 +293,7 @@ const iButtonProtocolGroupBase ibutton_protocol_group_misc = { .save = (iButtonProtocolGroupSaveFunc)ibutton_protocol_group_misc_save, .load = (iButtonProtocolGroupLoadFunc)ibutton_protocol_group_misc_load, + .render_uid = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_uid, .render_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_data, .render_brief_data = (iButtonProtocolGroupRenderFunc)ibutton_protocol_group_misc_render_brief_data, diff --git a/lib/nfc/SConscript b/lib/nfc/SConscript index ff41a9bc9..ea392de00 100644 --- a/lib/nfc/SConscript +++ b/lib/nfc/SConscript @@ -21,6 +21,7 @@ env.Append( File("protocols/iso14443_4b/iso14443_4b.h"), File("protocols/mf_ultralight/mf_ultralight.h"), File("protocols/mf_classic/mf_classic.h"), + File("protocols/mf_plus/mf_plus.h"), File("protocols/mf_desfire/mf_desfire.h"), File("protocols/emv/emv.h"), File("protocols/slix/slix.h"), @@ -33,6 +34,7 @@ env.Append( File("protocols/iso14443_4b/iso14443_4b_poller.h"), File("protocols/mf_ultralight/mf_ultralight_poller.h"), File("protocols/mf_classic/mf_classic_poller.h"), + File("protocols/mf_plus/mf_plus_poller.h"), File("protocols/mf_desfire/mf_desfire_poller.h"), File("protocols/emv/emv_poller.h"), File("protocols/st25tb/st25tb_poller.h"), diff --git a/lib/nfc/helpers/iso14443_4_layer.c b/lib/nfc/helpers/iso14443_4_layer.c index 87d820d64..0e742ce95 100644 --- a/lib/nfc/helpers/iso14443_4_layer.c +++ b/lib/nfc/helpers/iso14443_4_layer.c @@ -66,6 +66,11 @@ bool iso14443_4_layer_decode_block( bool ret = false; + // TODO: Fix properly! this is a very big kostyl na velosipede + // (bit_buffer_copy_right are called to copy bigger buffer into smaller buffer causing crash on furi check) issue comes iso14443_4a_poller_send_block at line 109 + if(bit_buffer_get_size_bytes(output_data) < bit_buffer_get_size_bytes(output_data) - 1) + return ret; + do { if(!bit_buffer_starts_with_byte(block_data, instance->pcb_prev)) break; bit_buffer_copy_right(output_data, block_data, 1); diff --git a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c index 2b8631849..deae2fa07 100644 --- a/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c +++ b/lib/nfc/protocols/mf_desfire/mf_desfire_poller_i.c @@ -75,10 +75,10 @@ MfDesfireError mf_desfire_send_chunks( const size_t rx_capacity_remaining = bit_buffer_get_capacity_bytes(rx_buffer) - bit_buffer_get_size_bytes(rx_buffer); - if(rx_size <= rx_capacity_remaining) { + if(rx_size - 1 <= rx_capacity_remaining) { bit_buffer_append_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); } else { - FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size); + FURI_LOG_W(TAG, "RX buffer overflow: ignoring %zu bytes", rx_size - 1); } } } while(false); @@ -327,36 +327,63 @@ MfDesfireError mf_desfire_poller_read_file_settings_multi( return error; } -MfDesfireError mf_desfire_poller_read_file_data( +static MfDesfireError mf_desfire_poller_read_file( MfDesfirePoller* instance, MfDesfireFileId id, + uint8_t read_cmd, uint32_t offset, size_t size, MfDesfireFileData* data) { furi_check(instance); furi_check(data); - bit_buffer_reset(instance->input_buffer); - bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_DATA); - bit_buffer_append_byte(instance->input_buffer, id); - bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3); - bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3); + MfDesfireError error = MfDesfireErrorNone; + simple_array_init(data->data, size); - MfDesfireError error; + size_t buffer_capacity = bit_buffer_get_capacity_bytes(instance->result_buffer); + uint32_t current_offset = offset; + uint32_t bytes_read = 0; + + while(bytes_read < size) { + size_t bytes_to_read = MIN(buffer_capacity, size - bytes_read); + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, read_cmd); + bit_buffer_append_byte(instance->input_buffer, id); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)¤t_offset, 3); + bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&bytes_to_read, 3); - do { error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); - if(error != MfDesfireErrorNone) break; - if(!mf_desfire_file_data_parse(data, instance->result_buffer)) { + size_t bytes_received = bit_buffer_get_size_bytes(instance->result_buffer); + if(bytes_received != bytes_to_read) { + FURI_LOG_W(TAG, "Read %zu out of %zu bytes", bytes_received, bytes_to_read); error = MfDesfireErrorProtocol; + break; } - } while(false); + + uint8_t* file_data = simple_array_get_data(data->data); + bit_buffer_write_bytes(instance->result_buffer, &file_data[current_offset], bytes_to_read); + bytes_read += bytes_to_read; + current_offset += bytes_to_read; + } + + if(error != MfDesfireErrorNone) { + simple_array_reset(data->data); + } return error; } +MfDesfireError mf_desfire_poller_read_file_data( + MfDesfirePoller* instance, + MfDesfireFileId id, + uint32_t offset, + size_t size, + MfDesfireFileData* data) { + return mf_desfire_poller_read_file(instance, id, MF_DESFIRE_CMD_READ_DATA, offset, size, data); +} + MfDesfireError mf_desfire_poller_read_file_value( MfDesfirePoller* instance, MfDesfireFileId id, @@ -389,28 +416,8 @@ MfDesfireError mf_desfire_poller_read_file_records( uint32_t offset, size_t size, MfDesfireFileData* data) { - furi_check(instance); - furi_check(data); - - bit_buffer_reset(instance->input_buffer); - bit_buffer_append_byte(instance->input_buffer, MF_DESFIRE_CMD_READ_RECORDS); - bit_buffer_append_byte(instance->input_buffer, id); - bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&offset, 3); - bit_buffer_append_bytes(instance->input_buffer, (const uint8_t*)&size, 3); - - MfDesfireError error; - - do { - error = mf_desfire_send_chunks(instance, instance->input_buffer, instance->result_buffer); - - if(error != MfDesfireErrorNone) break; - - if(!mf_desfire_file_data_parse(data, instance->result_buffer)) { - error = MfDesfireErrorProtocol; - } - } while(false); - - return error; + return mf_desfire_poller_read_file( + instance, id, MF_DESFIRE_CMD_READ_RECORDS, offset, size, data); } MfDesfireError mf_desfire_poller_read_file_data_multi( diff --git a/lib/nfc/protocols/mf_plus/mf_plus.c b/lib/nfc/protocols/mf_plus/mf_plus.c new file mode 100644 index 000000000..98b7cd82a --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus.c @@ -0,0 +1,189 @@ +#include "mf_plus_i.h" + +#include +#include + +#define MF_PLUS_PROTOCOL_NAME "Mifare Plus" + +static const char* mf_plus_type_strings[] = { + [MfPlusTypeS] = "Plus S", + [MfPlusTypeX] = "Plus X", + [MfPlusTypeSE] = "Plus SE", + [MfPlusTypeEV1] = "Plus EV1", + [MfPlusTypeEV2] = "Plus EV2", + [MfPlusTypePlus] = "Plus", + [MfPlusTypeUnknown] = "Unknown", +}; + +static const char* mf_plus_size_strings[] = { + [MfPlusSize1K] = "1K", + [MfPlusSize2K] = "2K", + [MfPlusSize4K] = "4K", + [MfPlusSizeUnknown] = "Unknown", +}; + +static const char* mf_plus_security_level_strings[] = { + [MfPlusSecurityLevel0] = "SL0", + [MfPlusSecurityLevel1] = "SL1", + [MfPlusSecurityLevel2] = "SL2", + [MfPlusSecurityLevel3] = "SL3", + [MfPlusSecurityLevelUnknown] = "Unknown", +}; + +const NfcDeviceBase nfc_device_mf_plus = { + .protocol_name = MF_PLUS_PROTOCOL_NAME, + .alloc = (NfcDeviceAlloc)mf_plus_alloc, + .free = (NfcDeviceFree)mf_plus_free, + .reset = (NfcDeviceReset)mf_plus_reset, + .copy = (NfcDeviceCopy)mf_plus_copy, + .verify = (NfcDeviceVerify)mf_plus_verify, + .load = (NfcDeviceLoad)mf_plus_load, + .save = (NfcDeviceSave)mf_plus_save, + .is_equal = (NfcDeviceEqual)mf_plus_is_equal, + .get_name = (NfcDeviceGetName)mf_plus_get_device_name, + .get_uid = (NfcDeviceGetUid)mf_plus_get_uid, + .set_uid = (NfcDeviceSetUid)mf_plus_set_uid, + .get_base_data = (NfcDeviceGetBaseData)mf_plus_get_base_data, +}; + +MfPlusData* mf_plus_alloc(void) { + MfPlusData* data = malloc(sizeof(MfPlusData)); + data->device_name = furi_string_alloc(); + data->iso14443_4a_data = iso14443_4a_alloc(); + + data->type = MfPlusTypeUnknown; + data->security_level = MfPlusSecurityLevelUnknown; + data->size = MfPlusSizeUnknown; + + return data; +} + +void mf_plus_free(MfPlusData* data) { + furi_check(data); + furi_string_free(data->device_name); + iso14443_4a_free(data->iso14443_4a_data); + free(data); +} + +void mf_plus_reset(MfPlusData* data) { + furi_check(data); + iso14443_4a_reset(data->iso14443_4a_data); + + memset(&data->version, 0, sizeof(data->version)); + data->type = MfPlusTypeUnknown; + data->security_level = MfPlusSecurityLevelUnknown; + data->size = MfPlusSizeUnknown; +} + +void mf_plus_copy(MfPlusData* data, const MfPlusData* other) { + furi_check(data); + furi_check(other); + iso14443_4a_copy(data->iso14443_4a_data, other->iso14443_4a_data); + + data->version = other->version; + data->type = other->type; + data->security_level = other->security_level; + data->size = other->size; +} + +bool mf_plus_verify(MfPlusData* data, const FuriString* device_type) { + UNUSED(data); + return furi_string_equal_str(device_type, MF_PLUS_PROTOCOL_NAME); +} + +bool mf_plus_load(MfPlusData* data, FlipperFormat* ff, uint32_t version) { + furi_assert(data); + + bool success = false; + + do { + if(!iso14443_4a_load(data->iso14443_4a_data, ff, version)) break; + if(!mf_plus_version_load(&data->version, ff)) break; + if(!mf_plus_type_load(&data->type, ff)) break; + if(!mf_plus_security_level_load(&data->security_level, ff)) break; + if(!mf_plus_size_load(&data->size, ff)) break; + success = true; + } while(false); + + return success; +} + +bool mf_plus_save(const MfPlusData* data, FlipperFormat* ff) { + furi_assert(data); + + bool success = false; + + do { + if(!iso14443_4a_save(data->iso14443_4a_data, ff)) break; + if(!flipper_format_write_comment_cstr(ff, MF_PLUS_PROTOCOL_NAME " specific data")) break; + if(!mf_plus_version_save(&data->version, ff)) break; + if(!mf_plus_type_save(&data->type, ff)) break; + if(!mf_plus_security_level_save(&data->security_level, ff)) break; + if(!mf_plus_size_save(&data->size, ff)) break; + success = true; + } while(false); + + return success; +} + +bool mf_plus_is_equal(const MfPlusData* data, const MfPlusData* other) { + furi_assert(data); + furi_assert(other); + bool equal = false; + + do { + if(!iso14443_4a_is_equal(data->iso14443_4a_data, other->iso14443_4a_data)) break; + if(memcmp(&data->version, &other->version, sizeof(data->version)) != 0) break; + if(data->security_level != other->security_level) break; + if(data->type != other->type) break; + if(data->size != other->size) break; + equal = true; + } while(false); + + return equal; +} + +const char* mf_plus_get_device_name(const MfPlusData* data, NfcDeviceNameType name_type) { + furi_check(data); + + FuriString* full_name = furi_string_alloc(); + const char* name = NULL; + + do { + if(name_type == NfcDeviceNameTypeFull) { + furi_string_reset(data->device_name); + furi_string_cat_printf( + data->device_name, + "Mifare %s %s %s", + mf_plus_type_strings[data->type], // Includes "Plus" for regular Mifare Plus cards + mf_plus_size_strings[data->size], + mf_plus_security_level_strings[data->security_level]); + name = furi_string_get_cstr(data->device_name); + FURI_LOG_D("Mifare Plus", "Full name: %s", name); + } else if(name_type == NfcDeviceNameTypeShort) { + name = "Mifare Plus"; + } else { + break; + } + } while(false); + + furi_string_free(full_name); + FURI_LOG_D("Mifare Plus", "Name: %s", name); + return name; +} + +const uint8_t* mf_plus_get_uid(const MfPlusData* data, size_t* uid_len) { + furi_assert(data); + + return iso14443_4a_get_uid(data->iso14443_4a_data, uid_len); +} + +bool mf_plus_set_uid(MfPlusData* data, const uint8_t* uid, size_t uid_len) { + furi_assert(data); + + return iso14443_4a_set_uid(data->iso14443_4a_data, uid, uid_len); +} +Iso14443_4aData* mf_plus_get_base_data(const MfPlusData* data) { + furi_check(data); + return data->iso14443_4a_data; +} \ No newline at end of file diff --git a/lib/nfc/protocols/mf_plus/mf_plus.h b/lib/nfc/protocols/mf_plus/mf_plus.h new file mode 100644 index 000000000..828d1c070 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus.h @@ -0,0 +1,117 @@ +#pragma once + +#include + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_PLUS_UID_SIZE (7) +#define MF_PLUS_BATCH_SIZE (5) + +#define MF_PLUS_CMD_GET_VERSION (0x60) + +typedef enum { + MfPlusErrorNone, + MfPlusErrorUnknown, + MfPlusErrorNotPresent, + MfPlusErrorProtocol, + MfPlusErrorAuth, + MfPlusErrorPartialRead, + MfPlusErrorTimeout, +} MfPlusError; + +typedef enum { + MfPlusTypePlus, + MfPlusTypeEV1, + MfPlusTypeEV2, + MfPlusTypeS, + MfPlusTypeSE, + MfPlusTypeX, + + MfPlusTypeUnknown, + MfPlusTypeNum, +} MfPlusType; + +typedef enum { + MfPlusSize1K, + MfPlusSize2K, + MfPlusSize4K, + + MfPlusSizeUnknown, + MfPlusSizeNum, +} MfPlusSize; + +typedef enum { + MfPlusSecurityLevel0, + MfPlusSecurityLevel1, + MfPlusSecurityLevel2, + MfPlusSecurityLevel3, + + MfPlusSecurityLevelUnknown, + MfPlusSecurityLevelNum, +} MfPlusSecurityLevel; + +typedef struct { + uint8_t hw_vendor; + uint8_t hw_type; + uint8_t hw_subtype; + uint8_t hw_major; + uint8_t hw_minor; + uint8_t hw_storage; + uint8_t hw_proto; + + uint8_t sw_vendor; + uint8_t sw_type; + uint8_t sw_subtype; + uint8_t sw_major; + uint8_t sw_minor; + uint8_t sw_storage; + uint8_t sw_proto; + + uint8_t uid[MF_PLUS_UID_SIZE]; + uint8_t batch[MF_PLUS_BATCH_SIZE]; + uint8_t prod_week; + uint8_t prod_year; +} MfPlusVersion; + +typedef struct { + Iso14443_4aData* iso14443_4a_data; + MfPlusVersion version; + MfPlusType type; + MfPlusSize size; + MfPlusSecurityLevel security_level; + FuriString* device_name; +} MfPlusData; + +extern const NfcDeviceBase nfc_device_mf_plus; + +MfPlusData* mf_plus_alloc(void); + +void mf_plus_free(MfPlusData* data); + +void mf_plus_reset(MfPlusData* data); + +void mf_plus_copy(MfPlusData* data, const MfPlusData* other); + +bool mf_plus_verify(MfPlusData* data, const FuriString* device_type); + +bool mf_plus_load(MfPlusData* data, FlipperFormat* ff, uint32_t version); + +bool mf_plus_save(const MfPlusData* data, FlipperFormat* ff); + +bool mf_plus_is_equal(const MfPlusData* data, const MfPlusData* other); + +const char* mf_plus_get_device_name(const MfPlusData* data, NfcDeviceNameType name_type); + +const uint8_t* mf_plus_get_uid(const MfPlusData* data, size_t* uid_len); + +bool mf_plus_set_uid(MfPlusData* data, const uint8_t* uid, size_t uid_len); + +Iso14443_4aData* mf_plus_get_base_data(const MfPlusData* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.c b/lib/nfc/protocols/mf_plus/mf_plus_i.c new file mode 100644 index 000000000..c248d2568 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.c @@ -0,0 +1,216 @@ +#include "mf_plus_i.h" + +#define MF_PLUS_FFF_VERSION_KEY \ + MF_PLUS_FFF_PICC_PREFIX " " \ + "Version" + +#define MF_PLUS_FFF_SECURITY_LEVEL_KEY "Security Level" +#define MF_PLUS_FFF_CARD_TYPE_KEY "Card Type" +#define MF_PLUS_FFF_MEMORY_SIZE_KEY "Memory Size" + +bool mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusVersion); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(MfPlusVersion)); + } + + return can_parse; +} + +bool mf_plus_security_level_parse(MfPlusSecurityLevel* data, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusSecurityLevel); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(MfPlusSecurityLevel)); + } + + return can_parse; +} + +bool mf_plus_type_parse(MfPlusType* data, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusType); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(MfPlusType)); + } + + return can_parse; +} + +bool mf_plus_size_parse(MfPlusSize* data, const BitBuffer* buf) { + const bool can_parse = bit_buffer_get_size_bytes(buf) == sizeof(MfPlusSize); + + if(can_parse) { + bit_buffer_write_bytes(buf, data, sizeof(MfPlusSize)); + } + + return can_parse; +} + +bool mf_plus_version_load(MfPlusVersion* data, FlipperFormat* ff) { + return flipper_format_read_hex( + ff, MF_PLUS_FFF_VERSION_KEY, (uint8_t*)data, sizeof(MfPlusVersion)); +} + +bool mf_plus_security_level_load(MfPlusSecurityLevel* data, FlipperFormat* ff) { + FuriString* security_level_string = furi_string_alloc(); + flipper_format_read_string(ff, MF_PLUS_FFF_SECURITY_LEVEL_KEY, security_level_string); + + // Take the last character of the string + char security_level_char = furi_string_get_char( + security_level_string, furi_string_utf8_length(security_level_string) - 1); + + switch(security_level_char) { + case '0': + *data = MfPlusSecurityLevel0; + break; + case '1': + *data = MfPlusSecurityLevel1; + break; + case '2': + *data = MfPlusSecurityLevel2; + break; + case '3': + *data = MfPlusSecurityLevel3; + break; + default: + *data = MfPlusSecurityLevelUnknown; + break; + } + + furi_string_free(security_level_string); + + return true; +} + +bool mf_plus_type_load(MfPlusType* data, FlipperFormat* ff) { + FuriString* type_string = furi_string_alloc(); + flipper_format_read_string(ff, MF_PLUS_FFF_CARD_TYPE_KEY, type_string); + + if(furi_string_equal_str(type_string, "Mifare Plus")) { + *data = MfPlusTypePlus; + } else if(furi_string_equal_str(type_string, "Mifare Plus X")) { + *data = MfPlusTypeX; + } else if(furi_string_equal_str(type_string, "Mifare Plus S")) { + *data = MfPlusTypeS; + } else if(furi_string_equal_str(type_string, "Mifare Plus SE")) { + *data = MfPlusTypeSE; + } else if(furi_string_equal_str(type_string, "Mifare Plus EV1")) { + *data = MfPlusTypeEV1; + } else if(furi_string_equal_str(type_string, "Mifare Plus EV2")) { + *data = MfPlusTypeEV2; + } else { + *data = MfPlusTypeUnknown; + } + + furi_string_free(type_string); + return true; +} + +bool mf_plus_size_load(MfPlusSize* data, FlipperFormat* ff) { + FuriString* size_string = furi_string_alloc(); + flipper_format_read_string(ff, MF_PLUS_FFF_MEMORY_SIZE_KEY, size_string); + + if(furi_string_equal_str(size_string, "1K")) { + *data = MfPlusSize1K; + } else if(furi_string_equal_str(size_string, "2K")) { + *data = MfPlusSize2K; + } else if(furi_string_equal_str(size_string, "4K")) { + *data = MfPlusSize4K; + } else { + *data = MfPlusSizeUnknown; + } + + furi_string_free(size_string); + return true; +} + +bool mf_plus_version_save(const MfPlusVersion* data, FlipperFormat* ff) { + return flipper_format_write_hex( + ff, MF_PLUS_FFF_VERSION_KEY, (const uint8_t*)data, sizeof(MfPlusVersion)); +} + +bool mf_plus_security_level_save(const MfPlusSecurityLevel* data, FlipperFormat* ff) { + FuriString* security_level_string = furi_string_alloc(); + + switch(*data) { + case MfPlusSecurityLevel0: + furi_string_cat(security_level_string, "SL0"); + break; + case MfPlusSecurityLevel1: + furi_string_cat(security_level_string, "SL1"); + break; + case MfPlusSecurityLevel2: + furi_string_cat(security_level_string, "SL2"); + break; + case MfPlusSecurityLevel3: + furi_string_cat(security_level_string, "SL3"); + break; + default: + furi_string_cat(security_level_string, "Unknown"); + break; + } + + flipper_format_write_string(ff, MF_PLUS_FFF_SECURITY_LEVEL_KEY, security_level_string); + furi_string_free(security_level_string); + + return true; +} + +bool mf_plus_type_save(const MfPlusType* data, FlipperFormat* ff) { + FuriString* type_string = furi_string_alloc(); + + switch(*data) { + case MfPlusTypePlus: + furi_string_cat(type_string, "Mifare Plus"); + break; + case MfPlusTypeX: + furi_string_cat(type_string, "Mifare Plus X"); + break; + case MfPlusTypeS: + furi_string_cat(type_string, "Mifare Plus S"); + break; + case MfPlusTypeSE: + furi_string_cat(type_string, "Mifare Plus SE"); + break; + case MfPlusTypeEV1: + furi_string_cat(type_string, "Mifare Plus EV1"); + break; + case MfPlusTypeEV2: + furi_string_cat(type_string, "Mifare Plus EV2"); + break; + default: + furi_string_cat(type_string, "Unknown"); + break; + } + + flipper_format_write_string(ff, MF_PLUS_FFF_CARD_TYPE_KEY, type_string); + furi_string_free(type_string); + + return true; +} + +bool mf_plus_size_save(const MfPlusSize* data, FlipperFormat* ff) { + FuriString* size_string = furi_string_alloc(); + + switch(*data) { + case MfPlusSize1K: + furi_string_cat(size_string, "1K"); + break; + case MfPlusSize2K: + furi_string_cat(size_string, "2K"); + break; + case MfPlusSize4K: + furi_string_cat(size_string, "4K"); + break; + default: + furi_string_cat(size_string, "Unknown"); + break; + } + + flipper_format_write_string(ff, MF_PLUS_FFF_MEMORY_SIZE_KEY, size_string); + furi_string_free(size_string); + + return true; +} \ No newline at end of file diff --git a/lib/nfc/protocols/mf_plus/mf_plus_i.h b/lib/nfc/protocols/mf_plus/mf_plus_i.h new file mode 100644 index 000000000..8ced4bdd0 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_i.h @@ -0,0 +1,29 @@ +#pragma once + +#include "mf_plus.h" + +#define MF_PLUS_FFF_PICC_PREFIX "PICC" + +bool mf_plus_version_parse(MfPlusVersion* data, const BitBuffer* buf); + +bool mf_plus_security_level_parse(MfPlusSecurityLevel* data, const BitBuffer* buf); + +bool mf_plus_type_parse(MfPlusType* data, const BitBuffer* buf); + +bool mf_plus_size_parse(MfPlusSize* data, const BitBuffer* buf); + +bool mf_plus_version_load(MfPlusVersion* data, FlipperFormat* ff); + +bool mf_plus_security_level_load(MfPlusSecurityLevel* data, FlipperFormat* ff); + +bool mf_plus_type_load(MfPlusType* data, FlipperFormat* ff); + +bool mf_plus_size_load(MfPlusSize* data, FlipperFormat* ff); + +bool mf_plus_version_save(const MfPlusVersion* data, FlipperFormat* ff); + +bool mf_plus_security_level_save(const MfPlusSecurityLevel* data, FlipperFormat* ff); + +bool mf_plus_type_save(const MfPlusType* data, FlipperFormat* ff); + +bool mf_plus_size_save(const MfPlusSize* data, FlipperFormat* ff); diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller.c b/lib/nfc/protocols/mf_plus/mf_plus_poller.c new file mode 100644 index 000000000..a218229c5 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller.c @@ -0,0 +1,367 @@ +#include "mf_plus_poller_i.h" + +#include + +#include + +#define TAG "MfPlusPoller" + +#define MF_PLUS_BUF_SIZE (64U) +#define MF_PLUS_RESULT_BUF_SIZE (512U) + +const char* mf_plus_ats_t1_tk_values[] = { + "\xC1\x05\x2F\x2F\x00\x35\xC7", // Mifare Plus S + "\xC1\x05\x2F\x2F\x01\xBC\xD6", // Mifare Plus X + "\xC1\x05\x2F\x2F\x00\xF6\xD1", // Mifare Plus SE + "\xC1\x05\x2F\x2F\x01\xF6\xD1", // Mifare Plus SE +}; + +typedef NfcCommand (*MfPlusPollerReadHandler)(MfPlusPoller* instance); + +const MfPlusData* mf_plus_poller_get_data(MfPlusPoller* instance) { + furi_assert(instance); + + return instance->data; +} + +bool mf_plus_poller_get_type_from_iso4(const Iso14443_4aData* iso4_data, MfPlusData* mf_plus_data) { + furi_assert(iso4_data); + furi_assert(mf_plus_data); + + switch(iso4_data->iso14443_3a_data->sak) { + case 0x08: + if(memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[0], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus S 2K SL1 + mf_plus_data->type = MfPlusTypeS; + mf_plus_data->size = MfPlusSize2K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus S 2K SL1"); + return true; + } else if( + memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[1], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus X 2K SL1 + mf_plus_data->type = MfPlusTypeX; + mf_plus_data->size = MfPlusSize2K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus X 2K SL1"); + return true; + } else if( + memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[2], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0 || + memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[3], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus SE 1K SL1 + mf_plus_data->type = MfPlusTypeSE; + mf_plus_data->size = MfPlusSize1K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus SE 1K SL1"); + return true; + } else { + FURI_LOG_D(TAG, "Sak 08 but no known Mifare Plus type"); + return false; + } + case 0x18: + if(memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[0], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus S 4K SL1 + mf_plus_data->type = MfPlusTypeS; + mf_plus_data->size = MfPlusSize4K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus S 4K SL1"); + return true; + } else if( + memcmp( + simple_array_get_data(iso4_data->ats_data.t1_tk), + mf_plus_ats_t1_tk_values[1], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus X 4K SL1 + mf_plus_data->type = MfPlusTypeX; + mf_plus_data->size = MfPlusSize4K; + mf_plus_data->security_level = MfPlusSecurityLevel1; + FURI_LOG_D(TAG, "Mifare Plus X 4K SL1"); + return true; + } else { + FURI_LOG_D(TAG, "Sak 18 but no known Mifare Plus type"); + return false; + } + case 0x20: + if(memcmp( + iso4_data->ats_data.t1_tk, + mf_plus_ats_t1_tk_values[0], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + // Mifare Plus S 2/4K SL3 + mf_plus_data->type = MfPlusTypeS; + mf_plus_data->security_level = MfPlusSecurityLevel3; + + if(iso4_data->iso14443_3a_data->atqa[1] == 0x04) { + // Mifare Plus S 2K SL3 + mf_plus_data->size = MfPlusSize2K; + FURI_LOG_D(TAG, "Mifare Plus S 2K SL3"); + } else if(iso4_data->iso14443_3a_data->atqa[1] == 0x02) { + // Mifare Plus S 4K SL3 + mf_plus_data->size = MfPlusSize4K; + FURI_LOG_D(TAG, "Mifare Plus S 4K SL3"); + } else { + FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (S)"); + return false; + } + return true; + + } else if( + memcmp( + iso4_data->ats_data.t1_tk, + mf_plus_ats_t1_tk_values[1], + simple_array_get_count(iso4_data->ats_data.t1_tk)) == 0) { + mf_plus_data->type = MfPlusTypeX; + mf_plus_data->security_level = MfPlusSecurityLevel3; + + if(iso4_data->iso14443_3a_data->atqa[1] == 0x04) { + mf_plus_data->size = MfPlusSize2K; + FURI_LOG_D(TAG, "Mifare Plus X 2K SL3"); + } else if(iso4_data->iso14443_3a_data->atqa[1] == 0x02) { + mf_plus_data->size = MfPlusSize4K; + FURI_LOG_D(TAG, "Mifare Plus X 4K SL3"); + } else { + FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type (X)"); + return false; + } + return true; + } else { + FURI_LOG_D(TAG, "Sak 20 but no known Mifare Plus type"); + return false; + } + } + + FURI_LOG_D(TAG, "No known Mifare Plus type"); + return false; +} + +static bool mf_plus_poller_detect_type(MfPlusPoller* instance) { + furi_assert(instance); + + bool detected = false; + + const Iso14443_4aData* iso14443_4a_data = + iso14443_4a_poller_get_data(instance->iso14443_4a_poller); + const MfPlusError error = mf_plus_poller_read_version(instance, &instance->data->version); + if(error == MfPlusErrorNone) { + FURI_LOG_D(TAG, "Read version success: %d", error); + if(instance->data->version.hw_major == 0x02 || instance->data->version.hw_major == 0x82) { + detected = true; + if(iso14443_4a_data->iso14443_3a_data->sak == 0x10) { + // Mifare Plus 2K SL2 + instance->data->type = MfPlusTypePlus; + instance->data->size = MfPlusSize2K; + instance->data->security_level = MfPlusSecurityLevel2; + } else if(iso14443_4a_data->iso14443_3a_data->sak == 0x11) { + // Mifare Plus 4K SL3 + instance->data->type = MfPlusTypePlus; + instance->data->size = MfPlusSize4K; + instance->data->security_level = MfPlusSecurityLevel3; + } else { + // Mifare Plus EV1/EV2 + + // Revision + switch(instance->data->version.hw_major) { + case 0x11: + instance->data->type = MfPlusTypeEV1; + break; + case 0x22: + instance->data->type = MfPlusTypeEV2; + break; + default: + instance->data->type = MfPlusTypeUnknown; + break; + } + + // Storage size + switch(instance->data->version.hw_storage) { + case 0x16: + instance->data->size = MfPlusSize2K; + break; + case 0x18: + instance->data->size = MfPlusSize4K; + break; + default: + instance->data->size = MfPlusSizeUnknown; + break; + } + + // Security level + if(iso14443_4a_data->iso14443_3a_data->sak == 0x20) { + // Mifare Plus EV1/2 SL3 + instance->data->security_level = MfPlusSecurityLevel3; + } else { + // Mifare Plus EV1/2 SL1 + instance->data->security_level = MfPlusSecurityLevel1; + } + } + } + + } else { + FURI_LOG_D(TAG, "Read version error: %d", error); + detected = mf_plus_poller_get_type_from_iso4(iso14443_4a_data, instance->data); + } + + return detected; +} + +MfPlusPoller* mf_plus_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller) { + furi_assert(iso14443_4a_poller); + + MfPlusPoller* instance = malloc(sizeof(MfPlusPoller)); + furi_assert(instance); + + instance->iso14443_4a_poller = iso14443_4a_poller; + + instance->data = mf_plus_alloc(); + + instance->tx_buffer = bit_buffer_alloc(MF_PLUS_BUF_SIZE); + instance->rx_buffer = bit_buffer_alloc(MF_PLUS_BUF_SIZE); + instance->input_buffer = bit_buffer_alloc(MF_PLUS_BUF_SIZE); + instance->result_buffer = bit_buffer_alloc(MF_PLUS_RESULT_BUF_SIZE); + + instance->general_event.protocol = NfcProtocolMfPlus; + instance->general_event.event_data = &instance->mfp_event; + instance->general_event.instance = instance; + + instance->mfp_event.data = &instance->mfp_event_data; + + return instance; +} + +static NfcCommand mf_plus_poller_handler_idle(MfPlusPoller* instance) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_reset(instance->result_buffer); + bit_buffer_reset(instance->tx_buffer); + bit_buffer_reset(instance->rx_buffer); + + iso14443_4a_copy( + instance->data->iso14443_4a_data, + iso14443_4a_poller_get_data(instance->iso14443_4a_poller)); + + instance->state = MfPlusPollerStateReadVersion; + return NfcCommandContinue; +} + +static NfcCommand mf_plus_poller_handler_read_version(MfPlusPoller* instance) { + bool success = mf_plus_poller_detect_type(instance); + if(success) { + instance->state = MfPlusPollerStateReadSuccess; + } else { + instance->state = MfPlusPollerStateReadFailed; + } + + return NfcCommandContinue; +} + +static NfcCommand mf_plus_poller_handler_read_failed(MfPlusPoller* instance) { + furi_assert(instance); + FURI_LOG_D(TAG, "Read failed"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->mfp_event.data->error = instance->error; + NfcCommand command = instance->callback(instance->general_event, instance->context); + instance->state = MfPlusPollerStateIdle; + return command; +} + +static NfcCommand mf_plus_poller_handler_read_success(MfPlusPoller* instance) { + furi_assert(instance); + FURI_LOG_D(TAG, "Read success"); + iso14443_4a_poller_halt(instance->iso14443_4a_poller); + instance->mfp_event.type = MfPlusPollerEventTypeReadSuccess; + NfcCommand command = instance->callback(instance->general_event, instance->context); + return command; +} + +static const MfPlusPollerReadHandler mf_plus_poller_read_handler[MfPlusPollerStateNum] = { + [MfPlusPollerStateIdle] = mf_plus_poller_handler_idle, + [MfPlusPollerStateReadVersion] = mf_plus_poller_handler_read_version, + [MfPlusPollerStateReadFailed] = mf_plus_poller_handler_read_failed, + [MfPlusPollerStateReadSuccess] = mf_plus_poller_handler_read_success, +}; + +static void mf_plus_poller_set_callback( + MfPlusPoller* instance, + NfcGenericCallback callback, + void* context) { + furi_assert(instance); + furi_assert(callback); + + instance->callback = callback; + instance->context = context; +} + +static NfcCommand mf_plus_poller_run(NfcGenericEvent event, void* context) { + furi_assert(event.protocol = NfcProtocolIso14443_4a); + + MfPlusPoller* instance = context; + furi_assert(instance); + + const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + NfcCommand command = NfcCommandContinue; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + command = mf_plus_poller_read_handler[instance->state](instance); + } else if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeError) { + instance->mfp_event.type = MfPlusPollerEventTypeReadFailed; + command = instance->callback(instance->general_event, instance->context); + } + + return command; +} + +void mf_plus_poller_free(MfPlusPoller* instance) { + furi_assert(instance); + furi_assert(instance->data); + + bit_buffer_free(instance->tx_buffer); + bit_buffer_free(instance->rx_buffer); + bit_buffer_free(instance->input_buffer); + bit_buffer_free(instance->result_buffer); + mf_plus_free(instance->data); + free(instance); +} + +static bool mf_plus_poller_detect(NfcGenericEvent event, void* context) { + furi_assert(event.protocol = NfcProtocolIso14443_4a); + + MfPlusPoller* instance = context; + furi_assert(instance); + + Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data; + furi_assert(iso14443_4a_event); + + bool detected = false; + + if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) { + detected = mf_plus_poller_detect_type(instance); + } + + return detected; +} + +const NfcPollerBase mf_plus_poller = { + .alloc = (NfcPollerAlloc)mf_plus_poller_alloc, + .free = (NfcPollerFree)mf_plus_poller_free, + .set_callback = (NfcPollerSetCallback)mf_plus_poller_set_callback, + .run = (NfcPollerRun)mf_plus_poller_run, + .detect = (NfcPollerDetect)mf_plus_poller_detect, + .get_data = (NfcPollerGetData)mf_plus_poller_get_data, +}; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller.h b/lib/nfc/protocols/mf_plus/mf_plus_poller.h new file mode 100644 index 000000000..7e892366f --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller.h @@ -0,0 +1,55 @@ +#pragma once + +#include "mf_plus.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief MIFARE Plus poller opaque type definition. + */ +typedef struct MfPlusPoller MfPlusPoller; + +/** + * @brief Enumeration of possible MfPlus poller event types. + */ + +typedef enum { + MfPlusPollerEventTypeReadSuccess, /**< Card was read successfully. */ + MfPlusPollerEventTypeReadFailed, /**< Poller failed to read the card. */ +} MfPlusPollerEventType; + +/** + * @brief MIFARE Plus poller event data. + */ +typedef union { + MfPlusError error; /**< Error code indicating card reading fail reason. */ +} MfPlusPollerEventData; + +/** + * @brief MIFARE Plus poller event structure. + * + * Upon emission of an event, an instance of this struct will be passed to the callback. + */ +typedef struct { + MfPlusPollerEventType type; /**< Type of emitted event. */ + MfPlusPollerEventData* data; /**< Pointer to event specific data. */ +} MfPlusPollerEvent; + +/** + * @brief Read MfPlus card version. + * + * Must ONLY be used inside the callback function. + * + * @param[in, out] instance pointer to the instance to be used in the transaction. + * @param[out] data pointer to the MfPlusVersion structure to be filled with version data. + * @return MfPlusErrorNone on success, an error code on failure. + */ +MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_defs.h b/lib/nfc/protocols/mf_plus/mf_plus_poller_defs.h new file mode 100644 index 000000000..366eb5116 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_defs.h @@ -0,0 +1,5 @@ +#pragma once + +#include + +extern const NfcPollerBase mf_plus_poller; diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c new file mode 100644 index 000000000..1a4298b67 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.c @@ -0,0 +1,73 @@ +#include "mf_plus_poller_i.h" + +#include + +#include "mf_plus_i.h" + +#define TAG "MfPlusPoller" + +MfPlusError mf_plus_process_error(Iso14443_4aError error) { + switch(error) { + case Iso14443_4aErrorNone: + return MfPlusErrorNone; + case Iso14443_4aErrorNotPresent: + return MfPlusErrorNotPresent; + case Iso14443_4aErrorTimeout: + return MfPlusErrorTimeout; + default: + return MfPlusErrorProtocol; + } +} + +MfPlusError + mf_plus_send_chunk(MfPlusPoller* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer) { + furi_assert(instance); + furi_assert(instance->iso14443_4a_poller); + furi_assert(instance->tx_buffer); + furi_assert(instance->rx_buffer); + furi_assert(tx_buffer); + furi_assert(rx_buffer); + + MfPlusError error = MfPlusErrorNone; + + do { + Iso14443_4aError iso14443_4a_error = iso14443_4a_poller_send_block( + instance->iso14443_4a_poller, tx_buffer, instance->rx_buffer); + + if(iso14443_4a_error != Iso14443_4aErrorNone) { + error = mf_plus_process_error(iso14443_4a_error); + break; + } + + bit_buffer_reset(instance->tx_buffer); + + if(bit_buffer_get_size_bytes(instance->rx_buffer) > sizeof(uint8_t)) { + bit_buffer_copy_right(rx_buffer, instance->rx_buffer, sizeof(uint8_t)); + } else { + bit_buffer_reset(rx_buffer); + } + } while(false); + + return error; +} + +MfPlusError mf_plus_poller_read_version(MfPlusPoller* instance, MfPlusVersion* data) { + furi_assert(instance); + + bit_buffer_reset(instance->input_buffer); + bit_buffer_append_byte(instance->input_buffer, MF_PLUS_CMD_GET_VERSION); + + MfPlusError error; + + do { + error = mf_plus_send_chunk(instance, instance->input_buffer, instance->result_buffer); + + if(error != MfPlusErrorNone) break; + + if(!mf_plus_version_parse(data, instance->result_buffer)) { + error = MfPlusErrorProtocol; + } + } while(false); + + return error; +} diff --git a/lib/nfc/protocols/mf_plus/mf_plus_poller_i.h b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.h new file mode 100644 index 000000000..79f46b8d8 --- /dev/null +++ b/lib/nfc/protocols/mf_plus/mf_plus_poller_i.h @@ -0,0 +1,54 @@ +#pragma once + +#include "mf_plus_poller.h" + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define MF_PLUS_FWT_FC (60000) + +typedef enum { + MfPlusCardStateDetected, + MfPlusCardStateLost, +} MfPlusCardState; + +typedef enum { + MfPlusPollerStateIdle, + MfPlusPollerStateReadVersion, + MfPlusPollerStateReadFailed, + MfPlusPollerStateReadSuccess, + + MfPlusPollerStateNum, +} MfPlusPollerState; + +struct MfPlusPoller { + Iso14443_4aPoller* iso14443_4a_poller; + + MfPlusData* data; + MfPlusPollerState state; + + BitBuffer* tx_buffer; + BitBuffer* rx_buffer; + BitBuffer* input_buffer; + BitBuffer* result_buffer; + + MfPlusError error; + NfcGenericEvent general_event; + MfPlusPollerEvent mfp_event; + MfPlusPollerEventData mfp_event_data; + NfcGenericCallback callback; + void* context; +}; + +MfPlusError mf_plus_process_error(Iso14443_4aError error); + +MfPlusPoller* mf_plus_poller_alloc(Iso14443_4aPoller* iso14443_4a_poller); + +void mf_plus_poller_free(MfPlusPoller* instance); + +#ifdef __cplusplus +} +#endif diff --git a/lib/nfc/protocols/nfc_device_defs.c b/lib/nfc/protocols/nfc_device_defs.c index e09523f23..36a58a243 100644 --- a/lib/nfc/protocols/nfc_device_defs.c +++ b/lib/nfc/protocols/nfc_device_defs.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -40,6 +41,7 @@ const NfcDeviceBase* nfc_devices[NfcProtocolNum] = { [NfcProtocolFelica] = &nfc_device_felica, [NfcProtocolMfUltralight] = &nfc_device_mf_ultralight, [NfcProtocolMfClassic] = &nfc_device_mf_classic, + [NfcProtocolMfPlus] = &nfc_device_mf_plus, [NfcProtocolMfDesfire] = &nfc_device_mf_desfire, [NfcProtocolSlix] = &nfc_device_slix, [NfcProtocolSt25tb] = &nfc_device_st25tb, diff --git a/lib/nfc/protocols/nfc_poller_defs.c b/lib/nfc/protocols/nfc_poller_defs.c index e79c96d98..a4717f1b1 100644 --- a/lib/nfc/protocols/nfc_poller_defs.c +++ b/lib/nfc/protocols/nfc_poller_defs.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -22,6 +23,7 @@ const NfcPollerBase* nfc_pollers_api[NfcProtocolNum] = { [NfcProtocolFelica] = &nfc_poller_felica, [NfcProtocolMfUltralight] = &mf_ultralight_poller, [NfcProtocolMfClassic] = &mf_classic_poller, + [NfcProtocolMfPlus] = &mf_plus_poller, [NfcProtocolMfDesfire] = &mf_desfire_poller, [NfcProtocolSlix] = &nfc_poller_slix, [NfcProtocolSt25tb] = &nfc_poller_st25tb, diff --git a/lib/nfc/protocols/nfc_protocol.c b/lib/nfc/protocols/nfc_protocol.c index 17c39fc9f..4106e689a 100644 --- a/lib/nfc/protocols/nfc_protocol.c +++ b/lib/nfc/protocols/nfc_protocol.c @@ -63,6 +63,7 @@ static const NfcProtocol nfc_protocol_iso14443_3b_children_protocol[] = { /** List of ISO14443-4A child protocols. */ static const NfcProtocol nfc_protocol_iso14443_4a_children_protocol[] = { NfcProtocolMfDesfire, + NfcProtocolMfPlus, NfcProtocolEmv, }; @@ -131,6 +132,12 @@ static const NfcProtocolTreeNode nfc_protocol_nodes[NfcProtocolNum] = { .children_num = 0, .children_protocol = NULL, }, + [NfcProtocolMfPlus] = + { + .parent_protocol = NfcProtocolIso14443_4a, + .children_num = 0, + .children_protocol = NULL, + }, [NfcProtocolMfDesfire] = { .parent_protocol = NfcProtocolIso14443_4a, diff --git a/lib/nfc/protocols/nfc_protocol.h b/lib/nfc/protocols/nfc_protocol.h index 39e8045fe..12866528e 100644 --- a/lib/nfc/protocols/nfc_protocol.h +++ b/lib/nfc/protocols/nfc_protocol.h @@ -184,6 +184,7 @@ typedef enum { NfcProtocolFelica, NfcProtocolMfUltralight, NfcProtocolMfClassic, + NfcProtocolMfPlus, NfcProtocolMfDesfire, NfcProtocolSlix, NfcProtocolSt25tb, diff --git a/lib/subghz/protocols/keeloq.c b/lib/subghz/protocols/keeloq.c index cd0c3f8c6..0da58a78c 100644 --- a/lib/subghz/protocols/keeloq.c +++ b/lib/subghz/protocols/keeloq.c @@ -428,6 +428,9 @@ static bool (strcmp(instance->manufacture_name, "Dea_Mio") == 0)) { klq_last_custom_btn = 0xF; } + if((strcmp(instance->manufacture_name, "FAAC_RC,XT") == 0)) { + klq_last_custom_btn = 0xB; + } btn = subghz_protocol_keeloq_get_btn_code(klq_last_custom_btn); diff --git a/lib/toolbox/pretty_format.c b/lib/toolbox/pretty_format.c index 2dcfdb6d9..f8319b69d 100644 --- a/lib/toolbox/pretty_format.c +++ b/lib/toolbox/pretty_format.c @@ -28,8 +28,7 @@ void pretty_format_bytes_hex_canonical( const size_t line_length = (line_prefix ? strlen(line_prefix) : 0) + 4 * num_places + 2; /* Reserve memory in adance in order to avoid unnecessary reallocs */ - furi_string_reset(result); - furi_string_reserve(result, line_count * line_length); + furi_string_reserve(result, furi_string_size(result) + line_count * line_length); for(size_t i = 0; i < data_size; i += num_places) { if(line_prefix) { diff --git a/lib/toolbox/protocols/protocol_dict.c b/lib/toolbox/protocols/protocol_dict.c index e8da4b1cd..71fa4fe28 100644 --- a/lib/toolbox/protocols/protocol_dict.c +++ b/lib/toolbox/protocols/protocol_dict.c @@ -198,12 +198,21 @@ LevelDuration protocol_dict_encoder_yield(ProtocolDict* dict, size_t protocol_in } } +void protocol_dict_render_uid(ProtocolDict* dict, FuriString* result, size_t protocol_index) { + furi_check(protocol_index < dict->count); + ProtocolRenderData fn = dict->base[protocol_index]->render_uid; + + if(fn) { + fn(dict->data[protocol_index], result); + } +} + void protocol_dict_render_data(ProtocolDict* dict, FuriString* result, size_t protocol_index) { furi_check(protocol_index < dict->count); ProtocolRenderData fn = dict->base[protocol_index]->render_data; if(fn) { - return fn(dict->data[protocol_index], result); + fn(dict->data[protocol_index], result); } } @@ -212,7 +221,7 @@ void protocol_dict_render_brief_data(ProtocolDict* dict, FuriString* result, siz ProtocolRenderData fn = dict->base[protocol_index]->render_brief_data; if(fn) { - return fn(dict->data[protocol_index], result); + fn(dict->data[protocol_index], result); } } diff --git a/lib/toolbox/protocols/protocol_dict.h b/lib/toolbox/protocols/protocol_dict.h index cd8503952..dd0711732 100644 --- a/lib/toolbox/protocols/protocol_dict.h +++ b/lib/toolbox/protocols/protocol_dict.h @@ -58,6 +58,8 @@ bool protocol_dict_encoder_start(ProtocolDict* dict, size_t protocol_index); LevelDuration protocol_dict_encoder_yield(ProtocolDict* dict, size_t protocol_index); +void protocol_dict_render_uid(ProtocolDict* dict, FuriString* result, size_t protocol_index); + void protocol_dict_render_data(ProtocolDict* dict, FuriString* result, size_t protocol_index); void protocol_dict_render_brief_data(ProtocolDict* dict, FuriString* result, size_t protocol_index); diff --git a/targets/f18/api_symbols.csv b/targets/f18/api_symbols.csv index 7cc7639bd..3cee044aa 100644 --- a/targets/f18/api_symbols.csv +++ b/targets/f18/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.1,, +Version,+,61.2,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, Header,+,applications/services/cli/cli_vcp.h,, @@ -2251,6 +2251,7 @@ Function,+,protocol_dict_get_validate_count,uint32_t,"ProtocolDict*, size_t" Function,+,protocol_dict_get_write_data,_Bool,"ProtocolDict*, size_t, void*" Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" +Function,+,protocol_dict_render_uid,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t" Function,-,pulse_reader_free,void,PulseReader* diff --git a/targets/f7/api_symbols.csv b/targets/f7/api_symbols.csv index 48c943a04..5794717c5 100644 --- a/targets/f7/api_symbols.csv +++ b/targets/f7/api_symbols.csv @@ -1,5 +1,5 @@ entry,status,name,type,params -Version,+,61.1,, +Version,+,61.2,, Header,+,applications/drivers/subghz/cc1101_ext/cc1101_ext_interconnect.h,, Header,+,applications/services/bt/bt_service/bt.h,, Header,+,applications/services/cli/cli.h,, @@ -154,6 +154,8 @@ Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller.h,, Header,+,lib/nfc/protocols/mf_classic/mf_classic_poller_sync.h,, Header,+,lib/nfc/protocols/mf_desfire/mf_desfire.h,, Header,+,lib/nfc/protocols/mf_desfire/mf_desfire_poller.h,, +Header,+,lib/nfc/protocols/mf_plus/mf_plus.h,, +Header,+,lib/nfc/protocols/mf_plus/mf_plus_poller.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h,, Header,+,lib/nfc/protocols/mf_ultralight/mf_ultralight_poller.h,, @@ -2546,6 +2548,19 @@ Function,+,mf_desfire_save,_Bool,"const MfDesfireData*, FlipperFormat*" Function,+,mf_desfire_send_chunks,MfDesfireError,"MfDesfirePoller*, const BitBuffer*, BitBuffer*" Function,+,mf_desfire_set_uid,_Bool,"MfDesfireData*, const uint8_t*, size_t" Function,+,mf_desfire_verify,_Bool,"MfDesfireData*, const FuriString*" +Function,+,mf_plus_alloc,MfPlusData*, +Function,+,mf_plus_copy,void,"MfPlusData*, const MfPlusData*" +Function,+,mf_plus_free,void,MfPlusData* +Function,+,mf_plus_get_base_data,Iso14443_4aData*,const MfPlusData* +Function,+,mf_plus_get_device_name,const char*,"const MfPlusData*, NfcDeviceNameType" +Function,+,mf_plus_get_uid,const uint8_t*,"const MfPlusData*, size_t*" +Function,+,mf_plus_is_equal,_Bool,"const MfPlusData*, const MfPlusData*" +Function,+,mf_plus_load,_Bool,"MfPlusData*, FlipperFormat*, uint32_t" +Function,+,mf_plus_poller_read_version,MfPlusError,"MfPlusPoller*, MfPlusVersion*" +Function,+,mf_plus_reset,void,MfPlusData* +Function,+,mf_plus_save,_Bool,"const MfPlusData*, FlipperFormat*" +Function,+,mf_plus_set_uid,_Bool,"MfPlusData*, const uint8_t*, size_t" +Function,+,mf_plus_verify,_Bool,"MfPlusData*, const FuriString*" Function,+,mf_ultralight_alloc,MfUltralightData*, Function,+,mf_ultralight_copy,void,"MfUltralightData*, const MfUltralightData*" Function,+,mf_ultralight_detect_protocol,_Bool,const Iso14443_3aData* @@ -2898,6 +2913,7 @@ Function,+,protocol_dict_get_validate_count,uint32_t,"ProtocolDict*, size_t" Function,+,protocol_dict_get_write_data,_Bool,"ProtocolDict*, size_t, void*" Function,+,protocol_dict_render_brief_data,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_render_data,void,"ProtocolDict*, FuriString*, size_t" +Function,+,protocol_dict_render_uid,void,"ProtocolDict*, FuriString*, size_t" Function,+,protocol_dict_set_data,void,"ProtocolDict*, size_t, const uint8_t*, size_t" Function,-,pulse_reader_alloc,PulseReader*,"const GpioPin*, uint32_t" Function,-,pulse_reader_free,void,PulseReader* @@ -3858,6 +3874,7 @@ Variable,-,nfc_device_emv,const NfcDeviceBase, Variable,-,nfc_device_felica,const NfcDeviceBase, Variable,-,nfc_device_mf_classic,const NfcDeviceBase, Variable,-,nfc_device_mf_desfire,const NfcDeviceBase, +Variable,-,nfc_device_mf_plus,const NfcDeviceBase, Variable,-,nfc_device_mf_ultralight,const NfcDeviceBase, Variable,-,nfc_device_st25tb,const NfcDeviceBase, Variable,+,sequence_audiovisual_alert,const NotificationSequence,