diff --git a/scenes/seader_scene_config.h b/scenes/seader_scene_config.h index 09e9323..0b93c48 100644 --- a/scenes/seader_scene_config.h +++ b/scenes/seader_scene_config.h @@ -3,6 +3,7 @@ ADD_SCENE(seader, sam_present, SamPresent) ADD_SCENE(seader, sam_missing, SamMissing) ADD_SCENE(seader, sam_wrong, SamWrong) ADD_SCENE(seader, read, Read) +ADD_SCENE(seader, read_card_type, ReadCardType) ADD_SCENE(seader, read_config_card, ReadConfigCard) ADD_SCENE(seader, read_card_success, ReadCardSuccess) ADD_SCENE(seader, read_config_card_success, ReadConfigCardSuccess) diff --git a/scenes/seader_scene_read.c b/scenes/seader_scene_read.c index 47c7ecf..3ed3bac 100644 --- a/scenes/seader_scene_read.c +++ b/scenes/seader_scene_read.c @@ -16,6 +16,10 @@ void seader_scene_read_on_enter(void* context) { seader_scene_read_prepare(seader); seader_credential_clear(seader->credential); + if(seader->selected_read_type == SeaderCredentialTypeNone) { + seader->detected_card_type_count = 0; + memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types)); + } seader_worker_start( seader->worker, SeaderWorkerStateReading, @@ -43,8 +47,14 @@ bool seader_scene_read_on_event(void* context, SceneManagerEvent event) { event.event == SeaderWorkerEventSuccess) { scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardSuccess); consumed = true; + } else if(event.event == SeaderWorkerEventSelectCardType) { + scene_manager_next_scene(seader->scene_manager, SeaderSceneReadCardType); + consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { + seader->selected_read_type = SeaderCredentialTypeNone; + seader->detected_card_type_count = 0; + memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types)); scene_manager_search_and_switch_to_previous_scene( seader->scene_manager, SeaderSceneSamPresent); consumed = true; diff --git a/scenes/seader_scene_read_card_success.c b/scenes/seader_scene_read_card_success.c index 8897dbb..35a08be 100644 --- a/scenes/seader_scene_read_card_success.c +++ b/scenes/seader_scene_read_card_success.c @@ -20,6 +20,9 @@ void seader_scene_read_card_success_on_enter(void* context) { SeaderCredential* credential = seader->credential; PluginWiegand* plugin = seader->plugin_wiegand; Widget* widget = seader->widget; + seader->selected_read_type = SeaderCredentialTypeNone; + seader->detected_card_type_count = 0; + memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types)); // Use reusable strings instead of allocating new ones FuriString* type_str = seader->temp_string1; diff --git a/scenes/seader_scene_read_card_type.c b/scenes/seader_scene_read_card_type.c new file mode 100644 index 0000000..ef9bd0f --- /dev/null +++ b/scenes/seader_scene_read_card_type.c @@ -0,0 +1,68 @@ +#include "../seader_i.h" + +static const char* seader_scene_read_card_type_label(SeaderCredentialType type) { + switch(type) { + case SeaderCredentialType14A: + return "Read as 14443A"; + case SeaderCredentialTypeMifareClassic: + return "Read as Mifare Classic"; + case SeaderCredentialTypePicopass: + return "Read as Picopass"; + default: + return "Read"; + } +} + +void seader_scene_read_card_type_submenu_callback(void* context, uint32_t index) { + Seader* seader = context; + view_dispatcher_send_custom_event(seader->view_dispatcher, index); +} + +void seader_scene_read_card_type_on_enter(void* context) { + Seader* seader = context; + Submenu* submenu = seader->submenu; + + submenu_reset(submenu); + for(size_t i = 0; i < seader->detected_card_type_count; i++) { + const SeaderCredentialType type = seader->detected_card_types[i]; + submenu_add_item( + submenu, + seader_scene_read_card_type_label(type), + type, + seader_scene_read_card_type_submenu_callback, + seader); + } + + view_dispatcher_switch_to_view(seader->view_dispatcher, SeaderViewMenu); +} + +bool seader_scene_read_card_type_on_event(void* context, SceneManagerEvent event) { + Seader* seader = context; + bool consumed = false; + + if(event.type == SceneManagerEventTypeCustom) { + const SeaderCredentialType type = event.event; + if(type == SeaderCredentialType14A || type == SeaderCredentialTypeMifareClassic || + type == SeaderCredentialTypePicopass) { + seader->selected_read_type = type; + seader->detected_card_type_count = 0; + memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types)); + scene_manager_next_scene(seader->scene_manager, SeaderSceneRead); + consumed = true; + } + } else if(event.type == SceneManagerEventTypeBack) { + seader->selected_read_type = SeaderCredentialTypeNone; + seader->detected_card_type_count = 0; + memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types)); + scene_manager_search_and_switch_to_previous_scene( + seader->scene_manager, SeaderSceneSamPresent); + consumed = true; + } + + return consumed; +} + +void seader_scene_read_card_type_on_exit(void* context) { + Seader* seader = context; + submenu_reset(seader->submenu); +} diff --git a/seader.c b/seader.c index 3758792..f45f73a 100644 --- a/seader.c +++ b/seader.c @@ -33,6 +33,9 @@ Seader* seader_alloc() { seader->samCommand = SamCommand_PR_NOTHING; seader->sam_state = SeaderSamStateIdle; seader->sam_intent = SeaderSamIntentNone; + memset(seader->detected_card_types, 0, sizeof(seader->detected_card_types)); + seader->detected_card_type_count = 0; + seader->selected_read_type = SeaderCredentialTypeNone; seader->worker = seader_worker_alloc(); seader->view_dispatcher = view_dispatcher_alloc(); diff --git a/seader_i.h b/seader_i.h index 535e8b6..1648374 100644 --- a/seader_i.h +++ b/seader_i.h @@ -62,9 +62,10 @@ WorkerEvtCtrlLineSet | WorkerEvtSamTxComplete) #define WORKER_ALL_TX_EVENTS (WorkerEvtTxStop | WorkerEvtSamRx) -#define SEADER_TEXT_STORE_SIZE 128 -#define SEADER_MAX_ATR_SIZE 33 -#define MAX_FRAME_HEADERS 32 +#define SEADER_TEXT_STORE_SIZE 128 +#define SEADER_MAX_ATR_SIZE 33 +#define MAX_FRAME_HEADERS 32 +#define SEADER_MAX_DETECTED_CARD_TYPES 3 enum SeaderCustomEvent { // Reserve first 100 events for button types and indexes, starting from 0 @@ -154,6 +155,9 @@ struct Seader { PicopassPoller* picopass_poller; NfcDevice* nfc_device; + SeaderCredentialType detected_card_types[SEADER_MAX_DETECTED_CARD_TYPES]; + size_t detected_card_type_count; + SeaderCredentialType selected_read_type; PluginManager* plugin_manager; PluginWiegand* plugin_wiegand; diff --git a/seader_worker.c b/seader_worker.c index bb3e15a..e7ce4b9 100644 --- a/seader_worker.c +++ b/seader_worker.c @@ -19,6 +19,135 @@ void seader_send_card_detected(SeaderUartBridge* seader_uart, CardDetails_t* car void seader_worker_reading(Seader* seader); void seader_worker_poller_conversation(Seader* seader, SeaderPollerContainer* spc); +typedef struct { + bool done; + bool detected; +} SeaderPicopassDetectContext; + +static NfcCommand + seader_worker_picopass_detect_callback(PicopassPollerEvent event, void* context) { + SeaderPicopassDetectContext* detect_context = context; + + if(event.type == PicopassPollerEventTypeCardDetected || + event.type == PicopassPollerEventTypeSuccess) { + detect_context->detected = true; + detect_context->done = true; + return NfcCommandStop; + } else if(event.type == PicopassPollerEventTypeFail) { + detect_context->done = true; + return NfcCommandStop; + } + + return NfcCommandContinue; +} + +static bool seader_worker_detect_picopass(Nfc* nfc) { + bool detected = false; + PicopassPoller* poller = picopass_poller_alloc(nfc); + SeaderPicopassDetectContext detect_context = {0}; + + picopass_poller_start(poller, seader_worker_picopass_detect_callback, &detect_context); + + for(uint8_t i = 0; i < 10 && !detect_context.done; i++) { + furi_delay_ms(10); + } + + detected = detect_context.detected; + picopass_poller_stop(poller); + picopass_poller_free(poller); + + return detected; +} + +static void seader_worker_add_detected_type( + SeaderCredentialType* detected_types, + size_t* detected_type_count, + SeaderCredentialType type) { + for(size_t i = 0; i < *detected_type_count; i++) { + if(detected_types[i] == type) { + return; + } + } + + if(*detected_type_count < SEADER_MAX_DETECTED_CARD_TYPES) { + detected_types[*detected_type_count] = type; + (*detected_type_count)++; + } +} + +static size_t seader_worker_detect_supported_types( + Seader* seader, + SeaderCredentialType* detected_types, + size_t detected_capacity) { + UNUSED(detected_capacity); + size_t detected_type_count = 0; + NfcPoller* poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolIso14443_4a); + if(nfc_poller_detect(poller_detect)) { + seader_worker_add_detected_type( + detected_types, &detected_type_count, SeaderCredentialType14A); + } + nfc_poller_free(poller_detect); + + poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolMfClassic); + if(nfc_poller_detect(poller_detect)) { + seader_worker_add_detected_type( + detected_types, &detected_type_count, SeaderCredentialTypeMifareClassic); + } + nfc_poller_free(poller_detect); + + if(seader_worker_detect_picopass(seader->nfc)) { + seader_worker_add_detected_type( + detected_types, &detected_type_count, SeaderCredentialTypePicopass); + } + + return detected_type_count; +} + +static bool seader_worker_start_read_for_type(Seader* seader, SeaderCredentialType type) { + NfcPoller* poller_detect = NULL; + + if(type == SeaderCredentialType14A) { + poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolIso14443_4a); + if(!nfc_poller_detect(poller_detect)) { + nfc_poller_free(poller_detect); + return false; + } + FURI_LOG_I(TAG, "Detected ISO14443-4A card"); + nfc_poller_free(poller_detect); + seader->poller = nfc_poller_alloc(seader->nfc, NfcProtocolIso14443_4a); + seader->worker->stage = SeaderPollerEventTypeCardDetect; + seader->credential->type = SeaderCredentialType14A; + nfc_poller_start(seader->poller, seader_worker_poller_callback_iso14443_4a, seader); + return true; + } else if(type == SeaderCredentialTypeMifareClassic) { + poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolMfClassic); + if(!nfc_poller_detect(poller_detect)) { + nfc_poller_free(poller_detect); + return false; + } + FURI_LOG_I(TAG, "Detected Mifare Classic card"); + nfc_poller_free(poller_detect); + seader->poller = nfc_poller_alloc(seader->nfc, NfcProtocolMfClassic); + seader->worker->stage = SeaderPollerEventTypeCardDetect; + seader->credential->type = SeaderCredentialTypeMifareClassic; + nfc_poller_start(seader->poller, seader_worker_poller_callback_mfc, seader); + return true; + } else if(type == SeaderCredentialTypePicopass) { + if(!seader_worker_detect_picopass(seader->nfc)) { + return false; + } + FURI_LOG_I(TAG, "Detected Picopass card"); + seader->picopass_poller = picopass_poller_alloc(seader->nfc); + seader->worker->stage = SeaderPollerEventTypeCardDetect; + seader->credential->type = SeaderCredentialTypePicopass; + picopass_poller_start( + seader->picopass_poller, seader_worker_poller_callback_picopass, seader); + return true; + } + + return false; +} + /***************************** Seader Worker API *******************************/ SeaderWorker* seader_worker_alloc() { @@ -307,67 +436,33 @@ void seader_worker_reading(Seader* seader) { while(seader_worker->state == SeaderWorkerStateReading) { bool detected = false; SeaderPollerEventType result_stage = SeaderPollerEventTypeFail; + SeaderCredentialType type_to_read = seader->selected_read_type; - // 1. HF Discovery (ISO14443-4A) - NfcPoller* poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolIso14443_4a); - if(nfc_poller_detect(poller_detect)) { - FURI_LOG_I(TAG, "Detected ISO14443-4A card"); - nfc_poller_free(poller_detect); - poller_detect = NULL; + if(type_to_read == SeaderCredentialTypeNone) { + SeaderCredentialType detected_types[SEADER_MAX_DETECTED_CARD_TYPES] = {0}; + const size_t detected_type_count = seader_worker_detect_supported_types( + seader, detected_types, COUNT_OF(detected_types)); - seader->poller = nfc_poller_alloc(seader->nfc, NfcProtocolIso14443_4a); - seader_worker->stage = SeaderPollerEventTypeCardDetect; - seader->credential->type = SeaderCredentialType14A; - nfc_poller_start(seader->poller, seader_worker_poller_callback_iso14443_4a, seader); - detected = true; - } else { - nfc_poller_free(poller_detect); - poller_detect = NULL; - } - - // 2. HF Discovery (Mifare Classic) - if(!detected) { - poller_detect = nfc_poller_alloc(seader->nfc, NfcProtocolMfClassic); - if(nfc_poller_detect(poller_detect)) { - FURI_LOG_I(TAG, "Detected Mifare Classic card"); - nfc_poller_free(poller_detect); - poller_detect = NULL; - - seader->poller = nfc_poller_alloc(seader->nfc, NfcProtocolMfClassic); - seader_worker->stage = SeaderPollerEventTypeCardDetect; - seader->credential->type = SeaderCredentialTypeMifareClassic; - nfc_poller_start(seader->poller, seader_worker_poller_callback_mfc, seader); - detected = true; - } else { - nfc_poller_free(poller_detect); - poller_detect = NULL; - } - } - - // 3. HF Discovery (Picopass/iCLASS) - if(!detected) { - seader->picopass_poller = picopass_poller_alloc(seader->nfc); - seader_worker->stage = SeaderPollerEventTypeCardDetect; - seader->credential->type = SeaderCredentialTypePicopass; - - picopass_poller_start( - seader->picopass_poller, seader_worker_poller_callback_picopass, seader); - - // Wait up to 100ms for picopass detection - for(int i = 0; i < 10; i++) { - if(seader_worker->stage != SeaderPollerEventTypeCardDetect) { - detected = true; - break; + if(detected_type_count > 1) { + memcpy( + seader->detected_card_types, + detected_types, + sizeof(seader->detected_card_types)); + seader->detected_card_type_count = detected_type_count; + if(seader_worker->callback) { + seader_worker->callback( + SeaderWorkerEventSelectCardType, seader_worker->context); } - furi_delay_ms(10); - } - if(!detected) { - picopass_poller_stop(seader->picopass_poller); - picopass_poller_free(seader->picopass_poller); - seader->picopass_poller = NULL; + break; + } else if(detected_type_count == 1) { + type_to_read = detected_types[0]; } } + if(type_to_read != SeaderCredentialTypeNone) { + detected = seader_worker_start_read_for_type(seader, type_to_read); + } + if(detected) { // Wait for conversation to finish while(seader_worker->stage != SeaderPollerEventTypeComplete && diff --git a/seader_worker.h b/seader_worker.h index 08396e2..d52e335 100644 --- a/seader_worker.h +++ b/seader_worker.h @@ -38,6 +38,7 @@ typedef enum { SeaderWorkerEventSamMissing, SeaderWorkerEventNoCardDetected, SeaderWorkerEventStartReading, + SeaderWorkerEventSelectCardType, SeaderWorkerEventAPDURunnerUpdate, SeaderWorkerEventAPDURunnerSuccess, SeaderWorkerEventAPDURunnerError,