NFC refactoring (#3050)
"A long time ago in a galaxy far, far away...." we started NFC subsystem refactoring. Starring: - @gornekich - NFC refactoring project lead, architect, senior developer - @gsurkov - architect, senior developer - @RebornedBrain - senior developer Supporting roles: - @skotopes, @DrZlo13, @hedger - general architecture advisors, code review - @Astrrra, @doomwastaken, @Hellitron, @ImagineVagon333 - quality assurance Special thanks: @bettse, @pcunning, @nxv, @noproto, @AloneLiberty and everyone else who has been helping us all this time and contributing valuable knowledges, ideas and source code.
This commit is contained in:
		
							parent
							
								
									35c903494c
								
							
						
					
					
						commit
						d92b0a82cc
					
				
							
								
								
									
										1
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/CODEOWNERS
									
									
									
									
										vendored
									
									
								
							@ -52,7 +52,6 @@
 | 
				
			|||||||
/scripts/toolchain/ @skotopes @DrZlo13 @hedger @drunkbatya
 | 
					/scripts/toolchain/ @skotopes @DrZlo13 @hedger @drunkbatya
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Lib
 | 
					# Lib
 | 
				
			||||||
/lib/ST25RFAL002/ @skotopes @DrZlo13 @hedger @gornekich
 | 
					 | 
				
			||||||
/lib/stm32wb_copro/ @skotopes @DrZlo13 @hedger @gornekich
 | 
					/lib/stm32wb_copro/ @skotopes @DrZlo13 @hedger @gornekich
 | 
				
			||||||
/lib/digital_signal/ @skotopes @DrZlo13 @hedger @gornekich
 | 
					/lib/digital_signal/ @skotopes @DrZlo13 @hedger @gornekich
 | 
				
			||||||
/lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
 | 
					/lib/infrared/ @skotopes @DrZlo13 @hedger @gsurkov
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.github/workflows/unit_tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/unit_tests.yml
									
									
									
									
										vendored
									
									
								
							@ -32,7 +32,7 @@ jobs:
 | 
				
			|||||||
        if: success()
 | 
					        if: success()
 | 
				
			||||||
        timeout-minutes: 10
 | 
					        timeout-minutes: 10
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          ./fbt flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1
 | 
					          ./fbt flash SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: 'Wait for flipper and format ext'
 | 
					      - name: 'Wait for flipper and format ext'
 | 
				
			||||||
        id: format_ext
 | 
					        id: format_ext
 | 
				
			||||||
@ -66,4 +66,4 @@ jobs:
 | 
				
			|||||||
      - name: 'Check GDB output'
 | 
					      - name: 'Check GDB output'
 | 
				
			||||||
        if: failure()
 | 
					        if: failure()
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          ./fbt gdb_trace_all SWD_TRANSPORT_SERIAL=2A0906016415303030303032 FIRMWARE_APP_SET=unit_tests FORCE=1
 | 
					          ./fbt gdb_trace_all SWD_TRANSPORT_SERIAL=2A0906016415303030303032 LIB_DEBUG=1 FIRMWARE_APP_SET=unit_tests FORCE=1
 | 
				
			||||||
 | 
				
			|||||||
@ -1 +1 @@
 | 
				
			|||||||
--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/ST25RFAL002 -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/*
 | 
					--ignore-ccache -C gccarm --rules-config .pvsconfig -e lib/cmsis_core -e lib/fatfs -e lib/fnv1a-hash -e lib/FreeRTOS-Kernel -e lib/heatshrink -e lib/libusb_stm32 -e lib/littlefs -e lib/mbedtls -e lib/micro-ecc -e lib/microtar -e lib/mlib -e lib/qrcode -e lib/stm32wb_cmsis -e lib/stm32wb_copro -e lib/stm32wb_hal -e lib/u8g2 -e lib/nanopb -e */arm-none-eabi/*
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										458
									
								
								applications/debug/unit_tests/nfc/nfc_transport.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										458
									
								
								applications/debug/unit_tests/nfc/nfc_transport.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,458 @@
 | 
				
			|||||||
 | 
					#ifdef FW_CFG_unit_tests
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <lib/nfc/nfc.h>
 | 
				
			||||||
 | 
					#include <lib/nfc/helpers/iso14443_crc.h>
 | 
				
			||||||
 | 
					#include <lib/nfc/protocols/iso14443_3a/iso14443_3a.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <furi/furi.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NFC_MAX_BUFFER_SIZE (256)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    NfcTransportLogLevelWarning,
 | 
				
			||||||
 | 
					    NfcTransportLogLevelInfo,
 | 
				
			||||||
 | 
					} NfcTransportLogLevel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					FuriMessageQueue* poller_queue = NULL;
 | 
				
			||||||
 | 
					FuriMessageQueue* listener_queue = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    NfcMessageTypeTx,
 | 
				
			||||||
 | 
					    NfcMessageTypeTimeout,
 | 
				
			||||||
 | 
					    NfcMessageTypeAbort,
 | 
				
			||||||
 | 
					} NfcMessageType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    uint16_t data_bits;
 | 
				
			||||||
 | 
					    uint8_t data[NFC_MAX_BUFFER_SIZE];
 | 
				
			||||||
 | 
					} NfcMessageData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    NfcMessageType type;
 | 
				
			||||||
 | 
					    NfcMessageData data;
 | 
				
			||||||
 | 
					} NfcMessage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    NfcStateIdle,
 | 
				
			||||||
 | 
					    NfcStateReady,
 | 
				
			||||||
 | 
					    NfcStateReset,
 | 
				
			||||||
 | 
					} NfcState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    Iso14443_3aColResStatusIdle,
 | 
				
			||||||
 | 
					    Iso14443_3aColResStatusInProgress,
 | 
				
			||||||
 | 
					    Iso14443_3aColResStatusDone,
 | 
				
			||||||
 | 
					} Iso14443_3aColResStatus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    Iso14443_3aSensResp sens_resp;
 | 
				
			||||||
 | 
					    Iso14443_3aSddResp sdd_resp[2];
 | 
				
			||||||
 | 
					    Iso14443_3aSelResp sel_resp[2];
 | 
				
			||||||
 | 
					} Iso14443_3aColResData;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Nfc {
 | 
				
			||||||
 | 
					    NfcState state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Iso14443_3aColResStatus col_res_status;
 | 
				
			||||||
 | 
					    Iso14443_3aColResData col_res_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcEventCallback callback;
 | 
				
			||||||
 | 
					    void* context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcMode mode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriThread* worker_thread;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_test_print(
 | 
				
			||||||
 | 
					    NfcTransportLogLevel log_level,
 | 
				
			||||||
 | 
					    const char* message,
 | 
				
			||||||
 | 
					    uint8_t* buffer,
 | 
				
			||||||
 | 
					    uint16_t bits) {
 | 
				
			||||||
 | 
					    FuriString* str = furi_string_alloc();
 | 
				
			||||||
 | 
					    size_t bytes = (bits + 7) / 8;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < bytes; i++) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, " %02X", buffer[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    if(log_level == NfcTransportLogLevelWarning) {
 | 
				
			||||||
 | 
					        FURI_LOG_W(message, "%s", furi_string_get_cstr(str));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        FURI_LOG_I(message, "%s", furi_string_get_cstr(str));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_prepare_col_res_data(
 | 
				
			||||||
 | 
					    Nfc* instance,
 | 
				
			||||||
 | 
					    uint8_t* uid,
 | 
				
			||||||
 | 
					    uint8_t uid_len,
 | 
				
			||||||
 | 
					    uint8_t* atqa,
 | 
				
			||||||
 | 
					    uint8_t sak) {
 | 
				
			||||||
 | 
					    memcpy(instance->col_res_data.sens_resp.sens_resp, atqa, 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(uid_len == 7) {
 | 
				
			||||||
 | 
					        instance->col_res_data.sdd_resp[0].nfcid[0] = 0x88;
 | 
				
			||||||
 | 
					        memcpy(&instance->col_res_data.sdd_resp[0].nfcid[1], uid, 3);
 | 
				
			||||||
 | 
					        uint8_t bss = 0;
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < 4; i++) {
 | 
				
			||||||
 | 
					            bss ^= instance->col_res_data.sdd_resp[0].nfcid[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        instance->col_res_data.sdd_resp[0].bss = bss;
 | 
				
			||||||
 | 
					        instance->col_res_data.sel_resp[0].sak = 0x04;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        memcpy(instance->col_res_data.sdd_resp[1].nfcid, &uid[3], 4);
 | 
				
			||||||
 | 
					        bss = 0;
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < 4; i++) {
 | 
				
			||||||
 | 
					            bss ^= instance->col_res_data.sdd_resp[1].nfcid[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        instance->col_res_data.sdd_resp[1].bss = bss;
 | 
				
			||||||
 | 
					        instance->col_res_data.sel_resp[1].sak = sak;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_crash("Not supporting not 7 bytes");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Nfc* nfc_alloc() {
 | 
				
			||||||
 | 
					    Nfc* instance = malloc(sizeof(Nfc));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_free(Nfc* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    free(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_config(Nfc* instance, NfcMode mode, NfcTech tech) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					    UNUSED(tech);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->mode = mode;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_set_fdt_poll_fc(Nfc* instance, uint32_t fdt_poll_fc) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					    UNUSED(fdt_poll_fc);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_set_fdt_listen_fc(Nfc* instance, uint32_t fdt_listen_fc) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					    UNUSED(fdt_listen_fc);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_set_mask_receive_time_fc(Nfc* instance, uint32_t mask_rx_time_fc) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					    UNUSED(mask_rx_time_fc);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_set_fdt_poll_poll_us(Nfc* instance, uint32_t fdt_poll_poll_us) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					    UNUSED(fdt_poll_poll_us);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_set_guard_time_us(Nfc* instance, uint32_t guard_time_us) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					    UNUSED(guard_time_us);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NfcError nfc_iso14443a_listener_set_col_res_data(
 | 
				
			||||||
 | 
					    Nfc* instance,
 | 
				
			||||||
 | 
					    uint8_t* uid,
 | 
				
			||||||
 | 
					    uint8_t uid_len,
 | 
				
			||||||
 | 
					    uint8_t* atqa,
 | 
				
			||||||
 | 
					    uint8_t sak) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(uid);
 | 
				
			||||||
 | 
					    furi_assert(atqa);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_prepare_col_res_data(instance, uid, uid_len, atqa, sak);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcErrorNone;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int32_t nfc_worker_poller(void* context) {
 | 
				
			||||||
 | 
					    Nfc* instance = context;
 | 
				
			||||||
 | 
					    furi_assert(instance->callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->state = NfcStateReady;
 | 
				
			||||||
 | 
					    NfcCommand command = NfcCommandContinue;
 | 
				
			||||||
 | 
					    NfcEvent event = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while(true) {
 | 
				
			||||||
 | 
					        event.type = NfcEventTypePollerReady;
 | 
				
			||||||
 | 
					        command = instance->callback(event, instance->context);
 | 
				
			||||||
 | 
					        if(command == NfcCommandStop) {
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->state = NfcStateIdle;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_worker_listener_pass_col_res(Nfc* instance, uint8_t* rx_data, uint16_t rx_bits) {
 | 
				
			||||||
 | 
					    furi_assert(instance->col_res_status != Iso14443_3aColResStatusDone);
 | 
				
			||||||
 | 
					    BitBuffer* tx_buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool processed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if((rx_bits == 7) && (rx_data[0] == 0x52)) {
 | 
				
			||||||
 | 
					        instance->col_res_status = Iso14443_3aColResStatusInProgress;
 | 
				
			||||||
 | 
					        bit_buffer_copy_bytes(
 | 
				
			||||||
 | 
					            tx_buffer,
 | 
				
			||||||
 | 
					            instance->col_res_data.sens_resp.sens_resp,
 | 
				
			||||||
 | 
					            sizeof(instance->col_res_data.sens_resp.sens_resp));
 | 
				
			||||||
 | 
					        nfc_listener_tx(instance, tx_buffer);
 | 
				
			||||||
 | 
					        processed = true;
 | 
				
			||||||
 | 
					    } else if(rx_bits == 2 * 8) {
 | 
				
			||||||
 | 
					        if((rx_data[0] == 0x93) && (rx_data[1] == 0x20)) {
 | 
				
			||||||
 | 
					            bit_buffer_copy_bytes(
 | 
				
			||||||
 | 
					                tx_buffer,
 | 
				
			||||||
 | 
					                (const uint8_t*)&instance->col_res_data.sdd_resp[0],
 | 
				
			||||||
 | 
					                sizeof(Iso14443_3aSddResp));
 | 
				
			||||||
 | 
					            nfc_listener_tx(instance, tx_buffer);
 | 
				
			||||||
 | 
					            processed = true;
 | 
				
			||||||
 | 
					        } else if((rx_data[0] == 0x95) && (rx_data[1] == 0x20)) {
 | 
				
			||||||
 | 
					            bit_buffer_copy_bytes(
 | 
				
			||||||
 | 
					                tx_buffer,
 | 
				
			||||||
 | 
					                (const uint8_t*)&instance->col_res_data.sdd_resp[1],
 | 
				
			||||||
 | 
					                sizeof(Iso14443_3aSddResp));
 | 
				
			||||||
 | 
					            nfc_listener_tx(instance, tx_buffer);
 | 
				
			||||||
 | 
					            processed = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else if(rx_bits == 9 * 8) {
 | 
				
			||||||
 | 
					        if((rx_data[0] == 0x93) && (rx_data[1] == 0x70)) {
 | 
				
			||||||
 | 
					            bit_buffer_set_size_bytes(tx_buffer, 1);
 | 
				
			||||||
 | 
					            bit_buffer_set_byte(tx_buffer, 0, instance->col_res_data.sel_resp[0].sak);
 | 
				
			||||||
 | 
					            iso14443_crc_append(Iso14443CrcTypeA, tx_buffer);
 | 
				
			||||||
 | 
					            nfc_listener_tx(instance, tx_buffer);
 | 
				
			||||||
 | 
					            processed = true;
 | 
				
			||||||
 | 
					        } else if((rx_data[0] == 0x95) && (rx_data[1] == 0x70)) {
 | 
				
			||||||
 | 
					            bit_buffer_set_size_bytes(tx_buffer, 1);
 | 
				
			||||||
 | 
					            bit_buffer_set_byte(tx_buffer, 0, instance->col_res_data.sel_resp[1].sak);
 | 
				
			||||||
 | 
					            iso14443_crc_append(Iso14443CrcTypeA, tx_buffer);
 | 
				
			||||||
 | 
					            nfc_listener_tx(instance, tx_buffer);
 | 
				
			||||||
 | 
					            instance->col_res_status = Iso14443_3aColResStatusDone;
 | 
				
			||||||
 | 
					            NfcEvent event = {.type = NfcEventTypeListenerActivated};
 | 
				
			||||||
 | 
					            instance->callback(event, instance->context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            processed = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(!processed) {
 | 
				
			||||||
 | 
					        NfcMessage message = {.type = NfcMessageTypeTimeout};
 | 
				
			||||||
 | 
					        furi_message_queue_put(poller_queue, &message, FuriWaitForever);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bit_buffer_free(tx_buffer);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int32_t nfc_worker_listener(void* context) {
 | 
				
			||||||
 | 
					    Nfc* instance = context;
 | 
				
			||||||
 | 
					    furi_assert(instance->callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcMessage message = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcEventData event_data = {};
 | 
				
			||||||
 | 
					    event_data.buffer = bit_buffer_alloc(NFC_MAX_BUFFER_SIZE);
 | 
				
			||||||
 | 
					    NfcEvent nfc_event = {.data = event_data};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while(true) {
 | 
				
			||||||
 | 
					        furi_message_queue_get(listener_queue, &message, FuriWaitForever);
 | 
				
			||||||
 | 
					        bit_buffer_copy_bits(event_data.buffer, message.data.data, message.data.data_bits);
 | 
				
			||||||
 | 
					        if((message.data.data[0] == 0x52) && (message.data.data_bits == 7)) {
 | 
				
			||||||
 | 
					            instance->col_res_status = Iso14443_3aColResStatusIdle;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(message.type == NfcMessageTypeAbort) {
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        } else if(message.type == NfcMessageTypeTx) {
 | 
				
			||||||
 | 
					            nfc_test_print(
 | 
				
			||||||
 | 
					                NfcTransportLogLevelInfo, "RDR", message.data.data, message.data.data_bits);
 | 
				
			||||||
 | 
					            if(instance->col_res_status != Iso14443_3aColResStatusDone) {
 | 
				
			||||||
 | 
					                nfc_worker_listener_pass_col_res(
 | 
				
			||||||
 | 
					                    instance, message.data.data, message.data.data_bits);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                instance->state = NfcStateReady;
 | 
				
			||||||
 | 
					                nfc_event.type = NfcEventTypeRxEnd;
 | 
				
			||||||
 | 
					                instance->callback(nfc_event, instance->context);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->state = NfcStateIdle;
 | 
				
			||||||
 | 
					    instance->col_res_status = Iso14443_3aColResStatusIdle;
 | 
				
			||||||
 | 
					    memset(&instance->col_res_data, 0, sizeof(instance->col_res_data));
 | 
				
			||||||
 | 
					    bit_buffer_free(nfc_event.data.buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_start(Nfc* instance, NfcEventCallback callback, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(instance->worker_thread == NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(instance->mode == NfcModeListener) {
 | 
				
			||||||
 | 
					        furi_assert(listener_queue == NULL);
 | 
				
			||||||
 | 
					        // Check that poller didn't start
 | 
				
			||||||
 | 
					        furi_assert(poller_queue == NULL);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_assert(poller_queue == NULL);
 | 
				
			||||||
 | 
					        // Check that poller is started after listener
 | 
				
			||||||
 | 
					        furi_assert(listener_queue);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->callback = callback;
 | 
				
			||||||
 | 
					    instance->context = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(instance->mode == NfcModeListener) {
 | 
				
			||||||
 | 
					        listener_queue = furi_message_queue_alloc(4, sizeof(NfcMessage));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        poller_queue = furi_message_queue_alloc(4, sizeof(NfcMessage));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->worker_thread = furi_thread_alloc();
 | 
				
			||||||
 | 
					    furi_thread_set_context(instance->worker_thread, instance);
 | 
				
			||||||
 | 
					    furi_thread_set_priority(instance->worker_thread, FuriThreadPriorityHigh);
 | 
				
			||||||
 | 
					    furi_thread_set_stack_size(instance->worker_thread, 8 * 1024);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(instance->mode == NfcModeListener) {
 | 
				
			||||||
 | 
					        furi_thread_set_name(instance->worker_thread, "NfcWorkerListener");
 | 
				
			||||||
 | 
					        furi_thread_set_callback(instance->worker_thread, nfc_worker_listener);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_thread_set_name(instance->worker_thread, "NfcWorkerPoller");
 | 
				
			||||||
 | 
					        furi_thread_set_callback(instance->worker_thread, nfc_worker_poller);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_thread_start(instance->worker_thread);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_stop(Nfc* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(instance->worker_thread);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(instance->mode == NfcModeListener) {
 | 
				
			||||||
 | 
					        NfcMessage message = {.type = NfcMessageTypeAbort};
 | 
				
			||||||
 | 
					        furi_message_queue_put(listener_queue, &message, FuriWaitForever);
 | 
				
			||||||
 | 
					        furi_thread_join(instance->worker_thread);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_message_queue_free(listener_queue);
 | 
				
			||||||
 | 
					        listener_queue = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_thread_free(instance->worker_thread);
 | 
				
			||||||
 | 
					        instance->worker_thread = NULL;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_thread_join(instance->worker_thread);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_message_queue_free(poller_queue);
 | 
				
			||||||
 | 
					        poller_queue = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_thread_free(instance->worker_thread);
 | 
				
			||||||
 | 
					        instance->worker_thread = NULL;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Called from worker thread
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NfcError nfc_listener_tx(Nfc* instance, const BitBuffer* tx_buffer) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(poller_queue);
 | 
				
			||||||
 | 
					    furi_assert(listener_queue);
 | 
				
			||||||
 | 
					    furi_assert(tx_buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcMessage message = {};
 | 
				
			||||||
 | 
					    message.type = NfcMessageTypeTx;
 | 
				
			||||||
 | 
					    message.data.data_bits = bit_buffer_get_size(tx_buffer);
 | 
				
			||||||
 | 
					    bit_buffer_write_bytes(tx_buffer, message.data.data, bit_buffer_get_size_bytes(tx_buffer));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_message_queue_put(poller_queue, &message, FuriWaitForever);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcErrorNone;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NfcError nfc_iso14443a_listener_tx_custom_parity(Nfc* instance, const BitBuffer* tx_buffer) {
 | 
				
			||||||
 | 
					    return nfc_listener_tx(instance, tx_buffer);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NfcError
 | 
				
			||||||
 | 
					    nfc_poller_trx(Nfc* instance, const BitBuffer* tx_buffer, BitBuffer* rx_buffer, uint32_t fwt) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(tx_buffer);
 | 
				
			||||||
 | 
					    furi_assert(rx_buffer);
 | 
				
			||||||
 | 
					    furi_assert(poller_queue);
 | 
				
			||||||
 | 
					    furi_assert(listener_queue);
 | 
				
			||||||
 | 
					    UNUSED(fwt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcError error = NfcErrorNone;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcMessage message = {};
 | 
				
			||||||
 | 
					    message.type = NfcMessageTypeTx;
 | 
				
			||||||
 | 
					    message.data.data_bits = bit_buffer_get_size(tx_buffer);
 | 
				
			||||||
 | 
					    bit_buffer_write_bytes(tx_buffer, message.data.data, bit_buffer_get_size_bytes(tx_buffer));
 | 
				
			||||||
 | 
					    // Tx
 | 
				
			||||||
 | 
					    furi_assert(furi_message_queue_put(listener_queue, &message, FuriWaitForever) == FuriStatusOk);
 | 
				
			||||||
 | 
					    // Rx
 | 
				
			||||||
 | 
					    FuriStatus status = furi_message_queue_get(poller_queue, &message, 50);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(status == FuriStatusErrorTimeout) {
 | 
				
			||||||
 | 
					        error = NfcErrorTimeout;
 | 
				
			||||||
 | 
					    } else if(message.type == NfcMessageTypeTx) {
 | 
				
			||||||
 | 
					        bit_buffer_copy_bits(rx_buffer, message.data.data, message.data.data_bits);
 | 
				
			||||||
 | 
					        nfc_test_print(
 | 
				
			||||||
 | 
					            NfcTransportLogLevelWarning, "TAG", message.data.data, message.data.data_bits);
 | 
				
			||||||
 | 
					    } else if(message.type == NfcMessageTypeTimeout) {
 | 
				
			||||||
 | 
					        error = NfcErrorTimeout;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return error;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NfcError nfc_iso14443a_poller_trx_custom_parity(
 | 
				
			||||||
 | 
					    Nfc* instance,
 | 
				
			||||||
 | 
					    const BitBuffer* tx_buffer,
 | 
				
			||||||
 | 
					    BitBuffer* rx_buffer,
 | 
				
			||||||
 | 
					    uint32_t fwt) {
 | 
				
			||||||
 | 
					    return nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Technology specific API
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NfcError nfc_iso14443a_poller_trx_short_frame(
 | 
				
			||||||
 | 
					    Nfc* instance,
 | 
				
			||||||
 | 
					    NfcIso14443aShortFrame frame,
 | 
				
			||||||
 | 
					    BitBuffer* rx_buffer,
 | 
				
			||||||
 | 
					    uint32_t fwt) {
 | 
				
			||||||
 | 
					    UNUSED(frame);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    BitBuffer* tx_buffer = bit_buffer_alloc(32);
 | 
				
			||||||
 | 
					    bit_buffer_set_size(tx_buffer, 7);
 | 
				
			||||||
 | 
					    bit_buffer_set_byte(tx_buffer, 0, 0x52);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcError error = nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bit_buffer_free(tx_buffer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return error;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NfcError nfc_iso14443a_poller_trx_sdd_frame(
 | 
				
			||||||
 | 
					    Nfc* instance,
 | 
				
			||||||
 | 
					    const BitBuffer* tx_buffer,
 | 
				
			||||||
 | 
					    BitBuffer* rx_buffer,
 | 
				
			||||||
 | 
					    uint32_t fwt) {
 | 
				
			||||||
 | 
					    return nfc_poller_trx(instance, tx_buffer, rx_buffer, fwt);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NfcError nfc_iso15693_listener_tx_sof(Nfc* instance) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcErrorNone;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
@ -12,6 +12,56 @@ App(
 | 
				
			|||||||
    fap_category="NFC",
 | 
					    fap_category="NFC",
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Parser plugins
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					App(
 | 
				
			||||||
 | 
					    appid="all_in_one_parser",
 | 
				
			||||||
 | 
					    apptype=FlipperAppType.PLUGIN,
 | 
				
			||||||
 | 
					    entry_point="all_in_one_plugin_ep",
 | 
				
			||||||
 | 
					    targets=["f7"],
 | 
				
			||||||
 | 
					    requires=["nfc"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					App(
 | 
				
			||||||
 | 
					    appid="opal_parser",
 | 
				
			||||||
 | 
					    apptype=FlipperAppType.PLUGIN,
 | 
				
			||||||
 | 
					    entry_point="opal_plugin_ep",
 | 
				
			||||||
 | 
					    targets=["f7"],
 | 
				
			||||||
 | 
					    requires=["nfc"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					App(
 | 
				
			||||||
 | 
					    appid="myki_parser",
 | 
				
			||||||
 | 
					    apptype=FlipperAppType.PLUGIN,
 | 
				
			||||||
 | 
					    entry_point="myki_plugin_ep",
 | 
				
			||||||
 | 
					    targets=["f7"],
 | 
				
			||||||
 | 
					    requires=["nfc"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					App(
 | 
				
			||||||
 | 
					    appid="troika_parser",
 | 
				
			||||||
 | 
					    apptype=FlipperAppType.PLUGIN,
 | 
				
			||||||
 | 
					    entry_point="troika_plugin_ep",
 | 
				
			||||||
 | 
					    targets=["f7"],
 | 
				
			||||||
 | 
					    requires=["nfc"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					App(
 | 
				
			||||||
 | 
					    appid="plantain_parser",
 | 
				
			||||||
 | 
					    apptype=FlipperAppType.PLUGIN,
 | 
				
			||||||
 | 
					    entry_point="plantain_plugin_ep",
 | 
				
			||||||
 | 
					    targets=["f7"],
 | 
				
			||||||
 | 
					    requires=["nfc"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					App(
 | 
				
			||||||
 | 
					    appid="two_cities_parser",
 | 
				
			||||||
 | 
					    apptype=FlipperAppType.PLUGIN,
 | 
				
			||||||
 | 
					    entry_point="two_cities_plugin_ep",
 | 
				
			||||||
 | 
					    targets=["f7"],
 | 
				
			||||||
 | 
					    requires=["nfc"],
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
App(
 | 
					App(
 | 
				
			||||||
    appid="nfc_start",
 | 
					    appid="nfc_start",
 | 
				
			||||||
    targets=["f7"],
 | 
					    targets=["f7"],
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										212
									
								
								applications/main/nfc/helpers/mf_classic_key_cache.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								applications/main/nfc/helpers/mf_classic_key_cache.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,212 @@
 | 
				
			|||||||
 | 
					#include "mf_classic_key_cache.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <furi/furi.h>
 | 
				
			||||||
 | 
					#include <storage/storage.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NFC_APP_KEYS_EXTENSION ".keys"
 | 
				
			||||||
 | 
					#define NFC_APP_KEY_CACHE_FOLDER "/ext/nfc/.cache"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char* mf_classic_key_cache_file_header = "Flipper NFC keys";
 | 
				
			||||||
 | 
					static const uint32_t mf_classic_key_cache_file_version = 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct MfClassicKeyCache {
 | 
				
			||||||
 | 
					    MfClassicDeviceKeys keys;
 | 
				
			||||||
 | 
					    MfClassicKeyType current_key_type;
 | 
				
			||||||
 | 
					    uint8_t current_sector;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_get_key_cache_file_path(const uint8_t* uid, size_t uid_len, FuriString* path) {
 | 
				
			||||||
 | 
					    furi_string_printf(path, "%s/", NFC_APP_KEY_CACHE_FOLDER);
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < uid_len; i++) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(path, "%02X", uid[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    furi_string_cat_printf(path, "%s", NFC_APP_KEYS_EXTENSION);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MfClassicKeyCache* mf_classic_key_cache_alloc() {
 | 
				
			||||||
 | 
					    MfClassicKeyCache* instance = malloc(sizeof(MfClassicKeyCache));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_classic_key_cache_free(MfClassicKeyCache* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    free(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mf_classic_key_cache_save(MfClassicKeyCache* instance, const MfClassicData* data) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					    furi_assert(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size_t uid_len = 0;
 | 
				
			||||||
 | 
					    const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
 | 
				
			||||||
 | 
					    FuriString* file_path = furi_string_alloc();
 | 
				
			||||||
 | 
					    nfc_get_key_cache_file_path(uid, uid_len, file_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Storage* storage = furi_record_open(RECORD_STORAGE);
 | 
				
			||||||
 | 
					    FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    bool save_success = false;
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        if(!storage_simply_mkdir(storage, NFC_APP_KEY_CACHE_FOLDER)) break;
 | 
				
			||||||
 | 
					        if(!storage_simply_remove(storage, furi_string_get_cstr(file_path))) break;
 | 
				
			||||||
 | 
					        if(!flipper_format_buffered_file_open_always(ff, furi_string_get_cstr(file_path))) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(!flipper_format_write_header_cstr(
 | 
				
			||||||
 | 
					               ff, mf_classic_key_cache_file_header, mf_classic_key_cache_file_version))
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        if(!flipper_format_write_string_cstr(
 | 
				
			||||||
 | 
					               ff, "Mifare Classic type", mf_classic_get_device_name(data, NfcDeviceNameTypeShort)))
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        if(!flipper_format_write_hex_uint64(ff, "Key A map", &data->key_a_mask, 1)) break;
 | 
				
			||||||
 | 
					        if(!flipper_format_write_hex_uint64(ff, "Key B map", &data->key_b_mask, 1)) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        uint8_t sector_num = mf_classic_get_total_sectors_num(data->type);
 | 
				
			||||||
 | 
					        bool key_save_success = true;
 | 
				
			||||||
 | 
					        for(size_t i = 0; (i < sector_num) && (key_save_success); i++) {
 | 
				
			||||||
 | 
					            MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i);
 | 
				
			||||||
 | 
					            if(FURI_BIT(data->key_a_mask, i)) {
 | 
				
			||||||
 | 
					                furi_string_printf(temp_str, "Key A sector %d", i);
 | 
				
			||||||
 | 
					                key_save_success = flipper_format_write_hex(
 | 
				
			||||||
 | 
					                    ff, furi_string_get_cstr(temp_str), sec_tr->key_a.data, sizeof(MfClassicKey));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if(!key_save_success) break;
 | 
				
			||||||
 | 
					            if(FURI_BIT(data->key_b_mask, i)) {
 | 
				
			||||||
 | 
					                furi_string_printf(temp_str, "Key B sector %d", i);
 | 
				
			||||||
 | 
					                key_save_success = flipper_format_write_hex(
 | 
				
			||||||
 | 
					                    ff, furi_string_get_cstr(temp_str), sec_tr->key_b.data, sizeof(MfClassicKey));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        save_success = key_save_success;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    flipper_format_free(ff);
 | 
				
			||||||
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					    furi_string_free(file_path);
 | 
				
			||||||
 | 
					    furi_record_close(RECORD_STORAGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return save_success;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mf_classic_key_cache_load(MfClassicKeyCache* instance, const uint8_t* uid, size_t uid_len) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(uid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mf_classic_key_cache_reset(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* file_path = furi_string_alloc();
 | 
				
			||||||
 | 
					    nfc_get_key_cache_file_path(uid, uid_len, file_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Storage* storage = furi_record_open(RECORD_STORAGE);
 | 
				
			||||||
 | 
					    FlipperFormat* ff = flipper_format_buffered_file_alloc(storage);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    bool load_success = false;
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        if(!flipper_format_buffered_file_open_existing(ff, furi_string_get_cstr(file_path))) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        uint32_t version = 0;
 | 
				
			||||||
 | 
					        if(!flipper_format_read_header(ff, temp_str, &version)) break;
 | 
				
			||||||
 | 
					        if(furi_string_cmp_str(temp_str, mf_classic_key_cache_file_header)) break;
 | 
				
			||||||
 | 
					        if(version != mf_classic_key_cache_file_version) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(!flipper_format_read_hex_uint64(ff, "Key A map", &instance->keys.key_a_mask, 1)) break;
 | 
				
			||||||
 | 
					        if(!flipper_format_read_hex_uint64(ff, "Key B map", &instance->keys.key_b_mask, 1)) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bool key_read_success = true;
 | 
				
			||||||
 | 
					        for(size_t i = 0; (i < MF_CLASSIC_TOTAL_SECTORS_MAX) && (key_read_success); i++) {
 | 
				
			||||||
 | 
					            if(FURI_BIT(instance->keys.key_a_mask, i)) {
 | 
				
			||||||
 | 
					                furi_string_printf(temp_str, "Key A sector %d", i);
 | 
				
			||||||
 | 
					                key_read_success = flipper_format_read_hex(
 | 
				
			||||||
 | 
					                    ff,
 | 
				
			||||||
 | 
					                    furi_string_get_cstr(temp_str),
 | 
				
			||||||
 | 
					                    instance->keys.key_a[i].data,
 | 
				
			||||||
 | 
					                    sizeof(MfClassicKey));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            if(!key_read_success) break;
 | 
				
			||||||
 | 
					            if(FURI_BIT(instance->keys.key_b_mask, i)) {
 | 
				
			||||||
 | 
					                furi_string_printf(temp_str, "Key B sector %d", i);
 | 
				
			||||||
 | 
					                key_read_success = flipper_format_read_hex(
 | 
				
			||||||
 | 
					                    ff,
 | 
				
			||||||
 | 
					                    furi_string_get_cstr(temp_str),
 | 
				
			||||||
 | 
					                    instance->keys.key_b[i].data,
 | 
				
			||||||
 | 
					                    sizeof(MfClassicKey));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        load_success = key_read_success;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    flipper_format_buffered_file_close(ff);
 | 
				
			||||||
 | 
					    flipper_format_free(ff);
 | 
				
			||||||
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					    furi_string_free(file_path);
 | 
				
			||||||
 | 
					    furi_record_close(RECORD_STORAGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return load_success;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfClassicData* data) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mf_classic_key_cache_reset(instance);
 | 
				
			||||||
 | 
					    instance->keys.key_a_mask = data->key_a_mask;
 | 
				
			||||||
 | 
					    instance->keys.key_b_mask = data->key_b_mask;
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < MF_CLASSIC_TOTAL_SECTORS_MAX; i++) {
 | 
				
			||||||
 | 
					        MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, i);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(FURI_BIT(data->key_a_mask, i)) {
 | 
				
			||||||
 | 
					            instance->keys.key_a[i] = sec_tr->key_a;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(FURI_BIT(data->key_b_mask, i)) {
 | 
				
			||||||
 | 
					            instance->keys.key_b[i] = sec_tr->key_b;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mf_classic_key_cahce_get_next_key(
 | 
				
			||||||
 | 
					    MfClassicKeyCache* instance,
 | 
				
			||||||
 | 
					    uint8_t* sector_num,
 | 
				
			||||||
 | 
					    MfClassicKey* key,
 | 
				
			||||||
 | 
					    MfClassicKeyType* key_type) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(sector_num);
 | 
				
			||||||
 | 
					    furi_assert(key);
 | 
				
			||||||
 | 
					    furi_assert(key_type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool next_key_found = false;
 | 
				
			||||||
 | 
					    for(uint8_t i = instance->current_sector; i < MF_CLASSIC_TOTAL_SECTORS_MAX; i++) {
 | 
				
			||||||
 | 
					        if(FURI_BIT(instance->keys.key_a_mask, i)) {
 | 
				
			||||||
 | 
					            FURI_BIT_CLEAR(instance->keys.key_a_mask, i);
 | 
				
			||||||
 | 
					            *key = instance->keys.key_a[i];
 | 
				
			||||||
 | 
					            *key_type = MfClassicKeyTypeA;
 | 
				
			||||||
 | 
					            *sector_num = i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            next_key_found = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(FURI_BIT(instance->keys.key_b_mask, i)) {
 | 
				
			||||||
 | 
					            FURI_BIT_CLEAR(instance->keys.key_b_mask, i);
 | 
				
			||||||
 | 
					            *key = instance->keys.key_b[i];
 | 
				
			||||||
 | 
					            *key_type = MfClassicKeyTypeB;
 | 
				
			||||||
 | 
					            *sector_num = i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            next_key_found = true;
 | 
				
			||||||
 | 
					            instance->current_sector = i;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return next_key_found;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_classic_key_cache_reset(MfClassicKeyCache* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->current_key_type = MfClassicKeyTypeA;
 | 
				
			||||||
 | 
					    instance->current_sector = 0;
 | 
				
			||||||
 | 
					    instance->keys.key_a_mask = 0;
 | 
				
			||||||
 | 
					    instance->keys.key_b_mask = 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										31
									
								
								applications/main/nfc/helpers/mf_classic_key_cache.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								applications/main/nfc/helpers/mf_classic_key_cache.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/mf_classic/mf_classic.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
 | 
					extern "C" {
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct MfClassicKeyCache MfClassicKeyCache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MfClassicKeyCache* mf_classic_key_cache_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_classic_key_cache_free(MfClassicKeyCache* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mf_classic_key_cache_load(MfClassicKeyCache* instance, const uint8_t* uid, size_t uid_len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfClassicData* data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mf_classic_key_cahce_get_next_key(
 | 
				
			||||||
 | 
					    MfClassicKeyCache* instance,
 | 
				
			||||||
 | 
					    uint8_t* sector_num,
 | 
				
			||||||
 | 
					    MfClassicKey* key,
 | 
				
			||||||
 | 
					    MfClassicKeyType* key_type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mf_classic_key_cache_save(MfClassicKeyCache* instance, const MfClassicData* data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_classic_key_cache_reset(MfClassicKeyCache* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										58
									
								
								applications/main/nfc/helpers/mf_ultralight_auth.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								applications/main/nfc/helpers/mf_ultralight_auth.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,58 @@
 | 
				
			|||||||
 | 
					#include "mf_ultralight_auth.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <furi.h>
 | 
				
			||||||
 | 
					#include <mbedtls/sha1.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MfUltralightAuth* mf_ultralight_auth_alloc() {
 | 
				
			||||||
 | 
					    MfUltralightAuth* instance = malloc(sizeof(MfUltralightAuth));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_ultralight_auth_free(MfUltralightAuth* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    free(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_ultralight_auth_reset(MfUltralightAuth* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->type = MfUltralightAuthTypeNone;
 | 
				
			||||||
 | 
					    memset(&instance->password, 0, sizeof(MfUltralightAuthPassword));
 | 
				
			||||||
 | 
					    memset(&instance->pack, 0, sizeof(MfUltralightAuthPack));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mf_ultralight_generate_amiibo_pass(MfUltralightAuth* instance, uint8_t* uid, uint16_t uid_len) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(uid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool generated = false;
 | 
				
			||||||
 | 
					    if(uid_len == 7) {
 | 
				
			||||||
 | 
					        instance->password.data[0] = uid[1] ^ uid[3] ^ 0xAA;
 | 
				
			||||||
 | 
					        instance->password.data[1] = uid[2] ^ uid[4] ^ 0x55;
 | 
				
			||||||
 | 
					        instance->password.data[2] = uid[3] ^ uid[5] ^ 0xAA;
 | 
				
			||||||
 | 
					        instance->password.data[3] = uid[4] ^ uid[6] ^ 0x55;
 | 
				
			||||||
 | 
					        generated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return generated;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mf_ultralight_generate_xiaomi_pass(MfUltralightAuth* instance, uint8_t* uid, uint16_t uid_len) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(uid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t hash[20];
 | 
				
			||||||
 | 
					    bool generated = false;
 | 
				
			||||||
 | 
					    if(uid_len == 7) {
 | 
				
			||||||
 | 
					        mbedtls_sha1(uid, uid_len, hash);
 | 
				
			||||||
 | 
					        instance->password.data[0] = (hash[hash[0] % 20]);
 | 
				
			||||||
 | 
					        instance->password.data[1] = (hash[(hash[0] + 5) % 20]);
 | 
				
			||||||
 | 
					        instance->password.data[2] = (hash[(hash[0] + 13) % 20]);
 | 
				
			||||||
 | 
					        instance->password.data[3] = (hash[(hash[0] + 17) % 20]);
 | 
				
			||||||
 | 
					        generated = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return generated;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										35
									
								
								applications/main/nfc/helpers/mf_ultralight_auth.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								applications/main/nfc/helpers/mf_ultralight_auth.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <lib/nfc/protocols/mf_ultralight/mf_ultralight.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
 | 
					extern "C" {
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    MfUltralightAuthTypeNone,
 | 
				
			||||||
 | 
					    MfUltralightAuthTypeReader,
 | 
				
			||||||
 | 
					    MfUltralightAuthTypeManual,
 | 
				
			||||||
 | 
					    MfUltralightAuthTypeXiaomi,
 | 
				
			||||||
 | 
					    MfUltralightAuthTypeAmiibo,
 | 
				
			||||||
 | 
					} MfUltralightAuthType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    MfUltralightAuthType type;
 | 
				
			||||||
 | 
					    MfUltralightAuthPassword password;
 | 
				
			||||||
 | 
					    MfUltralightAuthPack pack;
 | 
				
			||||||
 | 
					} MfUltralightAuth;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MfUltralightAuth* mf_ultralight_auth_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_ultralight_auth_free(MfUltralightAuth* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_ultralight_auth_reset(MfUltralightAuth* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mf_ultralight_generate_amiibo_pass(MfUltralightAuth* instance, uint8_t* uid, uint16_t uid_len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mf_ultralight_generate_xiaomi_pass(MfUltralightAuth* instance, uint8_t* uid, uint16_t uid_len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										83
									
								
								applications/main/nfc/helpers/mf_user_dict.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								applications/main/nfc/helpers/mf_user_dict.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,83 @@
 | 
				
			|||||||
 | 
					#include "mf_user_dict.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/helpers/nfc_dict.h>
 | 
				
			||||||
 | 
					#include <nfc/protocols/mf_classic/mf_classic.h>
 | 
				
			||||||
 | 
					#include <furi/furi.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NFC_APP_FOLDER ANY_PATH("nfc")
 | 
				
			||||||
 | 
					#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct MfUserDict {
 | 
				
			||||||
 | 
					    size_t keys_num;
 | 
				
			||||||
 | 
					    MfClassicKey* keys_arr;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MfUserDict* mf_user_dict_alloc(size_t max_keys_to_load) {
 | 
				
			||||||
 | 
					    MfUserDict* instance = malloc(sizeof(MfUserDict));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcDict* dict = nfc_dict_alloc(
 | 
				
			||||||
 | 
					        NFC_APP_MF_CLASSIC_DICT_USER_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey));
 | 
				
			||||||
 | 
					    furi_assert(dict);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size_t dict_keys_num = nfc_dict_get_total_keys(dict);
 | 
				
			||||||
 | 
					    instance->keys_num = MIN(max_keys_to_load, dict_keys_num);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(instance->keys_num > 0) {
 | 
				
			||||||
 | 
					        instance->keys_arr = malloc(instance->keys_num * sizeof(MfClassicKey));
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < instance->keys_num; i++) {
 | 
				
			||||||
 | 
					            bool key_loaded =
 | 
				
			||||||
 | 
					                nfc_dict_get_next_key(dict, instance->keys_arr[i].data, sizeof(MfClassicKey));
 | 
				
			||||||
 | 
					            furi_assert(key_loaded);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    nfc_dict_free(dict);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_user_dict_free(MfUserDict* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(instance->keys_num > 0) {
 | 
				
			||||||
 | 
					        free(instance->keys_arr);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    free(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					size_t mf_user_dict_get_keys_cnt(MfUserDict* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return instance->keys_num;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_user_dict_get_key_str(MfUserDict* instance, uint32_t index, FuriString* str) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(str);
 | 
				
			||||||
 | 
					    furi_assert(index < instance->keys_num);
 | 
				
			||||||
 | 
					    furi_assert(instance->keys_arr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_reset(str);
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < sizeof(MfClassicKey); i++) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "%02X", instance->keys_arr[index].data[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mf_user_dict_delete_key(MfUserDict* instance, uint32_t index) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(index < instance->keys_num);
 | 
				
			||||||
 | 
					    furi_assert(instance->keys_arr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcDict* dict = nfc_dict_alloc(
 | 
				
			||||||
 | 
					        NFC_APP_MF_CLASSIC_DICT_USER_PATH, NfcDictModeOpenAlways, sizeof(MfClassicKey));
 | 
				
			||||||
 | 
					    furi_assert(dict);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool key_delete_success =
 | 
				
			||||||
 | 
					        nfc_dict_delete_key(dict, instance->keys_arr[index].data, sizeof(MfClassicKey));
 | 
				
			||||||
 | 
					    nfc_dict_free(dict);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(key_delete_success) {
 | 
				
			||||||
 | 
					        instance->keys_num--;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return key_delete_success;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										23
									
								
								applications/main/nfc/helpers/mf_user_dict.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								applications/main/nfc/helpers/mf_user_dict.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <furi/core/string.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
 | 
					extern "C" {
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct MfUserDict MfUserDict;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					MfUserDict* mf_user_dict_alloc(size_t max_keys_to_load);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_user_dict_free(MfUserDict* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					size_t mf_user_dict_get_keys_cnt(MfUserDict* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mf_user_dict_get_key_str(MfUserDict* instance, uint32_t index, FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mf_user_dict_delete_key(MfUserDict* instance, uint32_t index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										173
									
								
								applications/main/nfc/helpers/mfkey32_logger.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								applications/main/nfc/helpers/mfkey32_logger.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,173 @@
 | 
				
			|||||||
 | 
					#include "mfkey32_logger.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <m-array.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/helpers/nfc_util.h>
 | 
				
			||||||
 | 
					#include <stream/stream.h>
 | 
				
			||||||
 | 
					#include <stream/buffered_file_stream.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define MFKEY32_LOGGER_MAX_NONCES_SAVED (100)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    bool is_filled;
 | 
				
			||||||
 | 
					    uint32_t cuid;
 | 
				
			||||||
 | 
					    uint8_t sector_num;
 | 
				
			||||||
 | 
					    MfClassicKeyType key_type;
 | 
				
			||||||
 | 
					    uint32_t nt0;
 | 
				
			||||||
 | 
					    uint32_t nr0;
 | 
				
			||||||
 | 
					    uint32_t ar0;
 | 
				
			||||||
 | 
					    uint32_t nt1;
 | 
				
			||||||
 | 
					    uint32_t nr1;
 | 
				
			||||||
 | 
					    uint32_t ar1;
 | 
				
			||||||
 | 
					} Mfkey32LoggerParams;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ARRAY_DEF(Mfkey32LoggerParams, Mfkey32LoggerParams, M_POD_OPLIST);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct Mfkey32Logger {
 | 
				
			||||||
 | 
					    uint32_t cuid;
 | 
				
			||||||
 | 
					    Mfkey32LoggerParams_t params_arr;
 | 
				
			||||||
 | 
					    size_t nonces_saves;
 | 
				
			||||||
 | 
					    size_t params_collected;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Mfkey32Logger* mfkey32_logger_alloc(uint32_t cuid) {
 | 
				
			||||||
 | 
					    Mfkey32Logger* instance = malloc(sizeof(Mfkey32Logger));
 | 
				
			||||||
 | 
					    instance->cuid = cuid;
 | 
				
			||||||
 | 
					    Mfkey32LoggerParams_init(instance->params_arr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mfkey32_logger_free(Mfkey32Logger* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(instance->params_arr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Mfkey32LoggerParams_clear(instance->params_arr);
 | 
				
			||||||
 | 
					    free(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool mfkey32_logger_add_nonce_to_existing_params(
 | 
				
			||||||
 | 
					    Mfkey32Logger* instance,
 | 
				
			||||||
 | 
					    MfClassicAuthContext* auth_context) {
 | 
				
			||||||
 | 
					    bool nonce_added = false;
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        if(Mfkey32LoggerParams_size(instance->params_arr) == 0) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Mfkey32LoggerParams_it_t it;
 | 
				
			||||||
 | 
					        for(Mfkey32LoggerParams_it(it, instance->params_arr); !Mfkey32LoggerParams_end_p(it);
 | 
				
			||||||
 | 
					            Mfkey32LoggerParams_next(it)) {
 | 
				
			||||||
 | 
					            Mfkey32LoggerParams* params = Mfkey32LoggerParams_ref(it);
 | 
				
			||||||
 | 
					            if(params->is_filled) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            uint8_t sector_num = mf_classic_get_sector_by_block(auth_context->block_num);
 | 
				
			||||||
 | 
					            if(params->sector_num != sector_num) continue;
 | 
				
			||||||
 | 
					            if(params->key_type != auth_context->key_type) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            params->nt1 = nfc_util_bytes2num(auth_context->nt.data, sizeof(MfClassicNt));
 | 
				
			||||||
 | 
					            params->nr1 = nfc_util_bytes2num(auth_context->nr.data, sizeof(MfClassicNr));
 | 
				
			||||||
 | 
					            params->ar1 = nfc_util_bytes2num(auth_context->ar.data, sizeof(MfClassicAr));
 | 
				
			||||||
 | 
					            params->is_filled = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            instance->params_collected++;
 | 
				
			||||||
 | 
					            nonce_added = true;
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return nonce_added;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mfkey32_logger_add_nonce(Mfkey32Logger* instance, MfClassicAuthContext* auth_context) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(auth_context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool nonce_added = mfkey32_logger_add_nonce_to_existing_params(instance, auth_context);
 | 
				
			||||||
 | 
					    if(!nonce_added && (instance->nonces_saves < MFKEY32_LOGGER_MAX_NONCES_SAVED)) {
 | 
				
			||||||
 | 
					        uint8_t sector_num = mf_classic_get_sector_by_block(auth_context->block_num);
 | 
				
			||||||
 | 
					        Mfkey32LoggerParams params = {
 | 
				
			||||||
 | 
					            .is_filled = false,
 | 
				
			||||||
 | 
					            .cuid = instance->cuid,
 | 
				
			||||||
 | 
					            .sector_num = sector_num,
 | 
				
			||||||
 | 
					            .key_type = auth_context->key_type,
 | 
				
			||||||
 | 
					            .nt0 = nfc_util_bytes2num(auth_context->nt.data, sizeof(MfClassicNt)),
 | 
				
			||||||
 | 
					            .nr0 = nfc_util_bytes2num(auth_context->nr.data, sizeof(MfClassicNr)),
 | 
				
			||||||
 | 
					            .ar0 = nfc_util_bytes2num(auth_context->ar.data, sizeof(MfClassicAr)),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        Mfkey32LoggerParams_push_back(instance->params_arr, params);
 | 
				
			||||||
 | 
					        instance->nonces_saves++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					size_t mfkey32_logger_get_params_num(Mfkey32Logger* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return instance->params_collected;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mfkey32_logger_save_params(Mfkey32Logger* instance, const char* path) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(path);
 | 
				
			||||||
 | 
					    furi_assert(instance->params_collected > 0);
 | 
				
			||||||
 | 
					    furi_assert(instance->params_arr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool params_saved = false;
 | 
				
			||||||
 | 
					    Storage* storage = furi_record_open(RECORD_STORAGE);
 | 
				
			||||||
 | 
					    Stream* stream = buffered_file_stream_alloc(storage);
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        if(!buffered_file_stream_open(stream, path, FSAM_WRITE, FSOM_OPEN_APPEND)) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        bool params_write_success = true;
 | 
				
			||||||
 | 
					        Mfkey32LoggerParams_it_t it;
 | 
				
			||||||
 | 
					        for(Mfkey32LoggerParams_it(it, instance->params_arr); !Mfkey32LoggerParams_end_p(it);
 | 
				
			||||||
 | 
					            Mfkey32LoggerParams_next(it)) {
 | 
				
			||||||
 | 
					            Mfkey32LoggerParams* params = Mfkey32LoggerParams_ref(it);
 | 
				
			||||||
 | 
					            if(!params->is_filled) continue;
 | 
				
			||||||
 | 
					            furi_string_printf(
 | 
				
			||||||
 | 
					                temp_str,
 | 
				
			||||||
 | 
					                "Sec %d key %c cuid %08lx nt0 %08lx nr0 %08lx ar0 %08lx nt1 %08lx nr1 %08lx ar1 %08lx\n",
 | 
				
			||||||
 | 
					                params->sector_num,
 | 
				
			||||||
 | 
					                params->key_type == MfClassicKeyTypeA ? 'A' : 'B',
 | 
				
			||||||
 | 
					                params->cuid,
 | 
				
			||||||
 | 
					                params->nt0,
 | 
				
			||||||
 | 
					                params->nr0,
 | 
				
			||||||
 | 
					                params->ar0,
 | 
				
			||||||
 | 
					                params->nt1,
 | 
				
			||||||
 | 
					                params->nr1,
 | 
				
			||||||
 | 
					                params->ar1);
 | 
				
			||||||
 | 
					            if(!stream_write_string(stream, temp_str)) {
 | 
				
			||||||
 | 
					                params_write_success = false;
 | 
				
			||||||
 | 
					                break;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(!params_write_success) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        params_saved = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					    buffered_file_stream_close(stream);
 | 
				
			||||||
 | 
					    stream_free(stream);
 | 
				
			||||||
 | 
					    furi_record_close(RECORD_STORAGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return params_saved;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mfkey32_logger_get_params_data(Mfkey32Logger* instance, FuriString* str) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(str);
 | 
				
			||||||
 | 
					    furi_assert(instance->params_collected > 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_reset(str);
 | 
				
			||||||
 | 
					    Mfkey32LoggerParams_it_t it;
 | 
				
			||||||
 | 
					    for(Mfkey32LoggerParams_it(it, instance->params_arr); !Mfkey32LoggerParams_end_p(it);
 | 
				
			||||||
 | 
					        Mfkey32LoggerParams_next(it)) {
 | 
				
			||||||
 | 
					        Mfkey32LoggerParams* params = Mfkey32LoggerParams_ref(it);
 | 
				
			||||||
 | 
					        if(!params->is_filled) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        char key_char = params->key_type == MfClassicKeyTypeA ? 'A' : 'B';
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "Sector %d, key %c\n", params->sector_num, key_char);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										25
									
								
								applications/main/nfc/helpers/mfkey32_logger.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								applications/main/nfc/helpers/mfkey32_logger.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/mf_classic/mf_classic.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
 | 
					extern "C" {
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct Mfkey32Logger Mfkey32Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Mfkey32Logger* mfkey32_logger_alloc(uint32_t cuid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mfkey32_logger_free(Mfkey32Logger* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mfkey32_logger_add_nonce(Mfkey32Logger* instance, MfClassicAuthContext* auth_context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					size_t mfkey32_logger_get_params_num(Mfkey32Logger* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool mfkey32_logger_save_params(Mfkey32Logger* instance, const char* path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void mfkey32_logger_get_params_data(Mfkey32Logger* instance, FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
@ -1,17 +1,33 @@
 | 
				
			|||||||
#pragma once
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum NfcCustomEvent {
 | 
					typedef enum {
 | 
				
			||||||
    // Reserve first 100 events for button types and indexes, starting from 0
 | 
					    // Reserve first 100 events for button types and indexes, starting from 0
 | 
				
			||||||
    NfcCustomEventReserved = 100,
 | 
					    NfcCustomEventReserved = 100,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Mf classic dict attack events
 | 
				
			||||||
 | 
					    NfcCustomEventDictAttackComplete,
 | 
				
			||||||
 | 
					    NfcCustomEventDictAttackSkip,
 | 
				
			||||||
 | 
					    NfcCustomEventDictAttackDataUpdate,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcCustomEventCardDetected,
 | 
				
			||||||
 | 
					    NfcCustomEventCardLost,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    NfcCustomEventViewExit,
 | 
					    NfcCustomEventViewExit,
 | 
				
			||||||
    NfcCustomEventWorkerExit,
 | 
					    NfcCustomEventWorkerExit,
 | 
				
			||||||
 | 
					    NfcCustomEventWorkerUpdate,
 | 
				
			||||||
 | 
					    NfcCustomEventWrongCard,
 | 
				
			||||||
 | 
					    NfcCustomEventTimerExpired,
 | 
				
			||||||
    NfcCustomEventByteInputDone,
 | 
					    NfcCustomEventByteInputDone,
 | 
				
			||||||
    NfcCustomEventTextInputDone,
 | 
					    NfcCustomEventTextInputDone,
 | 
				
			||||||
    NfcCustomEventDictAttackDone,
 | 
					    NfcCustomEventDictAttackDone,
 | 
				
			||||||
    NfcCustomEventDictAttackSkip,
 | 
					
 | 
				
			||||||
    NfcCustomEventRpcLoad,
 | 
					    NfcCustomEventRpcLoad,
 | 
				
			||||||
 | 
					    NfcCustomEventRpcExit,
 | 
				
			||||||
    NfcCustomEventRpcSessionClose,
 | 
					    NfcCustomEventRpcSessionClose,
 | 
				
			||||||
    NfcCustomEventUpdateLog,
 | 
					
 | 
				
			||||||
    NfcCustomEventSaveShadow,
 | 
					    NfcCustomEventPollerSuccess,
 | 
				
			||||||
};
 | 
					    NfcCustomEventPollerIncomplete,
 | 
				
			||||||
 | 
					    NfcCustomEventPollerFailure,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcCustomEventListenerUpdate,
 | 
				
			||||||
 | 
					} NfcCustomEvent;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										147
									
								
								applications/main/nfc/helpers/nfc_supported_cards.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								applications/main/nfc/helpers/nfc_supported_cards.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,147 @@
 | 
				
			|||||||
 | 
					#include "nfc_supported_cards.h"
 | 
				
			||||||
 | 
					#include "../plugins/supported_cards/nfc_supported_card_plugin.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <flipper_application/flipper_application.h>
 | 
				
			||||||
 | 
					#include <flipper_application/plugins/plugin_manager.h>
 | 
				
			||||||
 | 
					#include <loader/firmware_api/firmware_api.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <furi.h>
 | 
				
			||||||
 | 
					#include <path.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define TAG "NfcSupportedCards"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NFC_SUPPORTED_CARDS_PLUGINS_PATH APP_DATA_PATH("plugins")
 | 
				
			||||||
 | 
					#define NFC_SUPPORTED_CARDS_PLUGIN_SUFFIX "_parser.fal"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    Storage* storage;
 | 
				
			||||||
 | 
					    File* directory;
 | 
				
			||||||
 | 
					    FuriString* file_path;
 | 
				
			||||||
 | 
					    char file_name[256];
 | 
				
			||||||
 | 
					    FlipperApplication* app;
 | 
				
			||||||
 | 
					} NfcSupportedCards;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static NfcSupportedCards* nfc_supported_cards_alloc() {
 | 
				
			||||||
 | 
					    NfcSupportedCards* instance = malloc(sizeof(NfcSupportedCards));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->storage = furi_record_open(RECORD_STORAGE);
 | 
				
			||||||
 | 
					    instance->directory = storage_file_alloc(instance->storage);
 | 
				
			||||||
 | 
					    instance->file_path = furi_string_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(!storage_dir_open(instance->directory, NFC_SUPPORTED_CARDS_PLUGINS_PATH)) {
 | 
				
			||||||
 | 
					        FURI_LOG_D(TAG, "Failed to open directory: %s", NFC_SUPPORTED_CARDS_PLUGINS_PATH);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_supported_cards_free(NfcSupportedCards* instance) {
 | 
				
			||||||
 | 
					    if(instance->app) {
 | 
				
			||||||
 | 
					        flipper_application_free(instance->app);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(instance->file_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    storage_dir_close(instance->directory);
 | 
				
			||||||
 | 
					    storage_file_free(instance->directory);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_record_close(RECORD_STORAGE);
 | 
				
			||||||
 | 
					    free(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const NfcSupportedCardsPlugin*
 | 
				
			||||||
 | 
					    nfc_supported_cards_get_next_plugin(NfcSupportedCards* instance) {
 | 
				
			||||||
 | 
					    const NfcSupportedCardsPlugin* plugin = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        if(!storage_file_is_open(instance->directory)) break;
 | 
				
			||||||
 | 
					        if(!storage_dir_read(
 | 
				
			||||||
 | 
					               instance->directory, NULL, instance->file_name, sizeof(instance->file_name)))
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_set(instance->file_path, instance->file_name);
 | 
				
			||||||
 | 
					        if(!furi_string_end_with_str(instance->file_path, NFC_SUPPORTED_CARDS_PLUGIN_SUFFIX))
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        path_concat(NFC_SUPPORTED_CARDS_PLUGINS_PATH, instance->file_name, instance->file_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(instance->app) flipper_application_free(instance->app);
 | 
				
			||||||
 | 
					        instance->app = flipper_application_alloc(instance->storage, firmware_api_interface);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(flipper_application_preload(instance->app, furi_string_get_cstr(instance->file_path)) !=
 | 
				
			||||||
 | 
					           FlipperApplicationPreloadStatusSuccess)
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        if(!flipper_application_is_plugin(instance->app)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(flipper_application_map_to_memory(instance->app) != FlipperApplicationLoadStatusSuccess)
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const FlipperAppPluginDescriptor* descriptor =
 | 
				
			||||||
 | 
					            flipper_application_plugin_get_descriptor(instance->app);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(descriptor == NULL) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(strcmp(descriptor->appid, NFC_SUPPORTED_CARD_PLUGIN_APP_ID) != 0) continue;
 | 
				
			||||||
 | 
					        if(descriptor->ep_api_version != NFC_SUPPORTED_CARD_PLUGIN_API_VERSION) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        plugin = descriptor->entry_point;
 | 
				
			||||||
 | 
					    } while(plugin == NULL); //-V654
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return plugin;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_supported_cards_read(NfcDevice* device, Nfc* nfc) {
 | 
				
			||||||
 | 
					    furi_assert(device);
 | 
				
			||||||
 | 
					    furi_assert(nfc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool card_read = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcSupportedCards* supported_cards = nfc_supported_cards_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        const NfcSupportedCardsPlugin* plugin =
 | 
				
			||||||
 | 
					            nfc_supported_cards_get_next_plugin(supported_cards);
 | 
				
			||||||
 | 
					        if(plugin == NULL) break; //-V547
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const NfcProtocol protocol = nfc_device_get_protocol(device); //-V779
 | 
				
			||||||
 | 
					        if(plugin->protocol != protocol) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(plugin->verify) {
 | 
				
			||||||
 | 
					            if(!plugin->verify(nfc)) continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(plugin->read) {
 | 
				
			||||||
 | 
					            card_read = plugin->read(nfc, device);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } while(!card_read);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_supported_cards_free(supported_cards);
 | 
				
			||||||
 | 
					    return card_read;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_supported_cards_parse(const NfcDevice* device, FuriString* parsed_data) {
 | 
				
			||||||
 | 
					    furi_assert(device);
 | 
				
			||||||
 | 
					    furi_assert(parsed_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool parsed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcSupportedCards* supported_cards = nfc_supported_cards_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        const NfcSupportedCardsPlugin* plugin =
 | 
				
			||||||
 | 
					            nfc_supported_cards_get_next_plugin(supported_cards);
 | 
				
			||||||
 | 
					        if(plugin == NULL) break; //-V547
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const NfcProtocol protocol = nfc_device_get_protocol(device); //-V779
 | 
				
			||||||
 | 
					        if(plugin->protocol != protocol) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(plugin->parse) {
 | 
				
			||||||
 | 
					            parsed = plugin->parse(device, parsed_data);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } while(!parsed);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_supported_cards_free(supported_cards);
 | 
				
			||||||
 | 
					    return parsed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										50
									
								
								applications/main/nfc/helpers/nfc_supported_cards.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								applications/main/nfc/helpers/nfc_supported_cards.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @file nfc_supported_cards.h
 | 
				
			||||||
 | 
					 * @brief Supported card plugin loader interface.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see nfc_supported_card_plugin.h for instructions on adding a new plugin.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <core/string.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/nfc.h>
 | 
				
			||||||
 | 
					#include <nfc/nfc_device.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
 | 
					extern "C" {
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Read the card using a custom procedure.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This function will load all suitable supported card plugins one by one and
 | 
				
			||||||
 | 
					 * try to execute the custom read procedure specified in each. Upon first success,
 | 
				
			||||||
 | 
					 * no further attempts will be made and the function will return.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in,out] device pointer to a device instance to hold the read data.
 | 
				
			||||||
 | 
					 * @param[in,out] nfc pointer to an Nfc instance.
 | 
				
			||||||
 | 
					 * @returns true if the card was successfully read, false otherwise.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see NfcSupportedCardPluginRead for detailed description.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					bool nfc_supported_cards_read(NfcDevice* device, Nfc* nfc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Parse raw data into human-readable representation.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This function will load all suitable supported card plugins one by one and
 | 
				
			||||||
 | 
					 * try to parse the data according to each implementation. Upon first success,
 | 
				
			||||||
 | 
					 * no further attempts will be made and the function will return.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in] device pointer to a device instance holding the data is to be parsed.
 | 
				
			||||||
 | 
					 * @param[out] parsed_data pointer to the string to contain the formatted result.
 | 
				
			||||||
 | 
					 * @returns true if the card was successfully parsed, false otherwise.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see NfcSupportedCardPluginParse for detailed description.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					bool nfc_supported_cards_parse(const NfcDevice* device, FuriString* parsed_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifdef __cplusplus
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					#endif
 | 
				
			||||||
							
								
								
									
										108
									
								
								applications/main/nfc/helpers/protocol_support/felica/felica.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										108
									
								
								applications/main/nfc/helpers/protocol_support/felica/felica.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,108 @@
 | 
				
			|||||||
 | 
					#include "felica.h"
 | 
				
			||||||
 | 
					#include "felica_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/felica/felica_poller.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc/nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_common.h"
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_gui_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_info_on_enter_felica(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_felica_info(data, NfcProtocolFormatTypeFull, temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    widget_add_text_scroll_element(
 | 
				
			||||||
 | 
					        instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static NfcCommand nfc_scene_read_poller_callback_felica(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolFelica);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    const FelicaPollerEvent* felica_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(felica_event->type == FelicaPollerEventTypeReady) {
 | 
				
			||||||
 | 
					        nfc_device_set_data(
 | 
				
			||||||
 | 
					            instance->nfc_device, NfcProtocolFelica, nfc_poller_get_data(instance->poller));
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
 | 
				
			||||||
 | 
					        return NfcCommandStop;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_on_enter_felica(NfcApp* instance) {
 | 
				
			||||||
 | 
					    nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_felica, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_success_on_enter_felica(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const FelicaData* data = nfc_device_get_data(device, NfcProtocolFelica);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_felica_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 bool nfc_scene_saved_menu_on_event_felica(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    if(event == SubmenuIndexCommonEdit) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NfcProtocolSupportBase nfc_protocol_support_felica = {
 | 
				
			||||||
 | 
					    .features = NfcProtocolFeatureNone,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .scene_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_info_on_enter_felica,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_on_enter_felica,
 | 
				
			||||||
 | 
					            .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_felica,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_saved_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_protocol_support_common_on_enter_empty,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_saved_menu_on_event_felica,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .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_protocol_support_common_on_enter_empty,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern const NfcProtocolSupportBase nfc_protocol_support_felica;
 | 
				
			||||||
@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					#include "felica_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_felica_info(
 | 
				
			||||||
 | 
					    const FelicaData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str) {
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "IDm:");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < FELICA_IDM_SIZE; i++) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, " %02X", data->idm.data[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(format_type == NfcProtocolFormatTypeFull) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "\nPMm:");
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < FELICA_PMM_SIZE; ++i) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(str, " %02X", data->pmm.data[i]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/felica/felica.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_render_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_felica_info(
 | 
				
			||||||
 | 
					    const FelicaData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str);
 | 
				
			||||||
@ -0,0 +1,145 @@
 | 
				
			|||||||
 | 
					#include "iso14443_3a.h"
 | 
				
			||||||
 | 
					#include "iso14443_3a_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso14443_3a/iso14443_3a_poller.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc/nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_common.h"
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_gui_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_info_on_enter_iso14443_3a(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const Iso14443_3aData* data = nfc_device_get_data(device, NfcProtocolIso14443_3a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_iso14443_3a_info(data, NfcProtocolFormatTypeFull, temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    widget_add_text_scroll_element(
 | 
				
			||||||
 | 
					        instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static NfcCommand
 | 
				
			||||||
 | 
					    nfc_scene_read_poller_callback_iso14443_3a(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolIso14443_3a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    const Iso14443_3aPollerEvent* iso14443_3a_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(iso14443_3a_event->type == Iso14443_3aPollerEventTypeReady) {
 | 
				
			||||||
 | 
					        nfc_device_set_data(
 | 
				
			||||||
 | 
					            instance->nfc_device, NfcProtocolIso14443_3a, nfc_poller_get_data(instance->poller));
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
 | 
				
			||||||
 | 
					        return NfcCommandStop;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_on_enter_iso14443_3a(NfcApp* instance) {
 | 
				
			||||||
 | 
					    nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso14443_3a, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_success_on_enter_iso14443_3a(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const Iso14443_3aData* data = nfc_device_get_data(device, NfcProtocolIso14443_3a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_iso14443_3a_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 NfcCommand
 | 
				
			||||||
 | 
					    nfc_scene_emulate_listener_callback_iso14443_3a(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolIso14443_3a);
 | 
				
			||||||
 | 
					    furi_assert(event.event_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					    Iso14443_3aListenerEvent* iso14443_3a_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(iso14443_3a_event->type == Iso14443_3aListenerEventTypeReceivedStandardFrame) {
 | 
				
			||||||
 | 
					        if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(nfc->text_box_store, "R:");
 | 
				
			||||||
 | 
					            for(size_t i = 0; i < bit_buffer_get_size_bytes(iso14443_3a_event->data->buffer);
 | 
				
			||||||
 | 
					                i++) {
 | 
				
			||||||
 | 
					                furi_string_cat_printf(
 | 
				
			||||||
 | 
					                    nfc->text_box_store,
 | 
				
			||||||
 | 
					                    " %02X",
 | 
				
			||||||
 | 
					                    bit_buffer_get_byte(iso14443_3a_event->data->buffer, i));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            furi_string_push_back(nfc->text_box_store, '\n');
 | 
				
			||||||
 | 
					            view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_emulate_on_enter_iso14443_3a(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const Iso14443_3aData* data =
 | 
				
			||||||
 | 
					        nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_3a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_3a, data);
 | 
				
			||||||
 | 
					    nfc_listener_start(
 | 
				
			||||||
 | 
					        instance->listener, nfc_scene_emulate_listener_callback_iso14443_3a, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_scene_read_menu_on_event_iso14443_3a(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    if(event == SubmenuIndexCommonEmulate) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a = {
 | 
				
			||||||
 | 
					    .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureEditUid,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .scene_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_info_on_enter_iso14443_3a,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_on_enter_iso14443_3a,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_protocol_support_common_on_enter_empty,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_read_menu_on_event_iso14443_3a,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read_success =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_success_on_enter_iso14443_3a,
 | 
				
			||||||
 | 
					            .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_iso14443_3a,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a;
 | 
				
			||||||
@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					#include "iso14443_3a_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size) {
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < size; i++) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, " %02X", data[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_3a_info(
 | 
				
			||||||
 | 
					    const Iso14443_3aData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str) {
 | 
				
			||||||
 | 
					    if(format_type == NfcProtocolFormatTypeFull) {
 | 
				
			||||||
 | 
					        const char iso_type = iso14443_3a_supports_iso14443_4(data) ? '4' : '3';
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "ISO 14443-%c (NFC-A)\n", iso_type);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_render_iso14443_3a_brief(data, str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(format_type == NfcProtocolFormatTypeFull) {
 | 
				
			||||||
 | 
					        nfc_render_iso14443_3a_extra(data, str);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str) {
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "UID:");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_render_iso14443_3a_format_bytes(str, data->uid, data->uid_len);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_3a_extra(const Iso14443_3aData* data, FuriString* str) {
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "\nATQA: %02X %02X  ", data->atqa[1], data->atqa[0]);
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "SAK: %02X", data->sak);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso14443_3a/iso14443_3a.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_render_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_3a_info(
 | 
				
			||||||
 | 
					    const Iso14443_3aData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_3a_format_bytes(FuriString* str, const uint8_t* const data, size_t size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_3a_brief(const Iso14443_3aData* data, FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_3a_extra(const Iso14443_3aData* data, FuriString* str);
 | 
				
			||||||
@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					#include "iso14443_3b.h"
 | 
				
			||||||
 | 
					#include "iso14443_3b_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso14443_3b/iso14443_3b_poller.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc/nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_common.h"
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_gui_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_info_on_enter_iso14443_3b(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const Iso14443_3bData* data = nfc_device_get_data(device, NfcProtocolIso14443_3b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_iso14443_3b_info(data, NfcProtocolFormatTypeFull, temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    widget_add_text_scroll_element(
 | 
				
			||||||
 | 
					        instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static NfcCommand
 | 
				
			||||||
 | 
					    nfc_scene_read_poller_callback_iso14443_3b(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolIso14443_3b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    const Iso14443_3bPollerEvent* iso14443_3b_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(iso14443_3b_event->type == Iso14443_3bPollerEventTypeReady) {
 | 
				
			||||||
 | 
					        nfc_device_set_data(
 | 
				
			||||||
 | 
					            instance->nfc_device, NfcProtocolIso14443_3b, nfc_poller_get_data(instance->poller));
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
 | 
				
			||||||
 | 
					        return NfcCommandStop;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_on_enter_iso14443_3b(NfcApp* instance) {
 | 
				
			||||||
 | 
					    nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso14443_3b, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_success_on_enter_iso14443_3b(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const Iso14443_3bData* data = nfc_device_get_data(device, NfcProtocolIso14443_3b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_iso14443_3b_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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    if(event == SubmenuIndexCommonEdit) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_scene_saved_menu_on_event_iso14443_3b(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b = {
 | 
				
			||||||
 | 
					    .features = NfcProtocolFeatureNone,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .scene_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_info_on_enter_iso14443_3b,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_on_enter_iso14443_3b,
 | 
				
			||||||
 | 
					            .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_iso14443_3b,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_saved_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_protocol_support_common_on_enter_empty,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_saved_menu_on_event_iso14443_3b,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .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_protocol_support_common_on_enter_empty,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b;
 | 
				
			||||||
@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso14443_3b/iso14443_3b.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "iso14443_3b.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_scene_saved_menu_on_event_iso14443_3b_common(NfcApp* instance, uint32_t event);
 | 
				
			||||||
@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					#include "iso14443_3b_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_3b_info(
 | 
				
			||||||
 | 
					    const Iso14443_3bData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str) {
 | 
				
			||||||
 | 
					    if(format_type == NfcProtocolFormatTypeFull) {
 | 
				
			||||||
 | 
					        const char iso_type = iso14443_3b_supports_iso14443_4(data) ? '4' : '3';
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "ISO 14443-%c (NFC-B)\n", iso_type);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "UID:");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size_t uid_size;
 | 
				
			||||||
 | 
					    const uint8_t* uid = iso14443_3b_get_uid(data, &uid_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < uid_size; i++) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, " %02X", uid[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(format_type != NfcProtocolFormatTypeFull) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "\n\e#Protocol info\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRateBoth106Kbit)) {
 | 
				
			||||||
 | 
					        furi_string_cat(str, "Bit rate PICC <-> PCD:\n  106 kBit/s supported\n");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_string_cat(str, "Bit rate PICC -> PCD:\n");
 | 
				
			||||||
 | 
					        if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePiccToPcd212Kbit)) {
 | 
				
			||||||
 | 
					            furi_string_cat(str, "  212 kBit/s supported\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePiccToPcd424Kbit)) {
 | 
				
			||||||
 | 
					            furi_string_cat(str, "  424 kBit/s supported\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePiccToPcd848Kbit)) {
 | 
				
			||||||
 | 
					            furi_string_cat(str, "  848 kBit/s supported\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_cat(str, "Bit rate PICC <- PCD:\n");
 | 
				
			||||||
 | 
					        if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePcdToPicc212Kbit)) {
 | 
				
			||||||
 | 
					            furi_string_cat(str, "  212 kBit/s supported\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePcdToPicc424Kbit)) {
 | 
				
			||||||
 | 
					            furi_string_cat(str, "  424 kBit/s supported\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(iso14443_3b_supports_bit_rate(data, Iso14443_3bBitRatePcdToPicc848Kbit)) {
 | 
				
			||||||
 | 
					            furi_string_cat(str, "  848 kBit/s supported\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat(str, "Max frame size: ");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uint16_t max_frame_size = iso14443_3b_get_frame_size_max(data);
 | 
				
			||||||
 | 
					    if(max_frame_size != 0) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "%u bytes\n", max_frame_size);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_string_cat(str, "? (RFU)\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const double fwt = iso14443_3b_get_fwt_fc_max(data) / 13.56e6;
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "Max waiting time: %4.2g s\n", fwt);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const char* nad_support_str =
 | 
				
			||||||
 | 
					        iso14443_3b_supports_frame_option(data, Iso14443_3bFrameOptionNad) ? "" : "not ";
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "NAD: %ssupported\n", nad_support_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const char* cid_support_str =
 | 
				
			||||||
 | 
					        iso14443_3b_supports_frame_option(data, Iso14443_3bFrameOptionCid) ? "" : "not ";
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "CID: %ssupported", cid_support_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "\n\e#Application data\nRaw:");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size_t app_data_size;
 | 
				
			||||||
 | 
					    const uint8_t* app_data = iso14443_3b_get_application_data(data, &app_data_size);
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < app_data_size; ++i) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, " %02X", app_data[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso14443_3b/iso14443_3b.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_render_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_3b_info(
 | 
				
			||||||
 | 
					    const Iso14443_3bData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str);
 | 
				
			||||||
@ -0,0 +1,149 @@
 | 
				
			|||||||
 | 
					#include "iso14443_4a.h"
 | 
				
			||||||
 | 
					#include "iso14443_4a_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso14443_4a/iso14443_4a_poller.h>
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso14443_4a/iso14443_4a_listener.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc/nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_common.h"
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_gui_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_info_on_enter_iso14443_4a(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const Iso14443_4aData* data = nfc_device_get_data(device, NfcProtocolIso14443_4a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_iso14443_4a_info(data, NfcProtocolFormatTypeFull, temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    widget_add_text_scroll_element(
 | 
				
			||||||
 | 
					        instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static NfcCommand
 | 
				
			||||||
 | 
					    nfc_scene_read_poller_callback_iso14443_4a(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolIso14443_4a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    const Iso14443_4aPollerEvent* iso14443_4a_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(iso14443_4a_event->type == Iso14443_4aPollerEventTypeReady) {
 | 
				
			||||||
 | 
					        nfc_device_set_data(
 | 
				
			||||||
 | 
					            instance->nfc_device, NfcProtocolIso14443_4a, nfc_poller_get_data(instance->poller));
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
 | 
				
			||||||
 | 
					        return NfcCommandStop;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_on_enter_iso14443_4a(NfcApp* instance) {
 | 
				
			||||||
 | 
					    nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso14443_4a, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_success_on_enter_iso14443_4a(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const Iso14443_4aData* data = nfc_device_get_data(device, NfcProtocolIso14443_4a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_iso14443_4a_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_saved_menu_on_enter_iso14443_4a(NfcApp* instance) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NfcCommand nfc_scene_emulate_listener_callback_iso14443_4a(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolIso14443_4a);
 | 
				
			||||||
 | 
					    furi_assert(event.event_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					    Iso14443_4aListenerEvent* iso14443_4a_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(iso14443_4a_event->type == Iso14443_4aListenerEventTypeReceivedData) {
 | 
				
			||||||
 | 
					        if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(nfc->text_box_store, "R:");
 | 
				
			||||||
 | 
					            for(size_t i = 0; i < bit_buffer_get_size_bytes(iso14443_4a_event->data->buffer);
 | 
				
			||||||
 | 
					                i++) {
 | 
				
			||||||
 | 
					                furi_string_cat_printf(
 | 
				
			||||||
 | 
					                    nfc->text_box_store,
 | 
				
			||||||
 | 
					                    " %02X",
 | 
				
			||||||
 | 
					                    bit_buffer_get_byte(iso14443_4a_event->data->buffer, i));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            furi_string_push_back(nfc->text_box_store, '\n');
 | 
				
			||||||
 | 
					            view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_emulate_on_enter_iso14443_4a(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const Iso14443_4aData* data =
 | 
				
			||||||
 | 
					        nfc_device_get_data(instance->nfc_device, NfcProtocolIso14443_4a);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolIso14443_4a, data);
 | 
				
			||||||
 | 
					    nfc_listener_start(
 | 
				
			||||||
 | 
					        instance->listener, nfc_scene_emulate_listener_callback_iso14443_4a, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_scene_read_menu_on_event_iso14443_4a(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    if(event == SubmenuIndexCommonEmulate) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a = {
 | 
				
			||||||
 | 
					    .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureEditUid,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .scene_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_info_on_enter_iso14443_4a,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_on_enter_iso14443_4a,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_protocol_support_common_on_enter_empty,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_read_menu_on_event_iso14443_4a,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read_success =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_success_on_enter_iso14443_4a,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_saved_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_saved_menu_on_enter_iso14443_4a,
 | 
				
			||||||
 | 
					            .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_iso14443_4a,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a;
 | 
				
			||||||
@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso14443_4a/iso14443_4a.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "iso14443_4a.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NfcCommand nfc_scene_emulate_listener_callback_iso14443_4a(NfcGenericEvent event, void* context);
 | 
				
			||||||
@ -0,0 +1,84 @@
 | 
				
			|||||||
 | 
					#include "iso14443_4a_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../iso14443_3a/iso14443_3a_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_4a_info(
 | 
				
			||||||
 | 
					    const Iso14443_4aData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str) {
 | 
				
			||||||
 | 
					    nfc_render_iso14443_4a_brief(data, str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(format_type != NfcProtocolFormatTypeFull) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_render_iso14443_4a_extra(data, str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_4a_brief(const Iso14443_4aData* data, FuriString* str) {
 | 
				
			||||||
 | 
					    nfc_render_iso14443_3a_brief(iso14443_4a_get_base_data(data), str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str) {
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "\n\e#Protocol info\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRateBoth106Kbit)) {
 | 
				
			||||||
 | 
					        furi_string_cat(str, "Bit rate PICC <-> PCD:\n  106 kBit/s supported\n");
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_string_cat(str, "Bit rate PICC -> PCD:\n");
 | 
				
			||||||
 | 
					        if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePiccToPcd212Kbit)) {
 | 
				
			||||||
 | 
					            furi_string_cat(str, "  212 kBit/s supported\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePiccToPcd424Kbit)) {
 | 
				
			||||||
 | 
					            furi_string_cat(str, "  424 kBit/s supported\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePiccToPcd848Kbit)) {
 | 
				
			||||||
 | 
					            furi_string_cat(str, "  848 kBit/s supported\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_cat(str, "Bit rate PICC <- PCD:\n");
 | 
				
			||||||
 | 
					        if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePcdToPicc212Kbit)) {
 | 
				
			||||||
 | 
					            furi_string_cat(str, "  212 kBit/s supported\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePcdToPicc424Kbit)) {
 | 
				
			||||||
 | 
					            furi_string_cat(str, "  424 kBit/s supported\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(iso14443_4a_supports_bit_rate(data, Iso14443_4aBitRatePcdToPicc848Kbit)) {
 | 
				
			||||||
 | 
					            furi_string_cat(str, "  848 kBit/s supported\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat(str, "Max frame size: ");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uint16_t max_frame_size = iso14443_4a_get_frame_size_max(data);
 | 
				
			||||||
 | 
					    if(max_frame_size != 0) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "%u bytes\n", max_frame_size);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_string_cat(str, "? (RFU)\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uint32_t fwt_fc = iso14443_4a_get_fwt_fc_max(data);
 | 
				
			||||||
 | 
					    if(fwt_fc != 0) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "Max waiting time: %4.2g s\n", (double)(fwt_fc / 13.56e6));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const char* nad_support_str =
 | 
				
			||||||
 | 
					        iso14443_4a_supports_frame_option(data, Iso14443_4aFrameOptionNad) ? "" : "not ";
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "NAD: %ssupported\n", nad_support_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const char* cid_support_str =
 | 
				
			||||||
 | 
					        iso14443_4a_supports_frame_option(data, Iso14443_4aFrameOptionCid) ? "" : "not ";
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "CID: %ssupported", cid_support_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint32_t hist_bytes_count;
 | 
				
			||||||
 | 
					    const uint8_t* hist_bytes = iso14443_4a_get_historical_bytes(data, &hist_bytes_count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(hist_bytes_count > 0) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "\n\e#Historical bytes\nRaw:");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < hist_bytes_count; ++i) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(str, " %02X", hist_bytes[i]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat(str, "\n\e#ISO14443-3A data");
 | 
				
			||||||
 | 
					    nfc_render_iso14443_3a_extra(iso14443_4a_get_base_data(data), str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso14443_4a/iso14443_4a.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_render_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_4a_info(
 | 
				
			||||||
 | 
					    const Iso14443_4aData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_4a_brief(const Iso14443_4aData* data, FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_4a_extra(const Iso14443_4aData* data, FuriString* str);
 | 
				
			||||||
@ -0,0 +1,118 @@
 | 
				
			|||||||
 | 
					#include "iso14443_4b.h"
 | 
				
			||||||
 | 
					#include "iso14443_4b_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso14443_4b/iso14443_4b_poller.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc/nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_common.h"
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_gui_common.h"
 | 
				
			||||||
 | 
					#include "../iso14443_3b/iso14443_3b_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_info_on_enter_iso14443_4b(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const Iso14443_4bData* data = nfc_device_get_data(device, NfcProtocolIso14443_4b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_iso14443_4b_info(data, NfcProtocolFormatTypeFull, temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    widget_add_text_scroll_element(
 | 
				
			||||||
 | 
					        instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static NfcCommand
 | 
				
			||||||
 | 
					    nfc_scene_read_poller_callback_iso14443_4b(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolIso14443_4b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    const Iso14443_4bPollerEvent* iso14443_4b_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) {
 | 
				
			||||||
 | 
					        nfc_device_set_data(
 | 
				
			||||||
 | 
					            instance->nfc_device, NfcProtocolIso14443_4b, nfc_poller_get_data(instance->poller));
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
 | 
				
			||||||
 | 
					        return NfcCommandStop;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_on_enter_iso14443_4b(NfcApp* instance) {
 | 
				
			||||||
 | 
					    nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso14443_4b, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_success_on_enter_iso14443_4b(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const Iso14443_4bData* data = nfc_device_get_data(device, NfcProtocolIso14443_4b);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_iso14443_4b_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_saved_menu_on_enter_iso14443_4b(NfcApp* instance) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_scene_read_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    if(event == SubmenuIndexCommonEmulate) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_scene_saved_menu_on_event_iso14443_4b(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    return nfc_scene_saved_menu_on_event_iso14443_3b_common(instance, event);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b = {
 | 
				
			||||||
 | 
					    .features = NfcProtocolFeatureNone,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .scene_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_info_on_enter_iso14443_4b,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_on_enter_iso14443_4b,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_protocol_support_common_on_enter_empty,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_read_menu_on_event_iso14443_4b,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read_success =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_success_on_enter_iso14443_4b,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_saved_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_saved_menu_on_enter_iso14443_4b,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_saved_menu_on_event_iso14443_4b,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .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_protocol_support_common_on_enter_empty,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b;
 | 
				
			||||||
@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					#include "iso14443_4b_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../iso14443_3b/iso14443_3b_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_4b_info(
 | 
				
			||||||
 | 
					    const Iso14443_4bData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str) {
 | 
				
			||||||
 | 
					    nfc_render_iso14443_3b_info(iso14443_4b_get_base_data(data), format_type, str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso14443_4b/iso14443_4b.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_render_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso14443_4b_info(
 | 
				
			||||||
 | 
					    const Iso14443_4bData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str);
 | 
				
			||||||
@ -0,0 +1,144 @@
 | 
				
			|||||||
 | 
					#include "iso15693_3.h"
 | 
				
			||||||
 | 
					#include "iso15693_3_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso15693_3/iso15693_3_poller.h>
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso15693_3/iso15693_3_listener.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc/nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_common.h"
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_gui_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_info_on_enter_iso15693_3(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_iso15693_3_info(data, NfcProtocolFormatTypeFull, temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    widget_add_text_scroll_element(
 | 
				
			||||||
 | 
					        instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static NfcCommand nfc_scene_read_poller_callback_iso15693_3(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolIso15693_3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    const Iso15693_3PollerEvent* iso15693_3_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(iso15693_3_event->type == Iso15693_3PollerEventTypeReady) {
 | 
				
			||||||
 | 
					        nfc_device_set_data(
 | 
				
			||||||
 | 
					            instance->nfc_device, NfcProtocolIso15693_3, nfc_poller_get_data(instance->poller));
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
 | 
				
			||||||
 | 
					        return NfcCommandStop;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_on_enter_iso15693_3(NfcApp* instance) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					    nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_iso15693_3, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_success_on_enter_iso15693_3(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const Iso15693_3Data* data = nfc_device_get_data(device, NfcProtocolIso15693_3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_iso15693_3_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 NfcCommand
 | 
				
			||||||
 | 
					    nfc_scene_emulate_listener_callback_iso15693_3(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolIso15693_3);
 | 
				
			||||||
 | 
					    furi_assert(event.event_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					    Iso15693_3ListenerEvent* iso15693_3_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(iso15693_3_event->type == Iso15693_3ListenerEventTypeCustomCommand) {
 | 
				
			||||||
 | 
					        if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(nfc->text_box_store, "R:");
 | 
				
			||||||
 | 
					            for(size_t i = 0; i < bit_buffer_get_size_bytes(iso15693_3_event->data->buffer); i++) {
 | 
				
			||||||
 | 
					                furi_string_cat_printf(
 | 
				
			||||||
 | 
					                    nfc->text_box_store,
 | 
				
			||||||
 | 
					                    " %02X",
 | 
				
			||||||
 | 
					                    bit_buffer_get_byte(iso15693_3_event->data->buffer, i));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            furi_string_push_back(nfc->text_box_store, '\n');
 | 
				
			||||||
 | 
					            view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_emulate_on_enter_iso15693_3(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const Iso15693_3Data* data = nfc_device_get_data(instance->nfc_device, NfcProtocolIso15693_3);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolIso15693_3, data);
 | 
				
			||||||
 | 
					    nfc_listener_start(
 | 
				
			||||||
 | 
					        instance->listener, nfc_scene_emulate_listener_callback_iso15693_3, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_scene_saved_menu_on_event_iso15693_3(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    if(event == SubmenuIndexCommonEdit) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NfcProtocolSupportBase nfc_protocol_support_iso15693_3 = {
 | 
				
			||||||
 | 
					    .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureEditUid,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .scene_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_info_on_enter_iso15693_3,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_on_enter_iso15693_3,
 | 
				
			||||||
 | 
					            .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_iso15693_3,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_saved_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_protocol_support_common_on_enter_empty,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_saved_menu_on_event_iso15693_3,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .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_iso15693_3,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern const NfcProtocolSupportBase nfc_protocol_support_iso15693_3;
 | 
				
			||||||
@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					#include "iso15693_3_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NFC_RENDER_ISO15693_3_MAX_BYTES (128U)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso15693_3_info(
 | 
				
			||||||
 | 
					    const Iso15693_3Data* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str) {
 | 
				
			||||||
 | 
					    if(format_type == NfcProtocolFormatTypeFull) {
 | 
				
			||||||
 | 
					        furi_string_cat(str, "ISO15693-3 (NFC-V)\n");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_render_iso15693_3_brief(data, str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(format_type == NfcProtocolFormatTypeFull) {
 | 
				
			||||||
 | 
					        nfc_render_iso15693_3_extra(data, str);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str) {
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "UID:");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size_t uid_len;
 | 
				
			||||||
 | 
					    const uint8_t* uid = iso15693_3_get_uid(data, &uid_len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < uid_len; i++) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, " %02X", uid[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) {
 | 
				
			||||||
 | 
					        const uint16_t block_count = iso15693_3_get_block_count(data);
 | 
				
			||||||
 | 
					        const uint8_t block_size = iso15693_3_get_block_size(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "Memory: %u bytes\n", block_count * block_size);
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "(%u blocks x %u bytes)", block_count, block_size);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str) {
 | 
				
			||||||
 | 
					    furi_string_cat(str, "\n\e#General info\n");
 | 
				
			||||||
 | 
					    if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "DSFID: %02X\n", data->system_info.ic_ref);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "AFI: %02X\n", data->system_info.afi);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_IC_REF) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "IC Reference: %02X\n", data->system_info.ic_ref);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat(str, "\e#Lock bits\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_DSFID) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(
 | 
				
			||||||
 | 
					            str, "DSFID: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_AFI) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(
 | 
				
			||||||
 | 
					            str, "AFI: %s locked\n", data->settings.lock_bits.dsfid ? "" : "not");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(data->system_info.flags & ISO15693_3_SYSINFO_FLAG_MEMORY) {
 | 
				
			||||||
 | 
					        furi_string_cat(str, "\e#Memory data\n\e*--------------------\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const uint16_t block_count = iso15693_3_get_block_count(data);
 | 
				
			||||||
 | 
					        const uint8_t block_size = iso15693_3_get_block_size(data);
 | 
				
			||||||
 | 
					        const uint16_t display_block_count =
 | 
				
			||||||
 | 
					            MIN(NFC_RENDER_ISO15693_3_MAX_BYTES / block_size, block_count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for(uint32_t i = 0; i < display_block_count; ++i) {
 | 
				
			||||||
 | 
					            furi_string_cat(str, "\e*");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const uint8_t* block_data = iso15693_3_get_block_data(data, i);
 | 
				
			||||||
 | 
					            for(uint32_t j = 0; j < block_size; ++j) {
 | 
				
			||||||
 | 
					                furi_string_cat_printf(str, "%02X ", block_data[j]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const char* lock_str = iso15693_3_is_block_locked(data, i) ? "[LOCK]" : "";
 | 
				
			||||||
 | 
					            furi_string_cat_printf(str, "| %s\n", lock_str);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(block_count != display_block_count) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(
 | 
				
			||||||
 | 
					                str,
 | 
				
			||||||
 | 
					                "(Data is too big. Showing only the first %u bytes.)",
 | 
				
			||||||
 | 
					                display_block_count * block_size);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/iso15693_3/iso15693_3.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_render_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso15693_3_info(
 | 
				
			||||||
 | 
					    const Iso15693_3Data* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso15693_3_brief(const Iso15693_3Data* data, FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_iso15693_3_extra(const Iso15693_3Data* data, FuriString* str);
 | 
				
			||||||
@ -0,0 +1,252 @@
 | 
				
			|||||||
 | 
					#include "mf_classic.h"
 | 
				
			||||||
 | 
					#include "mf_classic_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/mf_classic/mf_classic_poller.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc/nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_common.h"
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_gui_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define TAG "MfClassicApp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum {
 | 
				
			||||||
 | 
					    SubmenuIndexDetectReader = SubmenuIndexCommonMax,
 | 
				
			||||||
 | 
					    SubmenuIndexWrite,
 | 
				
			||||||
 | 
					    SubmenuIndexUpdate,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_info_on_enter_mf_classic(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_mf_classic_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 void nfc_scene_more_info_on_enter_mf_classic(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const MfClassicData* mfc_data = nfc_device_get_data(device, NfcProtocolMfClassic);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_reset(instance->text_box_store);
 | 
				
			||||||
 | 
					    nfc_render_mf_classic_dump(mfc_data, instance->text_box_store);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    text_box_set_font(instance->text_box, TextBoxFontHex);
 | 
				
			||||||
 | 
					    text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store));
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static NfcCommand nfc_scene_read_poller_callback_mf_classic(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolMfClassic);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    const MfClassicPollerEvent* mfc_event = event.event_data;
 | 
				
			||||||
 | 
					    NfcCommand command = NfcCommandContinue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
 | 
				
			||||||
 | 
					        nfc_device_set_data(
 | 
				
			||||||
 | 
					            instance->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(instance->poller));
 | 
				
			||||||
 | 
					        size_t uid_len = 0;
 | 
				
			||||||
 | 
					        const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len);
 | 
				
			||||||
 | 
					        if(mf_classic_key_cache_load(instance->mfc_key_cache, uid, uid_len)) {
 | 
				
			||||||
 | 
					            FURI_LOG_I(TAG, "Key cache found");
 | 
				
			||||||
 | 
					            mfc_event->data->poller_mode.mode = MfClassicPollerModeRead;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            FURI_LOG_I(TAG, "Key cache not found");
 | 
				
			||||||
 | 
					            view_dispatcher_send_custom_event(
 | 
				
			||||||
 | 
					                instance->view_dispatcher, NfcCustomEventPollerIncomplete);
 | 
				
			||||||
 | 
					            command = NfcCommandStop;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) {
 | 
				
			||||||
 | 
					        uint8_t sector_num = 0;
 | 
				
			||||||
 | 
					        MfClassicKey key = {};
 | 
				
			||||||
 | 
					        MfClassicKeyType key_type = MfClassicKeyTypeA;
 | 
				
			||||||
 | 
					        if(mf_classic_key_cahce_get_next_key(
 | 
				
			||||||
 | 
					               instance->mfc_key_cache, §or_num, &key, &key_type)) {
 | 
				
			||||||
 | 
					            mfc_event->data->read_sector_request_data.sector_num = sector_num;
 | 
				
			||||||
 | 
					            mfc_event->data->read_sector_request_data.key = key;
 | 
				
			||||||
 | 
					            mfc_event->data->read_sector_request_data.key_type = key_type;
 | 
				
			||||||
 | 
					            mfc_event->data->read_sector_request_data.key_provided = true;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            mfc_event->data->read_sector_request_data.key_provided = false;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
 | 
				
			||||||
 | 
					        nfc_device_set_data(
 | 
				
			||||||
 | 
					            instance->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(instance->poller));
 | 
				
			||||||
 | 
					        const MfClassicData* mfc_data =
 | 
				
			||||||
 | 
					            nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
 | 
				
			||||||
 | 
					        NfcCustomEvent custom_event = mf_classic_is_card_read(mfc_data) ?
 | 
				
			||||||
 | 
					                                          NfcCustomEventPollerSuccess :
 | 
				
			||||||
 | 
					                                          NfcCustomEventPollerIncomplete;
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(instance->view_dispatcher, custom_event);
 | 
				
			||||||
 | 
					        command = NfcCommandStop;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return command;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_on_enter_mf_classic(NfcApp* instance) {
 | 
				
			||||||
 | 
					    mf_classic_key_cache_reset(instance->mfc_key_cache);
 | 
				
			||||||
 | 
					    nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_classic, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_scene_read_on_event_mf_classic(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    if(event == NfcCustomEventPollerIncomplete) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDictAttack);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_menu_on_enter_mf_classic(NfcApp* instance) {
 | 
				
			||||||
 | 
					    Submenu* submenu = instance->submenu;
 | 
				
			||||||
 | 
					    const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(!mf_classic_is_card_read(data)) {
 | 
				
			||||||
 | 
					        submenu_add_item(
 | 
				
			||||||
 | 
					            submenu,
 | 
				
			||||||
 | 
					            "Detect Reader",
 | 
				
			||||||
 | 
					            SubmenuIndexDetectReader,
 | 
				
			||||||
 | 
					            nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					            instance);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_success_on_enter_mf_classic(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_mf_classic_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_saved_menu_on_enter_mf_classic(NfcApp* instance) {
 | 
				
			||||||
 | 
					    Submenu* submenu = instance->submenu;
 | 
				
			||||||
 | 
					    const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(!mf_classic_is_card_read(data)) {
 | 
				
			||||||
 | 
					        submenu_add_item(
 | 
				
			||||||
 | 
					            submenu,
 | 
				
			||||||
 | 
					            "Detect Reader",
 | 
				
			||||||
 | 
					            SubmenuIndexDetectReader,
 | 
				
			||||||
 | 
					            nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					            instance);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    submenu_add_item(
 | 
				
			||||||
 | 
					        submenu,
 | 
				
			||||||
 | 
					        "Write to Initial Card",
 | 
				
			||||||
 | 
					        SubmenuIndexWrite,
 | 
				
			||||||
 | 
					        nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					        instance);
 | 
				
			||||||
 | 
					    submenu_add_item(
 | 
				
			||||||
 | 
					        submenu,
 | 
				
			||||||
 | 
					        "Update from Initial Card",
 | 
				
			||||||
 | 
					        SubmenuIndexUpdate,
 | 
				
			||||||
 | 
					        nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					        instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_emulate_on_enter_mf_classic(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const MfClassicData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic);
 | 
				
			||||||
 | 
					    instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolMfClassic, data);
 | 
				
			||||||
 | 
					    nfc_listener_start(instance->listener, NULL, NULL);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_scene_read_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    if(event == SubmenuIndexDetectReader) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader);
 | 
				
			||||||
 | 
					        dolphin_deed(DolphinDeedNfcDetectReader);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_scene_saved_menu_on_event_mf_classic(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event == SubmenuIndexDetectReader) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicDetectReader);
 | 
				
			||||||
 | 
					        consumed = true;
 | 
				
			||||||
 | 
					    } else if(event == SubmenuIndexWrite) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicWriteInitial);
 | 
				
			||||||
 | 
					        consumed = true;
 | 
				
			||||||
 | 
					    } else if(event == SubmenuIndexUpdate) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneMfClassicUpdateInitial);
 | 
				
			||||||
 | 
					        consumed = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_scene_save_name_on_event_mf_classic(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event == NfcCustomEventTextInputDone) {
 | 
				
			||||||
 | 
					        mf_classic_key_cache_save(
 | 
				
			||||||
 | 
					            instance->mfc_key_cache,
 | 
				
			||||||
 | 
					            nfc_device_get_data(instance->nfc_device, NfcProtocolMfClassic));
 | 
				
			||||||
 | 
					        consumed = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NfcProtocolSupportBase nfc_protocol_support_mf_classic = {
 | 
				
			||||||
 | 
					    .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .scene_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_info_on_enter_mf_classic,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_more_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_more_info_on_enter_mf_classic,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_on_enter_mf_classic,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_read_on_event_mf_classic,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_menu_on_enter_mf_classic,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_read_menu_on_event_mf_classic,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read_success =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_success_on_enter_mf_classic,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_saved_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_saved_menu_on_enter_mf_classic,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_saved_menu_on_event_mf_classic,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_save_name =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_protocol_support_common_on_enter_empty,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_save_name_on_event_mf_classic,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_emulate =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_emulate_on_enter_mf_classic,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern const NfcProtocolSupportBase nfc_protocol_support_mf_classic;
 | 
				
			||||||
@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					#include "mf_classic_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../iso14443_3a/iso14443_3a_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_classic_info(
 | 
				
			||||||
 | 
					    const MfClassicData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str) {
 | 
				
			||||||
 | 
					    nfc_render_iso14443_3a_info(data->iso14443_3a_data, format_type, str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type);
 | 
				
			||||||
 | 
					    uint8_t keys_total = sectors_total * 2;
 | 
				
			||||||
 | 
					    uint8_t keys_found = 0;
 | 
				
			||||||
 | 
					    uint8_t sectors_read = 0;
 | 
				
			||||||
 | 
					    mf_classic_get_read_sectors_and_keys(data, §ors_read, &keys_found);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "\nKeys Found: %u/%u", keys_found, keys_total);
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "\nSectors Read: %u/%u", sectors_read, sectors_total);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_classic_dump(const MfClassicData* data, FuriString* str) {
 | 
				
			||||||
 | 
					    uint16_t total_blocks = mf_classic_get_total_block_num(data->type);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < total_blocks; i++) {
 | 
				
			||||||
 | 
					        for(size_t j = 0; j < sizeof(MfClassicBlock); j += 2) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(
 | 
				
			||||||
 | 
					                str, "%02X%02X ", data->block[i].data[j], data->block[i].data[j + 1]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/mf_classic/mf_classic.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_render_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_classic_info(
 | 
				
			||||||
 | 
					    const MfClassicData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_classic_dump(const MfClassicData* data, FuriString* str);
 | 
				
			||||||
@ -0,0 +1,120 @@
 | 
				
			|||||||
 | 
					#include "mf_desfire.h"
 | 
				
			||||||
 | 
					#include "mf_desfire_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/mf_desfire/mf_desfire_poller.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#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_desfire(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_mf_desfire_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 void nfc_scene_more_info_on_enter_mf_desfire(NfcApp* instance) {
 | 
				
			||||||
 | 
					    // Jump to advanced scene right away
 | 
				
			||||||
 | 
					    scene_manager_next_scene(instance->scene_manager, NfcSceneMfDesfireMoreInfo);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static NfcCommand nfc_scene_read_poller_callback_mf_desfire(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolMfDesfire);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    const MfDesfirePollerEvent* mf_desfire_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(mf_desfire_event->type == MfDesfirePollerEventTypeReadSuccess) {
 | 
				
			||||||
 | 
					        nfc_device_set_data(
 | 
				
			||||||
 | 
					            instance->nfc_device, NfcProtocolMfDesfire, nfc_poller_get_data(instance->poller));
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
 | 
				
			||||||
 | 
					        return NfcCommandStop;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_on_enter_mf_desfire(NfcApp* instance) {
 | 
				
			||||||
 | 
					    nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_desfire, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_success_on_enter_mf_desfire(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_mf_desfire_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_desfire(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_desfire = {
 | 
				
			||||||
 | 
					    .features = NfcProtocolFeatureEmulateUid | NfcProtocolFeatureMoreInfo,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .scene_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_info_on_enter_mf_desfire,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_more_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_more_info_on_enter_mf_desfire,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_on_enter_mf_desfire,
 | 
				
			||||||
 | 
					            .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_desfire,
 | 
				
			||||||
 | 
					            .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_desfire,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern const NfcProtocolSupportBase nfc_protocol_support_mf_desfire;
 | 
				
			||||||
@ -0,0 +1,249 @@
 | 
				
			|||||||
 | 
					#include "mf_desfire_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../iso14443_4a/iso14443_4a_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_info(
 | 
				
			||||||
 | 
					    const MfDesfireData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str) {
 | 
				
			||||||
 | 
					    nfc_render_iso14443_4a_brief(mf_desfire_get_base_data(data), str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uint32_t bytes_total = 1UL << (data->version.sw_storage >> 1);
 | 
				
			||||||
 | 
					    const uint32_t bytes_free = data->free_memory.is_present ? data->free_memory.bytes_free : 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "\n%lu", bytes_total);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(data->version.sw_storage & 1) {
 | 
				
			||||||
 | 
					        furi_string_push_back(str, '+');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, " bytes, %lu bytes free\n", bytes_free);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uint32_t app_count = simple_array_get_count(data->applications);
 | 
				
			||||||
 | 
					    uint32_t file_count = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(uint32_t i = 0; i < app_count; ++i) {
 | 
				
			||||||
 | 
					        const MfDesfireApplication* app = simple_array_cget(data->applications, i);
 | 
				
			||||||
 | 
					        file_count += simple_array_get_count(app->file_ids);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "%lu Application%s", app_count, app_count != 1 ? "s" : "");
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, ", %lu File%s", file_count, file_count != 1 ? "s" : "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(format_type != NfcProtocolFormatTypeFull) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat(str, "\n\e#ISO14443-4 data");
 | 
				
			||||||
 | 
					    nfc_render_iso14443_4a_extra(mf_desfire_get_base_data(data), str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_data(const MfDesfireData* data, FuriString* str) {
 | 
				
			||||||
 | 
					    nfc_render_mf_desfire_version(&data->version, str);
 | 
				
			||||||
 | 
					    nfc_render_mf_desfire_free_memory(&data->free_memory, str);
 | 
				
			||||||
 | 
					    nfc_render_mf_desfire_key_settings(&data->master_key_settings, str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(uint32_t i = 0; i < simple_array_get_count(data->master_key_versions); ++i) {
 | 
				
			||||||
 | 
					        nfc_render_mf_desfire_key_version(simple_array_cget(data->master_key_versions, i), i, str);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_version(const MfDesfireVersion* 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);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_free_memory(const MfDesfireFreeMemory* data, FuriString* str) {
 | 
				
			||||||
 | 
					    if(data->is_present) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "freeMem %lu\n", data->bytes_free);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_key_settings(const MfDesfireKeySettings* data, FuriString* str) {
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "changeKeyID %d\n", data->change_key_id);
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "configChangeable %d\n", data->is_config_changeable);
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "freeCreateDelete %d\n", data->is_free_create_delete);
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "freeDirectoryList %d\n", data->is_free_directory_list);
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "masterChangeable %d\n", data->is_master_key_changeable);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(data->flags) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "flags %d\n", data->flags);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "maxKeys %d\n", data->max_keys);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_key_version(
 | 
				
			||||||
 | 
					    const MfDesfireKeyVersion* data,
 | 
				
			||||||
 | 
					    uint32_t index,
 | 
				
			||||||
 | 
					    FuriString* str) {
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "key %lu version %u\n", index, *data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_application_id(const MfDesfireApplicationId* data, FuriString* str) {
 | 
				
			||||||
 | 
					    const uint8_t* app_id = data->data;
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "Application %02x%02x%02x\n", app_id[0], app_id[1], app_id[2]);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_application(const MfDesfireApplication* data, FuriString* str) {
 | 
				
			||||||
 | 
					    nfc_render_mf_desfire_key_settings(&data->key_settings, str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(uint32_t i = 0; i < simple_array_get_count(data->key_versions); ++i) {
 | 
				
			||||||
 | 
					        nfc_render_mf_desfire_key_version(simple_array_cget(data->key_versions, i), i, str);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_file_id(const MfDesfireFileId* data, FuriString* str) {
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "File %d\n", *data);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_file_settings_data(
 | 
				
			||||||
 | 
					    const MfDesfireFileSettings* settings,
 | 
				
			||||||
 | 
					    const MfDesfireFileData* data,
 | 
				
			||||||
 | 
					    FuriString* str) {
 | 
				
			||||||
 | 
					    const char* type;
 | 
				
			||||||
 | 
					    switch(settings->type) {
 | 
				
			||||||
 | 
					    case MfDesfireFileTypeStandard:
 | 
				
			||||||
 | 
					        type = "standard";
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    case MfDesfireFileTypeBackup:
 | 
				
			||||||
 | 
					        type = "backup";
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    case MfDesfireFileTypeValue:
 | 
				
			||||||
 | 
					        type = "value";
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    case MfDesfireFileTypeLinearRecord:
 | 
				
			||||||
 | 
					        type = "linear";
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    case MfDesfireFileTypeCyclicRecord:
 | 
				
			||||||
 | 
					        type = "cyclic";
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					        type = "unknown";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const char* comm;
 | 
				
			||||||
 | 
					    switch(settings->comm) {
 | 
				
			||||||
 | 
					    case MfDesfireFileCommunicationSettingsPlaintext:
 | 
				
			||||||
 | 
					        comm = "plain";
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    case MfDesfireFileCommunicationSettingsAuthenticated:
 | 
				
			||||||
 | 
					        comm = "auth";
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    case MfDesfireFileCommunicationSettingsEnciphered:
 | 
				
			||||||
 | 
					        comm = "enciphered";
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					        comm = "unknown";
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "%s %s\n", type, comm);
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        str,
 | 
				
			||||||
 | 
					        "r %d w %d rw %d c %d\n",
 | 
				
			||||||
 | 
					        settings->access_rights >> 12 & 0xF,
 | 
				
			||||||
 | 
					        settings->access_rights >> 8 & 0xF,
 | 
				
			||||||
 | 
					        settings->access_rights >> 4 & 0xF,
 | 
				
			||||||
 | 
					        settings->access_rights & 0xF);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint32_t record_count = 1;
 | 
				
			||||||
 | 
					    uint32_t record_size = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch(settings->type) {
 | 
				
			||||||
 | 
					    case MfDesfireFileTypeStandard:
 | 
				
			||||||
 | 
					    case MfDesfireFileTypeBackup:
 | 
				
			||||||
 | 
					        record_size = settings->data.size;
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "size %lu\n", record_size);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    case MfDesfireFileTypeValue:
 | 
				
			||||||
 | 
					        furi_string_cat_printf(
 | 
				
			||||||
 | 
					            str, "lo %lu hi %lu\n", settings->value.lo_limit, settings->value.hi_limit);
 | 
				
			||||||
 | 
					        furi_string_cat_printf(
 | 
				
			||||||
 | 
					            str,
 | 
				
			||||||
 | 
					            "limit %lu enabled %d\n",
 | 
				
			||||||
 | 
					            settings->value.limited_credit_value,
 | 
				
			||||||
 | 
					            settings->value.limited_credit_enabled);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    case MfDesfireFileTypeLinearRecord:
 | 
				
			||||||
 | 
					    case MfDesfireFileTypeCyclicRecord:
 | 
				
			||||||
 | 
					        record_count = settings->record.cur;
 | 
				
			||||||
 | 
					        record_size = settings->record.size;
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "size %lu\n", record_size);
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "num %lu max %lu\n", record_count, settings->record.max);
 | 
				
			||||||
 | 
					        break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(simple_array_get_count(data->data) == 0) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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);
 | 
				
			||||||
 | 
					            for(uint32_t i = 0; i < 4; i++) {
 | 
				
			||||||
 | 
					                if(ch + i < record_size) {
 | 
				
			||||||
 | 
					                    const uint32_t data_index = rec * record_size + ch + i;
 | 
				
			||||||
 | 
					                    const uint8_t data_byte =
 | 
				
			||||||
 | 
					                        *(const uint8_t*)simple_array_cget(data->data, data_index);
 | 
				
			||||||
 | 
					                    furi_string_cat_printf(str, "%02x ", data_byte);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    furi_string_cat_printf(str, "   ");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            for(uint32_t i = 0; i < 4 && ch + i < record_size; i++) {
 | 
				
			||||||
 | 
					                const uint32_t data_index = rec * record_size + ch + i;
 | 
				
			||||||
 | 
					                const uint8_t data_byte =
 | 
				
			||||||
 | 
					                    *(const uint8_t*)simple_array_cget(data->data, data_index);
 | 
				
			||||||
 | 
					                if(isprint(data_byte)) {
 | 
				
			||||||
 | 
					                    furi_string_cat_printf(str, "%c", data_byte);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    furi_string_cat_printf(str, ".");
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            furi_string_push_back(str, '\n');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        furi_string_push_back(str, '\n');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/mf_desfire/mf_desfire.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_render_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_info(
 | 
				
			||||||
 | 
					    const MfDesfireData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_data(const MfDesfireData* data, FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_version(const MfDesfireVersion* data, FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_free_memory(const MfDesfireFreeMemory* data, FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_key_settings(const MfDesfireKeySettings* data, FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_key_version(
 | 
				
			||||||
 | 
					    const MfDesfireKeyVersion* data,
 | 
				
			||||||
 | 
					    uint32_t index,
 | 
				
			||||||
 | 
					    FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_application_id(const MfDesfireApplicationId* data, FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_application(const MfDesfireApplication* data, FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_file_id(const MfDesfireFileId* data, FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_desfire_file_settings_data(
 | 
				
			||||||
 | 
					    const MfDesfireFileSettings* settings,
 | 
				
			||||||
 | 
					    const MfDesfireFileData* data,
 | 
				
			||||||
 | 
					    FuriString* str);
 | 
				
			||||||
@ -0,0 +1,196 @@
 | 
				
			|||||||
 | 
					#include "mf_ultralight.h"
 | 
				
			||||||
 | 
					#include "mf_ultralight_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/mf_ultralight/mf_ultralight_poller.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc/nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_common.h"
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_gui_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum {
 | 
				
			||||||
 | 
					    SubmenuIndexUnlock = SubmenuIndexCommonMax,
 | 
				
			||||||
 | 
					    SubmenuIndexUnlockByReader,
 | 
				
			||||||
 | 
					    SubmenuIndexUnlockByPassword,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_info_on_enter_mf_ultralight(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_mf_ultralight_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 void nfc_scene_more_info_on_enter_mf_ultralight(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const MfUltralightData* mfu = nfc_device_get_data(device, NfcProtocolMfUltralight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_reset(instance->text_box_store);
 | 
				
			||||||
 | 
					    nfc_render_mf_ultralight_dump(mfu, instance->text_box_store);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    text_box_set_font(instance->text_box, TextBoxFontHex);
 | 
				
			||||||
 | 
					    text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store));
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static NfcCommand
 | 
				
			||||||
 | 
					    nfc_scene_read_poller_callback_mf_ultralight(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolMfUltralight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    const MfUltralightPollerEvent* mf_ultralight_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(mf_ultralight_event->type == MfUltralightPollerEventTypeReadSuccess) {
 | 
				
			||||||
 | 
					        nfc_device_set_data(
 | 
				
			||||||
 | 
					            instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller));
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
 | 
				
			||||||
 | 
					        return NfcCommandStop;
 | 
				
			||||||
 | 
					    } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthRequest) {
 | 
				
			||||||
 | 
					        nfc_device_set_data(
 | 
				
			||||||
 | 
					            instance->nfc_device, NfcProtocolMfUltralight, nfc_poller_get_data(instance->poller));
 | 
				
			||||||
 | 
					        const MfUltralightData* data =
 | 
				
			||||||
 | 
					            nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight);
 | 
				
			||||||
 | 
					        if(instance->mf_ul_auth->type == MfUltralightAuthTypeXiaomi) {
 | 
				
			||||||
 | 
					            if(mf_ultralight_generate_xiaomi_pass(
 | 
				
			||||||
 | 
					                   instance->mf_ul_auth,
 | 
				
			||||||
 | 
					                   data->iso14443_3a_data->uid,
 | 
				
			||||||
 | 
					                   data->iso14443_3a_data->uid_len)) {
 | 
				
			||||||
 | 
					                mf_ultralight_event->data->auth_context.skip_auth = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if(instance->mf_ul_auth->type == MfUltralightAuthTypeAmiibo) {
 | 
				
			||||||
 | 
					            if(mf_ultralight_generate_amiibo_pass(
 | 
				
			||||||
 | 
					                   instance->mf_ul_auth,
 | 
				
			||||||
 | 
					                   data->iso14443_3a_data->uid,
 | 
				
			||||||
 | 
					                   data->iso14443_3a_data->uid_len)) {
 | 
				
			||||||
 | 
					                mf_ultralight_event->data->auth_context.skip_auth = false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if(
 | 
				
			||||||
 | 
					            instance->mf_ul_auth->type == MfUltralightAuthTypeManual ||
 | 
				
			||||||
 | 
					            instance->mf_ul_auth->type == MfUltralightAuthTypeReader) {
 | 
				
			||||||
 | 
					            mf_ultralight_event->data->auth_context.skip_auth = false;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            mf_ultralight_event->data->auth_context.skip_auth = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(!mf_ultralight_event->data->auth_context.skip_auth) {
 | 
				
			||||||
 | 
					            mf_ultralight_event->data->auth_context.password = instance->mf_ul_auth->password;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else if(mf_ultralight_event->type == MfUltralightPollerEventTypeAuthSuccess) {
 | 
				
			||||||
 | 
					        instance->mf_ul_auth->pack = mf_ultralight_event->data->auth_context.pack;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_on_enter_mf_ultralight(NfcApp* instance) {
 | 
				
			||||||
 | 
					    nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_mf_ultralight, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_and_saved_menu_on_enter_mf_ultralight(NfcApp* instance) {
 | 
				
			||||||
 | 
					    Submenu* submenu = instance->submenu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const MfUltralightData* data =
 | 
				
			||||||
 | 
					        nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(!mf_ultralight_is_all_data_read(data)) {
 | 
				
			||||||
 | 
					        submenu_add_item(
 | 
				
			||||||
 | 
					            submenu,
 | 
				
			||||||
 | 
					            "Unlock",
 | 
				
			||||||
 | 
					            SubmenuIndexUnlock,
 | 
				
			||||||
 | 
					            nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					            instance);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_success_on_enter_mf_ultralight(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool unlocked =
 | 
				
			||||||
 | 
					        scene_manager_has_previous_scene(instance->scene_manager, NfcSceneMfUltralightUnlockWarn);
 | 
				
			||||||
 | 
					    if(unlocked) {
 | 
				
			||||||
 | 
					        nfc_render_mf_ultralight_pwd_pack(data, temp_str);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(
 | 
				
			||||||
 | 
					            temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        nfc_render_mf_ultralight_info(data, NfcProtocolFormatTypeShort, temp_str);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mf_ultralight_auth_reset(instance->mf_ul_auth);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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_ultralight(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const MfUltralightData* data =
 | 
				
			||||||
 | 
					        nfc_device_get_data(instance->nfc_device, NfcProtocolMfUltralight);
 | 
				
			||||||
 | 
					    instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolMfUltralight, data);
 | 
				
			||||||
 | 
					    nfc_listener_start(instance->listener, NULL, NULL);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					    nfc_scene_read_and_saved_menu_on_event_mf_ultralight(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    if(event == SubmenuIndexUnlock) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneMfUltralightUnlockMenu);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight = {
 | 
				
			||||||
 | 
					    .features = NfcProtocolFeatureEmulateFull | NfcProtocolFeatureMoreInfo,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .scene_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_info_on_enter_mf_ultralight,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_more_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_more_info_on_enter_mf_ultralight,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_on_enter_mf_ultralight,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_and_saved_menu_on_enter_mf_ultralight,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_read_and_saved_menu_on_event_mf_ultralight,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read_success =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_success_on_enter_mf_ultralight,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_saved_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_and_saved_menu_on_enter_mf_ultralight,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_read_and_saved_menu_on_event_mf_ultralight,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .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_ultralight,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight;
 | 
				
			||||||
@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					#include "mf_ultralight_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../iso14443_3a/iso14443_3a_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_render_mf_ultralight_pages_count(const MfUltralightData* data, FuriString* str) {
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "\nPages Read: %u/%u", data->pages_read, data->pages_total);
 | 
				
			||||||
 | 
					    if(data->pages_read != data->pages_total) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "\nPassword-protected pages!");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_ultralight_pwd_pack(const MfUltralightData* data, FuriString* str) {
 | 
				
			||||||
 | 
					    bool all_pages = mf_ultralight_is_all_data_read(data);
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "\e#%s pages unlocked!", all_pages ? "All" : "Not all");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MfUltralightConfigPages* config;
 | 
				
			||||||
 | 
					    mf_ultralight_get_config_page(data, &config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "\nPassword: ");
 | 
				
			||||||
 | 
					    nfc_render_iso14443_3a_format_bytes(
 | 
				
			||||||
 | 
					        str, config->password.data, MF_ULTRALIGHT_AUTH_PASSWORD_SIZE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "\nPACK: ");
 | 
				
			||||||
 | 
					    nfc_render_iso14443_3a_format_bytes(str, config->pack.data, MF_ULTRALIGHT_AUTH_PACK_SIZE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_render_mf_ultralight_pages_count(data, str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_ultralight_info(
 | 
				
			||||||
 | 
					    const MfUltralightData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str) {
 | 
				
			||||||
 | 
					    nfc_render_iso14443_3a_info(data->iso14443_3a_data, format_type, str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_render_mf_ultralight_pages_count(data, str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_ultralight_dump(const MfUltralightData* data, FuriString* str) {
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < data->pages_read; i++) {
 | 
				
			||||||
 | 
					        const uint8_t* page_data = data->page[i].data;
 | 
				
			||||||
 | 
					        for(size_t j = 0; j < MF_ULTRALIGHT_PAGE_SIZE; j += 2) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(str, "%02X%02X ", page_data[j], page_data[j + 1]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_render_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_ultralight_info(
 | 
				
			||||||
 | 
					    const MfUltralightData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_ultralight_dump(const MfUltralightData* data, FuriString* str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_mf_ultralight_pwd_pack(const MfUltralightData* data, FuriString* str);
 | 
				
			||||||
@ -0,0 +1,786 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @file nfc_protocol_support.c
 | 
				
			||||||
 | 
					 * @brief Common implementation of application-level protocol support.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see nfc_protocol_support_base.h
 | 
				
			||||||
 | 
					 * @see nfc_protocol_support_common.h
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#include "nfc_protocol_support.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc/nfc_app_i.h"
 | 
				
			||||||
 | 
					#include "nfc/helpers/nfc_supported_cards.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc_protocol_support_defs.h"
 | 
				
			||||||
 | 
					#include "nfc_protocol_support_gui_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Common scene entry handler.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in,out] instance pointer to the NFC application instance.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef void (*NfcProtocolSupportCommonOnEnter)(NfcApp* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Common scene custom event handler.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in,out] instance pointer to the NFC application instance.
 | 
				
			||||||
 | 
					 * @param[in] event custom event to be handled.
 | 
				
			||||||
 | 
					 * @returns true if the event was handled, false otherwise.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef bool (*NfcProtocolSupportCommonOnEvent)(NfcApp* instance, SceneManagerEvent event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Common scene exit handler.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in,out] instance pointer to the NFC application instance.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef void (*NfcProtocolSupportCommonOnExit)(NfcApp* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Structure containing common scene handler pointers.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    NfcProtocolSupportCommonOnEnter on_enter; /**< Pointer to the on_enter() function. */
 | 
				
			||||||
 | 
					    NfcProtocolSupportCommonOnEvent on_event; /**< Pointer to the on_event() function. */
 | 
				
			||||||
 | 
					    NfcProtocolSupportCommonOnExit on_exit; /**< Pointer to the on_exit() function. */
 | 
				
			||||||
 | 
					} NfcProtocolSupportCommonSceneBase;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const NfcProtocolSupportCommonSceneBase nfc_protocol_support_scenes[];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Interface functions
 | 
				
			||||||
 | 
					void nfc_protocol_support_on_enter(NfcProtocolSupportScene scene, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(scene < NfcProtocolSupportSceneCount);
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    nfc_protocol_support_scenes[scene].on_enter(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_protocol_support_on_event(
 | 
				
			||||||
 | 
					    NfcProtocolSupportScene scene,
 | 
				
			||||||
 | 
					    void* context,
 | 
				
			||||||
 | 
					    SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    furi_assert(scene < NfcProtocolSupportSceneCount);
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    return nfc_protocol_support_scenes[scene].on_event(instance, event);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(scene < NfcProtocolSupportSceneCount);
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    nfc_protocol_support_scenes[scene].on_exit(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_protocol_support_has_feature(NfcProtocol protocol, NfcProtocolFeature feature) {
 | 
				
			||||||
 | 
					    return nfc_protocol_support[protocol]->features & feature;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Common scene handlers
 | 
				
			||||||
 | 
					// SceneInfo
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_info_on_enter(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 | 
				
			||||||
 | 
					    nfc_protocol_support[protocol]->scene_info.on_enter(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureMoreInfo)) {
 | 
				
			||||||
 | 
					        widget_add_button_element(
 | 
				
			||||||
 | 
					            instance->widget,
 | 
				
			||||||
 | 
					            GuiButtonTypeRight,
 | 
				
			||||||
 | 
					            "More",
 | 
				
			||||||
 | 
					            nfc_protocol_support_common_widget_callback,
 | 
				
			||||||
 | 
					            instance);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_protocol_support_scene_info_on_event(NfcApp* instance, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        if(event.event == GuiButtonTypeRight) {
 | 
				
			||||||
 | 
					            scene_manager_next_scene(instance->scene_manager, NfcSceneMoreInfo);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else if(event.type == SceneManagerEventTypeBack) {
 | 
				
			||||||
 | 
					        // If the card could not be parsed, return to the respective menu
 | 
				
			||||||
 | 
					        if(!scene_manager_get_scene_state(instance->scene_manager, NfcSceneSupportedCard)) {
 | 
				
			||||||
 | 
					            const uint32_t scenes[] = {NfcSceneSavedMenu, NfcSceneReadMenu};
 | 
				
			||||||
 | 
					            scene_manager_search_and_switch_to_previous_scene_one_of(
 | 
				
			||||||
 | 
					                instance->scene_manager, scenes, COUNT_OF(scenes));
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_info_on_exit(NfcApp* instance) {
 | 
				
			||||||
 | 
					    widget_reset(instance->widget);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SceneMoreInfo
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_more_info_on_enter(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 | 
				
			||||||
 | 
					    nfc_protocol_support[protocol]->scene_more_info.on_enter(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					    nfc_protocol_support_scene_more_info_on_event(NfcApp* instance, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 | 
				
			||||||
 | 
					        consumed = nfc_protocol_support[protocol]->scene_more_info.on_event(instance, event.event);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_more_info_on_exit(NfcApp* instance) {
 | 
				
			||||||
 | 
					    text_box_reset(instance->text_box);
 | 
				
			||||||
 | 
					    furi_string_reset(instance->text_box_store);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SceneRead
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_read_on_enter(NfcApp* instance) {
 | 
				
			||||||
 | 
					    popup_set_header(
 | 
				
			||||||
 | 
					        instance->popup, "Reading card\nDon't move...", 85, 24, AlignCenter, AlignTop);
 | 
				
			||||||
 | 
					    popup_set_icon(instance->popup, 12, 23, &A_Loading_24);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const NfcProtocol protocol =
 | 
				
			||||||
 | 
					        instance->protocols_detected[instance->protocols_detected_selected_idx];
 | 
				
			||||||
 | 
					    instance->poller = nfc_poller_alloc(instance->nfc, protocol);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Start poller with the appropriate callback
 | 
				
			||||||
 | 
					    nfc_protocol_support[protocol]->scene_read.on_enter(instance);
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_blink_detect_start(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_protocol_support_scene_read_on_event(NfcApp* instance, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        if(event.event == NfcCustomEventPollerSuccess) {
 | 
				
			||||||
 | 
					            nfc_poller_stop(instance->poller);
 | 
				
			||||||
 | 
					            nfc_poller_free(instance->poller);
 | 
				
			||||||
 | 
					            notification_message(instance->notifications, &sequence_success);
 | 
				
			||||||
 | 
					            scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
 | 
				
			||||||
 | 
					            dolphin_deed(DolphinDeedNfcReadSuccess);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        } else if(event.event == NfcCustomEventPollerIncomplete) {
 | 
				
			||||||
 | 
					            nfc_poller_stop(instance->poller);
 | 
				
			||||||
 | 
					            nfc_poller_free(instance->poller);
 | 
				
			||||||
 | 
					            bool card_read = nfc_supported_cards_read(instance->nfc_device, instance->nfc);
 | 
				
			||||||
 | 
					            if(card_read) {
 | 
				
			||||||
 | 
					                notification_message(instance->notifications, &sequence_success);
 | 
				
			||||||
 | 
					                scene_manager_next_scene(instance->scene_manager, NfcSceneReadSuccess);
 | 
				
			||||||
 | 
					                dolphin_deed(DolphinDeedNfcReadSuccess);
 | 
				
			||||||
 | 
					                consumed = true;
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                const NfcProtocol protocol =
 | 
				
			||||||
 | 
					                    instance->protocols_detected[instance->protocols_detected_selected_idx];
 | 
				
			||||||
 | 
					                consumed =
 | 
				
			||||||
 | 
					                    nfc_protocol_support[protocol]->scene_read.on_event(instance, event.event);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        } else if(event.event == NfcCustomEventPollerFailure) {
 | 
				
			||||||
 | 
					            nfc_poller_stop(instance->poller);
 | 
				
			||||||
 | 
					            nfc_poller_free(instance->poller);
 | 
				
			||||||
 | 
					            if(scene_manager_has_previous_scene(instance->scene_manager, NfcSceneDetect)) {
 | 
				
			||||||
 | 
					                scene_manager_search_and_switch_to_previous_scene(
 | 
				
			||||||
 | 
					                    instance->scene_manager, NfcSceneDetect);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else if(event.type == SceneManagerEventTypeBack) {
 | 
				
			||||||
 | 
					        nfc_poller_stop(instance->poller);
 | 
				
			||||||
 | 
					        nfc_poller_free(instance->poller);
 | 
				
			||||||
 | 
					        static const uint32_t possible_scenes[] = {NfcSceneSelectProtocol, NfcSceneStart};
 | 
				
			||||||
 | 
					        scene_manager_search_and_switch_to_previous_scene_one_of(
 | 
				
			||||||
 | 
					            instance->scene_manager, possible_scenes, COUNT_OF(possible_scenes));
 | 
				
			||||||
 | 
					        consumed = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_read_on_exit(NfcApp* instance) {
 | 
				
			||||||
 | 
					    popup_reset(instance->popup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_blink_stop(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SceneReadMenu
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_read_menu_on_enter(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Submenu* submenu = instance->submenu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    submenu_add_item(
 | 
				
			||||||
 | 
					        submenu,
 | 
				
			||||||
 | 
					        "Save",
 | 
				
			||||||
 | 
					        SubmenuIndexCommonSave,
 | 
				
			||||||
 | 
					        nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					        instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) {
 | 
				
			||||||
 | 
					        submenu_add_item(
 | 
				
			||||||
 | 
					            submenu,
 | 
				
			||||||
 | 
					            "Emulate UID",
 | 
				
			||||||
 | 
					            SubmenuIndexCommonEmulate,
 | 
				
			||||||
 | 
					            nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					            instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } else if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateFull)) {
 | 
				
			||||||
 | 
					        submenu_add_item(
 | 
				
			||||||
 | 
					            submenu,
 | 
				
			||||||
 | 
					            "Emulate",
 | 
				
			||||||
 | 
					            SubmenuIndexCommonEmulate,
 | 
				
			||||||
 | 
					            nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					            instance);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_protocol_support[protocol]->scene_read_menu.on_enter(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    submenu_add_item(
 | 
				
			||||||
 | 
					        submenu,
 | 
				
			||||||
 | 
					        "Info",
 | 
				
			||||||
 | 
					        SubmenuIndexCommonInfo,
 | 
				
			||||||
 | 
					        nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					        instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    submenu_set_selected_item(
 | 
				
			||||||
 | 
					        instance->submenu,
 | 
				
			||||||
 | 
					        scene_manager_get_scene_state(instance->scene_manager, NfcSceneReadMenu));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					    nfc_protocol_support_scene_read_menu_on_event(NfcApp* instance, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        scene_manager_set_scene_state(instance->scene_manager, NfcSceneReadMenu, event.event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(event.event == SubmenuIndexCommonSave) {
 | 
				
			||||||
 | 
					            scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        } else if(event.event == SubmenuIndexCommonInfo) {
 | 
				
			||||||
 | 
					            scene_manager_next_scene(instance->scene_manager, NfcSceneInfo);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        } else if(event.event == SubmenuIndexCommonEmulate) {
 | 
				
			||||||
 | 
					            dolphin_deed(DolphinDeedNfcEmulate);
 | 
				
			||||||
 | 
					            scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 | 
				
			||||||
 | 
					            consumed =
 | 
				
			||||||
 | 
					                nfc_protocol_support[protocol]->scene_read_menu.on_event(instance, event.event);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } else if(event.type == SceneManagerEventTypeBack) {
 | 
				
			||||||
 | 
					        scene_manager_set_scene_state(instance->scene_manager, NfcSceneSavedMenu, 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Same for read_menu and saved_menu
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_read_saved_menu_on_exit(NfcApp* instance) {
 | 
				
			||||||
 | 
					    submenu_reset(instance->submenu);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SceneReadSuccess
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_read_success_on_enter(NfcApp* instance) {
 | 
				
			||||||
 | 
					    Widget* widget = instance->widget;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    if(nfc_supported_cards_parse(instance->nfc_device, temp_str)) {
 | 
				
			||||||
 | 
					        widget_add_text_scroll_element(
 | 
				
			||||||
 | 
					            instance->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 | 
				
			||||||
 | 
					        nfc_protocol_support[protocol]->scene_read_success.on_enter(instance);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    widget_add_button_element(
 | 
				
			||||||
 | 
					        widget, GuiButtonTypeLeft, "Retry", nfc_protocol_support_common_widget_callback, instance);
 | 
				
			||||||
 | 
					    widget_add_button_element(
 | 
				
			||||||
 | 
					        widget, GuiButtonTypeRight, "More", nfc_protocol_support_common_widget_callback, instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    notification_message_block(instance->notifications, &sequence_set_green_255);
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					    nfc_protocol_support_scene_read_success_on_event(NfcApp* instance, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        if(event.event == GuiButtonTypeLeft) {
 | 
				
			||||||
 | 
					            scene_manager_next_scene(instance->scene_manager, NfcSceneRetryConfirm);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        } else if(event.event == GuiButtonTypeRight) {
 | 
				
			||||||
 | 
					            scene_manager_next_scene(instance->scene_manager, NfcSceneReadMenu);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } else if(event.type == SceneManagerEventTypeBack) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneExitConfirm);
 | 
				
			||||||
 | 
					        consumed = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_read_success_on_exit(NfcApp* instance) {
 | 
				
			||||||
 | 
					    notification_message_block(instance->notifications, &sequence_reset_green);
 | 
				
			||||||
 | 
					    widget_reset(instance->widget);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SceneSavedMenu
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_saved_menu_on_enter(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Submenu* submenu = instance->submenu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Header submenu items
 | 
				
			||||||
 | 
					    if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) {
 | 
				
			||||||
 | 
					        submenu_add_item(
 | 
				
			||||||
 | 
					            submenu,
 | 
				
			||||||
 | 
					            "Emulate UID",
 | 
				
			||||||
 | 
					            SubmenuIndexCommonEmulate,
 | 
				
			||||||
 | 
					            nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					            instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } else if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateFull)) {
 | 
				
			||||||
 | 
					        submenu_add_item(
 | 
				
			||||||
 | 
					            submenu,
 | 
				
			||||||
 | 
					            "Emulate",
 | 
				
			||||||
 | 
					            SubmenuIndexCommonEmulate,
 | 
				
			||||||
 | 
					            nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					            instance);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEditUid)) {
 | 
				
			||||||
 | 
					        submenu_add_item(
 | 
				
			||||||
 | 
					            submenu,
 | 
				
			||||||
 | 
					            "Edit UID",
 | 
				
			||||||
 | 
					            SubmenuIndexCommonEdit,
 | 
				
			||||||
 | 
					            nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					            instance);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Protocol-dependent menu items
 | 
				
			||||||
 | 
					    nfc_protocol_support[protocol]->scene_saved_menu.on_enter(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Trailer submenu items
 | 
				
			||||||
 | 
					    submenu_add_item(
 | 
				
			||||||
 | 
					        submenu,
 | 
				
			||||||
 | 
					        "Info",
 | 
				
			||||||
 | 
					        SubmenuIndexCommonInfo,
 | 
				
			||||||
 | 
					        nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					        instance);
 | 
				
			||||||
 | 
					    submenu_add_item(
 | 
				
			||||||
 | 
					        submenu,
 | 
				
			||||||
 | 
					        "Rename",
 | 
				
			||||||
 | 
					        SubmenuIndexCommonRename,
 | 
				
			||||||
 | 
					        nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					        instance);
 | 
				
			||||||
 | 
					    submenu_add_item(
 | 
				
			||||||
 | 
					        submenu,
 | 
				
			||||||
 | 
					        "Delete",
 | 
				
			||||||
 | 
					        SubmenuIndexCommonDelete,
 | 
				
			||||||
 | 
					        nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					        instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(nfc_has_shadow_file(instance)) {
 | 
				
			||||||
 | 
					        submenu_add_item(
 | 
				
			||||||
 | 
					            submenu,
 | 
				
			||||||
 | 
					            "Restore Data Changes",
 | 
				
			||||||
 | 
					            SubmenuIndexCommonRestore,
 | 
				
			||||||
 | 
					            nfc_protocol_support_common_submenu_callback,
 | 
				
			||||||
 | 
					            instance);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    submenu_set_selected_item(
 | 
				
			||||||
 | 
					        instance->submenu,
 | 
				
			||||||
 | 
					        scene_manager_get_scene_state(instance->scene_manager, NfcSceneSavedMenu));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewMenu);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					    nfc_protocol_support_scene_saved_menu_on_event(NfcApp* instance, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        scene_manager_set_scene_state(instance->scene_manager, NfcSceneSavedMenu, event.event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(event.event == SubmenuIndexCommonRestore) {
 | 
				
			||||||
 | 
					            scene_manager_next_scene(instance->scene_manager, NfcSceneRestoreOriginalConfirm);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        } else if(event.event == SubmenuIndexCommonInfo) {
 | 
				
			||||||
 | 
					            scene_manager_next_scene(instance->scene_manager, NfcSceneSupportedCard);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        } else if(event.event == SubmenuIndexCommonRename) {
 | 
				
			||||||
 | 
					            scene_manager_next_scene(instance->scene_manager, NfcSceneSaveName);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        } else if(event.event == SubmenuIndexCommonDelete) {
 | 
				
			||||||
 | 
					            scene_manager_next_scene(instance->scene_manager, NfcSceneDelete);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        } else if(event.event == SubmenuIndexCommonEmulate) {
 | 
				
			||||||
 | 
					            const bool is_added =
 | 
				
			||||||
 | 
					                scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSetType);
 | 
				
			||||||
 | 
					            dolphin_deed(is_added ? DolphinDeedNfcAddEmulate : DolphinDeedNfcEmulate);
 | 
				
			||||||
 | 
					            scene_manager_next_scene(instance->scene_manager, NfcSceneEmulate);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        } else if(event.event == SubmenuIndexCommonEdit) {
 | 
				
			||||||
 | 
					            scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 | 
				
			||||||
 | 
					            consumed =
 | 
				
			||||||
 | 
					                nfc_protocol_support[protocol]->scene_saved_menu.on_event(instance, event.event);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } else if(event.type == SceneManagerEventTypeBack) {
 | 
				
			||||||
 | 
					        scene_manager_set_scene_state(instance->scene_manager, NfcSceneSavedMenu, 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SceneSaveName
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_save_name_on_enter(NfcApp* instance) {
 | 
				
			||||||
 | 
					    FuriString* folder_path = furi_string_alloc();
 | 
				
			||||||
 | 
					    TextInput* text_input = instance->text_input;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool name_is_empty = furi_string_empty(instance->file_name);
 | 
				
			||||||
 | 
					    if(name_is_empty) {
 | 
				
			||||||
 | 
					        furi_string_set(instance->file_path, NFC_APP_FOLDER);
 | 
				
			||||||
 | 
					        name_generator_make_auto(
 | 
				
			||||||
 | 
					            instance->text_store, NFC_TEXT_STORE_SIZE, NFC_APP_FILENAME_PREFIX);
 | 
				
			||||||
 | 
					        furi_string_set(folder_path, NFC_APP_FOLDER);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        nfc_text_store_set(instance, "%s", furi_string_get_cstr(instance->file_name));
 | 
				
			||||||
 | 
					        path_extract_dirname(furi_string_get_cstr(instance->file_path), folder_path);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    text_input_set_header_text(text_input, "Name the card");
 | 
				
			||||||
 | 
					    text_input_set_result_callback(
 | 
				
			||||||
 | 
					        text_input,
 | 
				
			||||||
 | 
					        nfc_protocol_support_common_text_input_done_callback,
 | 
				
			||||||
 | 
					        instance,
 | 
				
			||||||
 | 
					        instance->text_store,
 | 
				
			||||||
 | 
					        NFC_NAME_SIZE,
 | 
				
			||||||
 | 
					        name_is_empty);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ValidatorIsFile* validator_is_file = validator_is_file_alloc_init(
 | 
				
			||||||
 | 
					        furi_string_get_cstr(folder_path),
 | 
				
			||||||
 | 
					        NFC_APP_EXTENSION,
 | 
				
			||||||
 | 
					        furi_string_get_cstr(instance->file_name));
 | 
				
			||||||
 | 
					    text_input_set_validator(text_input, validator_is_file_callback, validator_is_file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(folder_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextInput);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					    nfc_protocol_support_scene_save_name_on_event(NfcApp* instance, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        if(event.event == NfcCustomEventTextInputDone) {
 | 
				
			||||||
 | 
					            if(!furi_string_empty(instance->file_name)) {
 | 
				
			||||||
 | 
					                nfc_delete(instance);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            furi_string_set(instance->file_name, instance->text_store);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if(nfc_save(instance)) {
 | 
				
			||||||
 | 
					                scene_manager_next_scene(instance->scene_manager, NfcSceneSaveSuccess);
 | 
				
			||||||
 | 
					                dolphin_deed(
 | 
				
			||||||
 | 
					                    scene_manager_has_previous_scene(instance->scene_manager, NfcSceneSetType) ?
 | 
				
			||||||
 | 
					                        DolphinDeedNfcAddSave :
 | 
				
			||||||
 | 
					                        DolphinDeedNfcSave);
 | 
				
			||||||
 | 
					                const NfcProtocol protocol =
 | 
				
			||||||
 | 
					                    instance->protocols_detected[instance->protocols_detected_selected_idx];
 | 
				
			||||||
 | 
					                consumed = nfc_protocol_support[protocol]->scene_save_name.on_event(
 | 
				
			||||||
 | 
					                    instance, event.event);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                consumed = scene_manager_search_and_switch_to_previous_scene(
 | 
				
			||||||
 | 
					                    instance->scene_manager, NfcSceneStart);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_save_name_on_exit(NfcApp* instance) {
 | 
				
			||||||
 | 
					    void* validator_context = text_input_get_validator_callback_context(instance->text_input);
 | 
				
			||||||
 | 
					    text_input_set_validator(instance->text_input, NULL, NULL);
 | 
				
			||||||
 | 
					    validator_is_file_free(validator_context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    text_input_reset(instance->text_input);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// SceneEmulate
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Current view displayed on the emulation scene.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The emulation scehe has two states: the default one showing information about
 | 
				
			||||||
 | 
					 * the card being emulated, and the logs which show the raw data received from the reader.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The user has the ability to switch betweeen these two scenes, however the prompt to switch is
 | 
				
			||||||
 | 
					 * only shown after some information had appered in the log view.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					enum {
 | 
				
			||||||
 | 
					    NfcSceneEmulateStateWidget, /**< Widget view is displayed. */
 | 
				
			||||||
 | 
					    NfcSceneEmulateStateTextBox, /**< TextBox view is displayed. */
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_emulate_on_enter(NfcApp* instance) {
 | 
				
			||||||
 | 
					    Widget* widget = instance->widget;
 | 
				
			||||||
 | 
					    TextBox* text_box = instance->text_box;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(nfc_protocol_support_has_feature(protocol, NfcProtocolFeatureEmulateUid)) {
 | 
				
			||||||
 | 
					        widget_add_string_element(
 | 
				
			||||||
 | 
					            widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating UID");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        size_t uid_len;
 | 
				
			||||||
 | 
					        const uint8_t* uid = nfc_device_get_uid(instance->nfc_device, &uid_len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < uid_len; ++i) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(temp_str, "%02X ", uid[i]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_trim(temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        widget_add_string_element(widget, 90, 13, AlignCenter, AlignTop, FontPrimary, "Emulating");
 | 
				
			||||||
 | 
					        furi_string_set(
 | 
				
			||||||
 | 
					            temp_str, nfc_device_get_name(instance->nfc_device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    widget_add_text_box_element(
 | 
				
			||||||
 | 
					        widget, 56, 28, 71, 25, AlignCenter, AlignTop, furi_string_get_cstr(temp_str), false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    text_box_set_font(text_box, TextBoxFontHex);
 | 
				
			||||||
 | 
					    text_box_set_focus(text_box, TextBoxFocusEnd);
 | 
				
			||||||
 | 
					    furi_string_reset(instance->text_box_store);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // instance->listener is allocated in the respective on_enter() handler
 | 
				
			||||||
 | 
					    nfc_protocol_support[protocol]->scene_emulate.on_enter(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					        instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
 | 
				
			||||||
 | 
					    nfc_blink_emulate_start(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool
 | 
				
			||||||
 | 
					    nfc_protocol_support_scene_emulate_on_event(NfcApp* instance, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const uint32_t state = scene_manager_get_scene_state(instance->scene_manager, NfcSceneEmulate);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        if(event.event == NfcCustomEventListenerUpdate) {
 | 
				
			||||||
 | 
					            // Add data button to widget if data is received for the first time
 | 
				
			||||||
 | 
					            if(furi_string_size(instance->text_box_store)) {
 | 
				
			||||||
 | 
					                widget_add_button_element(
 | 
				
			||||||
 | 
					                    instance->widget,
 | 
				
			||||||
 | 
					                    GuiButtonTypeCenter,
 | 
				
			||||||
 | 
					                    "Log",
 | 
				
			||||||
 | 
					                    nfc_protocol_support_common_widget_callback,
 | 
				
			||||||
 | 
					                    instance);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // Update TextBox data
 | 
				
			||||||
 | 
					            text_box_set_text(instance->text_box, furi_string_get_cstr(instance->text_box_store));
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        } else if(event.event == GuiButtonTypeCenter) {
 | 
				
			||||||
 | 
					            if(state == NfcSceneEmulateStateWidget) {
 | 
				
			||||||
 | 
					                view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewTextBox);
 | 
				
			||||||
 | 
					                scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					                    instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateTextBox);
 | 
				
			||||||
 | 
					                consumed = true;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else if(event.type == SceneManagerEventTypeBack) {
 | 
				
			||||||
 | 
					        if(state == NfcSceneEmulateStateTextBox) {
 | 
				
			||||||
 | 
					            view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewWidget);
 | 
				
			||||||
 | 
					            scene_manager_set_scene_state(
 | 
				
			||||||
 | 
					                instance->scene_manager, NfcSceneEmulate, NfcSceneEmulateStateWidget);
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_emulate_stop_listener(NfcApp* instance) {
 | 
				
			||||||
 | 
					    nfc_listener_stop(instance->listener);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(protocol == nfc_listener_get_protocol(instance->listener)) {
 | 
				
			||||||
 | 
					        const NfcDeviceData* data = nfc_listener_get_data(instance->listener, protocol);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(!nfc_device_is_equal_data(instance->nfc_device, protocol, data)) {
 | 
				
			||||||
 | 
					            nfc_device_set_data(instance->nfc_device, protocol, data);
 | 
				
			||||||
 | 
					            nfc_save_shadow_file(instance);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_listener_free(instance->listener);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_emulate_on_exit(NfcApp* instance) {
 | 
				
			||||||
 | 
					    nfc_protocol_support_scene_emulate_stop_listener(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Clear view
 | 
				
			||||||
 | 
					    widget_reset(instance->widget);
 | 
				
			||||||
 | 
					    text_box_reset(instance->text_box);
 | 
				
			||||||
 | 
					    furi_string_reset(instance->text_box_store);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_blink_stop(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_rpc_on_enter(NfcApp* instance) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_rpc_setup_ui_and_emulate(NfcApp* instance) {
 | 
				
			||||||
 | 
					    nfc_text_store_set(instance, "emulating\n%s", furi_string_get_cstr(instance->file_name));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    popup_set_header(instance->popup, "NFC", 89, 42, AlignCenter, AlignBottom);
 | 
				
			||||||
 | 
					    popup_set_text(instance->popup, instance->text_store, 89, 44, AlignCenter, AlignTop);
 | 
				
			||||||
 | 
					    popup_set_icon(instance->popup, 0, 12, &I_RFIDDolphinSend_97x61);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    notification_message(instance->notifications, &sequence_display_backlight_on);
 | 
				
			||||||
 | 
					    nfc_blink_emulate_start(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const NfcProtocol protocol = nfc_device_get_protocol(instance->nfc_device);
 | 
				
			||||||
 | 
					    nfc_protocol_support[protocol]->scene_emulate.on_enter(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->rpc_state = NfcRpcStateEmulating;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_protocol_support_scene_rpc_on_event(NfcApp* instance, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        if(event.event == NfcCustomEventRpcLoad && instance->rpc_state == NfcRpcStateIdle) {
 | 
				
			||||||
 | 
					            furi_string_set(instance->file_path, rpc_system_app_get_data(instance->rpc_ctx));
 | 
				
			||||||
 | 
					            const bool load_success = nfc_load_file(instance, instance->file_path, false);
 | 
				
			||||||
 | 
					            if(load_success) {
 | 
				
			||||||
 | 
					                nfc_protocol_support_scene_rpc_setup_ui_and_emulate(instance);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            rpc_system_app_confirm(instance->rpc_ctx, RpcAppEventLoadFile, load_success);
 | 
				
			||||||
 | 
					        } else if(event.event == NfcCustomEventRpcExit) {
 | 
				
			||||||
 | 
					            rpc_system_app_confirm(instance->rpc_ctx, RpcAppEventAppExit, true);
 | 
				
			||||||
 | 
					            scene_manager_stop(instance->scene_manager);
 | 
				
			||||||
 | 
					            view_dispatcher_stop(instance->view_dispatcher);
 | 
				
			||||||
 | 
					        } else if(event.event == NfcCustomEventRpcSessionClose) {
 | 
				
			||||||
 | 
					            scene_manager_stop(instance->scene_manager);
 | 
				
			||||||
 | 
					            view_dispatcher_stop(instance->view_dispatcher);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        consumed = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_protocol_support_scene_rpc_on_exit(NfcApp* instance) {
 | 
				
			||||||
 | 
					    if(instance->rpc_state == NfcRpcStateEmulating) {
 | 
				
			||||||
 | 
					        nfc_protocol_support_scene_emulate_stop_listener(instance);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    popup_reset(instance->popup);
 | 
				
			||||||
 | 
					    text_box_reset(instance->text_box);
 | 
				
			||||||
 | 
					    furi_string_reset(instance->text_box_store);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_blink_stop(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const NfcProtocolSupportCommonSceneBase
 | 
				
			||||||
 | 
					    nfc_protocol_support_scenes[NfcProtocolSupportSceneCount] = {
 | 
				
			||||||
 | 
					        [NfcProtocolSupportSceneInfo] =
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                .on_enter = nfc_protocol_support_scene_info_on_enter,
 | 
				
			||||||
 | 
					                .on_event = nfc_protocol_support_scene_info_on_event,
 | 
				
			||||||
 | 
					                .on_exit = nfc_protocol_support_scene_info_on_exit,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        [NfcProtocolSupportSceneMoreInfo] =
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                .on_enter = nfc_protocol_support_scene_more_info_on_enter,
 | 
				
			||||||
 | 
					                .on_event = nfc_protocol_support_scene_more_info_on_event,
 | 
				
			||||||
 | 
					                .on_exit = nfc_protocol_support_scene_more_info_on_exit,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        [NfcProtocolSupportSceneRead] =
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                .on_enter = nfc_protocol_support_scene_read_on_enter,
 | 
				
			||||||
 | 
					                .on_event = nfc_protocol_support_scene_read_on_event,
 | 
				
			||||||
 | 
					                .on_exit = nfc_protocol_support_scene_read_on_exit,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        [NfcProtocolSupportSceneReadMenu] =
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                .on_enter = nfc_protocol_support_scene_read_menu_on_enter,
 | 
				
			||||||
 | 
					                .on_event = nfc_protocol_support_scene_read_menu_on_event,
 | 
				
			||||||
 | 
					                .on_exit = nfc_protocol_support_scene_read_saved_menu_on_exit,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        [NfcProtocolSupportSceneReadSuccess] =
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                .on_enter = nfc_protocol_support_scene_read_success_on_enter,
 | 
				
			||||||
 | 
					                .on_event = nfc_protocol_support_scene_read_success_on_event,
 | 
				
			||||||
 | 
					                .on_exit = nfc_protocol_support_scene_read_success_on_exit,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        [NfcProtocolSupportSceneSavedMenu] =
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                .on_enter = nfc_protocol_support_scene_saved_menu_on_enter,
 | 
				
			||||||
 | 
					                .on_event = nfc_protocol_support_scene_saved_menu_on_event,
 | 
				
			||||||
 | 
					                .on_exit = nfc_protocol_support_scene_read_saved_menu_on_exit,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        [NfcProtocolSupportSceneSaveName] =
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                .on_enter = nfc_protocol_support_scene_save_name_on_enter,
 | 
				
			||||||
 | 
					                .on_event = nfc_protocol_support_scene_save_name_on_event,
 | 
				
			||||||
 | 
					                .on_exit = nfc_protocol_support_scene_save_name_on_exit,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        [NfcProtocolSupportSceneEmulate] =
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                .on_enter = nfc_protocol_support_scene_emulate_on_enter,
 | 
				
			||||||
 | 
					                .on_event = nfc_protocol_support_scene_emulate_on_event,
 | 
				
			||||||
 | 
					                .on_exit = nfc_protocol_support_scene_emulate_on_exit,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        [NfcProtocolSupportSceneRpc] =
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                .on_enter = nfc_protocol_support_scene_rpc_on_enter,
 | 
				
			||||||
 | 
					                .on_event = nfc_protocol_support_scene_rpc_on_event,
 | 
				
			||||||
 | 
					                .on_exit = nfc_protocol_support_scene_rpc_on_exit,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,113 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @file nfc_protocol_support.h
 | 
				
			||||||
 | 
					 * @brief Interface for application-level protocol support.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * NFC protocol support helper abstracts common scenes with a single interface
 | 
				
			||||||
 | 
					 * and lets each protocol decide on concrete implementation.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * # Integrating a new protocol into the application
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Most of the scenes in the NFC application work through abstract APIs, so they do not need
 | 
				
			||||||
 | 
					 * protocol-specific versions of themselves. However, when such a situation
 | 
				
			||||||
 | 
					 * occurs, the protocol support helper provides another level of abstraction to hide
 | 
				
			||||||
 | 
					 * the protocol-specific details and isolate them to separate modules.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see nfc_protocol.h for more information on adding library protocols.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The steps for adding support for a library protocol are described below.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * ## 1. Create the files
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * ### 1.1 Recommended file structure
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The recommended file structure for a protocol support is as follows:
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * ```text
 | 
				
			||||||
 | 
					 * protocol_support
 | 
				
			||||||
 | 
					 *        |
 | 
				
			||||||
 | 
					 *        +- protocol_name
 | 
				
			||||||
 | 
					 *                |
 | 
				
			||||||
 | 
					 *                +- protocol_name.h
 | 
				
			||||||
 | 
					 *                |
 | 
				
			||||||
 | 
					 *                +- protocol_name.c
 | 
				
			||||||
 | 
					 *                |
 | 
				
			||||||
 | 
					 *                +- protocol_name_render.h
 | 
				
			||||||
 | 
					 *                |
 | 
				
			||||||
 | 
					 *                +- protocol_name_render.c
 | 
				
			||||||
 | 
					 *                |
 | 
				
			||||||
 | 
					 * ```
 | 
				
			||||||
 | 
					 * ### 1.2 File structure explanation
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * | Filename               | Explanation |
 | 
				
			||||||
 | 
					 * |:-----------------------|:------------|
 | 
				
			||||||
 | 
					 * | protocol_name.h        | Interface structure declaration used in `nfc_protocol_support_defs.c`. |
 | 
				
			||||||
 | 
					 * | protocol_name.c        | Protocol-specific scene implemenatations and definitions. |
 | 
				
			||||||
 | 
					 * | protocol_name_render.h | Protocol-specific rendering (formatting) functions. Used for converting protocol data into textual descriptions. |
 | 
				
			||||||
 | 
					 * | protocol_name_render.c | Implementations for functions declared in `protocol_name_render.h`.|
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * ## 2. Implement the code
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * ### 2.1 Features
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Decide what features the protocol will be providing. The features can be combined using bitwise OR (`"|"`).
 | 
				
			||||||
 | 
					 * This choice influences which scenes will have to be implemented in step 2.2.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see NfcProtocolFeature for the enumeration of possible features to implement.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * ### 2.2 Scenes
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * If a particular scene is not implemented, its empty placeholder from nfc_protocol_support_gui_common.h must be used instead.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see nfc_protocol_support_common.h for the enumeration of all scenes that can be implemented.
 | 
				
			||||||
 | 
					 * @see nfc_protocol_support_base.h for the scene implementation details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * ### 2.3. Registering the protocol support
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * After completing the protocol support, it must be registered within the application in order for it to be usable.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * In nfc_protocol_support_defs.c, include the `protocol_name.h` file and add a new entry in the `nfc_protocol_support[]`
 | 
				
			||||||
 | 
					 * array under the appropriate index.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * ## Done!
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @note It will not always be possible to abstract all of the protocol's functionality using the protocol support helper.
 | 
				
			||||||
 | 
					 * In such cases, creating separate protocol-specific scenes is okay (as an example, note the `nfc/scenes/nfc_scene_mf_classic_*` scenes which didn't fit this paradigm).
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <gui/scene_manager.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc_protocol_support_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Abstract interface for on_enter() scene handler.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Is to be called whenever a scene is entered to.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in] scene identifier of the scene associated with the handler.
 | 
				
			||||||
 | 
					 * @param[in,out] context pointer to a user-specified context (will be passed to concrete handler).
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void nfc_protocol_support_on_enter(NfcProtocolSupportScene scene, void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Abstract interface for on_event() scene handler.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in] scene identifier of the scene associated with the handler.
 | 
				
			||||||
 | 
					 * @param[in,out] context pointer to a user-specified context (will be passed to concrete handler).
 | 
				
			||||||
 | 
					 * @param[in] event SceneManager event to be handled by the scene.
 | 
				
			||||||
 | 
					 * @returns true if the event was consumed, false otherwise.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					bool nfc_protocol_support_on_event(
 | 
				
			||||||
 | 
					    NfcProtocolSupportScene scene,
 | 
				
			||||||
 | 
					    void* context,
 | 
				
			||||||
 | 
					    SceneManagerEvent event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Abstract interface for on_exit() scene handler.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Is to be called whenever a scene is exited from.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in] scene identifier of the scene associated with the handler.
 | 
				
			||||||
 | 
					 * @param[in,out] context pointer to a user-specified context (will be passed to concrete handler).
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void nfc_protocol_support_on_exit(NfcProtocolSupportScene scene, void* context);
 | 
				
			||||||
@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @file nfc_protocol_support_base.h
 | 
				
			||||||
 | 
					 * @brief Abstract interface for application-level protocol support.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <core/string.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../../nfc_app.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Scene entry handler.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in,out] instance pointer to the NFC application instance.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef void (*NfcProtocolSupportOnEnter)(NfcApp* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Scene event handler.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in,out] instance pointer to the NFC application instance.
 | 
				
			||||||
 | 
					 * @param[in] event custom event that has occurred.
 | 
				
			||||||
 | 
					 * @returns true if the event was handled, false otherwise.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef bool (*NfcProtocolSupportOnEvent)(NfcApp* instance, uint32_t event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Abstract scene interface.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * on_exit() handler is not implemented due to being redundant.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    NfcProtocolSupportOnEnter on_enter; /**< Pointer to the on_enter() function. */
 | 
				
			||||||
 | 
					    NfcProtocolSupportOnEvent on_event; /**< Pointer to the on_event() function. */
 | 
				
			||||||
 | 
					} NfcProtocolSupportSceneBase;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Abstract protocol support interface.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    const uint32_t features; /**< Feature bitmask supported by the protocol. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief Handlers for protocol-specific info scene.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * This scene displays general information about a saved or recently read card.
 | 
				
			||||||
 | 
					     * It may include a button that will lead to more information being shown.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneBase scene_info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief Handlers for protocol-specific extended info scene.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * This scene shows more information about a saved or
 | 
				
			||||||
 | 
					     * recently read card, such as memory dumps.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * It may include (a) button(s) and/or menu(s) that will lead to
 | 
				
			||||||
 | 
					     * protocol-specific scenes not covered in this helper.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneBase scene_more_info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief Handlers for protocol-specific read scene.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * This scene is activated when a read operation is in progress.
 | 
				
			||||||
 | 
					     * It is responsible for creating a poller and for handling its events.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneBase scene_read;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief Handlers for protocol-specific read menu scene.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * This scene presents the user with options available for the
 | 
				
			||||||
 | 
					     * recenly read card. Such options may include:
 | 
				
			||||||
 | 
					     * * Saving
 | 
				
			||||||
 | 
					     * * Getting information
 | 
				
			||||||
 | 
					     * * Emulating etc.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneBase scene_read_menu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief Handlers for protocol-specific read success scene.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * This scene is activated after a successful read operation.
 | 
				
			||||||
 | 
					     * It is responsible for displaying a very short summary about
 | 
				
			||||||
 | 
					     * the card that was just read.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneBase scene_read_success;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief Handlers for protocol-specific saved file menu scene.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * This scene presents the user with options available for a
 | 
				
			||||||
 | 
					     * card loaded from file. Such options may include:
 | 
				
			||||||
 | 
					     * * Renaming
 | 
				
			||||||
 | 
					     * * Deleting
 | 
				
			||||||
 | 
					     * * Getting information
 | 
				
			||||||
 | 
					     * * Emulating etc.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneBase scene_saved_menu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief Handlers for protocol-specific name entry scene.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * This scene is used to enter a file name when saving or renaming a file.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneBase scene_save_name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @brief Handlers for protocol-specific emulate scene.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * This scene is activated when an emulation operation is in progress.
 | 
				
			||||||
 | 
					     * It is responsible for creating a listener and for handling its events.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneBase scene_emulate;
 | 
				
			||||||
 | 
					} NfcProtocolSupportBase;
 | 
				
			||||||
@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @file nfc_protocol_support_common.h
 | 
				
			||||||
 | 
					 * @brief Common application-level protocol support definitions.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Enumeration of protocol features.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    NfcProtocolFeatureNone = 0, /**< No features are supported. */
 | 
				
			||||||
 | 
					    NfcProtocolFeatureEmulateUid = 1UL << 0, /**< Partial emulation is supported. */
 | 
				
			||||||
 | 
					    NfcProtocolFeatureEmulateFull = 1UL << 1, /**< Complete emulation is supported. */
 | 
				
			||||||
 | 
					    NfcProtocolFeatureEditUid = 1UL << 2, /**< UID editing is supported. */
 | 
				
			||||||
 | 
					    NfcProtocolFeatureMoreInfo = 1UL << 3, /**< More information is provided. */
 | 
				
			||||||
 | 
					} NfcProtocolFeature;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Enumeration of protocol-aware scenes.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * These are the scenes that are common to all protocols, but require
 | 
				
			||||||
 | 
					 * a protocol-specific implementation.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneInfo, /**< Display general card information. */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneMoreInfo, /**< Display more card information. */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneRead, /**< Shown when reading a card. */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneReadMenu, /**< Menu with options available for the recently read card. */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneReadSuccess, /**< Shown after having successfully read a card. */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneSavedMenu, /**< Menu for the card that was loaded from file. */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneSaveName, /**< Shown when saving or renaming a file. */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneEmulate, /**< Shown when emulating a card. */
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneRpc, /**< Shown in remote-controlled (RPC) mode. */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcProtocolSupportSceneCount, /**< Special value equal to total scene count. Internal use. */
 | 
				
			||||||
 | 
					} NfcProtocolSupportScene;
 | 
				
			||||||
@ -0,0 +1,45 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @file nfc_protocol_support_defs.c
 | 
				
			||||||
 | 
					 * @brief Application-level protocol support definitions.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This file is to be modified whenever support for
 | 
				
			||||||
 | 
					 * a new protocol is to be added.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#include "nfc_protocol_support_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/nfc_protocol.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "iso14443_3a/iso14443_3a.h"
 | 
				
			||||||
 | 
					#include "iso14443_3b/iso14443_3b.h"
 | 
				
			||||||
 | 
					#include "iso14443_4a/iso14443_4a.h"
 | 
				
			||||||
 | 
					#include "iso14443_4b/iso14443_4b.h"
 | 
				
			||||||
 | 
					#include "iso15693_3/iso15693_3.h"
 | 
				
			||||||
 | 
					#include "felica/felica.h"
 | 
				
			||||||
 | 
					#include "mf_ultralight/mf_ultralight.h"
 | 
				
			||||||
 | 
					#include "mf_classic/mf_classic.h"
 | 
				
			||||||
 | 
					#include "mf_desfire/mf_desfire.h"
 | 
				
			||||||
 | 
					#include "slix/slix.h"
 | 
				
			||||||
 | 
					#include "st25tb/st25tb.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Array of pointers to concrete protocol support implementations.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * When adding support for a new protocol, add it to the end of this array
 | 
				
			||||||
 | 
					 * under its respective index.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see nfc_protocol.h
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					const NfcProtocolSupportBase* nfc_protocol_support[NfcProtocolNum] = {
 | 
				
			||||||
 | 
					    [NfcProtocolIso14443_3a] = &nfc_protocol_support_iso14443_3a,
 | 
				
			||||||
 | 
					    [NfcProtocolIso14443_3b] = &nfc_protocol_support_iso14443_3b,
 | 
				
			||||||
 | 
					    [NfcProtocolIso14443_4a] = &nfc_protocol_support_iso14443_4a,
 | 
				
			||||||
 | 
					    [NfcProtocolIso14443_4b] = &nfc_protocol_support_iso14443_4b,
 | 
				
			||||||
 | 
					    [NfcProtocolIso15693_3] = &nfc_protocol_support_iso15693_3,
 | 
				
			||||||
 | 
					    [NfcProtocolFelica] = &nfc_protocol_support_felica,
 | 
				
			||||||
 | 
					    [NfcProtocolMfUltralight] = &nfc_protocol_support_mf_ultralight,
 | 
				
			||||||
 | 
					    [NfcProtocolMfClassic] = &nfc_protocol_support_mf_classic,
 | 
				
			||||||
 | 
					    [NfcProtocolMfDesfire] = &nfc_protocol_support_mf_desfire,
 | 
				
			||||||
 | 
					    [NfcProtocolSlix] = &nfc_protocol_support_slix,
 | 
				
			||||||
 | 
					    [NfcProtocolSt25tb] = &nfc_protocol_support_st25tb,
 | 
				
			||||||
 | 
					    /* Add new protocol support implementations here */
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @file nfc_protocol_support_defs.h
 | 
				
			||||||
 | 
					 * @brief Application-level protocol support declarations.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc_protocol_support_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Declaraion of array of pointers to protocol support implementations.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					extern const NfcProtocolSupportBase* nfc_protocol_support[];
 | 
				
			||||||
@ -0,0 +1,42 @@
 | 
				
			|||||||
 | 
					#include "nfc_protocol_support_gui_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc/nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_protocol_support_common_submenu_callback(void* context, uint32_t index) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    view_dispatcher_send_custom_event(instance->view_dispatcher, index);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_protocol_support_common_widget_callback(
 | 
				
			||||||
 | 
					    GuiButtonType result,
 | 
				
			||||||
 | 
					    InputType type,
 | 
				
			||||||
 | 
					    void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    if(type == InputTypeShort) {
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(instance->view_dispatcher, result);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_protocol_support_common_byte_input_done_callback(void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventByteInputDone);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_protocol_support_common_text_input_done_callback(void* context) {
 | 
				
			||||||
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventTextInputDone);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_protocol_support_common_on_enter_empty(NfcApp* instance) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    UNUSED(instance);
 | 
				
			||||||
 | 
					    UNUSED(event);
 | 
				
			||||||
 | 
					    return true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,85 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @file nfc_protocol_support_gui_common.h
 | 
				
			||||||
 | 
					 * @brief Common GUI functions and definitions.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <gui/modules/widget.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc/nfc_app.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Common submenu indices.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					enum {
 | 
				
			||||||
 | 
					    SubmenuIndexCommonSave, /**< Save menu option. */
 | 
				
			||||||
 | 
					    SubmenuIndexCommonEmulate, /**< Emulate menu option. */
 | 
				
			||||||
 | 
					    SubmenuIndexCommonEdit, /**< Edit menu option. */
 | 
				
			||||||
 | 
					    SubmenuIndexCommonInfo, /**< Info menu option. */
 | 
				
			||||||
 | 
					    SubmenuIndexCommonRename, /**< Rename menu option. */
 | 
				
			||||||
 | 
					    SubmenuIndexCommonDelete, /**< Delete menu option. */
 | 
				
			||||||
 | 
					    SubmenuIndexCommonRestore, /**< Restore menu option. */
 | 
				
			||||||
 | 
					    SubmenuIndexCommonMax, /**< Special value, internal use. */
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Common submenu callback.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Called each time the user presses on a selected submenu item.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in,out] context pointer to a user-defined context object.
 | 
				
			||||||
 | 
					 * @param[in] index index of the item that was activated.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void nfc_protocol_support_common_submenu_callback(void* context, uint32_t index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Common widget callback.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Called each time the user presses on a selected widget element.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in] result identifier of the activated button.
 | 
				
			||||||
 | 
					 * @param[in] type type of press action.
 | 
				
			||||||
 | 
					 * @param[in,out] context pointer to a user-defined context object.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void nfc_protocol_support_common_widget_callback(
 | 
				
			||||||
 | 
					    GuiButtonType result,
 | 
				
			||||||
 | 
					    InputType type,
 | 
				
			||||||
 | 
					    void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Common byte input callback.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Called each time the user accepts the byte input.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in,out] context pointer to a user-defined context object.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void nfc_protocol_support_common_byte_input_done_callback(void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Common text input callback.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Called each time the user accepts the text input.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in,out] context pointer to a user-defined context object.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void nfc_protocol_support_common_text_input_done_callback(void* context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Empty on_enter() handler.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Does nothing.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in] instance pointer to the NFC application instance.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					void nfc_protocol_support_common_on_enter_empty(NfcApp* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Empty on_event() handler.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Does nothing and returns true.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in] instance pointer to the NFC application instance.
 | 
				
			||||||
 | 
					 * @param[in] event custom event type that has occurred.
 | 
				
			||||||
 | 
					 * @returns always true.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					bool nfc_protocol_support_common_on_event_empty(NfcApp* instance, uint32_t event);
 | 
				
			||||||
@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @file nfc_protocol_support_render_common.h
 | 
				
			||||||
 | 
					 * @brief Common formatting-related defines.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Displayed information verbosity level.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    NfcProtocolFormatTypeShort, /**< Short format, terse info. */
 | 
				
			||||||
 | 
					    NfcProtocolFormatTypeFull, /**< Full format, verbose info. */
 | 
				
			||||||
 | 
					} NfcProtocolFormatType;
 | 
				
			||||||
							
								
								
									
										141
									
								
								applications/main/nfc/helpers/protocol_support/slix/slix.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								applications/main/nfc/helpers/protocol_support/slix/slix.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,141 @@
 | 
				
			|||||||
 | 
					#include "slix.h"
 | 
				
			||||||
 | 
					#include "slix_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/slix/slix_poller.h>
 | 
				
			||||||
 | 
					#include <nfc/protocols/slix/slix_listener.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc/nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_common.h"
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_gui_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_info_on_enter_slix(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_slix_info(data, NfcProtocolFormatTypeFull, temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    widget_add_text_scroll_element(
 | 
				
			||||||
 | 
					        instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static NfcCommand nfc_scene_read_poller_callback_slix(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolSlix);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    const SlixPollerEvent* slix_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(slix_event->type == SlixPollerEventTypeReady) {
 | 
				
			||||||
 | 
					        nfc_device_set_data(
 | 
				
			||||||
 | 
					            instance->nfc_device, NfcProtocolSlix, nfc_poller_get_data(instance->poller));
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
 | 
				
			||||||
 | 
					        return NfcCommandStop;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_on_enter_slix(NfcApp* instance) {
 | 
				
			||||||
 | 
					    nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_slix, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_success_on_enter_slix(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const SlixData* data = nfc_device_get_data(device, NfcProtocolSlix);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_slix_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 NfcCommand nfc_scene_emulate_listener_callback_slix(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolSlix);
 | 
				
			||||||
 | 
					    furi_assert(event.event_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					    SlixListenerEvent* slix_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(slix_event->type == SlixListenerEventTypeCustomCommand) {
 | 
				
			||||||
 | 
					        if(furi_string_size(nfc->text_box_store) < NFC_LOG_SIZE_MAX) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(nfc->text_box_store, "R:");
 | 
				
			||||||
 | 
					            for(size_t i = 0; i < bit_buffer_get_size_bytes(slix_event->data->buffer); i++) {
 | 
				
			||||||
 | 
					                furi_string_cat_printf(
 | 
				
			||||||
 | 
					                    nfc->text_box_store,
 | 
				
			||||||
 | 
					                    " %02X",
 | 
				
			||||||
 | 
					                    bit_buffer_get_byte(slix_event->data->buffer, i));
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            furi_string_push_back(nfc->text_box_store, '\n');
 | 
				
			||||||
 | 
					            view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventListenerUpdate);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_emulate_on_enter_slix(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const SlixData* data = nfc_device_get_data(instance->nfc_device, NfcProtocolSlix);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->listener = nfc_listener_alloc(instance->nfc, NfcProtocolSlix, data);
 | 
				
			||||||
 | 
					    nfc_listener_start(instance->listener, nfc_scene_emulate_listener_callback_slix, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_scene_saved_menu_on_event_slix(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    if(event == SubmenuIndexCommonEdit) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NfcProtocolSupportBase nfc_protocol_support_slix = {
 | 
				
			||||||
 | 
					    .features = NfcProtocolFeatureEmulateFull,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .scene_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_info_on_enter_slix,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_on_enter_slix,
 | 
				
			||||||
 | 
					            .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_slix,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_saved_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_protocol_support_common_on_enter_empty,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_saved_menu_on_event_slix,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .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_slix,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern const NfcProtocolSupportBase nfc_protocol_support_slix;
 | 
				
			||||||
@ -0,0 +1,74 @@
 | 
				
			|||||||
 | 
					#include "slix_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../iso15693_3/iso15693_3_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str) {
 | 
				
			||||||
 | 
					    nfc_render_iso15693_3_brief(slix_get_base_data(data), str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(format_type != NfcProtocolFormatTypeFull) return;
 | 
				
			||||||
 | 
					    const SlixType slix_type = slix_get_type(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat(str, "\n\e#Passwords\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    static const char* slix_password_names[] = {
 | 
				
			||||||
 | 
					        "Read",
 | 
				
			||||||
 | 
					        "Write",
 | 
				
			||||||
 | 
					        "Privacy",
 | 
				
			||||||
 | 
					        "Destroy",
 | 
				
			||||||
 | 
					        "EAS/AFI",
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(uint32_t i = 0; i < SlixPasswordTypeCount; ++i) {
 | 
				
			||||||
 | 
					        if(slix_type_supports_password(slix_type, i)) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(
 | 
				
			||||||
 | 
					                str, "%s :  %08lX\n", slix_password_names[i], data->passwords[i]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat(str, "\e#Lock bits\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_EAS)) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(
 | 
				
			||||||
 | 
					            str, "EAS: %s locked\n", data->system_info.lock_bits.eas ? "" : "not");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PROTECTION)) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(
 | 
				
			||||||
 | 
					            str, "PPL: %s locked\n", data->system_info.lock_bits.ppl ? "" : "not");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const SlixProtection protection = data->system_info.protection;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_cat(str, "\e#Page protection\n");
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "Pointer: H >= %02X\n", protection.pointer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const char* rh = (protection.condition & SLIX_PP_CONDITION_RH) ? "" : "un";
 | 
				
			||||||
 | 
					        const char* rl = (protection.condition & SLIX_PP_CONDITION_RL) ? "" : "un";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const char* wh = (protection.condition & SLIX_PP_CONDITION_WH) ? "" : "un";
 | 
				
			||||||
 | 
					        const char* wl = (protection.condition & SLIX_PP_CONDITION_WL) ? "" : "un";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "R:  H %sprotec. L %sprotec.\n", rh, rl);
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "W: H %sprotec. L %sprotec.\n", wh, wl);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_PRIVACY)) {
 | 
				
			||||||
 | 
					        furi_string_cat(str, "\e#Privacy\n");
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "Privacy mode: %sabled\n", data->privacy ? "en" : "dis");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(slix_type_has_features(slix_type, SLIX_TYPE_FEATURE_SIGNATURE)) {
 | 
				
			||||||
 | 
					        furi_string_cat(str, "\e#Signature\n");
 | 
				
			||||||
 | 
					        for(uint32_t i = 0; i < 4; ++i) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(str, "%02X ", data->signature[i]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_cat(str, "[ ... ]");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for(uint32_t i = 0; i < 3; ++i) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(str, " %02X", data->signature[sizeof(SlixSignature) - i - 1]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat(str, "\n\e#ISO15693-3 data");
 | 
				
			||||||
 | 
					    nfc_render_iso15693_3_extra(slix_get_base_data(data), str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/slix/slix.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_render_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_slix_info(const SlixData* data, NfcProtocolFormatType format_type, FuriString* str);
 | 
				
			||||||
							
								
								
									
										103
									
								
								applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								applications/main/nfc/helpers/protocol_support/st25tb/st25tb.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,103 @@
 | 
				
			|||||||
 | 
					#include "st25tb.h"
 | 
				
			||||||
 | 
					#include "st25tb_render.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/st25tb/st25tb_poller.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc/nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_common.h"
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_gui_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_info_on_enter_st25tb(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const St25tbData* data = nfc_device_get_data(device, NfcProtocolSt25tb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_st25tb_info(data, NfcProtocolFormatTypeFull, temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    widget_add_text_scroll_element(
 | 
				
			||||||
 | 
					        instance->widget, 0, 0, 128, 64, furi_string_get_cstr(temp_str));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static NfcCommand nfc_scene_read_poller_callback_st25tb(NfcGenericEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(event.protocol == NfcProtocolSt25tb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    const St25tbPollerEvent* st25tb_event = event.event_data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(st25tb_event->type == St25tbPollerEventTypeReady) {
 | 
				
			||||||
 | 
					        nfc_device_set_data(
 | 
				
			||||||
 | 
					            instance->nfc_device, NfcProtocolSt25tb, nfc_poller_get_data(instance->poller));
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventPollerSuccess);
 | 
				
			||||||
 | 
					        return NfcCommandStop;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return NfcCommandContinue;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_on_enter_st25tb(NfcApp* instance) {
 | 
				
			||||||
 | 
					    nfc_poller_start(instance->poller, nfc_scene_read_poller_callback_st25tb, instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_scene_read_success_on_enter_st25tb(NfcApp* instance) {
 | 
				
			||||||
 | 
					    const NfcDevice* device = instance->nfc_device;
 | 
				
			||||||
 | 
					    const St25tbData* data = nfc_device_get_data(device, NfcProtocolSt25tb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        temp_str, "\e#%s\n", nfc_device_get_name(device, NfcDeviceNameTypeFull));
 | 
				
			||||||
 | 
					    nfc_render_st25tb_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 bool nfc_scene_saved_menu_on_event_st25tb(NfcApp* instance, uint32_t event) {
 | 
				
			||||||
 | 
					    if(event == SubmenuIndexCommonEdit) {
 | 
				
			||||||
 | 
					        scene_manager_next_scene(instance->scene_manager, NfcSceneSetUid);
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const NfcProtocolSupportBase nfc_protocol_support_st25tb = {
 | 
				
			||||||
 | 
					    .features = NfcProtocolFeatureNone,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .scene_info =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_info_on_enter_st25tb,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_read =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_scene_read_on_enter_st25tb,
 | 
				
			||||||
 | 
					            .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_st25tb,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_saved_menu =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_protocol_support_common_on_enter_empty,
 | 
				
			||||||
 | 
					            .on_event = nfc_scene_saved_menu_on_event_st25tb,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					    .scene_emulate =
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            .on_enter = nfc_protocol_support_common_on_enter_empty,
 | 
				
			||||||
 | 
					            .on_event = nfc_protocol_support_common_on_event_empty,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_base.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern const NfcProtocolSupportBase nfc_protocol_support_st25tb;
 | 
				
			||||||
@ -0,0 +1,22 @@
 | 
				
			|||||||
 | 
					#include "st25tb_render.h"
 | 
				
			||||||
 | 
					#include <nfc/protocols/st25tb/st25tb.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_st25tb_info(
 | 
				
			||||||
 | 
					    const St25tbData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str) {
 | 
				
			||||||
 | 
					    furi_string_cat_printf(str, "UID");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(size_t i = 0; i < ST25TB_UID_SIZE; i++) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, " %02X", data->uid[i]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(format_type == NfcProtocolFormatTypeFull) {
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "\nSys. OTP: %08lX", data->system_otp_block);
 | 
				
			||||||
 | 
					        furi_string_cat_printf(str, "\nBlocks:");
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < st25tb_get_block_count(data->type); i += 2) {
 | 
				
			||||||
 | 
					            furi_string_cat_printf(
 | 
				
			||||||
 | 
					                str, "\n %02X   %08lX  %08lX", i, data->blocks[i], data->blocks[i + 1]);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/protocols/st25tb/st25tb.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "../nfc_protocol_support_render_common.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_render_st25tb_info(
 | 
				
			||||||
 | 
					    const St25tbData* data,
 | 
				
			||||||
 | 
					    NfcProtocolFormatType format_type,
 | 
				
			||||||
 | 
					    FuriString* str);
 | 
				
			||||||
@ -1,323 +0,0 @@
 | 
				
			|||||||
#include "nfc_i.h"
 | 
					 | 
				
			||||||
#include <furi_hal_nfc.h>
 | 
					 | 
				
			||||||
#include <dolphin/dolphin.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool nfc_custom_event_callback(void* context, uint32_t event) {
 | 
					 | 
				
			||||||
    furi_assert(context);
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    return scene_manager_handle_custom_event(nfc->scene_manager, event);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool nfc_back_event_callback(void* context) {
 | 
					 | 
				
			||||||
    furi_assert(context);
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    return scene_manager_handle_back_event(nfc->scene_manager);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void nfc_rpc_command_callback(RpcAppSystemEvent event, void* context) {
 | 
					 | 
				
			||||||
    furi_assert(context);
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    furi_assert(nfc->rpc_ctx);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(event == RpcAppEventSessionClose) {
 | 
					 | 
				
			||||||
        view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcSessionClose);
 | 
					 | 
				
			||||||
        rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL);
 | 
					 | 
				
			||||||
        nfc->rpc_ctx = NULL;
 | 
					 | 
				
			||||||
    } else if(event == RpcAppEventAppExit) {
 | 
					 | 
				
			||||||
        view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
 | 
					 | 
				
			||||||
    } else if(event == RpcAppEventLoadFile) {
 | 
					 | 
				
			||||||
        view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoad);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        rpc_system_app_confirm(nfc->rpc_ctx, event, false);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Nfc* nfc_alloc() {
 | 
					 | 
				
			||||||
    Nfc* nfc = malloc(sizeof(Nfc));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    nfc->worker = nfc_worker_alloc();
 | 
					 | 
				
			||||||
    nfc->view_dispatcher = view_dispatcher_alloc();
 | 
					 | 
				
			||||||
    nfc->scene_manager = scene_manager_alloc(&nfc_scene_handlers, nfc);
 | 
					 | 
				
			||||||
    view_dispatcher_enable_queue(nfc->view_dispatcher);
 | 
					 | 
				
			||||||
    view_dispatcher_set_event_callback_context(nfc->view_dispatcher, nfc);
 | 
					 | 
				
			||||||
    view_dispatcher_set_custom_event_callback(nfc->view_dispatcher, nfc_custom_event_callback);
 | 
					 | 
				
			||||||
    view_dispatcher_set_navigation_event_callback(nfc->view_dispatcher, nfc_back_event_callback);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Nfc device
 | 
					 | 
				
			||||||
    nfc->dev = nfc_device_alloc();
 | 
					 | 
				
			||||||
    furi_string_set(nfc->dev->folder, NFC_APP_FOLDER);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Open GUI record
 | 
					 | 
				
			||||||
    nfc->gui = furi_record_open(RECORD_GUI);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Open Notification record
 | 
					 | 
				
			||||||
    nfc->notifications = furi_record_open(RECORD_NOTIFICATION);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Submenu
 | 
					 | 
				
			||||||
    nfc->submenu = submenu_alloc();
 | 
					 | 
				
			||||||
    view_dispatcher_add_view(nfc->view_dispatcher, NfcViewMenu, submenu_get_view(nfc->submenu));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Dialog
 | 
					 | 
				
			||||||
    nfc->dialog_ex = dialog_ex_alloc();
 | 
					 | 
				
			||||||
    view_dispatcher_add_view(
 | 
					 | 
				
			||||||
        nfc->view_dispatcher, NfcViewDialogEx, dialog_ex_get_view(nfc->dialog_ex));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Popup
 | 
					 | 
				
			||||||
    nfc->popup = popup_alloc();
 | 
					 | 
				
			||||||
    view_dispatcher_add_view(nfc->view_dispatcher, NfcViewPopup, popup_get_view(nfc->popup));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Loading
 | 
					 | 
				
			||||||
    nfc->loading = loading_alloc();
 | 
					 | 
				
			||||||
    view_dispatcher_add_view(nfc->view_dispatcher, NfcViewLoading, loading_get_view(nfc->loading));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Text Input
 | 
					 | 
				
			||||||
    nfc->text_input = text_input_alloc();
 | 
					 | 
				
			||||||
    view_dispatcher_add_view(
 | 
					 | 
				
			||||||
        nfc->view_dispatcher, NfcViewTextInput, text_input_get_view(nfc->text_input));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Byte Input
 | 
					 | 
				
			||||||
    nfc->byte_input = byte_input_alloc();
 | 
					 | 
				
			||||||
    view_dispatcher_add_view(
 | 
					 | 
				
			||||||
        nfc->view_dispatcher, NfcViewByteInput, byte_input_get_view(nfc->byte_input));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // TextBox
 | 
					 | 
				
			||||||
    nfc->text_box = text_box_alloc();
 | 
					 | 
				
			||||||
    view_dispatcher_add_view(
 | 
					 | 
				
			||||||
        nfc->view_dispatcher, NfcViewTextBox, text_box_get_view(nfc->text_box));
 | 
					 | 
				
			||||||
    nfc->text_box_store = furi_string_alloc();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Custom Widget
 | 
					 | 
				
			||||||
    nfc->widget = widget_alloc();
 | 
					 | 
				
			||||||
    view_dispatcher_add_view(nfc->view_dispatcher, NfcViewWidget, widget_get_view(nfc->widget));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Mifare Classic Dict Attack
 | 
					 | 
				
			||||||
    nfc->dict_attack = dict_attack_alloc();
 | 
					 | 
				
			||||||
    view_dispatcher_add_view(
 | 
					 | 
				
			||||||
        nfc->view_dispatcher, NfcViewDictAttack, dict_attack_get_view(nfc->dict_attack));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Detect Reader
 | 
					 | 
				
			||||||
    nfc->detect_reader = detect_reader_alloc();
 | 
					 | 
				
			||||||
    view_dispatcher_add_view(
 | 
					 | 
				
			||||||
        nfc->view_dispatcher, NfcViewDetectReader, detect_reader_get_view(nfc->detect_reader));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Generator
 | 
					 | 
				
			||||||
    nfc->generator = NULL;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return nfc;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_free(Nfc* nfc) {
 | 
					 | 
				
			||||||
    furi_assert(nfc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(nfc->rpc_state == NfcRpcStateEmulating) {
 | 
					 | 
				
			||||||
        // Stop worker
 | 
					 | 
				
			||||||
        nfc_worker_stop(nfc->worker);
 | 
					 | 
				
			||||||
    } else if(nfc->rpc_state == NfcRpcStateEmulated) {
 | 
					 | 
				
			||||||
        // Stop worker
 | 
					 | 
				
			||||||
        nfc_worker_stop(nfc->worker);
 | 
					 | 
				
			||||||
        // Save data in shadow file
 | 
					 | 
				
			||||||
        if(furi_string_size(nfc->dev->load_path)) {
 | 
					 | 
				
			||||||
            nfc_device_save_shadow(nfc->dev, furi_string_get_cstr(nfc->dev->load_path));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(nfc->rpc_ctx) {
 | 
					 | 
				
			||||||
        rpc_system_app_send_exited(nfc->rpc_ctx);
 | 
					 | 
				
			||||||
        rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL);
 | 
					 | 
				
			||||||
        nfc->rpc_ctx = NULL;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Nfc device
 | 
					 | 
				
			||||||
    nfc_device_free(nfc->dev);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Submenu
 | 
					 | 
				
			||||||
    view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewMenu);
 | 
					 | 
				
			||||||
    submenu_free(nfc->submenu);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // DialogEx
 | 
					 | 
				
			||||||
    view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewDialogEx);
 | 
					 | 
				
			||||||
    dialog_ex_free(nfc->dialog_ex);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Popup
 | 
					 | 
				
			||||||
    view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewPopup);
 | 
					 | 
				
			||||||
    popup_free(nfc->popup);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Loading
 | 
					 | 
				
			||||||
    view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewLoading);
 | 
					 | 
				
			||||||
    loading_free(nfc->loading);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // TextInput
 | 
					 | 
				
			||||||
    view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewTextInput);
 | 
					 | 
				
			||||||
    text_input_free(nfc->text_input);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // ByteInput
 | 
					 | 
				
			||||||
    view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewByteInput);
 | 
					 | 
				
			||||||
    byte_input_free(nfc->byte_input);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // TextBox
 | 
					 | 
				
			||||||
    view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewTextBox);
 | 
					 | 
				
			||||||
    text_box_free(nfc->text_box);
 | 
					 | 
				
			||||||
    furi_string_free(nfc->text_box_store);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Custom Widget
 | 
					 | 
				
			||||||
    view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewWidget);
 | 
					 | 
				
			||||||
    widget_free(nfc->widget);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Mifare Classic Dict Attack
 | 
					 | 
				
			||||||
    view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewDictAttack);
 | 
					 | 
				
			||||||
    dict_attack_free(nfc->dict_attack);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Detect Reader
 | 
					 | 
				
			||||||
    view_dispatcher_remove_view(nfc->view_dispatcher, NfcViewDetectReader);
 | 
					 | 
				
			||||||
    detect_reader_free(nfc->detect_reader);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Worker
 | 
					 | 
				
			||||||
    nfc_worker_stop(nfc->worker);
 | 
					 | 
				
			||||||
    nfc_worker_free(nfc->worker);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // View Dispatcher
 | 
					 | 
				
			||||||
    view_dispatcher_free(nfc->view_dispatcher);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Scene Manager
 | 
					 | 
				
			||||||
    scene_manager_free(nfc->scene_manager);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // GUI
 | 
					 | 
				
			||||||
    furi_record_close(RECORD_GUI);
 | 
					 | 
				
			||||||
    nfc->gui = NULL;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Notifications
 | 
					 | 
				
			||||||
    furi_record_close(RECORD_NOTIFICATION);
 | 
					 | 
				
			||||||
    nfc->notifications = NULL;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    free(nfc);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_text_store_set(Nfc* nfc, const char* text, ...) {
 | 
					 | 
				
			||||||
    va_list args;
 | 
					 | 
				
			||||||
    va_start(args, text);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    vsnprintf(nfc->text_store, sizeof(nfc->text_store), text, args);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    va_end(args);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_text_store_clear(Nfc* nfc) {
 | 
					 | 
				
			||||||
    memset(nfc->text_store, 0, sizeof(nfc->text_store));
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_blink_read_start(Nfc* nfc) {
 | 
					 | 
				
			||||||
    notification_message(nfc->notifications, &sequence_blink_start_cyan);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_blink_emulate_start(Nfc* nfc) {
 | 
					 | 
				
			||||||
    notification_message(nfc->notifications, &sequence_blink_start_magenta);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_blink_detect_start(Nfc* nfc) {
 | 
					 | 
				
			||||||
    notification_message(nfc->notifications, &sequence_blink_start_yellow);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_blink_stop(Nfc* nfc) {
 | 
					 | 
				
			||||||
    notification_message(nfc->notifications, &sequence_blink_stop);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool nfc_save_file(Nfc* nfc) {
 | 
					 | 
				
			||||||
    furi_string_printf(
 | 
					 | 
				
			||||||
        nfc->dev->load_path,
 | 
					 | 
				
			||||||
        "%s/%s%s",
 | 
					 | 
				
			||||||
        NFC_APP_FOLDER,
 | 
					 | 
				
			||||||
        nfc->dev->dev_name,
 | 
					 | 
				
			||||||
        NFC_APP_FILENAME_EXTENSION);
 | 
					 | 
				
			||||||
    bool file_saved = nfc_device_save(nfc->dev, furi_string_get_cstr(nfc->dev->load_path));
 | 
					 | 
				
			||||||
    return file_saved;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_show_loading_popup(void* context, bool show) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(show) {
 | 
					 | 
				
			||||||
        // Raise timer priority so that animations can play
 | 
					 | 
				
			||||||
        vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
 | 
					 | 
				
			||||||
        view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewLoading);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        // Restore default timer priority
 | 
					 | 
				
			||||||
        vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static bool nfc_is_hal_ready() {
 | 
					 | 
				
			||||||
    if(!furi_hal_nfc_is_init()) {
 | 
					 | 
				
			||||||
        // No connection to the chip, show an error screen
 | 
					 | 
				
			||||||
        DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
 | 
					 | 
				
			||||||
        DialogMessage* message = dialog_message_alloc();
 | 
					 | 
				
			||||||
        dialog_message_set_text(
 | 
					 | 
				
			||||||
            message,
 | 
					 | 
				
			||||||
            "Error!\nNFC chip failed to start\n\n\nSend a photo of this to:\nsupport@flipperzero.one",
 | 
					 | 
				
			||||||
            0,
 | 
					 | 
				
			||||||
            0,
 | 
					 | 
				
			||||||
            AlignLeft,
 | 
					 | 
				
			||||||
            AlignTop);
 | 
					 | 
				
			||||||
        dialog_message_show(dialogs, message);
 | 
					 | 
				
			||||||
        dialog_message_free(message);
 | 
					 | 
				
			||||||
        furi_record_close(RECORD_DIALOGS);
 | 
					 | 
				
			||||||
        return false;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int32_t nfc_app(void* p) {
 | 
					 | 
				
			||||||
    if(!nfc_is_hal_ready()) return 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    Nfc* nfc = nfc_alloc();
 | 
					 | 
				
			||||||
    char* args = p;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Check argument and run corresponding scene
 | 
					 | 
				
			||||||
    if(args && strlen(args)) {
 | 
					 | 
				
			||||||
        nfc_device_set_loading_callback(nfc->dev, nfc_show_loading_popup, nfc);
 | 
					 | 
				
			||||||
        uint32_t rpc_ctx = 0;
 | 
					 | 
				
			||||||
        if(sscanf(p, "RPC %lX", &rpc_ctx) == 1) {
 | 
					 | 
				
			||||||
            nfc->rpc_ctx = (void*)rpc_ctx;
 | 
					 | 
				
			||||||
            rpc_system_app_set_callback(nfc->rpc_ctx, nfc_rpc_command_callback, nfc);
 | 
					 | 
				
			||||||
            rpc_system_app_send_started(nfc->rpc_ctx);
 | 
					 | 
				
			||||||
            view_dispatcher_attach_to_gui(
 | 
					 | 
				
			||||||
                nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeDesktop);
 | 
					 | 
				
			||||||
            scene_manager_next_scene(nfc->scene_manager, NfcSceneRpc);
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            view_dispatcher_attach_to_gui(
 | 
					 | 
				
			||||||
                nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen);
 | 
					 | 
				
			||||||
            if(nfc_device_load(nfc->dev, p, true)) {
 | 
					 | 
				
			||||||
                if(nfc->dev->format == NfcDeviceSaveFormatMifareUl) {
 | 
					 | 
				
			||||||
                    scene_manager_next_scene(nfc->scene_manager, NfcSceneMfUltralightEmulate);
 | 
					 | 
				
			||||||
                    dolphin_deed(DolphinDeedNfcEmulate);
 | 
					 | 
				
			||||||
                } else if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) {
 | 
					 | 
				
			||||||
                    scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicEmulate);
 | 
					 | 
				
			||||||
                    dolphin_deed(DolphinDeedNfcEmulate);
 | 
					 | 
				
			||||||
                } else if(nfc->dev->format == NfcDeviceSaveFormatNfcV) {
 | 
					 | 
				
			||||||
                    scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcVEmulate);
 | 
					 | 
				
			||||||
                    dolphin_deed(DolphinDeedNfcEmulate);
 | 
					 | 
				
			||||||
                } else if(nfc->dev->format == NfcDeviceSaveFormatBankCard) {
 | 
					 | 
				
			||||||
                    scene_manager_next_scene(nfc->scene_manager, NfcSceneDeviceInfo);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateUid);
 | 
					 | 
				
			||||||
                    dolphin_deed(DolphinDeedNfcEmulate);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                // Exit app
 | 
					 | 
				
			||||||
                view_dispatcher_stop(nfc->view_dispatcher);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        nfc_device_set_loading_callback(nfc->dev, NULL, nfc);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        view_dispatcher_attach_to_gui(
 | 
					 | 
				
			||||||
            nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen);
 | 
					 | 
				
			||||||
        scene_manager_next_scene(nfc->scene_manager, NfcSceneStart);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    view_dispatcher_run(nfc->view_dispatcher);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    nfc_free(nfc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return 0;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,3 +0,0 @@
 | 
				
			|||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef struct Nfc Nfc;
 | 
					 | 
				
			||||||
							
								
								
									
										499
									
								
								applications/main/nfc/nfc_app.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										499
									
								
								applications/main/nfc/nfc_app.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,499 @@
 | 
				
			|||||||
 | 
					#include "nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <dolphin/dolphin.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_custom_event_callback(void* context, uint32_t event) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					    return scene_manager_handle_custom_event(nfc->scene_manager, event);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_back_event_callback(void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					    return scene_manager_handle_back_event(nfc->scene_manager);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void nfc_app_rpc_command_callback(RpcAppSystemEvent rpc_event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					    NfcApp* nfc = (NfcApp*)context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_assert(nfc->rpc_ctx);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(rpc_event == RpcAppEventSessionClose) {
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcSessionClose);
 | 
				
			||||||
 | 
					        rpc_system_app_set_callback(nfc->rpc_ctx, NULL, NULL);
 | 
				
			||||||
 | 
					        nfc->rpc_ctx = NULL;
 | 
				
			||||||
 | 
					    } else if(rpc_event == RpcAppEventAppExit) {
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcExit);
 | 
				
			||||||
 | 
					    } else if(rpc_event == RpcAppEventLoadFile) {
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventRpcLoad);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        rpc_system_app_confirm(nfc->rpc_ctx, rpc_event, false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					NfcApp* nfc_app_alloc() {
 | 
				
			||||||
 | 
					    NfcApp* instance = malloc(sizeof(NfcApp));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->view_dispatcher = view_dispatcher_alloc();
 | 
				
			||||||
 | 
					    instance->scene_manager = scene_manager_alloc(&nfc_scene_handlers, instance);
 | 
				
			||||||
 | 
					    view_dispatcher_enable_queue(instance->view_dispatcher);
 | 
				
			||||||
 | 
					    view_dispatcher_set_event_callback_context(instance->view_dispatcher, instance);
 | 
				
			||||||
 | 
					    view_dispatcher_set_custom_event_callback(
 | 
				
			||||||
 | 
					        instance->view_dispatcher, nfc_custom_event_callback);
 | 
				
			||||||
 | 
					    view_dispatcher_set_navigation_event_callback(
 | 
				
			||||||
 | 
					        instance->view_dispatcher, nfc_back_event_callback);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->nfc = nfc_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->mf_ul_auth = mf_ultralight_auth_alloc();
 | 
				
			||||||
 | 
					    instance->mfc_key_cache = mf_classic_key_cache_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Nfc device
 | 
				
			||||||
 | 
					    instance->nfc_device = nfc_device_alloc();
 | 
				
			||||||
 | 
					    nfc_device_set_loading_callback(instance->nfc_device, nfc_show_loading_popup, instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Open GUI record
 | 
				
			||||||
 | 
					    instance->gui = furi_record_open(RECORD_GUI);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Open Notification record
 | 
				
			||||||
 | 
					    instance->notifications = furi_record_open(RECORD_NOTIFICATION);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Open Storage record
 | 
				
			||||||
 | 
					    instance->storage = furi_record_open(RECORD_STORAGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Open Dialogs record
 | 
				
			||||||
 | 
					    instance->dialogs = furi_record_open(RECORD_DIALOGS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Submenu
 | 
				
			||||||
 | 
					    instance->submenu = submenu_alloc();
 | 
				
			||||||
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
 | 
					        instance->view_dispatcher, NfcViewMenu, submenu_get_view(instance->submenu));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Dialog
 | 
				
			||||||
 | 
					    instance->dialog_ex = dialog_ex_alloc();
 | 
				
			||||||
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
 | 
					        instance->view_dispatcher, NfcViewDialogEx, dialog_ex_get_view(instance->dialog_ex));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Popup
 | 
				
			||||||
 | 
					    instance->popup = popup_alloc();
 | 
				
			||||||
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
 | 
					        instance->view_dispatcher, NfcViewPopup, popup_get_view(instance->popup));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Loading
 | 
				
			||||||
 | 
					    instance->loading = loading_alloc();
 | 
				
			||||||
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
 | 
					        instance->view_dispatcher, NfcViewLoading, loading_get_view(instance->loading));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Text Input
 | 
				
			||||||
 | 
					    instance->text_input = text_input_alloc();
 | 
				
			||||||
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
 | 
					        instance->view_dispatcher, NfcViewTextInput, text_input_get_view(instance->text_input));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Byte Input
 | 
				
			||||||
 | 
					    instance->byte_input = byte_input_alloc();
 | 
				
			||||||
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
 | 
					        instance->view_dispatcher, NfcViewByteInput, byte_input_get_view(instance->byte_input));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TextBox
 | 
				
			||||||
 | 
					    instance->text_box = text_box_alloc();
 | 
				
			||||||
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
 | 
					        instance->view_dispatcher, NfcViewTextBox, text_box_get_view(instance->text_box));
 | 
				
			||||||
 | 
					    instance->text_box_store = furi_string_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Custom Widget
 | 
				
			||||||
 | 
					    instance->widget = widget_alloc();
 | 
				
			||||||
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
 | 
					        instance->view_dispatcher, NfcViewWidget, widget_get_view(instance->widget));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Dict attack
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->dict_attack = dict_attack_alloc();
 | 
				
			||||||
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
 | 
					        instance->view_dispatcher, NfcViewDictAttack, dict_attack_get_view(instance->dict_attack));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Detect Reader
 | 
				
			||||||
 | 
					    instance->detect_reader = detect_reader_alloc();
 | 
				
			||||||
 | 
					    view_dispatcher_add_view(
 | 
				
			||||||
 | 
					        instance->view_dispatcher,
 | 
				
			||||||
 | 
					        NfcViewDetectReader,
 | 
				
			||||||
 | 
					        detect_reader_get_view(instance->detect_reader));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->iso14443_3a_edit_data = iso14443_3a_alloc();
 | 
				
			||||||
 | 
					    instance->file_path = furi_string_alloc_set(NFC_APP_FOLDER);
 | 
				
			||||||
 | 
					    instance->file_name = furi_string_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return instance;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_app_free(NfcApp* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(instance->rpc_ctx) {
 | 
				
			||||||
 | 
					        rpc_system_app_send_exited(instance->rpc_ctx);
 | 
				
			||||||
 | 
					        rpc_system_app_set_callback(instance->rpc_ctx, NULL, NULL);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_free(instance->nfc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mf_ultralight_auth_free(instance->mf_ul_auth);
 | 
				
			||||||
 | 
					    mf_classic_key_cache_free(instance->mfc_key_cache);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Nfc device
 | 
				
			||||||
 | 
					    nfc_device_free(instance->nfc_device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Submenu
 | 
				
			||||||
 | 
					    view_dispatcher_remove_view(instance->view_dispatcher, NfcViewMenu);
 | 
				
			||||||
 | 
					    submenu_free(instance->submenu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // DialogEx
 | 
				
			||||||
 | 
					    view_dispatcher_remove_view(instance->view_dispatcher, NfcViewDialogEx);
 | 
				
			||||||
 | 
					    dialog_ex_free(instance->dialog_ex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Popup
 | 
				
			||||||
 | 
					    view_dispatcher_remove_view(instance->view_dispatcher, NfcViewPopup);
 | 
				
			||||||
 | 
					    popup_free(instance->popup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Loading
 | 
				
			||||||
 | 
					    view_dispatcher_remove_view(instance->view_dispatcher, NfcViewLoading);
 | 
				
			||||||
 | 
					    loading_free(instance->loading);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TextInput
 | 
				
			||||||
 | 
					    view_dispatcher_remove_view(instance->view_dispatcher, NfcViewTextInput);
 | 
				
			||||||
 | 
					    text_input_free(instance->text_input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // ByteInput
 | 
				
			||||||
 | 
					    view_dispatcher_remove_view(instance->view_dispatcher, NfcViewByteInput);
 | 
				
			||||||
 | 
					    byte_input_free(instance->byte_input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // TextBox
 | 
				
			||||||
 | 
					    view_dispatcher_remove_view(instance->view_dispatcher, NfcViewTextBox);
 | 
				
			||||||
 | 
					    text_box_free(instance->text_box);
 | 
				
			||||||
 | 
					    furi_string_free(instance->text_box_store);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Custom Widget
 | 
				
			||||||
 | 
					    view_dispatcher_remove_view(instance->view_dispatcher, NfcViewWidget);
 | 
				
			||||||
 | 
					    widget_free(instance->widget);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Dict attack
 | 
				
			||||||
 | 
					    view_dispatcher_remove_view(instance->view_dispatcher, NfcViewDictAttack);
 | 
				
			||||||
 | 
					    dict_attack_free(instance->dict_attack);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Detect reader
 | 
				
			||||||
 | 
					    view_dispatcher_remove_view(instance->view_dispatcher, NfcViewDetectReader);
 | 
				
			||||||
 | 
					    detect_reader_free(instance->detect_reader);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // View Dispatcher
 | 
				
			||||||
 | 
					    view_dispatcher_free(instance->view_dispatcher);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Scene Manager
 | 
				
			||||||
 | 
					    scene_manager_free(instance->scene_manager);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_record_close(RECORD_DIALOGS);
 | 
				
			||||||
 | 
					    furi_record_close(RECORD_STORAGE);
 | 
				
			||||||
 | 
					    furi_record_close(RECORD_NOTIFICATION);
 | 
				
			||||||
 | 
					    // GUI
 | 
				
			||||||
 | 
					    furi_record_close(RECORD_GUI);
 | 
				
			||||||
 | 
					    instance->gui = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->notifications = NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    iso14443_3a_free(instance->iso14443_3a_edit_data);
 | 
				
			||||||
 | 
					    furi_string_free(instance->file_path);
 | 
				
			||||||
 | 
					    furi_string_free(instance->file_name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    free(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_text_store_set(NfcApp* nfc, const char* text, ...) {
 | 
				
			||||||
 | 
					    va_list args;
 | 
				
			||||||
 | 
					    va_start(args, text);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    vsnprintf(nfc->text_store, sizeof(nfc->text_store), text, args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    va_end(args);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_text_store_clear(NfcApp* nfc) {
 | 
				
			||||||
 | 
					    memset(nfc->text_store, 0, sizeof(nfc->text_store));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_blink_read_start(NfcApp* nfc) {
 | 
				
			||||||
 | 
					    notification_message(nfc->notifications, &sequence_blink_start_cyan);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_blink_emulate_start(NfcApp* nfc) {
 | 
				
			||||||
 | 
					    notification_message(nfc->notifications, &sequence_blink_start_magenta);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_blink_detect_start(NfcApp* nfc) {
 | 
				
			||||||
 | 
					    notification_message(nfc->notifications, &sequence_blink_start_yellow);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_blink_stop(NfcApp* nfc) {
 | 
				
			||||||
 | 
					    notification_message(nfc->notifications, &sequence_blink_stop);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_make_app_folders(NfcApp* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(!storage_simply_mkdir(instance->storage, NFC_APP_FOLDER)) {
 | 
				
			||||||
 | 
					        dialog_message_show_storage_error(instance->dialogs, "Cannot create\napp folder");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_save_file(NfcApp* instance, FuriString* path) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool result = nfc_device_save(instance->nfc_device, furi_string_get_cstr(instance->file_path));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(!result) {
 | 
				
			||||||
 | 
					        dialog_message_show_storage_error(instance->dialogs, "Cannot save\nkey file");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_set_shadow_file_path(FuriString* file_path, FuriString* shadow_file_path) {
 | 
				
			||||||
 | 
					    furi_assert(file_path);
 | 
				
			||||||
 | 
					    furi_assert(shadow_file_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool shadow_file_path_set = false;
 | 
				
			||||||
 | 
					    if(furi_string_end_with(file_path, NFC_APP_SHADOW_EXTENSION)) {
 | 
				
			||||||
 | 
					        furi_string_set(shadow_file_path, file_path);
 | 
				
			||||||
 | 
					        shadow_file_path_set = true;
 | 
				
			||||||
 | 
					    } else if(furi_string_end_with(file_path, NFC_APP_EXTENSION)) {
 | 
				
			||||||
 | 
					        size_t path_len = furi_string_size(file_path);
 | 
				
			||||||
 | 
					        // Cut .nfc
 | 
				
			||||||
 | 
					        furi_string_set_n(shadow_file_path, file_path, 0, path_len - 4);
 | 
				
			||||||
 | 
					        furi_string_cat_printf(shadow_file_path, "%s", NFC_APP_SHADOW_EXTENSION);
 | 
				
			||||||
 | 
					        shadow_file_path_set = true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return shadow_file_path_set;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_has_shadow_file_internal(NfcApp* instance, FuriString* path) {
 | 
				
			||||||
 | 
					    furi_assert(path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool has_shadow_file = false;
 | 
				
			||||||
 | 
					    FuriString* shadow_file_path = furi_string_alloc();
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        if(furi_string_empty(path)) break;
 | 
				
			||||||
 | 
					        if(!nfc_set_shadow_file_path(path, shadow_file_path)) break;
 | 
				
			||||||
 | 
					        has_shadow_file =
 | 
				
			||||||
 | 
					            storage_common_exists(instance->storage, furi_string_get_cstr(shadow_file_path));
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(shadow_file_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return has_shadow_file;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_has_shadow_file(NfcApp* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return nfc_has_shadow_file_internal(instance, instance->file_path);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_save_internal(NfcApp* instance, const char* extension) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(extension);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool result = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_make_app_folders(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(furi_string_end_with(instance->file_path, NFC_APP_EXTENSION) ||
 | 
				
			||||||
 | 
					       (furi_string_end_with(instance->file_path, NFC_APP_SHADOW_EXTENSION))) {
 | 
				
			||||||
 | 
					        size_t filename_start = furi_string_search_rchar(instance->file_path, '/');
 | 
				
			||||||
 | 
					        furi_string_left(instance->file_path, filename_start);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_cat_printf(
 | 
				
			||||||
 | 
					        instance->file_path, "/%s%s", furi_string_get_cstr(instance->file_name), extension);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    result = nfc_save_file(instance, instance->file_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_save_shadow_file(NfcApp* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return nfc_save_internal(instance, NFC_APP_SHADOW_EXTENSION);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_save(NfcApp* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return nfc_save_internal(instance, NFC_APP_EXTENSION);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_load_file(NfcApp* instance, FuriString* path, bool show_dialog) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(path);
 | 
				
			||||||
 | 
					    bool result = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* load_path = furi_string_alloc();
 | 
				
			||||||
 | 
					    if(nfc_has_shadow_file_internal(instance, path)) {
 | 
				
			||||||
 | 
					        nfc_set_shadow_file_path(path, load_path);
 | 
				
			||||||
 | 
					    } else if(furi_string_end_with(path, NFC_APP_SHADOW_EXTENSION)) {
 | 
				
			||||||
 | 
					        size_t path_len = furi_string_size(path);
 | 
				
			||||||
 | 
					        furi_string_set_n(load_path, path, 0, path_len - 4);
 | 
				
			||||||
 | 
					        furi_string_cat_printf(load_path, "%s", NFC_APP_EXTENSION);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        furi_string_set(load_path, path);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    result = nfc_device_load(instance->nfc_device, furi_string_get_cstr(load_path));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(result) {
 | 
				
			||||||
 | 
					        path_extract_filename(load_path, instance->file_name, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if((!result) && (show_dialog)) {
 | 
				
			||||||
 | 
					        dialog_message_show_storage_error(instance->dialogs, "Cannot load\nkey file");
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(load_path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_delete(NfcApp* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(nfc_has_shadow_file(instance)) {
 | 
				
			||||||
 | 
					        nfc_delete_shadow_file(instance);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(furi_string_end_with_str(instance->file_path, NFC_APP_SHADOW_EXTENSION)) {
 | 
				
			||||||
 | 
					        size_t path_len = furi_string_size(instance->file_path);
 | 
				
			||||||
 | 
					        furi_string_replace_at(instance->file_path, path_len - 4, 4, NFC_APP_EXTENSION);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return storage_simply_remove(instance->storage, furi_string_get_cstr(instance->file_path));
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_delete_shadow_file(NfcApp* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FuriString* shadow_file_path = furi_string_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool result = nfc_set_shadow_file_path(instance->file_path, shadow_file_path) &&
 | 
				
			||||||
 | 
					                  storage_simply_remove(instance->storage, furi_string_get_cstr(shadow_file_path));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    furi_string_free(shadow_file_path);
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_load_from_file_select(NfcApp* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    DialogsFileBrowserOptions browser_options;
 | 
				
			||||||
 | 
					    dialog_file_browser_set_basic_options(&browser_options, NFC_APP_EXTENSION, &I_Nfc_10px);
 | 
				
			||||||
 | 
					    browser_options.base_path = NFC_APP_FOLDER;
 | 
				
			||||||
 | 
					    browser_options.hide_dot_files = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Input events and views are managed by file_browser
 | 
				
			||||||
 | 
					    bool result = dialog_file_browser_show(
 | 
				
			||||||
 | 
					        instance->dialogs, instance->file_path, instance->file_path, &browser_options);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(result) {
 | 
				
			||||||
 | 
					        result = nfc_load_file(instance, instance->file_path, true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_show_loading_popup(void* context, bool show) {
 | 
				
			||||||
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					    TaskHandle_t timer_task = xTaskGetHandle(configTIMER_SERVICE_TASK_NAME);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(show) {
 | 
				
			||||||
 | 
					        // Raise timer priority so that animations can play
 | 
				
			||||||
 | 
					        vTaskPrioritySet(timer_task, configMAX_PRIORITIES - 1);
 | 
				
			||||||
 | 
					        view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewLoading);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        // Restore default timer priority
 | 
				
			||||||
 | 
					        vTaskPrioritySet(timer_task, configTIMER_TASK_PRIORITY);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					    furi_assert(types);
 | 
				
			||||||
 | 
					    furi_assert(count < NfcProtocolNum);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    memcpy(instance->protocols_detected, types, count);
 | 
				
			||||||
 | 
					    instance->protocols_detected_num = count;
 | 
				
			||||||
 | 
					    instance->protocols_detected_selected_idx = 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_app_reset_detected_protocols(NfcApp* instance) {
 | 
				
			||||||
 | 
					    furi_assert(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->protocols_detected_selected_idx = 0;
 | 
				
			||||||
 | 
					    instance->protocols_detected_num = 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool nfc_is_hal_ready() {
 | 
				
			||||||
 | 
					    if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) {
 | 
				
			||||||
 | 
					        // No connection to the chip, show an error screen
 | 
				
			||||||
 | 
					        DialogsApp* dialogs = furi_record_open(RECORD_DIALOGS);
 | 
				
			||||||
 | 
					        DialogMessage* message = dialog_message_alloc();
 | 
				
			||||||
 | 
					        dialog_message_set_text(
 | 
				
			||||||
 | 
					            message,
 | 
				
			||||||
 | 
					            "Error!\nNFC chip failed to start\n\n\nSend a photo of this to:\nsupport@flipperzero.one",
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					            0,
 | 
				
			||||||
 | 
					            AlignLeft,
 | 
				
			||||||
 | 
					            AlignTop);
 | 
				
			||||||
 | 
					        dialog_message_show(dialogs, message);
 | 
				
			||||||
 | 
					        dialog_message_free(message);
 | 
				
			||||||
 | 
					        furi_record_close(RECORD_DIALOGS);
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int32_t nfc_app(void* p) {
 | 
				
			||||||
 | 
					    if(!nfc_is_hal_ready()) return 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* nfc = nfc_app_alloc();
 | 
				
			||||||
 | 
					    const char* args = p;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(args && strlen(args)) {
 | 
				
			||||||
 | 
					        if(sscanf(args, "RPC %p", &nfc->rpc_ctx) == 1) {
 | 
				
			||||||
 | 
					            rpc_system_app_set_callback(nfc->rpc_ctx, nfc_app_rpc_command_callback, nfc);
 | 
				
			||||||
 | 
					            rpc_system_app_send_started(nfc->rpc_ctx);
 | 
				
			||||||
 | 
					            view_dispatcher_attach_to_gui(
 | 
				
			||||||
 | 
					                nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeDesktop);
 | 
				
			||||||
 | 
					            scene_manager_next_scene(nfc->scene_manager, NfcSceneRpc);
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            view_dispatcher_attach_to_gui(
 | 
				
			||||||
 | 
					                nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            furi_string_set(nfc->file_path, args);
 | 
				
			||||||
 | 
					            if(nfc_load_file(nfc, nfc->file_path, false)) {
 | 
				
			||||||
 | 
					                scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulate);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                view_dispatcher_stop(nfc->view_dispatcher);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        view_dispatcher_attach_to_gui(
 | 
				
			||||||
 | 
					            nfc->view_dispatcher, nfc->gui, ViewDispatcherTypeFullscreen);
 | 
				
			||||||
 | 
					        scene_manager_next_scene(nfc->scene_manager, NfcSceneStart);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    view_dispatcher_run(nfc->view_dispatcher);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_app_free(nfc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return 0;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								applications/main/nfc/nfc_app.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								applications/main/nfc/nfc_app.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @file nfc_app.h
 | 
				
			||||||
 | 
					 * @brief NFC application -- start here.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Application for interfacing with NFC cards and other devices via Flipper's built-in NFC hardware.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Main features:
 | 
				
			||||||
 | 
					 * * Multiple protocols support
 | 
				
			||||||
 | 
					 * * Card emulation
 | 
				
			||||||
 | 
					 * * Shadow file support
 | 
				
			||||||
 | 
					 * * Dynamically loaded parser plugins
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see nfc_protocol.h for information on adding a new library protocol.
 | 
				
			||||||
 | 
					 * @see nfc_protocol_support.h for information on integrating a library protocol into the app.
 | 
				
			||||||
 | 
					 * @see nfc_supported_card_plugin.h for information on adding supported card plugins (parsers).
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct NfcApp NfcApp;
 | 
				
			||||||
							
								
								
									
										192
									
								
								applications/main/nfc/nfc_app_i.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										192
									
								
								applications/main/nfc/nfc_app_i.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,192 @@
 | 
				
			|||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc_app.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <furi.h>
 | 
				
			||||||
 | 
					#include <furi_hal.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <gui/gui.h>
 | 
				
			||||||
 | 
					#include <gui/view.h>
 | 
				
			||||||
 | 
					#include <assets_icons.h>
 | 
				
			||||||
 | 
					#include <gui/view_dispatcher.h>
 | 
				
			||||||
 | 
					#include <gui/scene_manager.h>
 | 
				
			||||||
 | 
					#include <cli/cli.h>
 | 
				
			||||||
 | 
					#include <notification/notification_messages.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <gui/modules/submenu.h>
 | 
				
			||||||
 | 
					#include <gui/modules/dialog_ex.h>
 | 
				
			||||||
 | 
					#include <gui/modules/popup.h>
 | 
				
			||||||
 | 
					#include <gui/modules/loading.h>
 | 
				
			||||||
 | 
					#include <gui/modules/text_input.h>
 | 
				
			||||||
 | 
					#include <gui/modules/byte_input.h>
 | 
				
			||||||
 | 
					#include <gui/modules/text_box.h>
 | 
				
			||||||
 | 
					#include <gui/modules/widget.h>
 | 
				
			||||||
 | 
					#include "views/dict_attack.h"
 | 
				
			||||||
 | 
					#include "views/detect_reader.h"
 | 
				
			||||||
 | 
					#include "views/dict_attack.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/scenes/nfc_scene.h>
 | 
				
			||||||
 | 
					#include "helpers/nfc_custom_event.h"
 | 
				
			||||||
 | 
					#include "helpers/mf_ultralight_auth.h"
 | 
				
			||||||
 | 
					#include "helpers/mf_user_dict.h"
 | 
				
			||||||
 | 
					#include "helpers/mfkey32_logger.h"
 | 
				
			||||||
 | 
					#include "helpers/mf_classic_key_cache.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <dialogs/dialogs.h>
 | 
				
			||||||
 | 
					#include <storage/storage.h>
 | 
				
			||||||
 | 
					#include <toolbox/path.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "rpc/rpc_app.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <m-array.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <lib/nfc/nfc.h>
 | 
				
			||||||
 | 
					#include <lib/nfc/protocols/iso14443_3a/iso14443_3a.h>
 | 
				
			||||||
 | 
					#include <lib/nfc/protocols/iso14443_3a/iso14443_3a_listener.h>
 | 
				
			||||||
 | 
					#include <lib/nfc/protocols/mf_ultralight/mf_ultralight_listener.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/nfc_poller.h>
 | 
				
			||||||
 | 
					#include <nfc/nfc_scanner.h>
 | 
				
			||||||
 | 
					#include <nfc/nfc_listener.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/nfc_device.h>
 | 
				
			||||||
 | 
					#include <nfc/helpers/nfc_data_generator.h>
 | 
				
			||||||
 | 
					#include <nfc/helpers/nfc_dict.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <gui/modules/validators.h>
 | 
				
			||||||
 | 
					#include <toolbox/path.h>
 | 
				
			||||||
 | 
					#include <toolbox/name_generator.h>
 | 
				
			||||||
 | 
					#include <dolphin/dolphin.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NFC_NAME_SIZE 22
 | 
				
			||||||
 | 
					#define NFC_TEXT_STORE_SIZE 128
 | 
				
			||||||
 | 
					#define NFC_BYTE_INPUT_STORE_SIZE 10
 | 
				
			||||||
 | 
					#define NFC_LOG_SIZE_MAX (1024)
 | 
				
			||||||
 | 
					#define NFC_APP_FOLDER ANY_PATH("nfc")
 | 
				
			||||||
 | 
					#define NFC_APP_EXTENSION ".nfc"
 | 
				
			||||||
 | 
					#define NFC_APP_SHADOW_EXTENSION ".shd"
 | 
				
			||||||
 | 
					#define NFC_APP_FILENAME_PREFIX "NFC"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NFC_APP_MFKEY32_LOGS_FILE_NAME ".mfkey32.log"
 | 
				
			||||||
 | 
					#define NFC_APP_MFKEY32_LOGS_FILE_PATH (NFC_APP_FOLDER "/" NFC_APP_MFKEY32_LOGS_FILE_NAME)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define NFC_APP_MF_CLASSIC_DICT_USER_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict_user.nfc")
 | 
				
			||||||
 | 
					#define NFC_APP_MF_CLASSIC_DICT_SYSTEM_PATH (NFC_APP_FOLDER "/assets/mf_classic_dict.nfc")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    NfcRpcStateIdle,
 | 
				
			||||||
 | 
					    NfcRpcStateEmulating,
 | 
				
			||||||
 | 
					} NfcRpcState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    NfcDict* dict;
 | 
				
			||||||
 | 
					    uint8_t sectors_total;
 | 
				
			||||||
 | 
					    uint8_t sectors_read;
 | 
				
			||||||
 | 
					    uint8_t current_sector;
 | 
				
			||||||
 | 
					    uint8_t keys_found;
 | 
				
			||||||
 | 
					    size_t dict_keys_total;
 | 
				
			||||||
 | 
					    size_t dict_keys_current;
 | 
				
			||||||
 | 
					    bool is_key_attack;
 | 
				
			||||||
 | 
					    uint8_t key_attack_current_sector;
 | 
				
			||||||
 | 
					    bool is_card_present;
 | 
				
			||||||
 | 
					} NfcMfClassicDictAttackContext;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct NfcApp {
 | 
				
			||||||
 | 
					    DialogsApp* dialogs;
 | 
				
			||||||
 | 
					    Storage* storage;
 | 
				
			||||||
 | 
					    Gui* gui;
 | 
				
			||||||
 | 
					    ViewDispatcher* view_dispatcher;
 | 
				
			||||||
 | 
					    NotificationApp* notifications;
 | 
				
			||||||
 | 
					    SceneManager* scene_manager;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    char text_store[NFC_TEXT_STORE_SIZE + 1];
 | 
				
			||||||
 | 
					    FuriString* text_box_store;
 | 
				
			||||||
 | 
					    uint8_t byte_input_store[NFC_BYTE_INPUT_STORE_SIZE];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    uint32_t protocols_detected_num;
 | 
				
			||||||
 | 
					    NfcProtocol protocols_detected[NfcProtocolNum];
 | 
				
			||||||
 | 
					    uint32_t protocols_detected_selected_idx;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    RpcAppSystem* rpc_ctx;
 | 
				
			||||||
 | 
					    NfcRpcState rpc_state;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Common Views
 | 
				
			||||||
 | 
					    Submenu* submenu;
 | 
				
			||||||
 | 
					    DialogEx* dialog_ex;
 | 
				
			||||||
 | 
					    Popup* popup;
 | 
				
			||||||
 | 
					    Loading* loading;
 | 
				
			||||||
 | 
					    TextInput* text_input;
 | 
				
			||||||
 | 
					    ByteInput* byte_input;
 | 
				
			||||||
 | 
					    TextBox* text_box;
 | 
				
			||||||
 | 
					    Widget* widget;
 | 
				
			||||||
 | 
					    DetectReader* detect_reader;
 | 
				
			||||||
 | 
					    DictAttack* dict_attack;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Nfc* nfc;
 | 
				
			||||||
 | 
					    NfcPoller* poller;
 | 
				
			||||||
 | 
					    NfcScanner* scanner;
 | 
				
			||||||
 | 
					    NfcListener* listener;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MfUltralightAuth* mf_ul_auth;
 | 
				
			||||||
 | 
					    NfcMfClassicDictAttackContext nfc_dict_context;
 | 
				
			||||||
 | 
					    Mfkey32Logger* mfkey32_logger;
 | 
				
			||||||
 | 
					    MfUserDict* mf_user_dict;
 | 
				
			||||||
 | 
					    MfClassicKeyCache* mfc_key_cache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcDevice* nfc_device;
 | 
				
			||||||
 | 
					    Iso14443_3aData* iso14443_3a_edit_data;
 | 
				
			||||||
 | 
					    FuriString* file_path;
 | 
				
			||||||
 | 
					    FuriString* file_name;
 | 
				
			||||||
 | 
					    FuriTimer* timer;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    NfcViewMenu,
 | 
				
			||||||
 | 
					    NfcViewDialogEx,
 | 
				
			||||||
 | 
					    NfcViewPopup,
 | 
				
			||||||
 | 
					    NfcViewLoading,
 | 
				
			||||||
 | 
					    NfcViewTextInput,
 | 
				
			||||||
 | 
					    NfcViewByteInput,
 | 
				
			||||||
 | 
					    NfcViewTextBox,
 | 
				
			||||||
 | 
					    NfcViewWidget,
 | 
				
			||||||
 | 
					    NfcViewDictAttack,
 | 
				
			||||||
 | 
					    NfcViewDetectReader,
 | 
				
			||||||
 | 
					} NfcView;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int32_t nfc_task(void* p);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_text_store_set(NfcApp* nfc, const char* text, ...);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_text_store_clear(NfcApp* nfc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_blink_read_start(NfcApp* nfc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_blink_emulate_start(NfcApp* nfc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_blink_detect_start(NfcApp* nfc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_blink_stop(NfcApp* nfc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_show_loading_popup(void* context, bool show);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_has_shadow_file(NfcApp* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_save_shadow_file(NfcApp* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_delete_shadow_file(NfcApp* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_save(NfcApp* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_delete(NfcApp* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_load_from_file_select(NfcApp* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_load_file(NfcApp* instance, FuriString* path, bool show_dialog);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_save_file(NfcApp* instance, FuriString* path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_make_app_folder(NfcApp* instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_app_set_detected_protocols(NfcApp* instance, const NfcProtocol* types, uint32_t count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_app_reset_detected_protocols(NfcApp* instance);
 | 
				
			||||||
@ -4,90 +4,30 @@
 | 
				
			|||||||
#include <lib/toolbox/args.h>
 | 
					#include <lib/toolbox/args.h>
 | 
				
			||||||
#include <lib/toolbox/hex.h>
 | 
					#include <lib/toolbox/hex.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <lib/nfc/nfc_types.h>
 | 
					#include <furi_hal_nfc.h>
 | 
				
			||||||
#include <lib/nfc/nfc_device.h>
 | 
					
 | 
				
			||||||
 | 
					#define FLAG_EVENT (1 << 10)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void nfc_cli_print_usage() {
 | 
					static void nfc_cli_print_usage() {
 | 
				
			||||||
    printf("Usage:\r\n");
 | 
					    printf("Usage:\r\n");
 | 
				
			||||||
    printf("nfc <cmd>\r\n");
 | 
					    printf("nfc <cmd>\r\n");
 | 
				
			||||||
    printf("Cmd list:\r\n");
 | 
					    printf("Cmd list:\r\n");
 | 
				
			||||||
    printf("\tdetect\t - detect nfc device\r\n");
 | 
					 | 
				
			||||||
    printf("\temulate\t - emulate predefined nfca card\r\n");
 | 
					 | 
				
			||||||
    printf("\tapdu\t - Send APDU and print response \r\n");
 | 
					 | 
				
			||||||
    if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
 | 
					    if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
 | 
				
			||||||
        printf("\tfield\t - turn field on\r\n");
 | 
					        printf("\tfield\t - turn field on\r\n");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void nfc_cli_detect(Cli* cli, FuriString* args) {
 | 
					 | 
				
			||||||
    UNUSED(args);
 | 
					 | 
				
			||||||
    // Check if nfc worker is not busy
 | 
					 | 
				
			||||||
    if(furi_hal_nfc_is_busy()) {
 | 
					 | 
				
			||||||
        printf("Nfc is busy\r\n");
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    FuriHalNfcDevData dev_data = {};
 | 
					 | 
				
			||||||
    bool cmd_exit = false;
 | 
					 | 
				
			||||||
    furi_hal_nfc_exit_sleep();
 | 
					 | 
				
			||||||
    printf("Detecting nfc...\r\nPress Ctrl+C to abort\r\n");
 | 
					 | 
				
			||||||
    while(!cmd_exit) {
 | 
					 | 
				
			||||||
        cmd_exit |= cli_cmd_interrupt_received(cli);
 | 
					 | 
				
			||||||
        if(furi_hal_nfc_detect(&dev_data, 400)) {
 | 
					 | 
				
			||||||
            printf("Found: %s ", nfc_get_dev_type(dev_data.type));
 | 
					 | 
				
			||||||
            printf("UID length: %d, UID:", dev_data.uid_len);
 | 
					 | 
				
			||||||
            for(size_t i = 0; i < dev_data.uid_len; i++) {
 | 
					 | 
				
			||||||
                printf("%02X", dev_data.uid[i]);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            printf("\r\n");
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        furi_hal_nfc_sleep();
 | 
					 | 
				
			||||||
        furi_delay_ms(50);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    furi_hal_nfc_sleep();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void nfc_cli_emulate(Cli* cli, FuriString* args) {
 | 
					 | 
				
			||||||
    UNUSED(args);
 | 
					 | 
				
			||||||
    // Check if nfc worker is not busy
 | 
					 | 
				
			||||||
    if(furi_hal_nfc_is_busy()) {
 | 
					 | 
				
			||||||
        printf("Nfc is busy\r\n");
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    furi_hal_nfc_exit_sleep();
 | 
					 | 
				
			||||||
    printf("Emulating NFC-A Type: T2T UID: 36 9C E7 B1 0A C1 34 SAK: 00 ATQA: 00/44\r\n");
 | 
					 | 
				
			||||||
    printf("Press Ctrl+C to abort\r\n");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    FuriHalNfcDevData params = {
 | 
					 | 
				
			||||||
        .uid = {0x36, 0x9C, 0xe7, 0xb1, 0x0A, 0xC1, 0x34},
 | 
					 | 
				
			||||||
        .uid_len = 7,
 | 
					 | 
				
			||||||
        .atqa = {0x44, 0x00},
 | 
					 | 
				
			||||||
        .sak = 0x00,
 | 
					 | 
				
			||||||
        .type = FuriHalNfcTypeA,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    while(!cli_cmd_interrupt_received(cli)) {
 | 
					 | 
				
			||||||
        if(furi_hal_nfc_listen(params.uid, params.uid_len, params.atqa, params.sak, false, 100)) {
 | 
					 | 
				
			||||||
            printf("Reader detected\r\n");
 | 
					 | 
				
			||||||
            furi_hal_nfc_sleep();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        furi_delay_ms(50);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    furi_hal_nfc_sleep();
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void nfc_cli_field(Cli* cli, FuriString* args) {
 | 
					static void nfc_cli_field(Cli* cli, FuriString* args) {
 | 
				
			||||||
    UNUSED(args);
 | 
					    UNUSED(args);
 | 
				
			||||||
    // Check if nfc worker is not busy
 | 
					    // Check if nfc worker is not busy
 | 
				
			||||||
    if(furi_hal_nfc_is_busy()) {
 | 
					    if(furi_hal_nfc_is_hal_ready() != FuriHalNfcErrorNone) {
 | 
				
			||||||
        printf("Nfc is busy\r\n");
 | 
					        printf("NFC chip failed to start\r\n");
 | 
				
			||||||
        return;
 | 
					        return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    furi_hal_nfc_exit_sleep();
 | 
					    furi_hal_nfc_acquire();
 | 
				
			||||||
    furi_hal_nfc_field_on();
 | 
					    furi_hal_nfc_low_power_mode_stop();
 | 
				
			||||||
 | 
					    furi_hal_nfc_poller_field_on();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    printf("Field is on. Don't leave device in this mode for too long.\r\n");
 | 
					    printf("Field is on. Don't leave device in this mode for too long.\r\n");
 | 
				
			||||||
    printf("Press Ctrl+C to abort\r\n");
 | 
					    printf("Press Ctrl+C to abort\r\n");
 | 
				
			||||||
@ -96,73 +36,8 @@ static void nfc_cli_field(Cli* cli, FuriString* args) {
 | 
				
			|||||||
        furi_delay_ms(50);
 | 
					        furi_delay_ms(50);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    furi_hal_nfc_field_off();
 | 
					    furi_hal_nfc_low_power_mode_start();
 | 
				
			||||||
    furi_hal_nfc_sleep();
 | 
					    furi_hal_nfc_release();
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void nfc_cli_apdu(Cli* cli, FuriString* args) {
 | 
					 | 
				
			||||||
    UNUSED(cli);
 | 
					 | 
				
			||||||
    if(furi_hal_nfc_is_busy()) {
 | 
					 | 
				
			||||||
        printf("Nfc is busy\r\n");
 | 
					 | 
				
			||||||
        return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    furi_hal_nfc_exit_sleep();
 | 
					 | 
				
			||||||
    FuriString* data = NULL;
 | 
					 | 
				
			||||||
    data = furi_string_alloc();
 | 
					 | 
				
			||||||
    FuriHalNfcTxRxContext tx_rx = {};
 | 
					 | 
				
			||||||
    FuriHalNfcDevData dev_data = {};
 | 
					 | 
				
			||||||
    uint8_t* req_buffer = NULL;
 | 
					 | 
				
			||||||
    uint8_t* resp_buffer = NULL;
 | 
					 | 
				
			||||||
    size_t apdu_size = 0;
 | 
					 | 
				
			||||||
    size_t resp_size = 0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    do {
 | 
					 | 
				
			||||||
        if(!args_read_string_and_trim(args, data)) {
 | 
					 | 
				
			||||||
            printf(
 | 
					 | 
				
			||||||
                "Use like `nfc apdu 00a404000e325041592e5359532e444446303100 00a4040008a0000003010102` \r\n");
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        printf("detecting tag\r\n");
 | 
					 | 
				
			||||||
        if(!furi_hal_nfc_detect(&dev_data, 300)) {
 | 
					 | 
				
			||||||
            printf("Failed to detect tag\r\n");
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        do {
 | 
					 | 
				
			||||||
            apdu_size = furi_string_size(data) / 2;
 | 
					 | 
				
			||||||
            req_buffer = malloc(apdu_size);
 | 
					 | 
				
			||||||
            hex_chars_to_uint8(furi_string_get_cstr(data), req_buffer);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            memcpy(tx_rx.tx_data, req_buffer, apdu_size);
 | 
					 | 
				
			||||||
            tx_rx.tx_bits = apdu_size * 8;
 | 
					 | 
				
			||||||
            tx_rx.tx_rx_type = FuriHalNfcTxRxTypeDefault;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            printf("Sending APDU:%s to Tag\r\n", furi_string_get_cstr(data));
 | 
					 | 
				
			||||||
            if(!furi_hal_nfc_tx_rx(&tx_rx, 300)) {
 | 
					 | 
				
			||||||
                printf("Failed to tx_rx\r\n");
 | 
					 | 
				
			||||||
                break;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            resp_size = (tx_rx.rx_bits / 8) * 2;
 | 
					 | 
				
			||||||
            if(!resp_size) {
 | 
					 | 
				
			||||||
                printf("No response\r\n");
 | 
					 | 
				
			||||||
                continue;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            resp_buffer = malloc(resp_size);
 | 
					 | 
				
			||||||
            uint8_to_hex_chars(tx_rx.rx_data, resp_buffer, resp_size);
 | 
					 | 
				
			||||||
            resp_buffer[resp_size] = 0;
 | 
					 | 
				
			||||||
            printf("Response: %s\r\n", resp_buffer);
 | 
					 | 
				
			||||||
            free(req_buffer);
 | 
					 | 
				
			||||||
            free(resp_buffer);
 | 
					 | 
				
			||||||
            req_buffer = NULL;
 | 
					 | 
				
			||||||
            resp_buffer = NULL;
 | 
					 | 
				
			||||||
        } while(args_read_string_and_trim(args, data));
 | 
					 | 
				
			||||||
    } while(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    free(req_buffer);
 | 
					 | 
				
			||||||
    free(resp_buffer);
 | 
					 | 
				
			||||||
    furi_string_free(data);
 | 
					 | 
				
			||||||
    furi_hal_nfc_sleep();
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void nfc_cli(Cli* cli, FuriString* args, void* context) {
 | 
					static void nfc_cli(Cli* cli, FuriString* args, void* context) {
 | 
				
			||||||
@ -175,20 +50,6 @@ static void nfc_cli(Cli* cli, FuriString* args, void* context) {
 | 
				
			|||||||
            nfc_cli_print_usage();
 | 
					            nfc_cli_print_usage();
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if(furi_string_cmp_str(cmd, "detect") == 0) {
 | 
					 | 
				
			||||||
            nfc_cli_detect(cli, args);
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        if(furi_string_cmp_str(cmd, "emulate") == 0) {
 | 
					 | 
				
			||||||
            nfc_cli_emulate(cli, args);
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if(furi_string_cmp_str(cmd, "apdu") == 0) {
 | 
					 | 
				
			||||||
            nfc_cli_apdu(cli, args);
 | 
					 | 
				
			||||||
            break;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
 | 
					        if(furi_hal_rtc_is_flag_set(FuriHalRtcFlagDebug)) {
 | 
				
			||||||
            if(furi_string_cmp_str(cmd, "field") == 0) {
 | 
					            if(furi_string_cmp_str(cmd, "field") == 0) {
 | 
				
			||||||
                nfc_cli_field(cli, args);
 | 
					                nfc_cli_field(cli, args);
 | 
				
			||||||
 | 
				
			|||||||
@ -1,118 +0,0 @@
 | 
				
			|||||||
#pragma once
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "nfc.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <furi.h>
 | 
					 | 
				
			||||||
#include <furi_hal.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <gui/gui.h>
 | 
					 | 
				
			||||||
#include <gui/view.h>
 | 
					 | 
				
			||||||
#include <assets_icons.h>
 | 
					 | 
				
			||||||
#include <gui/view_dispatcher.h>
 | 
					 | 
				
			||||||
#include <gui/scene_manager.h>
 | 
					 | 
				
			||||||
#include <cli/cli.h>
 | 
					 | 
				
			||||||
#include <notification/notification_messages.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <gui/modules/submenu.h>
 | 
					 | 
				
			||||||
#include <gui/modules/dialog_ex.h>
 | 
					 | 
				
			||||||
#include <gui/modules/popup.h>
 | 
					 | 
				
			||||||
#include <gui/modules/loading.h>
 | 
					 | 
				
			||||||
#include <gui/modules/text_input.h>
 | 
					 | 
				
			||||||
#include <gui/modules/byte_input.h>
 | 
					 | 
				
			||||||
#include <gui/modules/text_box.h>
 | 
					 | 
				
			||||||
#include <gui/modules/widget.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <lib/nfc/nfc_types.h>
 | 
					 | 
				
			||||||
#include <lib/nfc/nfc_worker.h>
 | 
					 | 
				
			||||||
#include <lib/nfc/nfc_device.h>
 | 
					 | 
				
			||||||
#include <lib/nfc/helpers/mf_classic_dict.h>
 | 
					 | 
				
			||||||
#include <lib/nfc/parsers/nfc_supported_card.h>
 | 
					 | 
				
			||||||
#include <lib/nfc/helpers/nfc_generators.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "views/dict_attack.h"
 | 
					 | 
				
			||||||
#include "views/detect_reader.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <nfc/scenes/nfc_scene.h>
 | 
					 | 
				
			||||||
#include <nfc/helpers/nfc_custom_event.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <dialogs/dialogs.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include "rpc/rpc_app.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#include <m-array.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
ARRAY_DEF(MfClassicUserKeys, char*, M_PTR_OPLIST);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#define NFC_TEXT_STORE_SIZE 128
 | 
					 | 
				
			||||||
#define NFC_APP_FOLDER ANY_PATH("nfc")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef enum {
 | 
					 | 
				
			||||||
    NfcRpcStateIdle,
 | 
					 | 
				
			||||||
    NfcRpcStateEmulating,
 | 
					 | 
				
			||||||
    NfcRpcStateEmulated,
 | 
					 | 
				
			||||||
} NfcRpcState;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
struct Nfc {
 | 
					 | 
				
			||||||
    NfcWorker* worker;
 | 
					 | 
				
			||||||
    ViewDispatcher* view_dispatcher;
 | 
					 | 
				
			||||||
    Gui* gui;
 | 
					 | 
				
			||||||
    NotificationApp* notifications;
 | 
					 | 
				
			||||||
    SceneManager* scene_manager;
 | 
					 | 
				
			||||||
    NfcDevice* dev;
 | 
					 | 
				
			||||||
    FuriHalNfcDevData dev_edit_data;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    char text_store[NFC_TEXT_STORE_SIZE + 1];
 | 
					 | 
				
			||||||
    FuriString* text_box_store;
 | 
					 | 
				
			||||||
    uint8_t byte_input_store[6];
 | 
					 | 
				
			||||||
    MfClassicUserKeys_t mfc_key_strs; // Used in MFC key listing
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    void* rpc_ctx;
 | 
					 | 
				
			||||||
    NfcRpcState rpc_state;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Common Views
 | 
					 | 
				
			||||||
    Submenu* submenu;
 | 
					 | 
				
			||||||
    DialogEx* dialog_ex;
 | 
					 | 
				
			||||||
    Popup* popup;
 | 
					 | 
				
			||||||
    Loading* loading;
 | 
					 | 
				
			||||||
    TextInput* text_input;
 | 
					 | 
				
			||||||
    ByteInput* byte_input;
 | 
					 | 
				
			||||||
    TextBox* text_box;
 | 
					 | 
				
			||||||
    Widget* widget;
 | 
					 | 
				
			||||||
    DictAttack* dict_attack;
 | 
					 | 
				
			||||||
    DetectReader* detect_reader;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const NfcGenerator* generator;
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef enum {
 | 
					 | 
				
			||||||
    NfcViewMenu,
 | 
					 | 
				
			||||||
    NfcViewDialogEx,
 | 
					 | 
				
			||||||
    NfcViewPopup,
 | 
					 | 
				
			||||||
    NfcViewLoading,
 | 
					 | 
				
			||||||
    NfcViewTextInput,
 | 
					 | 
				
			||||||
    NfcViewByteInput,
 | 
					 | 
				
			||||||
    NfcViewTextBox,
 | 
					 | 
				
			||||||
    NfcViewWidget,
 | 
					 | 
				
			||||||
    NfcViewDictAttack,
 | 
					 | 
				
			||||||
    NfcViewDetectReader,
 | 
					 | 
				
			||||||
} NfcView;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
Nfc* nfc_alloc();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int32_t nfc_task(void* p);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_text_store_set(Nfc* nfc, const char* text, ...);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_text_store_clear(Nfc* nfc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_blink_read_start(Nfc* nfc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_blink_emulate_start(Nfc* nfc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_blink_detect_start(Nfc* nfc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_blink_stop(Nfc* nfc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool nfc_save_file(Nfc* nfc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_show_loading_popup(void* context, bool show);
 | 
					 | 
				
			||||||
							
								
								
									
										107
									
								
								applications/main/nfc/plugins/supported_cards/all_in_one.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										107
									
								
								applications/main/nfc/plugins/supported_cards/all_in_one.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,107 @@
 | 
				
			|||||||
 | 
					#include "nfc_supported_card_plugin.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <flipper_application/flipper_application.h>
 | 
				
			||||||
 | 
					#include <nfc/protocols/mf_ultralight/mf_ultralight.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define TAG "AllInOne"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef enum {
 | 
				
			||||||
 | 
					    AllInOneLayoutTypeA,
 | 
				
			||||||
 | 
					    AllInOneLayoutTypeD,
 | 
				
			||||||
 | 
					    AllInOneLayoutTypeE2,
 | 
				
			||||||
 | 
					    AllInOneLayoutTypeE3,
 | 
				
			||||||
 | 
					    AllInOneLayoutTypeE5,
 | 
				
			||||||
 | 
					    AllInOneLayoutType2,
 | 
				
			||||||
 | 
					    AllInOneLayoutTypeUnknown,
 | 
				
			||||||
 | 
					} AllInOneLayoutType;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static AllInOneLayoutType all_in_one_get_layout(const MfUltralightData* data) {
 | 
				
			||||||
 | 
					    // Switch on the second half of the third byte of page 5
 | 
				
			||||||
 | 
					    const uint8_t layout_byte = data->page[5].data[2];
 | 
				
			||||||
 | 
					    const uint8_t layout_half_byte = data->page[5].data[2] & 0x0F;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    FURI_LOG_I(TAG, "Layout byte: %02x", layout_byte);
 | 
				
			||||||
 | 
					    FURI_LOG_I(TAG, "Layout half-byte: %02x", layout_half_byte);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    switch(layout_half_byte) {
 | 
				
			||||||
 | 
					    // If it is A, the layout type is a type A layout
 | 
				
			||||||
 | 
					    case 0x0A:
 | 
				
			||||||
 | 
					        return AllInOneLayoutTypeA;
 | 
				
			||||||
 | 
					    case 0x0D:
 | 
				
			||||||
 | 
					        return AllInOneLayoutTypeD;
 | 
				
			||||||
 | 
					    case 0x02:
 | 
				
			||||||
 | 
					        return AllInOneLayoutType2;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					        FURI_LOG_I(TAG, "Unknown layout type: %d", layout_half_byte);
 | 
				
			||||||
 | 
					        return AllInOneLayoutTypeUnknown;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool all_in_one_parse(const NfcDevice* device, FuriString* parsed_data) {
 | 
				
			||||||
 | 
					    furi_assert(device);
 | 
				
			||||||
 | 
					    furi_assert(parsed_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const MfUltralightData* data = nfc_device_get_data(device, NfcProtocolMfUltralight);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool parsed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        if(data->page[4].data[0] != 0x45 || data->page[4].data[1] != 0xD9) {
 | 
				
			||||||
 | 
					            FURI_LOG_I(TAG, "Pass not verified");
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        uint8_t ride_count = 0;
 | 
				
			||||||
 | 
					        uint32_t serial = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const AllInOneLayoutType layout_type = all_in_one_get_layout(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(layout_type == AllInOneLayoutTypeA) {
 | 
				
			||||||
 | 
					            // If the layout is A then the ride count is stored in the first byte of page 8
 | 
				
			||||||
 | 
					            ride_count = data->page[8].data[0];
 | 
				
			||||||
 | 
					        } else if(layout_type == AllInOneLayoutTypeD) {
 | 
				
			||||||
 | 
					            // If the layout is D, the ride count is stored in the second byte of page 9
 | 
				
			||||||
 | 
					            ride_count = data->page[9].data[1];
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            FURI_LOG_I(TAG, "Unknown layout: %d", layout_type);
 | 
				
			||||||
 | 
					            ride_count = 137;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // // The number starts at the second half of the third byte on page 4, and is 32 bits long
 | 
				
			||||||
 | 
					        // // So we get the second half of the third byte, then bytes 4-6, and then the first half of the 7th byte
 | 
				
			||||||
 | 
					        // // B8 17 A2 A4 BD becomes 81 7A 2A 4B
 | 
				
			||||||
 | 
					        const uint8_t* serial_data_lo = data->page[4].data;
 | 
				
			||||||
 | 
					        const uint8_t* serial_data_hi = data->page[5].data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        serial = (serial_data_lo[2] & 0x0F) << 28 | serial_data_lo[3] << 20 |
 | 
				
			||||||
 | 
					                 serial_data_hi[0] << 12 | serial_data_hi[1] << 4 | serial_data_hi[2] >> 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Format string for rides count
 | 
				
			||||||
 | 
					        furi_string_printf(
 | 
				
			||||||
 | 
					            parsed_data, "\e#All-In-One\nNumber: %lu\nRides left: %u", serial, ride_count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parsed = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return parsed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Actual implementation of app<>plugin interface */
 | 
				
			||||||
 | 
					static const NfcSupportedCardsPlugin all_in_one_plugin = {
 | 
				
			||||||
 | 
					    .protocol = NfcProtocolMfUltralight,
 | 
				
			||||||
 | 
					    .verify = NULL,
 | 
				
			||||||
 | 
					    .read = NULL,
 | 
				
			||||||
 | 
					    .parse = all_in_one_parse,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Plugin descriptor to comply with basic plugin specification */
 | 
				
			||||||
 | 
					static const FlipperAppPluginDescriptor all_in_one_plugin_descriptor = {
 | 
				
			||||||
 | 
					    .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
 | 
				
			||||||
 | 
					    .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
 | 
				
			||||||
 | 
					    .entry_point = &all_in_one_plugin,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Plugin entry point - must return a pointer to const descriptor  */
 | 
				
			||||||
 | 
					const FlipperAppPluginDescriptor* all_in_one_plugin_ep() {
 | 
				
			||||||
 | 
					    return &all_in_one_plugin_descriptor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										116
									
								
								applications/main/nfc/plugins/supported_cards/myki.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								applications/main/nfc/plugins/supported_cards/myki.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,116 @@
 | 
				
			|||||||
 | 
					/* myki.c - Parser for myki cards (Melbourne, Australia).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Based on the code by Emily Trau (https://github.com/emilytrau)
 | 
				
			||||||
 | 
					 * Original pull request URL: https://github.com/flipperdevices/flipperzero-firmware/pull/2326
 | 
				
			||||||
 | 
					 * Reference: https://github.com/metrodroid/metrodroid/wiki/Myki
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#include "nfc_supported_card_plugin.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <flipper_application/flipper_application.h>
 | 
				
			||||||
 | 
					#include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const MfDesfireApplicationId myki_app_id = {.data = {0x00, 0x11, 0xf2}};
 | 
				
			||||||
 | 
					static const MfDesfireFileId myki_file_id = 0x0f;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static uint8_t myki_calculate_luhn(uint64_t number) {
 | 
				
			||||||
 | 
					    // https://en.wikipedia.org/wiki/Luhn_algorithm
 | 
				
			||||||
 | 
					    // Drop existing check digit to form payload
 | 
				
			||||||
 | 
					    uint64_t payload = number / 10;
 | 
				
			||||||
 | 
					    int sum = 0;
 | 
				
			||||||
 | 
					    int position = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while(payload > 0) {
 | 
				
			||||||
 | 
					        int digit = payload % 10;
 | 
				
			||||||
 | 
					        if(position % 2 == 0) {
 | 
				
			||||||
 | 
					            digit *= 2;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if(digit > 9) {
 | 
				
			||||||
 | 
					            digit = (digit / 10) + (digit % 10);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        sum += digit;
 | 
				
			||||||
 | 
					        payload /= 10;
 | 
				
			||||||
 | 
					        position++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return (10 - (sum % 10)) % 10;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool myki_parse(const NfcDevice* device, FuriString* parsed_data) {
 | 
				
			||||||
 | 
					    furi_assert(device);
 | 
				
			||||||
 | 
					    furi_assert(parsed_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool parsed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const MfDesfireApplication* app = mf_desfire_get_application(data, &myki_app_id);
 | 
				
			||||||
 | 
					        if(app == NULL) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        typedef struct {
 | 
				
			||||||
 | 
					            uint32_t top;
 | 
				
			||||||
 | 
					            uint32_t bottom;
 | 
				
			||||||
 | 
					        } MykiFile;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const MfDesfireFileSettings* file_settings =
 | 
				
			||||||
 | 
					            mf_desfire_get_file_settings(app, &myki_file_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
 | 
				
			||||||
 | 
					           file_settings->data.size < sizeof(MykiFile))
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &myki_file_id);
 | 
				
			||||||
 | 
					        if(file_data == NULL) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const MykiFile* myki_file = simple_array_cget_data(file_data->data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // All myki card numbers are prefixed with "308425"
 | 
				
			||||||
 | 
					        if(myki_file->top != 308425UL) break;
 | 
				
			||||||
 | 
					        // Card numbers are always 15 digits in length
 | 
				
			||||||
 | 
					        if(myki_file->bottom < 10000000UL || myki_file->bottom >= 100000000UL) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        uint64_t card_number = myki_file->top * 1000000000ULL + myki_file->bottom * 10UL;
 | 
				
			||||||
 | 
					        // Stored card number doesn't include check digit
 | 
				
			||||||
 | 
					        card_number += myki_calculate_luhn(card_number);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_set(parsed_data, "\e#myki\n");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Stylise card number according to the physical card
 | 
				
			||||||
 | 
					        char card_string[20];
 | 
				
			||||||
 | 
					        snprintf(card_string, sizeof(card_string), "%llu", card_number);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Digit count in each space-separated group
 | 
				
			||||||
 | 
					        static const uint8_t digit_count[] = {1, 5, 4, 4, 1};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for(uint32_t i = 0, k = 0; i < COUNT_OF(digit_count); k += digit_count[i++]) {
 | 
				
			||||||
 | 
					            for(uint32_t j = 0; j < digit_count[i]; ++j) {
 | 
				
			||||||
 | 
					                furi_string_push_back(parsed_data, card_string[j + k]);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            furi_string_push_back(parsed_data, ' ');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parsed = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return parsed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Actual implementation of app<>plugin interface */
 | 
				
			||||||
 | 
					static const NfcSupportedCardsPlugin myki_plugin = {
 | 
				
			||||||
 | 
					    .protocol = NfcProtocolMfDesfire,
 | 
				
			||||||
 | 
					    .verify = NULL,
 | 
				
			||||||
 | 
					    .read = NULL,
 | 
				
			||||||
 | 
					    .parse = myki_parse,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Plugin descriptor to comply with basic plugin specification */
 | 
				
			||||||
 | 
					static const FlipperAppPluginDescriptor myki_plugin_descriptor = {
 | 
				
			||||||
 | 
					    .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
 | 
				
			||||||
 | 
					    .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
 | 
				
			||||||
 | 
					    .entry_point = &myki_plugin,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Plugin entry point - must return a pointer to const descriptor  */
 | 
				
			||||||
 | 
					const FlipperAppPluginDescriptor* myki_plugin_ep() {
 | 
				
			||||||
 | 
					    return &myki_plugin_descriptor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,94 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @file nfc_supported_card_plugin.h
 | 
				
			||||||
 | 
					 * @brief Supported card plugin abstract interface.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Supported card plugins are dynamically loaded libraries that help making sense of
 | 
				
			||||||
 | 
					 * a particular card's raw data, if a suitable plugin exists.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * For example, if some card serves as a bus ticket, instead of just displaying a data dump,
 | 
				
			||||||
 | 
					 * a suitable plugin will transform that data into a human-readable format, showing the number
 | 
				
			||||||
 | 
					 * of rides or balance left.
 | 
				
			||||||
 | 
					 * Because of the highly specialised nature of application-specific cards, a separate plugin
 | 
				
			||||||
 | 
					 * for each such card type must be implemented.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * To add a new plugin, create a uniquely-named .c file in the `supported_cards` directory
 | 
				
			||||||
 | 
					 * and implement at least the parse() function in the NfcSupportedCardsPlugin structure.
 | 
				
			||||||
 | 
					 * Then, register the plugin in the `application.fam` file in the `nfc` directory. Use the existing
 | 
				
			||||||
 | 
					 * entries as an example. After being registered, the plugin will be automatically deployed with the application.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @note the APPID field MUST end with `_parser` so the applicaton would know that this particular file
 | 
				
			||||||
 | 
					 * is a supported card plugin.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @see nfc_supported_cards.h
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <furi/core/string.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/nfc.h>
 | 
				
			||||||
 | 
					#include <nfc/nfc_device.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Unique string identifier for supported card plugins.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#define NFC_SUPPORTED_CARD_PLUGIN_APP_ID "NfcSupportedCardPlugin"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Currently supported plugin API version.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					#define NFC_SUPPORTED_CARD_PLUGIN_API_VERSION 1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Verify that the card is of a supported type.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This function should be implemented if a quick check exists
 | 
				
			||||||
 | 
					 * allowing to verify that the plugin is working with the appropriate card type.
 | 
				
			||||||
 | 
					 * Such checks may include, but are not limited to: reading a specific sector,
 | 
				
			||||||
 | 
					 * performing a certain read operation, etc.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in,out] nfc pointer to an Nfc instance.
 | 
				
			||||||
 | 
					 * @returns true if the card was successfully verified, false otherwise.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef bool (*NfcSupportedCardPluginVerify)(Nfc* nfc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Read the card using a custom procedure.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This function should be implemented if a card requires some special reading
 | 
				
			||||||
 | 
					 * procedure not covered in the vanilla poller. Examples include, but are not
 | 
				
			||||||
 | 
					 * limited to: reading with particular security keys, mandatory order of read
 | 
				
			||||||
 | 
					 * operations, etc.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in,out] nfc pointer to an Nfc instance.
 | 
				
			||||||
 | 
					 * @param[in,out] device pointer to a device instance to hold the read data.
 | 
				
			||||||
 | 
					 * @returns true if the card was successfully read, false otherwise.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef bool (*NfcSupportedCardPluginRead)(Nfc* nfc, NfcDevice* device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Parse raw data into human-readable representation.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * A supported card plugin may contain only this function, if no special verification
 | 
				
			||||||
 | 
					 * or reading procedures are not required. In any case, the data must be complete and
 | 
				
			||||||
 | 
					 * available through the `device` parameter at the time of calling.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The output format is free and application-dependent. Multiple lines should
 | 
				
			||||||
 | 
					 * be separated by newline character.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @param[in] device pointer to a device instance holding the data is to be parsed.
 | 
				
			||||||
 | 
					 * @param[out] parsed_data pointer to the string to contain the formatted result.
 | 
				
			||||||
 | 
					 * @returns true if the card was successfully parsed, false otherwise.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef bool (*NfcSupportedCardPluginParse)(const NfcDevice* device, FuriString* parsed_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @brief Supported card plugin interface.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * For a minimally functional plugin, only the parse() function must be implemented.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    NfcProtocol protocol; /**< Identifier of the protocol this card type works on top of. */
 | 
				
			||||||
 | 
					    NfcSupportedCardPluginVerify verify; /**< Pointer to the verify() function. */
 | 
				
			||||||
 | 
					    NfcSupportedCardPluginRead read; /**< Pointer to the read() function. */
 | 
				
			||||||
 | 
					    NfcSupportedCardPluginParse parse; /**< Pointer to the parse() function. */
 | 
				
			||||||
 | 
					} NfcSupportedCardsPlugin;
 | 
				
			||||||
							
								
								
									
										233
									
								
								applications/main/nfc/plugins/supported_cards/opal.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										233
									
								
								applications/main/nfc/plugins/supported_cards/opal.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,233 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * opal.c - Parser for Opal card (Sydney, Australia).
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Copyright 2023 Michael Farrell <micolous+git@gmail.com>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This will only read "standard" MIFARE DESFire-based Opal cards. Free travel
 | 
				
			||||||
 | 
					 * cards (including School Opal cards, veteran, vision-impaired persons and
 | 
				
			||||||
 | 
					 * TfNSW employees' cards) and single-trip tickets are MIFARE Ultralight C
 | 
				
			||||||
 | 
					 * cards and not supported.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Reference: https://github.com/metrodroid/metrodroid/wiki/Opal
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Note: The card values are all little-endian (like Flipper), but the above
 | 
				
			||||||
 | 
					 * reference was originally written based on Java APIs, which are big-endian.
 | 
				
			||||||
 | 
					 * This implementation presumes a little-endian system.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is free software: you can redistribute it and/or modify it
 | 
				
			||||||
 | 
					 * under the terms of the GNU General Public License as published by
 | 
				
			||||||
 | 
					 * the Free Software Foundation, either version 3 of the License, or
 | 
				
			||||||
 | 
					 * (at your option) any later version.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This program is distributed in the hope that it will be useful, but
 | 
				
			||||||
 | 
					 * WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
				
			||||||
 | 
					 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
				
			||||||
 | 
					 * General Public License for more details.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * You should have received a copy of the GNU General Public License
 | 
				
			||||||
 | 
					 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "nfc_supported_card_plugin.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <flipper_application/flipper_application.h>
 | 
				
			||||||
 | 
					#include <applications/services/locale/locale.h>
 | 
				
			||||||
 | 
					#include <furi_hal_rtc.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <lib/nfc/protocols/mf_desfire/mf_desfire.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const MfDesfireApplicationId opal_app_id = {.data = {0x31, 0x45, 0x53}};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const MfDesfireFileId opal_file_id = 0x07;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char* opal_modes[5] =
 | 
				
			||||||
 | 
					    {"Rail / Metro", "Ferry / Light Rail", "Bus", "Unknown mode", "Manly Ferry"};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const char* opal_usages[14] = {
 | 
				
			||||||
 | 
					    "New / Unused",
 | 
				
			||||||
 | 
					    "Tap on: new journey",
 | 
				
			||||||
 | 
					    "Tap on: transfer from same mode",
 | 
				
			||||||
 | 
					    "Tap on: transfer from other mode",
 | 
				
			||||||
 | 
					    NULL, // Manly Ferry: new journey
 | 
				
			||||||
 | 
					    NULL, // Manly Ferry: transfer from ferry
 | 
				
			||||||
 | 
					    NULL, // Manly Ferry: transfer from other
 | 
				
			||||||
 | 
					    "Tap off: distance fare",
 | 
				
			||||||
 | 
					    "Tap off: flat fare",
 | 
				
			||||||
 | 
					    "Automated tap off: failed to tap off",
 | 
				
			||||||
 | 
					    "Tap off: end of trip without start",
 | 
				
			||||||
 | 
					    "Tap off: reversal",
 | 
				
			||||||
 | 
					    "Tap on: rejected",
 | 
				
			||||||
 | 
					    "Unknown usage",
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Opal file 0x7 structure. Assumes a little-endian CPU.
 | 
				
			||||||
 | 
					typedef struct __attribute__((__packed__)) {
 | 
				
			||||||
 | 
					    uint32_t serial : 32;
 | 
				
			||||||
 | 
					    uint8_t check_digit : 4;
 | 
				
			||||||
 | 
					    bool blocked : 1;
 | 
				
			||||||
 | 
					    uint16_t txn_number : 16;
 | 
				
			||||||
 | 
					    int32_t balance : 21;
 | 
				
			||||||
 | 
					    uint16_t days : 15;
 | 
				
			||||||
 | 
					    uint16_t minutes : 11;
 | 
				
			||||||
 | 
					    uint8_t mode : 3;
 | 
				
			||||||
 | 
					    uint16_t usage : 4;
 | 
				
			||||||
 | 
					    bool auto_topup : 1;
 | 
				
			||||||
 | 
					    uint8_t weekly_journeys : 4;
 | 
				
			||||||
 | 
					    uint16_t checksum : 16;
 | 
				
			||||||
 | 
					} OpalFile;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static_assert(sizeof(OpalFile) == 16, "OpalFile");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Converts an Opal timestamp to FuriHalRtcDateTime.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Opal measures days since 1980-01-01 and minutes since midnight, and presumes
 | 
				
			||||||
 | 
					// all days are 1440 minutes.
 | 
				
			||||||
 | 
					static void opal_date_time_to_furi(uint16_t days, uint16_t minutes, FuriHalRtcDateTime* out) {
 | 
				
			||||||
 | 
					    out->year = 1980;
 | 
				
			||||||
 | 
					    out->month = 1;
 | 
				
			||||||
 | 
					    // 1980-01-01 is a Tuesday
 | 
				
			||||||
 | 
					    out->weekday = ((days + 1) % 7) + 1;
 | 
				
			||||||
 | 
					    out->hour = minutes / 60;
 | 
				
			||||||
 | 
					    out->minute = minutes % 60;
 | 
				
			||||||
 | 
					    out->second = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // What year is it?
 | 
				
			||||||
 | 
					    for(;;) {
 | 
				
			||||||
 | 
					        const uint16_t num_days_in_year = furi_hal_rtc_get_days_per_year(out->year);
 | 
				
			||||||
 | 
					        if(days < num_days_in_year) break;
 | 
				
			||||||
 | 
					        days -= num_days_in_year;
 | 
				
			||||||
 | 
					        out->year++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // 1-index the day of the year
 | 
				
			||||||
 | 
					    days++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for(;;) {
 | 
				
			||||||
 | 
					        // What month is it?
 | 
				
			||||||
 | 
					        const bool is_leap = furi_hal_rtc_is_leap_year(out->year);
 | 
				
			||||||
 | 
					        const uint8_t num_days_in_month = furi_hal_rtc_get_days_per_month(is_leap, out->month);
 | 
				
			||||||
 | 
					        if(days <= num_days_in_month) break;
 | 
				
			||||||
 | 
					        days -= num_days_in_month;
 | 
				
			||||||
 | 
					        out->month++;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    out->day = days;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool opal_parse(const NfcDevice* device, FuriString* parsed_data) {
 | 
				
			||||||
 | 
					    furi_assert(device);
 | 
				
			||||||
 | 
					    furi_assert(parsed_data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const MfDesfireData* data = nfc_device_get_data(device, NfcProtocolMfDesfire);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool parsed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        const MfDesfireApplication* app = mf_desfire_get_application(data, &opal_app_id);
 | 
				
			||||||
 | 
					        if(app == NULL) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const MfDesfireFileSettings* file_settings =
 | 
				
			||||||
 | 
					            mf_desfire_get_file_settings(app, &opal_file_id);
 | 
				
			||||||
 | 
					        if(file_settings == NULL || file_settings->type != MfDesfireFileTypeStandard ||
 | 
				
			||||||
 | 
					           file_settings->data.size != sizeof(OpalFile))
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const MfDesfireFileData* file_data = mf_desfire_get_file_data(app, &opal_file_id);
 | 
				
			||||||
 | 
					        if(file_data == NULL) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const OpalFile* opal_file = simple_array_cget_data(file_data->data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const uint8_t serial2 = opal_file->serial / 10000000;
 | 
				
			||||||
 | 
					        const uint16_t serial3 = (opal_file->serial / 1000) % 10000;
 | 
				
			||||||
 | 
					        const uint16_t serial4 = (opal_file->serial % 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(opal_file->check_digit > 9) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Negative balance. Make this a positive value again and record the
 | 
				
			||||||
 | 
					        // sign separately, because then we can handle balances of -99..-1
 | 
				
			||||||
 | 
					        // cents, as the "dollars" division below would result in a positive
 | 
				
			||||||
 | 
					        // zero value.
 | 
				
			||||||
 | 
					        const bool is_negative_balance = (opal_file->balance < 0);
 | 
				
			||||||
 | 
					        const char* sign = is_negative_balance ? "-" : "";
 | 
				
			||||||
 | 
					        const int32_t balance = is_negative_balance ? labs(opal_file->balance) : //-V1081
 | 
				
			||||||
 | 
					                                                      opal_file->balance;
 | 
				
			||||||
 | 
					        const uint8_t balance_cents = balance % 100;
 | 
				
			||||||
 | 
					        const int32_t balance_dollars = balance / 100;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        FuriHalRtcDateTime timestamp;
 | 
				
			||||||
 | 
					        opal_date_time_to_furi(opal_file->days, opal_file->minutes, ×tamp);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Usages 4..6 associated with the Manly Ferry, which correspond to
 | 
				
			||||||
 | 
					        // usages 1..3 for other modes.
 | 
				
			||||||
 | 
					        const bool is_manly_ferry = (opal_file->usage >= 4) && (opal_file->usage <= 6);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // 3..7 are "reserved", but we use 4 to indicate the Manly Ferry.
 | 
				
			||||||
 | 
					        const uint8_t mode = is_manly_ferry ? 4 : opal_file->mode;
 | 
				
			||||||
 | 
					        const uint8_t usage = is_manly_ferry ? opal_file->usage - 3 : opal_file->usage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const char* mode_str = opal_modes[mode > 4 ? 3 : mode];
 | 
				
			||||||
 | 
					        const char* usage_str = opal_usages[usage > 12 ? 13 : usage];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_printf(
 | 
				
			||||||
 | 
					            parsed_data,
 | 
				
			||||||
 | 
					            "\e#Opal: $%s%ld.%02hu\n3085 22%02hhu %04hu %03hu%01hhu\n%s, %s\n",
 | 
				
			||||||
 | 
					            sign,
 | 
				
			||||||
 | 
					            balance_dollars,
 | 
				
			||||||
 | 
					            balance_cents,
 | 
				
			||||||
 | 
					            serial2,
 | 
				
			||||||
 | 
					            serial3,
 | 
				
			||||||
 | 
					            serial4,
 | 
				
			||||||
 | 
					            opal_file->check_digit,
 | 
				
			||||||
 | 
					            mode_str,
 | 
				
			||||||
 | 
					            usage_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        FuriString* timestamp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        locale_format_date(timestamp_str, ×tamp, locale_get_date_format(), "-");
 | 
				
			||||||
 | 
					        furi_string_cat(parsed_data, timestamp_str);
 | 
				
			||||||
 | 
					        furi_string_cat(parsed_data, " at ");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        locale_format_time(timestamp_str, ×tamp, locale_get_time_format(), false);
 | 
				
			||||||
 | 
					        furi_string_cat(parsed_data, timestamp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_free(timestamp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_cat_printf(
 | 
				
			||||||
 | 
					            parsed_data,
 | 
				
			||||||
 | 
					            "\nWeekly journeys: %hhu, Txn #%hu\n",
 | 
				
			||||||
 | 
					            opal_file->weekly_journeys,
 | 
				
			||||||
 | 
					            opal_file->txn_number);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(opal_file->auto_topup) {
 | 
				
			||||||
 | 
					            furi_string_cat_str(parsed_data, "Auto-topup enabled\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if(opal_file->blocked) {
 | 
				
			||||||
 | 
					            furi_string_cat_str(parsed_data, "Card blocked\n");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parsed = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return parsed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Actual implementation of app<>plugin interface */
 | 
				
			||||||
 | 
					static const NfcSupportedCardsPlugin opal_plugin = {
 | 
				
			||||||
 | 
					    .protocol = NfcProtocolMfDesfire,
 | 
				
			||||||
 | 
					    .verify = NULL,
 | 
				
			||||||
 | 
					    .read = NULL,
 | 
				
			||||||
 | 
					    .parse = opal_parse,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Plugin descriptor to comply with basic plugin specification */
 | 
				
			||||||
 | 
					static const FlipperAppPluginDescriptor opal_plugin_descriptor = {
 | 
				
			||||||
 | 
					    .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
 | 
				
			||||||
 | 
					    .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
 | 
				
			||||||
 | 
					    .entry_point = &opal_plugin,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Plugin entry point - must return a pointer to const descriptor  */
 | 
				
			||||||
 | 
					const FlipperAppPluginDescriptor* opal_plugin_ep() {
 | 
				
			||||||
 | 
					    return &opal_plugin_descriptor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										219
									
								
								applications/main/nfc/plugins/supported_cards/plantain.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								applications/main/nfc/plugins/supported_cards/plantain.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,219 @@
 | 
				
			|||||||
 | 
					#include "nfc_supported_card_plugin.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <flipper_application/flipper_application.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/nfc_device.h>
 | 
				
			||||||
 | 
					#include <nfc/helpers/nfc_util.h>
 | 
				
			||||||
 | 
					#include <nfc/protocols/mf_classic/mf_classic_poller_sync_api.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define TAG "Plantain"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    uint64_t a;
 | 
				
			||||||
 | 
					    uint64_t b;
 | 
				
			||||||
 | 
					} MfClassicKeyPair;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    const MfClassicKeyPair* keys;
 | 
				
			||||||
 | 
					    uint32_t data_sector;
 | 
				
			||||||
 | 
					} PlantainCardConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const MfClassicKeyPair plantain_1k_keys[] = {
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0xe56ac127dd45, .b = 0x19fc84a3784b},
 | 
				
			||||||
 | 
					    {.a = 0x77dabc9825e1, .b = 0x9764fec3154a},
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0x26973ea74321, .b = 0xd27058c6e2c7},
 | 
				
			||||||
 | 
					    {.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3},
 | 
				
			||||||
 | 
					    {.a = 0xea0fd73cb149, .b = 0x29c35fa068fb},
 | 
				
			||||||
 | 
					    {.a = 0xc76bf71a2509, .b = 0x9ba241db3f56},
 | 
				
			||||||
 | 
					    {.a = 0xacffffffffff, .b = 0x71f3a315ad26},
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const MfClassicKeyPair plantain_4k_keys[] = {
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, {.a = 0x77dabc9825e1, .b = 0x9764fec3154a},
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, {.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3},
 | 
				
			||||||
 | 
					    {.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, {.a = 0xc76bf71a2509, .b = 0x9ba241db3f56},
 | 
				
			||||||
 | 
					    {.a = 0xacffffffffff, .b = 0x71f3a315ad26}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0x72f96bdd3714, .b = 0x462225cd34cf}, {.a = 0x044ce1872bc3, .b = 0x8c90c70cff4a},
 | 
				
			||||||
 | 
					    {.a = 0xbc2d1791dec1, .b = 0xca96a487de0b}, {.a = 0x8791b2ccb5c4, .b = 0xc956c3b80da3},
 | 
				
			||||||
 | 
					    {.a = 0x8e26e45e7d65, .b = 0x8e65b3af7d22}, {.a = 0x0f318130ed18, .b = 0x0c420a20e056},
 | 
				
			||||||
 | 
					    {.a = 0x045ceca15535, .b = 0x31bec3d9e510}, {.a = 0x9d993c5d4ef4, .b = 0x86120e488abf},
 | 
				
			||||||
 | 
					    {.a = 0xc65d4eaa645b, .b = 0xb69d40d1a439}, {.a = 0x3a8a139c20b4, .b = 0x8818a9c5d406},
 | 
				
			||||||
 | 
					    {.a = 0xbaff3053b496, .b = 0x4b7cb25354d3}, {.a = 0x7413b599c4ea, .b = 0xb0a2AAF3A1BA},
 | 
				
			||||||
 | 
					    {.a = 0x0ce7cd2cc72b, .b = 0xfa1fbb3f0f1f}, {.a = 0x0be5fac8b06a, .b = 0x6f95887a4fd3},
 | 
				
			||||||
 | 
					    {.a = 0x0eb23cc8110b, .b = 0x04dc35277635}, {.a = 0xbc4580b7f20b, .b = 0xd0a4131fb290},
 | 
				
			||||||
 | 
					    {.a = 0x7a396f0d633d, .b = 0xad2bdc097023}, {.a = 0xa3faa6daff67, .b = 0x7600e889adf9},
 | 
				
			||||||
 | 
					    {.a = 0xfd8705e721b0, .b = 0x296fc317a513}, {.a = 0x22052b480d11, .b = 0xe19504c39461},
 | 
				
			||||||
 | 
					    {.a = 0xa7141147d430, .b = 0xff16014fefc7}, {.a = 0x8a8d88151a00, .b = 0x038b5f9b5a2a},
 | 
				
			||||||
 | 
					    {.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, {.a = 0x7259fa0197c6, .b = 0x5583698df085},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool plantain_get_card_config(PlantainCardConfig* config, MfClassicType type) {
 | 
				
			||||||
 | 
					    bool success = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(type == MfClassicType1k) {
 | 
				
			||||||
 | 
					        config->data_sector = 8;
 | 
				
			||||||
 | 
					        config->keys = plantain_1k_keys;
 | 
				
			||||||
 | 
					    } else if(type == MfClassicType4k) {
 | 
				
			||||||
 | 
					        config->data_sector = 8;
 | 
				
			||||||
 | 
					        config->keys = plantain_4k_keys;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        success = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return success;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool plantain_verify_type(Nfc* nfc, MfClassicType type) {
 | 
				
			||||||
 | 
					    bool verified = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        PlantainCardConfig cfg = {};
 | 
				
			||||||
 | 
					        if(!plantain_get_card_config(&cfg, type)) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector);
 | 
				
			||||||
 | 
					        FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MfClassicKey key = {0};
 | 
				
			||||||
 | 
					        nfc_util_num2bytes(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MfClassicAuthContext auth_context;
 | 
				
			||||||
 | 
					        MfClassicError error =
 | 
				
			||||||
 | 
					            mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
 | 
				
			||||||
 | 
					        if(error != MfClassicErrorNone) {
 | 
				
			||||||
 | 
					            FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        verified = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return verified;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool plantain_verify(Nfc* nfc) {
 | 
				
			||||||
 | 
					    return plantain_verify_type(nfc, MfClassicType1k) ||
 | 
				
			||||||
 | 
					           plantain_verify_type(nfc, MfClassicType4k);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool plantain_read(Nfc* nfc, NfcDevice* device) {
 | 
				
			||||||
 | 
					    furi_assert(nfc);
 | 
				
			||||||
 | 
					    furi_assert(device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool is_read = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MfClassicData* data = mf_classic_alloc();
 | 
				
			||||||
 | 
					    nfc_device_copy_data(device, NfcProtocolMfClassic, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        MfClassicType type = MfClassicTypeMini;
 | 
				
			||||||
 | 
					        MfClassicError error = mf_classic_poller_detect_type(nfc, &type);
 | 
				
			||||||
 | 
					        if(error != MfClassicErrorNone) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data->type = type;
 | 
				
			||||||
 | 
					        PlantainCardConfig cfg = {};
 | 
				
			||||||
 | 
					        if(!plantain_get_card_config(&cfg, data->type)) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MfClassicDeviceKeys keys = {};
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
 | 
				
			||||||
 | 
					            nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
 | 
				
			||||||
 | 
					            FURI_BIT_SET(keys.key_a_mask, i);
 | 
				
			||||||
 | 
					            nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
 | 
				
			||||||
 | 
					            FURI_BIT_SET(keys.key_b_mask, i);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        error = mf_classic_poller_read(nfc, &keys, data);
 | 
				
			||||||
 | 
					        if(error != MfClassicErrorNone) {
 | 
				
			||||||
 | 
					            FURI_LOG_W(TAG, "Failed to read data");
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        nfc_device_set_data(device, NfcProtocolMfClassic, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        is_read = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mf_classic_free(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return is_read;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool plantain_parse(const NfcDevice* device, FuriString* parsed_data) {
 | 
				
			||||||
 | 
					    furi_assert(device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool parsed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        // Verify card type
 | 
				
			||||||
 | 
					        PlantainCardConfig cfg = {};
 | 
				
			||||||
 | 
					        if(!plantain_get_card_config(&cfg, data->type)) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Verify key
 | 
				
			||||||
 | 
					        const MfClassicSectorTrailer* sec_tr =
 | 
				
			||||||
 | 
					            mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
 | 
				
			||||||
 | 
					        if(key != cfg.keys[cfg.data_sector].a) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Point to block 0 of sector 4, value 0
 | 
				
			||||||
 | 
					        const uint8_t* temp_ptr = data->block[16].data;
 | 
				
			||||||
 | 
					        // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t
 | 
				
			||||||
 | 
					        // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal
 | 
				
			||||||
 | 
					        uint32_t balance =
 | 
				
			||||||
 | 
					            ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100;
 | 
				
			||||||
 | 
					        // Read card number
 | 
				
			||||||
 | 
					        // Point to block 0 of sector 0, value 0
 | 
				
			||||||
 | 
					        temp_ptr = data->block[0].data;
 | 
				
			||||||
 | 
					        // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t
 | 
				
			||||||
 | 
					        // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal
 | 
				
			||||||
 | 
					        uint8_t card_number_arr[7];
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < 7; i++) {
 | 
				
			||||||
 | 
					            card_number_arr[i] = temp_ptr[6 - i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // Copy card number to uint64_t
 | 
				
			||||||
 | 
					        uint64_t card_number = 0;
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < 7; i++) {
 | 
				
			||||||
 | 
					            card_number = (card_number << 8) | card_number_arr[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_printf(
 | 
				
			||||||
 | 
					            parsed_data, "\e#Plantain\nN:%llu-\nBalance:%lu\n", card_number, balance);
 | 
				
			||||||
 | 
					        parsed = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return parsed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Actual implementation of app<>plugin interface */
 | 
				
			||||||
 | 
					static const NfcSupportedCardsPlugin plantain_plugin = {
 | 
				
			||||||
 | 
					    .protocol = NfcProtocolMfClassic,
 | 
				
			||||||
 | 
					    .verify = plantain_verify,
 | 
				
			||||||
 | 
					    .read = plantain_read,
 | 
				
			||||||
 | 
					    .parse = plantain_parse,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Plugin descriptor to comply with basic plugin specification */
 | 
				
			||||||
 | 
					static const FlipperAppPluginDescriptor plantain_plugin_descriptor = {
 | 
				
			||||||
 | 
					    .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
 | 
				
			||||||
 | 
					    .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
 | 
				
			||||||
 | 
					    .entry_point = &plantain_plugin,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Plugin entry point - must return a pointer to const descriptor  */
 | 
				
			||||||
 | 
					const FlipperAppPluginDescriptor* plantain_plugin_ep() {
 | 
				
			||||||
 | 
					    return &plantain_plugin_descriptor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										214
									
								
								applications/main/nfc/plugins/supported_cards/troika.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										214
									
								
								applications/main/nfc/plugins/supported_cards/troika.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,214 @@
 | 
				
			|||||||
 | 
					#include "nfc_supported_card_plugin.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <flipper_application/flipper_application.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/nfc_device.h>
 | 
				
			||||||
 | 
					#include <nfc/helpers/nfc_util.h>
 | 
				
			||||||
 | 
					#include <nfc/protocols/mf_classic/mf_classic_poller_sync_api.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define TAG "Troika"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    uint64_t a;
 | 
				
			||||||
 | 
					    uint64_t b;
 | 
				
			||||||
 | 
					} MfClassicKeyPair;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    const MfClassicKeyPair* keys;
 | 
				
			||||||
 | 
					    uint32_t data_sector;
 | 
				
			||||||
 | 
					} TroikaCardConfig;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const MfClassicKeyPair troika_1k_keys[] = {
 | 
				
			||||||
 | 
					    {.a = 0xa0a1a2a3a4a5, .b = 0xfbf225dc5d58},
 | 
				
			||||||
 | 
					    {.a = 0xa82607b01c0d, .b = 0x2910989b6880},
 | 
				
			||||||
 | 
					    {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99},
 | 
				
			||||||
 | 
					    {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99},
 | 
				
			||||||
 | 
					    {.a = 0x73068f118c13, .b = 0x2b7f3253fac5},
 | 
				
			||||||
 | 
					    {.a = 0xfbc2793d540b, .b = 0xd3a297dc2698},
 | 
				
			||||||
 | 
					    {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99},
 | 
				
			||||||
 | 
					    {.a = 0xae3d65a3dad4, .b = 0x0f1c63013dba},
 | 
				
			||||||
 | 
					    {.a = 0xa73f5dc1d333, .b = 0xe35173494a81},
 | 
				
			||||||
 | 
					    {.a = 0x69a32f1c2f19, .b = 0x6b8bd9860763},
 | 
				
			||||||
 | 
					    {.a = 0x9becdf3d9273, .b = 0xf8493407799d},
 | 
				
			||||||
 | 
					    {.a = 0x08b386463229, .b = 0x5efbaecef46b},
 | 
				
			||||||
 | 
					    {.a = 0xcd4c61c26e3d, .b = 0x31c7610de3b0},
 | 
				
			||||||
 | 
					    {.a = 0xa82607b01c0d, .b = 0x2910989b6880},
 | 
				
			||||||
 | 
					    {.a = 0x0e8f64340ba4, .b = 0x4acec1205d75},
 | 
				
			||||||
 | 
					    {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const MfClassicKeyPair troika_4k_keys[] = {
 | 
				
			||||||
 | 
					    {.a = 0xa0a1a2a3a4a5, .b = 0xfbf225dc5d58}, {.a = 0xa82607b01c0d, .b = 0x2910989b6880},
 | 
				
			||||||
 | 
					    {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99},
 | 
				
			||||||
 | 
					    {.a = 0x73068f118c13, .b = 0x2b7f3253fac5}, {.a = 0xfbc2793d540b, .b = 0xd3a297dc2698},
 | 
				
			||||||
 | 
					    {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0xae3d65a3dad4, .b = 0x0f1c63013dbb},
 | 
				
			||||||
 | 
					    {.a = 0xa73f5dc1d333, .b = 0xe35173494a81}, {.a = 0x69a32f1c2f19, .b = 0x6b8bd9860763},
 | 
				
			||||||
 | 
					    {.a = 0x9becdf3d9273, .b = 0xf8493407799d}, {.a = 0x08b386463229, .b = 0x5efbaecef46b},
 | 
				
			||||||
 | 
					    {.a = 0xcd4c61c26e3d, .b = 0x31c7610de3b0}, {.a = 0xa82607b01c0d, .b = 0x2910989b6880},
 | 
				
			||||||
 | 
					    {.a = 0x0e8f64340ba4, .b = 0x4acec1205d75}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99},
 | 
				
			||||||
 | 
					    {.a = 0x6b02733bb6ec, .b = 0x7038cd25c408}, {.a = 0x403d706ba880, .b = 0xb39d19a280df},
 | 
				
			||||||
 | 
					    {.a = 0xc11f4597efb5, .b = 0x70d901648cb9}, {.a = 0x0db520c78c1c, .b = 0x73e5b9d9d3a4},
 | 
				
			||||||
 | 
					    {.a = 0x3ebce0925b2f, .b = 0x372cc880f216}, {.a = 0x16a27af45407, .b = 0x9868925175ba},
 | 
				
			||||||
 | 
					    {.a = 0xaba208516740, .b = 0xce26ecb95252}, {.a = 0xcd64e567abcd, .b = 0x8f79c4fd8a01},
 | 
				
			||||||
 | 
					    {.a = 0x764cd061f1e6, .b = 0xa74332f74994}, {.a = 0x1cc219e9fec1, .b = 0xb90de525ceb6},
 | 
				
			||||||
 | 
					    {.a = 0x2fe3cb83ea43, .b = 0xfba88f109b32}, {.a = 0x07894ffec1d6, .b = 0xefcb0e689db3},
 | 
				
			||||||
 | 
					    {.a = 0x04c297b91308, .b = 0xc8454c154cb5}, {.a = 0x7a38e3511a38, .b = 0xab16584c972a},
 | 
				
			||||||
 | 
					    {.a = 0x7545df809202, .b = 0xecf751084a80}, {.a = 0x5125974cd391, .b = 0xd3eafb5df46d},
 | 
				
			||||||
 | 
					    {.a = 0x7a86aa203788, .b = 0xe41242278ca2}, {.a = 0xafcef64c9913, .b = 0x9db96dca4324},
 | 
				
			||||||
 | 
					    {.a = 0x04eaa462f70b, .b = 0xac17b93e2fae}, {.a = 0xe734c210f27e, .b = 0x29ba8c3e9fda},
 | 
				
			||||||
 | 
					    {.a = 0xd5524f591eed, .b = 0x5daf42861b4d}, {.a = 0xe4821a377b75, .b = 0xe8709e486465},
 | 
				
			||||||
 | 
					    {.a = 0x518dc6eea089, .b = 0x97c64ac98ca4}, {.a = 0xbb52f8cce07f, .b = 0x6b6119752c70},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool troika_get_card_config(TroikaCardConfig* config, MfClassicType type) {
 | 
				
			||||||
 | 
					    bool success = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(type == MfClassicType1k) {
 | 
				
			||||||
 | 
					        config->data_sector = 8;
 | 
				
			||||||
 | 
					        config->keys = troika_1k_keys;
 | 
				
			||||||
 | 
					    } else if(type == MfClassicType4k) {
 | 
				
			||||||
 | 
					        config->data_sector = 4;
 | 
				
			||||||
 | 
					        config->keys = troika_4k_keys;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        success = false;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return success;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool troika_verify_type(Nfc* nfc, MfClassicType type) {
 | 
				
			||||||
 | 
					    bool verified = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        TroikaCardConfig cfg = {};
 | 
				
			||||||
 | 
					        if(!troika_get_card_config(&cfg, type)) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const uint8_t block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector);
 | 
				
			||||||
 | 
					        FURI_LOG_D(TAG, "Verifying sector %lu", cfg.data_sector);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MfClassicKey key = {0};
 | 
				
			||||||
 | 
					        nfc_util_num2bytes(cfg.keys[cfg.data_sector].a, COUNT_OF(key.data), key.data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MfClassicAuthContext auth_context;
 | 
				
			||||||
 | 
					        MfClassicError error =
 | 
				
			||||||
 | 
					            mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_context);
 | 
				
			||||||
 | 
					        if(error != MfClassicErrorNone) {
 | 
				
			||||||
 | 
					            FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        verified = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return verified;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool troika_verify(Nfc* nfc) {
 | 
				
			||||||
 | 
					    return troika_verify_type(nfc, MfClassicType1k) || troika_verify_type(nfc, MfClassicType4k);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool troika_read(Nfc* nfc, NfcDevice* device) {
 | 
				
			||||||
 | 
					    furi_assert(nfc);
 | 
				
			||||||
 | 
					    furi_assert(device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool is_read = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MfClassicData* data = mf_classic_alloc();
 | 
				
			||||||
 | 
					    nfc_device_copy_data(device, NfcProtocolMfClassic, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        MfClassicType type = MfClassicTypeMini;
 | 
				
			||||||
 | 
					        MfClassicError error = mf_classic_poller_detect_type(nfc, &type);
 | 
				
			||||||
 | 
					        if(error != MfClassicErrorNone) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data->type = type;
 | 
				
			||||||
 | 
					        TroikaCardConfig cfg = {};
 | 
				
			||||||
 | 
					        if(!troika_get_card_config(&cfg, data->type)) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MfClassicDeviceKeys keys = {
 | 
				
			||||||
 | 
					            .key_a_mask = 0,
 | 
				
			||||||
 | 
					            .key_b_mask = 0,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
 | 
				
			||||||
 | 
					            nfc_util_num2bytes(cfg.keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
 | 
				
			||||||
 | 
					            FURI_BIT_SET(keys.key_a_mask, i);
 | 
				
			||||||
 | 
					            nfc_util_num2bytes(cfg.keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
 | 
				
			||||||
 | 
					            FURI_BIT_SET(keys.key_b_mask, i);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        error = mf_classic_poller_read(nfc, &keys, data);
 | 
				
			||||||
 | 
					        if(error != MfClassicErrorNone) {
 | 
				
			||||||
 | 
					            FURI_LOG_W(TAG, "Failed to read data");
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        nfc_device_set_data(device, NfcProtocolMfClassic, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        is_read = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mf_classic_free(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return is_read;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool troika_parse(const NfcDevice* device, FuriString* parsed_data) {
 | 
				
			||||||
 | 
					    furi_assert(device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool parsed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        // Verify card type
 | 
				
			||||||
 | 
					        TroikaCardConfig cfg = {};
 | 
				
			||||||
 | 
					        if(!troika_get_card_config(&cfg, data->type)) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Verify key
 | 
				
			||||||
 | 
					        const MfClassicSectorTrailer* sec_tr =
 | 
				
			||||||
 | 
					            mf_classic_get_sector_trailer_by_sector(data, cfg.data_sector);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
 | 
				
			||||||
 | 
					        if(key != cfg.keys[cfg.data_sector].a) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Parse data
 | 
				
			||||||
 | 
					        const uint8_t start_block_num = mf_classic_get_first_block_num_of_sector(cfg.data_sector);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const uint8_t* temp_ptr = &data->block[start_block_num + 1].data[5];
 | 
				
			||||||
 | 
					        uint16_t balance = ((temp_ptr[0] << 8) | temp_ptr[1]) / 25;
 | 
				
			||||||
 | 
					        temp_ptr = &data->block[start_block_num].data[2];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        uint32_t number = 0;
 | 
				
			||||||
 | 
					        for(size_t i = 1; i < 5; i++) {
 | 
				
			||||||
 | 
					            number <<= 8;
 | 
				
			||||||
 | 
					            number |= temp_ptr[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        number >>= 4;
 | 
				
			||||||
 | 
					        number |= (temp_ptr[0] & 0xf) << 28;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_printf(parsed_data, "\e#Troika\nNum: %lu\nBalance: %u RUR", number, balance);
 | 
				
			||||||
 | 
					        parsed = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return parsed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Actual implementation of app<>plugin interface */
 | 
				
			||||||
 | 
					static const NfcSupportedCardsPlugin troika_plugin = {
 | 
				
			||||||
 | 
					    .protocol = NfcProtocolMfClassic,
 | 
				
			||||||
 | 
					    .verify = troika_verify,
 | 
				
			||||||
 | 
					    .read = troika_read,
 | 
				
			||||||
 | 
					    .parse = troika_parse,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Plugin descriptor to comply with basic plugin specification */
 | 
				
			||||||
 | 
					static const FlipperAppPluginDescriptor troika_plugin_descriptor = {
 | 
				
			||||||
 | 
					    .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
 | 
				
			||||||
 | 
					    .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
 | 
				
			||||||
 | 
					    .entry_point = &troika_plugin,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Plugin entry point - must return a pointer to const descriptor  */
 | 
				
			||||||
 | 
					const FlipperAppPluginDescriptor* troika_plugin_ep() {
 | 
				
			||||||
 | 
					    return &troika_plugin_descriptor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										189
									
								
								applications/main/nfc/plugins/supported_cards/two_cities.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										189
									
								
								applications/main/nfc/plugins/supported_cards/two_cities.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,189 @@
 | 
				
			|||||||
 | 
					#include "nfc_supported_card_plugin.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <flipper_application/flipper_application.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <nfc/nfc_device.h>
 | 
				
			||||||
 | 
					#include <nfc/helpers/nfc_util.h>
 | 
				
			||||||
 | 
					#include <nfc/protocols/mf_classic/mf_classic_poller_sync_api.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define TAG "TwoCities"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct {
 | 
				
			||||||
 | 
					    uint64_t a;
 | 
				
			||||||
 | 
					    uint64_t b;
 | 
				
			||||||
 | 
					} MfClassicKeyPair;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static const MfClassicKeyPair two_cities_4k_keys[] = {
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99},
 | 
				
			||||||
 | 
					    {.a = 0xe56ac127dd45, .b = 0x19fc84a3784b}, {.a = 0x77dabc9825e1, .b = 0x9764fec3154a},
 | 
				
			||||||
 | 
					    {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0xa73f5dc1d333, .b = 0xe35173494a81}, {.a = 0x69a32f1c2f19, .b = 0x6b8bd9860763},
 | 
				
			||||||
 | 
					    {.a = 0xea0fd73cb149, .b = 0x29c35fa068fb}, {.a = 0xc76bf71a2509, .b = 0x9ba241db3f56},
 | 
				
			||||||
 | 
					    {.a = 0xacffffffffff, .b = 0x71f3a315ad26}, {.a = 0xffffffffffff, .b = 0xffffffffffff},
 | 
				
			||||||
 | 
					    {.a = 0xffffffffffff, .b = 0xffffffffffff}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99},
 | 
				
			||||||
 | 
					    {.a = 0x72f96bdd3714, .b = 0x462225cd34cf}, {.a = 0x044ce1872bc3, .b = 0x8c90c70cff4a},
 | 
				
			||||||
 | 
					    {.a = 0xbc2d1791dec1, .b = 0xca96a487de0b}, {.a = 0x8791b2ccb5c4, .b = 0xc956c3b80da3},
 | 
				
			||||||
 | 
					    {.a = 0x8e26e45e7d65, .b = 0x8e65b3af7d22}, {.a = 0x0f318130ed18, .b = 0x0c420a20e056},
 | 
				
			||||||
 | 
					    {.a = 0x045ceca15535, .b = 0x31bec3d9e510}, {.a = 0x9d993c5d4ef4, .b = 0x86120e488abf},
 | 
				
			||||||
 | 
					    {.a = 0xc65d4eaa645b, .b = 0xb69d40d1a439}, {.a = 0x3a8a139c20b4, .b = 0x8818a9c5d406},
 | 
				
			||||||
 | 
					    {.a = 0xbaff3053b496, .b = 0x4b7cb25354d3}, {.a = 0x7413b599c4ea, .b = 0xb0a2AAF3A1BA},
 | 
				
			||||||
 | 
					    {.a = 0x0ce7cd2cc72b, .b = 0xfa1fbb3f0f1f}, {.a = 0x0be5fac8b06a, .b = 0x6f95887a4fd3},
 | 
				
			||||||
 | 
					    {.a = 0x26973ea74321, .b = 0xd27058c6e2c7}, {.a = 0xeb0a8ff88ade, .b = 0x578a9ada41e3},
 | 
				
			||||||
 | 
					    {.a = 0x7a396f0d633d, .b = 0xad2bdc097023}, {.a = 0xa3faa6daff67, .b = 0x7600e889adf9},
 | 
				
			||||||
 | 
					    {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99}, {.a = 0x2aa05ed1856f, .b = 0xeaac88e5dc99},
 | 
				
			||||||
 | 
					    {.a = 0xa7141147d430, .b = 0xff16014fefc7}, {.a = 0x8a8d88151a00, .b = 0x038b5f9b5a2a},
 | 
				
			||||||
 | 
					    {.a = 0xb27addfb64b0, .b = 0x152fd0c420a7}, {.a = 0x7259fa0197c6, .b = 0x5583698df085},
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool two_cities_verify(Nfc* nfc) {
 | 
				
			||||||
 | 
					    bool verified = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        const uint8_t verify_sector = 4;
 | 
				
			||||||
 | 
					        uint8_t block_num = mf_classic_get_first_block_num_of_sector(verify_sector);
 | 
				
			||||||
 | 
					        FURI_LOG_D(TAG, "Verifying sector %u", verify_sector);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MfClassicKey key = {};
 | 
				
			||||||
 | 
					        nfc_util_num2bytes(two_cities_4k_keys[verify_sector].a, COUNT_OF(key.data), key.data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        MfClassicAuthContext auth_ctx = {};
 | 
				
			||||||
 | 
					        MfClassicError error =
 | 
				
			||||||
 | 
					            mf_classic_poller_auth(nfc, block_num, &key, MfClassicKeyTypeA, &auth_ctx);
 | 
				
			||||||
 | 
					        if(error != MfClassicErrorNone) {
 | 
				
			||||||
 | 
					            FURI_LOG_D(TAG, "Failed to read block %u: %d", block_num, error);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        verified = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return verified;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool two_cities_read(Nfc* nfc, NfcDevice* device) {
 | 
				
			||||||
 | 
					    furi_assert(nfc);
 | 
				
			||||||
 | 
					    furi_assert(device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool is_read = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    MfClassicData* data = mf_classic_alloc();
 | 
				
			||||||
 | 
					    nfc_device_copy_data(device, NfcProtocolMfClassic, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        MfClassicType type = MfClassicTypeMini;
 | 
				
			||||||
 | 
					        MfClassicError error = mf_classic_poller_detect_type(nfc, &type);
 | 
				
			||||||
 | 
					        if(error != MfClassicErrorNone) break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        data->type = type;
 | 
				
			||||||
 | 
					        MfClassicDeviceKeys keys = {};
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < mf_classic_get_total_sectors_num(data->type); i++) {
 | 
				
			||||||
 | 
					            nfc_util_num2bytes(two_cities_4k_keys[i].a, sizeof(MfClassicKey), keys.key_a[i].data);
 | 
				
			||||||
 | 
					            FURI_BIT_SET(keys.key_a_mask, i);
 | 
				
			||||||
 | 
					            nfc_util_num2bytes(two_cities_4k_keys[i].b, sizeof(MfClassicKey), keys.key_b[i].data);
 | 
				
			||||||
 | 
					            FURI_BIT_SET(keys.key_b_mask, i);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        error = mf_classic_poller_read(nfc, &keys, data);
 | 
				
			||||||
 | 
					        if(error != MfClassicErrorNone) {
 | 
				
			||||||
 | 
					            FURI_LOG_W(TAG, "Failed to read data");
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        nfc_device_set_data(device, NfcProtocolMfClassic, data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        is_read = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    mf_classic_free(data);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return is_read;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static bool two_cities_parse(const NfcDevice* device, FuriString* parsed_data) {
 | 
				
			||||||
 | 
					    furi_assert(device);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const MfClassicData* data = nfc_device_get_data(device, NfcProtocolMfClassic);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    bool parsed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    do {
 | 
				
			||||||
 | 
					        // Verify key
 | 
				
			||||||
 | 
					        MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(data, 4);
 | 
				
			||||||
 | 
					        uint64_t key = nfc_util_bytes2num(sec_tr->key_a.data, 6);
 | 
				
			||||||
 | 
					        if(key != two_cities_4k_keys[4].a) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // =====
 | 
				
			||||||
 | 
					        // PLANTAIN
 | 
				
			||||||
 | 
					        // =====
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Point to block 0 of sector 4, value 0
 | 
				
			||||||
 | 
					        const uint8_t* temp_ptr = data->block[16].data;
 | 
				
			||||||
 | 
					        // Read first 4 bytes of block 0 of sector 4 from last to first and convert them to uint32_t
 | 
				
			||||||
 | 
					        // 38 18 00 00 becomes 00 00 18 38, and equals to 6200 decimal
 | 
				
			||||||
 | 
					        uint32_t balance =
 | 
				
			||||||
 | 
					            ((temp_ptr[3] << 24) | (temp_ptr[2] << 16) | (temp_ptr[1] << 8) | temp_ptr[0]) / 100;
 | 
				
			||||||
 | 
					        // Read card number
 | 
				
			||||||
 | 
					        // Point to block 0 of sector 0, value 0
 | 
				
			||||||
 | 
					        temp_ptr = data->block[0].data;
 | 
				
			||||||
 | 
					        // Read first 7 bytes of block 0 of sector 0 from last to first and convert them to uint64_t
 | 
				
			||||||
 | 
					        // 04 31 16 8A 23 5C 80 becomes 80 5C 23 8A 16 31 04, and equals to 36130104729284868 decimal
 | 
				
			||||||
 | 
					        uint8_t card_number_arr[7];
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < 7; i++) {
 | 
				
			||||||
 | 
					            card_number_arr[i] = temp_ptr[6 - i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        // Copy card number to uint64_t
 | 
				
			||||||
 | 
					        uint64_t card_number = 0;
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < 7; i++) {
 | 
				
			||||||
 | 
					            card_number = (card_number << 8) | card_number_arr[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // =====
 | 
				
			||||||
 | 
					        // --PLANTAIN--
 | 
				
			||||||
 | 
					        // =====
 | 
				
			||||||
 | 
					        // TROIKA
 | 
				
			||||||
 | 
					        // =====
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const uint8_t* troika_temp_ptr = &data->block[33].data[5];
 | 
				
			||||||
 | 
					        uint16_t troika_balance = ((troika_temp_ptr[0] << 8) | troika_temp_ptr[1]) / 25;
 | 
				
			||||||
 | 
					        troika_temp_ptr = &data->block[32].data[2];
 | 
				
			||||||
 | 
					        uint32_t troika_number = 0;
 | 
				
			||||||
 | 
					        for(size_t i = 0; i < 4; i++) {
 | 
				
			||||||
 | 
					            troika_number <<= 8;
 | 
				
			||||||
 | 
					            troika_number |= troika_temp_ptr[i];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        troika_number >>= 4;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        furi_string_printf(
 | 
				
			||||||
 | 
					            parsed_data,
 | 
				
			||||||
 | 
					            "\e#Troika+Plantain\nPN: %llu-\nPB: %lu rur.\nTN: %lu\nTB: %u rur.\n",
 | 
				
			||||||
 | 
					            card_number,
 | 
				
			||||||
 | 
					            balance,
 | 
				
			||||||
 | 
					            troika_number,
 | 
				
			||||||
 | 
					            troika_balance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        parsed = true;
 | 
				
			||||||
 | 
					    } while(false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return parsed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Actual implementation of app<>plugin interface */
 | 
				
			||||||
 | 
					static const NfcSupportedCardsPlugin two_cities_plugin = {
 | 
				
			||||||
 | 
					    .protocol = NfcProtocolMfClassic,
 | 
				
			||||||
 | 
					    .verify = two_cities_verify,
 | 
				
			||||||
 | 
					    .read = two_cities_read,
 | 
				
			||||||
 | 
					    .parse = two_cities_parse,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Plugin descriptor to comply with basic plugin specification */
 | 
				
			||||||
 | 
					static const FlipperAppPluginDescriptor two_cities_plugin_descriptor = {
 | 
				
			||||||
 | 
					    .appid = NFC_SUPPORTED_CARD_PLUGIN_APP_ID,
 | 
				
			||||||
 | 
					    .ep_api_version = NFC_SUPPORTED_CARD_PLUGIN_API_VERSION,
 | 
				
			||||||
 | 
					    .entry_point = &two_cities_plugin,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/* Plugin entry point - must return a pointer to const descriptor  */
 | 
				
			||||||
 | 
					const FlipperAppPluginDescriptor* two_cities_plugin_ep() {
 | 
				
			||||||
 | 
					    return &two_cities_plugin_descriptor;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,72 +1,57 @@
 | 
				
			|||||||
ADD_SCENE(nfc, start, Start)
 | 
					ADD_SCENE(nfc, start, Start)
 | 
				
			||||||
ADD_SCENE(nfc, read, Read)
 | 
					ADD_SCENE(nfc, file_select, FileSelect)
 | 
				
			||||||
ADD_SCENE(nfc, saved_menu, SavedMenu)
 | 
					ADD_SCENE(nfc, saved_menu, SavedMenu)
 | 
				
			||||||
ADD_SCENE(nfc, extra_actions, ExtraActions)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, set_type, SetType)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, set_sak, SetSak)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, set_atqa, SetAtqa)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, set_uid, SetUid)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, generate_info, GenerateInfo)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, read_card_success, ReadCardSuccess)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, save_name, SaveName)
 | 
					ADD_SCENE(nfc, save_name, SaveName)
 | 
				
			||||||
ADD_SCENE(nfc, save_success, SaveSuccess)
 | 
					ADD_SCENE(nfc, save_success, SaveSuccess)
 | 
				
			||||||
ADD_SCENE(nfc, file_select, FileSelect)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, emulate_uid, EmulateUid)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, nfca_read_success, NfcaReadSuccess)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, nfca_menu, NfcaMenu)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, nfcv_menu, NfcVMenu)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, nfcv_unlock_menu, NfcVUnlockMenu)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, nfcv_key_input, NfcVKeyInput)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, nfcv_unlock, NfcVUnlock)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, nfcv_emulate, NfcVEmulate)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, nfcv_sniff, NfcVSniff)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, nfcv_read_success, NfcVReadSuccess)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_ultralight_read_success, MfUltralightReadSuccess)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_ultralight_data, MfUltralightData)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_ultralight_menu, MfUltralightMenu)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_ultralight_emulate, MfUltralightEmulate)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_ultralight_read_auth, MfUltralightReadAuth)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_ultralight_read_auth_result, MfUltralightReadAuthResult)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_ultralight_unlock_auto, MfUltralightUnlockAuto)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_desfire_read_success, MfDesfireReadSuccess)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_desfire_menu, MfDesfireMenu)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_desfire_data, MfDesfireData)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_read_success, MfClassicReadSuccess)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_data, MfClassicData)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_menu, MfClassicMenu)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_emulate, MfClassicEmulate)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_write, MfClassicWrite)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_write_success, MfClassicWriteSuccess)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_write_fail, MfClassicWriteFail)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_update, MfClassicUpdate)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_update_success, MfClassicUpdateSuccess)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, emv_read_success, EmvReadSuccess)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, emv_menu, EmvMenu)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, device_info, DeviceInfo)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, delete, Delete)
 | 
					ADD_SCENE(nfc, delete, Delete)
 | 
				
			||||||
ADD_SCENE(nfc, delete_success, DeleteSuccess)
 | 
					ADD_SCENE(nfc, delete_success, DeleteSuccess)
 | 
				
			||||||
ADD_SCENE(nfc, restore_original_confirm, RestoreOriginalConfirm)
 | 
					ADD_SCENE(nfc, restore_original_confirm, RestoreOriginalConfirm)
 | 
				
			||||||
ADD_SCENE(nfc, restore_original, RestoreOriginal)
 | 
					ADD_SCENE(nfc, restore_original, RestoreOriginal)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, detect, Detect)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, read, Read)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, info, Info)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, more_info, MoreInfo)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, supported_card, SupportedCard)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, select_protocol, SelectProtocol)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, extra_actions, ExtraActions)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, read_success, ReadSuccess)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, read_menu, ReadMenu)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, emulate, Emulate)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, rpc, Rpc)
 | 
				
			||||||
ADD_SCENE(nfc, debug, Debug)
 | 
					ADD_SCENE(nfc, debug, Debug)
 | 
				
			||||||
ADD_SCENE(nfc, field, Field)
 | 
					ADD_SCENE(nfc, field, Field)
 | 
				
			||||||
ADD_SCENE(nfc, dict_not_found, DictNotFound)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, rpc, Rpc)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, exit_confirm, ExitConfirm)
 | 
					 | 
				
			||||||
ADD_SCENE(nfc, retry_confirm, RetryConfirm)
 | 
					ADD_SCENE(nfc, retry_confirm, RetryConfirm)
 | 
				
			||||||
ADD_SCENE(nfc, detect_reader, DetectReader)
 | 
					ADD_SCENE(nfc, exit_confirm, ExitConfirm)
 | 
				
			||||||
ADD_SCENE(nfc, mfkey_nonces_info, MfkeyNoncesInfo)
 | 
					
 | 
				
			||||||
ADD_SCENE(nfc, mfkey_complete, MfkeyComplete)
 | 
					ADD_SCENE(nfc, mf_ultralight_unlock_menu, MfUltralightUnlockMenu)
 | 
				
			||||||
ADD_SCENE(nfc, nfc_data_info, NfcDataInfo)
 | 
					ADD_SCENE(nfc, mf_ultralight_unlock_warn, MfUltralightUnlockWarn)
 | 
				
			||||||
ADD_SCENE(nfc, read_card_type, ReadCardType)
 | 
					ADD_SCENE(nfc, mf_ultralight_key_input, MfUltralightKeyInput)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_ultralight_capture_pass, MfUltralightCapturePass)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_desfire_more_info, MfDesfireMoreInfo)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_desfire_app, MfDesfireApp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_detect_reader, MfClassicDetectReader)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_mfkey_nonces_info, MfClassicMfkeyNoncesInfo)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_mfkey_complete, MfClassicMfkeyComplete)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_update_initial, MfClassicUpdateInitial)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_update_initial_success, MfClassicUpdateInitialSuccess)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_write_initial, MfClassicWriteInitial)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_write_initial_success, MfClassicWriteInitialSuccess)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_write_initial_fail, MfClassicWriteInitialFail)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_keys, MfClassicKeys)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_keys_add, MfClassicKeysAdd)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, set_type, SetType)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, set_sak, SetSak)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, set_atqa, SetAtqa)
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, set_uid, SetUid)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					ADD_SCENE(nfc, generate_info, GenerateInfo)
 | 
				
			||||||
 | 
				
			|||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
#include "../nfc_i.h"
 | 
					#include "../nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum SubmenuDebugIndex {
 | 
					enum SubmenuDebugIndex {
 | 
				
			||||||
    SubmenuDebugIndexField,
 | 
					    SubmenuDebugIndexField,
 | 
				
			||||||
@ -6,29 +6,26 @@ enum SubmenuDebugIndex {
 | 
				
			|||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nfc_scene_debug_submenu_callback(void* context, uint32_t index) {
 | 
					void nfc_scene_debug_submenu_callback(void* context, uint32_t index) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
 | 
					    view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nfc_scene_debug_on_enter(void* context) {
 | 
					void nfc_scene_debug_on_enter(void* context) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
    Submenu* submenu = nfc->submenu;
 | 
					    Submenu* submenu = nfc->submenu;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    submenu_add_item(
 | 
					    submenu_add_item(
 | 
				
			||||||
        submenu, "Field", SubmenuDebugIndexField, nfc_scene_debug_submenu_callback, nfc);
 | 
					        submenu, "Field", SubmenuDebugIndexField, nfc_scene_debug_submenu_callback, nfc);
 | 
				
			||||||
    submenu_add_item(
 | 
					 | 
				
			||||||
        submenu, "Apdu", SubmenuDebugIndexApdu, nfc_scene_debug_submenu_callback, nfc);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    submenu_set_selected_item(
 | 
					    submenu_set_selected_item(
 | 
				
			||||||
        submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneDebug));
 | 
					        submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneDebug));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    nfc_device_clear(nfc->dev);
 | 
					 | 
				
			||||||
    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
 | 
					    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool nfc_scene_debug_on_event(void* context, SceneManagerEvent event) {
 | 
					bool nfc_scene_debug_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
    bool consumed = false;
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
@ -37,18 +34,13 @@ bool nfc_scene_debug_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
                nfc->scene_manager, NfcSceneDebug, SubmenuDebugIndexField);
 | 
					                nfc->scene_manager, NfcSceneDebug, SubmenuDebugIndexField);
 | 
				
			||||||
            scene_manager_next_scene(nfc->scene_manager, NfcSceneField);
 | 
					            scene_manager_next_scene(nfc->scene_manager, NfcSceneField);
 | 
				
			||||||
            consumed = true;
 | 
					            consumed = true;
 | 
				
			||||||
        } else if(event.event == SubmenuDebugIndexApdu) {
 | 
					 | 
				
			||||||
            scene_manager_set_scene_state(
 | 
					 | 
				
			||||||
                nfc->scene_manager, NfcSceneDebug, SubmenuDebugIndexApdu);
 | 
					 | 
				
			||||||
            scene_manager_next_scene(nfc->scene_manager, NfcSceneEmulateApduSequence);
 | 
					 | 
				
			||||||
            consumed = true;
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return consumed;
 | 
					    return consumed;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nfc_scene_debug_on_exit(void* context) {
 | 
					void nfc_scene_debug_on_exit(void* context) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    submenu_reset(nfc->submenu);
 | 
					    submenu_reset(nfc->submenu);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,21 +1,20 @@
 | 
				
			|||||||
#include "../nfc_i.h"
 | 
					#include "../nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nfc_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) {
 | 
					void nfc_scene_delete_widget_callback(GuiButtonType result, InputType type, void* context) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
    if(type == InputTypeShort) {
 | 
					    if(type == InputTypeShort) {
 | 
				
			||||||
        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
 | 
					        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nfc_scene_delete_on_enter(void* context) {
 | 
					void nfc_scene_delete_on_enter(void* context) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
    FuriHalNfcDevData* nfc_data = &nfc->dev->dev_data.nfc_data;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Setup Custom Widget view
 | 
					    // Setup Custom Widget view
 | 
				
			||||||
    FuriString* temp_str;
 | 
					    FuriString* temp_str;
 | 
				
			||||||
    temp_str = furi_string_alloc();
 | 
					    temp_str = furi_string_alloc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    furi_string_printf(temp_str, "\e#Delete %s?\e#", nfc->dev->dev_name);
 | 
					    furi_string_printf(temp_str, "\e#Delete %s?\e#", furi_string_get_cstr(nfc->file_name));
 | 
				
			||||||
    widget_add_text_box_element(
 | 
					    widget_add_text_box_element(
 | 
				
			||||||
        nfc->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, furi_string_get_cstr(temp_str), false);
 | 
					        nfc->widget, 0, 0, 128, 23, AlignCenter, AlignCenter, furi_string_get_cstr(temp_str), false);
 | 
				
			||||||
    widget_add_button_element(
 | 
					    widget_add_button_element(
 | 
				
			||||||
@ -23,47 +22,33 @@ void nfc_scene_delete_on_enter(void* context) {
 | 
				
			|||||||
    widget_add_button_element(
 | 
					    widget_add_button_element(
 | 
				
			||||||
        nfc->widget, GuiButtonTypeRight, "Delete", nfc_scene_delete_widget_callback, nfc);
 | 
					        nfc->widget, GuiButtonTypeRight, "Delete", nfc_scene_delete_widget_callback, nfc);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    size_t uid_len;
 | 
				
			||||||
 | 
					    const uint8_t* uid = nfc_device_get_uid(nfc->nfc_device, &uid_len);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    furi_string_set(temp_str, "UID:");
 | 
					    furi_string_set(temp_str, "UID:");
 | 
				
			||||||
    for(size_t i = 0; i < nfc_data->uid_len; i++) {
 | 
					    for(size_t i = 0; i < uid_len; i++) {
 | 
				
			||||||
        furi_string_cat_printf(temp_str, " %02X", nfc_data->uid[i]);
 | 
					        furi_string_cat_printf(temp_str, " %02X", uid[i]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    widget_add_string_element(
 | 
					    widget_add_string_element(
 | 
				
			||||||
        nfc->widget, 64, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str));
 | 
					        nfc->widget, 64, 24, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    NfcProtocol protocol = nfc->dev->dev_data.protocol;
 | 
					    furi_string_set_str(temp_str, nfc_device_get_name(nfc->nfc_device, NfcDeviceNameTypeFull));
 | 
				
			||||||
    const char* nfc_type = "NFC-A";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(protocol == NfcDeviceProtocolEMV) {
 | 
					 | 
				
			||||||
        furi_string_set(temp_str, "EMV bank card");
 | 
					 | 
				
			||||||
    } else if(protocol == NfcDeviceProtocolMifareUl) {
 | 
					 | 
				
			||||||
        furi_string_set(temp_str, nfc_mf_ul_type(nfc->dev->dev_data.mf_ul_data.type, true));
 | 
					 | 
				
			||||||
    } else if(protocol == NfcDeviceProtocolMifareClassic) {
 | 
					 | 
				
			||||||
        furi_string_set(temp_str, nfc_mf_classic_type(nfc->dev->dev_data.mf_classic_data.type));
 | 
					 | 
				
			||||||
    } else if(protocol == NfcDeviceProtocolMifareDesfire) {
 | 
					 | 
				
			||||||
        furi_string_set(temp_str, "MIFARE DESFire");
 | 
					 | 
				
			||||||
    } else if(protocol == NfcDeviceProtocolNfcV) {
 | 
					 | 
				
			||||||
        furi_string_set(temp_str, "ISO15693 tag");
 | 
					 | 
				
			||||||
        nfc_type = "NFC-V";
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        furi_string_set(temp_str, "Unknown ISO tag");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    widget_add_string_element(
 | 
					    widget_add_string_element(
 | 
				
			||||||
        nfc->widget, 64, 34, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str));
 | 
					        nfc->widget, 64, 34, AlignCenter, AlignTop, FontSecondary, furi_string_get_cstr(temp_str));
 | 
				
			||||||
    widget_add_string_element(nfc->widget, 64, 44, AlignCenter, AlignTop, FontSecondary, nfc_type);
 | 
					 | 
				
			||||||
    furi_string_free(temp_str);
 | 
					    furi_string_free(temp_str);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
 | 
					    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) {
 | 
					bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
    bool consumed = false;
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        if(event.event == GuiButtonTypeLeft) {
 | 
					        if(event.event == GuiButtonTypeLeft) {
 | 
				
			||||||
            consumed = scene_manager_previous_scene(nfc->scene_manager);
 | 
					            consumed = scene_manager_previous_scene(nfc->scene_manager);
 | 
				
			||||||
        } else if(event.event == GuiButtonTypeRight) {
 | 
					        } else if(event.event == GuiButtonTypeRight) {
 | 
				
			||||||
            if(nfc_device_delete(nfc->dev, true)) {
 | 
					            if(nfc_delete(nfc)) {
 | 
				
			||||||
                scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess);
 | 
					                scene_manager_next_scene(nfc->scene_manager, NfcSceneDeleteSuccess);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                scene_manager_search_and_switch_to_previous_scene(
 | 
					                scene_manager_search_and_switch_to_previous_scene(
 | 
				
			||||||
@ -76,7 +61,7 @@ bool nfc_scene_delete_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nfc_scene_delete_on_exit(void* context) {
 | 
					void nfc_scene_delete_on_exit(void* context) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    widget_reset(nfc->widget);
 | 
					    widget_reset(nfc->widget);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,12 +1,12 @@
 | 
				
			|||||||
#include "../nfc_i.h"
 | 
					#include "../nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nfc_scene_delete_success_popup_callback(void* context) {
 | 
					void nfc_scene_delete_success_popup_callback(void* context) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
 | 
					    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nfc_scene_delete_success_on_enter(void* context) {
 | 
					void nfc_scene_delete_success_on_enter(void* context) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Setup view
 | 
					    // Setup view
 | 
				
			||||||
    Popup* popup = nfc->popup;
 | 
					    Popup* popup = nfc->popup;
 | 
				
			||||||
@ -20,7 +20,7 @@ void nfc_scene_delete_success_on_enter(void* context) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
 | 
					bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
    bool consumed = false;
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
@ -38,7 +38,7 @@ bool nfc_scene_delete_success_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nfc_scene_delete_success_on_exit(void* context) {
 | 
					void nfc_scene_delete_success_on_exit(void* context) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Clear view
 | 
					    // Clear view
 | 
				
			||||||
    popup_reset(nfc->popup);
 | 
					    popup_reset(nfc->popup);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										59
									
								
								applications/main/nfc/scenes/nfc_scene_detect.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								applications/main/nfc/scenes/nfc_scene_detect.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,59 @@
 | 
				
			|||||||
 | 
					#include "../nfc_app_i.h"
 | 
				
			||||||
 | 
					#include <dolphin/dolphin.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_scene_detect_scan_callback(NfcScannerEvent event, void* context) {
 | 
				
			||||||
 | 
					    furi_assert(context);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == NfcScannerEventTypeDetected) {
 | 
				
			||||||
 | 
					        nfc_app_set_detected_protocols(instance, event.data.protocols, event.data.protocol_num);
 | 
				
			||||||
 | 
					        view_dispatcher_send_custom_event(instance->view_dispatcher, NfcCustomEventWorkerExit);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_scene_detect_on_enter(void* context) {
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Setup view
 | 
				
			||||||
 | 
					    popup_reset(instance->popup);
 | 
				
			||||||
 | 
					    popup_set_text(
 | 
				
			||||||
 | 
					        instance->popup, "Apply card to\nFlipper's back", 97, 24, AlignCenter, AlignTop);
 | 
				
			||||||
 | 
					    popup_set_icon(instance->popup, 0, 8, &I_NFC_manual_60x50);
 | 
				
			||||||
 | 
					    view_dispatcher_switch_to_view(instance->view_dispatcher, NfcViewPopup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_app_reset_detected_protocols(instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    instance->scanner = nfc_scanner_alloc(instance->nfc);
 | 
				
			||||||
 | 
					    nfc_scanner_start(instance->scanner, nfc_scene_detect_scan_callback, instance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_blink_detect_start(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_scene_detect_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
 | 
					        if(event.event == NfcCustomEventWorkerExit) {
 | 
				
			||||||
 | 
					            if(instance->protocols_detected_num > 1) {
 | 
				
			||||||
 | 
					                scene_manager_next_scene(instance->scene_manager, NfcSceneSelectProtocol);
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                scene_manager_next_scene(instance->scene_manager, NfcSceneRead);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            consumed = true;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return consumed;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_scene_detect_on_exit(void* context) {
 | 
				
			||||||
 | 
					    NfcApp* instance = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_scanner_stop(instance->scanner);
 | 
				
			||||||
 | 
					    nfc_scanner_free(instance->scanner);
 | 
				
			||||||
 | 
					    popup_reset(instance->popup);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    nfc_blink_stop(instance);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,103 +0,0 @@
 | 
				
			|||||||
#include "../nfc_i.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#define NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX (10U)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static const NotificationSequence sequence_detect_reader = {
 | 
					 | 
				
			||||||
    &message_green_255,
 | 
					 | 
				
			||||||
    &message_blue_255,
 | 
					 | 
				
			||||||
    &message_do_not_reset,
 | 
					 | 
				
			||||||
    NULL,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool nfc_detect_reader_worker_callback(NfcWorkerEvent event, void* context) {
 | 
					 | 
				
			||||||
    UNUSED(event);
 | 
					 | 
				
			||||||
    furi_assert(context);
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    view_dispatcher_send_custom_event(nfc->view_dispatcher, event);
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_detect_reader_callback(void* context) {
 | 
					 | 
				
			||||||
    furi_assert(context);
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_detect_reader_on_enter(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    detect_reader_set_callback(nfc->detect_reader, nfc_scene_detect_reader_callback, nfc);
 | 
					 | 
				
			||||||
    detect_reader_set_nonces_max(nfc->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX);
 | 
					 | 
				
			||||||
    NfcDeviceData* dev_data = &nfc->dev->dev_data;
 | 
					 | 
				
			||||||
    if(dev_data->nfc_data.uid_len) {
 | 
					 | 
				
			||||||
        detect_reader_set_uid(
 | 
					 | 
				
			||||||
            nfc->detect_reader, dev_data->nfc_data.uid, dev_data->nfc_data.uid_len);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Store number of collected nonces in scene state
 | 
					 | 
				
			||||||
    scene_manager_set_scene_state(nfc->scene_manager, NfcSceneDetectReader, 0);
 | 
					 | 
				
			||||||
    notification_message(nfc->notifications, &sequence_detect_reader);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    nfc_worker_start(
 | 
					 | 
				
			||||||
        nfc->worker,
 | 
					 | 
				
			||||||
        NfcWorkerStateAnalyzeReader,
 | 
					 | 
				
			||||||
        &nfc->dev->dev_data,
 | 
					 | 
				
			||||||
        nfc_detect_reader_worker_callback,
 | 
					 | 
				
			||||||
        nfc);
 | 
					 | 
				
			||||||
    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewDetectReader);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool nfc_scene_detect_reader_on_event(void* context, SceneManagerEvent event) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    bool consumed = false;
 | 
					 | 
				
			||||||
    uint32_t nonces_collected =
 | 
					 | 
				
			||||||
        scene_manager_get_scene_state(nfc->scene_manager, NfcSceneDetectReader);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					 | 
				
			||||||
        if(event.event == NfcCustomEventViewExit) {
 | 
					 | 
				
			||||||
            nfc_worker_stop(nfc->worker);
 | 
					 | 
				
			||||||
            scene_manager_next_scene(nfc->scene_manager, NfcSceneMfkeyNoncesInfo);
 | 
					 | 
				
			||||||
            consumed = true;
 | 
					 | 
				
			||||||
        } else if(event.event == NfcWorkerEventDetectReaderMfkeyCollected) {
 | 
					 | 
				
			||||||
            nonces_collected += 2;
 | 
					 | 
				
			||||||
            scene_manager_set_scene_state(
 | 
					 | 
				
			||||||
                nfc->scene_manager, NfcSceneDetectReader, nonces_collected);
 | 
					 | 
				
			||||||
            detect_reader_set_nonces_collected(nfc->detect_reader, nonces_collected);
 | 
					 | 
				
			||||||
            if(nonces_collected >= NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) {
 | 
					 | 
				
			||||||
                detect_reader_set_state(nfc->detect_reader, DetectReaderStateDone);
 | 
					 | 
				
			||||||
                nfc_blink_stop(nfc);
 | 
					 | 
				
			||||||
                notification_message(nfc->notifications, &sequence_single_vibro);
 | 
					 | 
				
			||||||
                notification_message(nfc->notifications, &sequence_set_green_255);
 | 
					 | 
				
			||||||
                nfc_worker_stop(nfc->worker);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            consumed = true;
 | 
					 | 
				
			||||||
        } else if(event.event == NfcWorkerEventDetectReaderDetected) {
 | 
					 | 
				
			||||||
            if(nonces_collected < NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) {
 | 
					 | 
				
			||||||
                notification_message(nfc->notifications, &sequence_blink_start_cyan);
 | 
					 | 
				
			||||||
                detect_reader_set_state(nfc->detect_reader, DetectReaderStateReaderDetected);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else if(event.event == NfcWorkerEventDetectReaderLost) {
 | 
					 | 
				
			||||||
            if(nonces_collected < NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX) {
 | 
					 | 
				
			||||||
                nfc_blink_stop(nfc);
 | 
					 | 
				
			||||||
                notification_message(nfc->notifications, &sequence_detect_reader);
 | 
					 | 
				
			||||||
                detect_reader_set_state(nfc->detect_reader, DetectReaderStateReaderLost);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return consumed;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_detect_reader_on_exit(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Stop worker
 | 
					 | 
				
			||||||
    nfc_worker_stop(nfc->worker);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Clear view
 | 
					 | 
				
			||||||
    detect_reader_reset(nfc->detect_reader);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Stop notifications
 | 
					 | 
				
			||||||
    nfc_blink_stop(nfc);
 | 
					 | 
				
			||||||
    notification_message(nfc->notifications, &sequence_reset_green);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,87 +0,0 @@
 | 
				
			|||||||
#include "../nfc_i.h"
 | 
					 | 
				
			||||||
#include "../helpers/nfc_emv_parser.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_device_info_widget_callback(GuiButtonType result, InputType type, void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    if(type == InputTypeShort) {
 | 
					 | 
				
			||||||
        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_device_info_on_enter(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    NfcDeviceData* dev_data = &nfc->dev->dev_data;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    FuriString* temp_str;
 | 
					 | 
				
			||||||
    temp_str = furi_string_alloc();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(dev_data->protocol == NfcDeviceProtocolEMV) {
 | 
					 | 
				
			||||||
        EmvData* emv_data = &dev_data->emv_data;
 | 
					 | 
				
			||||||
        furi_string_printf(temp_str, "\e#%s\n", emv_data->name);
 | 
					 | 
				
			||||||
        for(uint8_t i = 0; i < emv_data->number_len; i += 2) {
 | 
					 | 
				
			||||||
            furi_string_cat_printf(
 | 
					 | 
				
			||||||
                temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        furi_string_trim(temp_str);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Add expiration date
 | 
					 | 
				
			||||||
        if(emv_data->exp_mon) {
 | 
					 | 
				
			||||||
            furi_string_cat_printf(
 | 
					 | 
				
			||||||
                temp_str, "\nExp: %02X/%02X", emv_data->exp_mon, emv_data->exp_year);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // Parse currency code
 | 
					 | 
				
			||||||
        if((emv_data->currency_code)) {
 | 
					 | 
				
			||||||
            FuriString* currency_name;
 | 
					 | 
				
			||||||
            currency_name = furi_string_alloc();
 | 
					 | 
				
			||||||
            if(nfc_emv_parser_get_currency_name(
 | 
					 | 
				
			||||||
                   nfc->dev->storage, emv_data->currency_code, currency_name)) {
 | 
					 | 
				
			||||||
                furi_string_cat_printf(
 | 
					 | 
				
			||||||
                    temp_str, "\nCur: %s  ", furi_string_get_cstr(currency_name));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            furi_string_free(currency_name);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        // Parse country code
 | 
					 | 
				
			||||||
        if((emv_data->country_code)) {
 | 
					 | 
				
			||||||
            FuriString* country_name;
 | 
					 | 
				
			||||||
            country_name = furi_string_alloc();
 | 
					 | 
				
			||||||
            if(nfc_emv_parser_get_country_name(
 | 
					 | 
				
			||||||
                   nfc->dev->storage, emv_data->country_code, country_name)) {
 | 
					 | 
				
			||||||
                furi_string_cat_printf(temp_str, "Reg: %s", furi_string_get_cstr(country_name));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            furi_string_free(country_name);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    } else if(
 | 
					 | 
				
			||||||
        dev_data->protocol == NfcDeviceProtocolMifareClassic ||
 | 
					 | 
				
			||||||
        dev_data->protocol == NfcDeviceProtocolMifareDesfire ||
 | 
					 | 
				
			||||||
        dev_data->protocol == NfcDeviceProtocolMifareUl) {
 | 
					 | 
				
			||||||
        furi_string_set(temp_str, nfc->dev->dev_data.parsed_data);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
 | 
					 | 
				
			||||||
    furi_string_free(temp_str);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    widget_add_button_element(
 | 
					 | 
				
			||||||
        nfc->widget, GuiButtonTypeRight, "More", nfc_scene_device_info_widget_callback, nfc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool nfc_scene_device_info_on_event(void* context, SceneManagerEvent event) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    bool consumed = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					 | 
				
			||||||
        if(event.event == GuiButtonTypeRight) {
 | 
					 | 
				
			||||||
            scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo);
 | 
					 | 
				
			||||||
            consumed = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return consumed;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_device_info_on_exit(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Clear views
 | 
					 | 
				
			||||||
    widget_reset(nfc->widget);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,52 +0,0 @@
 | 
				
			|||||||
#include "../nfc_i.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_dict_not_found_popup_callback(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_dict_not_found_on_enter(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Setup view
 | 
					 | 
				
			||||||
    Popup* popup = nfc->popup;
 | 
					 | 
				
			||||||
    popup_set_text(
 | 
					 | 
				
			||||||
        popup,
 | 
					 | 
				
			||||||
        "Function requires\nan SD card with\nfresh databases.",
 | 
					 | 
				
			||||||
        82,
 | 
					 | 
				
			||||||
        24,
 | 
					 | 
				
			||||||
        AlignCenter,
 | 
					 | 
				
			||||||
        AlignCenter);
 | 
					 | 
				
			||||||
    popup_set_icon(popup, 6, 10, &I_SDQuestion_35x43);
 | 
					 | 
				
			||||||
    popup_set_timeout(popup, 2500);
 | 
					 | 
				
			||||||
    popup_set_context(popup, nfc);
 | 
					 | 
				
			||||||
    popup_set_callback(popup, nfc_scene_dict_not_found_popup_callback);
 | 
					 | 
				
			||||||
    popup_enable_timeout(popup);
 | 
					 | 
				
			||||||
    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool nfc_scene_dict_not_found_on_event(void* context, SceneManagerEvent event) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    bool consumed = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					 | 
				
			||||||
        if(event.event == NfcCustomEventViewExit) {
 | 
					 | 
				
			||||||
            if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneMfClassicKeys)) {
 | 
					 | 
				
			||||||
                consumed = scene_manager_search_and_switch_to_previous_scene(
 | 
					 | 
				
			||||||
                    nfc->scene_manager, NfcSceneMfClassicKeys);
 | 
					 | 
				
			||||||
            } else if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneExtraActions)) {
 | 
					 | 
				
			||||||
                consumed = scene_manager_search_and_switch_to_previous_scene(
 | 
					 | 
				
			||||||
                    nfc->scene_manager, NfcSceneExtraActions);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                consumed = scene_manager_search_and_switch_to_previous_scene(
 | 
					 | 
				
			||||||
                    nfc->scene_manager, NfcSceneStart);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return consumed;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_dict_not_found_on_exit(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    popup_reset(nfc->popup);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										13
									
								
								applications/main/nfc/scenes/nfc_scene_emulate.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								applications/main/nfc/scenes/nfc_scene_emulate.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					#include "../helpers/protocol_support/nfc_protocol_support.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_scene_emulate_on_enter(void* context) {
 | 
				
			||||||
 | 
					    nfc_protocol_support_on_enter(NfcProtocolSupportSceneEmulate, context);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					bool nfc_scene_emulate_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
 | 
					    return nfc_protocol_support_on_event(NfcProtocolSupportSceneEmulate, context, event);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					void nfc_scene_emulate_on_exit(void* context) {
 | 
				
			||||||
 | 
					    nfc_protocol_support_on_exit(NfcProtocolSupportSceneEmulate, context);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -1,34 +0,0 @@
 | 
				
			|||||||
#include "../nfc_i.h"
 | 
					 | 
				
			||||||
#include <core/common_defines.h>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_emulate_apdu_sequence_on_enter(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Setup view
 | 
					 | 
				
			||||||
    Popup* popup = nfc->popup;
 | 
					 | 
				
			||||||
    popup_set_header(popup, "Run APDU reader", 64, 31, AlignCenter, AlignTop);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Setup and start worker
 | 
					 | 
				
			||||||
    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup);
 | 
					 | 
				
			||||||
    nfc_worker_start(nfc->worker, NfcWorkerStateEmulateApdu, &nfc->dev->dev_data, NULL, nfc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    nfc_blink_emulate_start(nfc);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool nfc_scene_emulate_apdu_sequence_on_event(void* context, SceneManagerEvent event) {
 | 
					 | 
				
			||||||
    UNUSED(context);
 | 
					 | 
				
			||||||
    UNUSED(event);
 | 
					 | 
				
			||||||
    bool consumed = false;
 | 
					 | 
				
			||||||
    return consumed;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_emulate_apdu_sequence_on_exit(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Stop worker
 | 
					 | 
				
			||||||
    nfc_worker_stop(nfc->worker);
 | 
					 | 
				
			||||||
    // Clear view
 | 
					 | 
				
			||||||
    popup_reset(nfc->popup);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    nfc_blink_stop(nfc);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,144 +0,0 @@
 | 
				
			|||||||
#include "../nfc_i.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#define NFC_SCENE_EMULATE_UID_LOG_SIZE_MAX (200)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum {
 | 
					 | 
				
			||||||
    NfcSceneEmulateUidStateWidget,
 | 
					 | 
				
			||||||
    NfcSceneEmulateUidStateTextBox,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool nfc_emulate_uid_worker_callback(NfcWorkerEvent event, void* context) {
 | 
					 | 
				
			||||||
    UNUSED(event);
 | 
					 | 
				
			||||||
    furi_assert(context);
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventWorkerExit);
 | 
					 | 
				
			||||||
    return true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_emulate_uid_widget_callback(GuiButtonType result, InputType type, void* context) {
 | 
					 | 
				
			||||||
    furi_assert(context);
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    if(type == InputTypeShort) {
 | 
					 | 
				
			||||||
        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_emulate_uid_textbox_callback(void* context) {
 | 
					 | 
				
			||||||
    furi_assert(context);
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Add widget with device name or inform that data received
 | 
					 | 
				
			||||||
static void nfc_scene_emulate_uid_widget_config(Nfc* nfc, bool data_received) {
 | 
					 | 
				
			||||||
    FuriHalNfcDevData* data = &nfc->dev->dev_data.nfc_data;
 | 
					 | 
				
			||||||
    Widget* widget = nfc->widget;
 | 
					 | 
				
			||||||
    widget_reset(widget);
 | 
					 | 
				
			||||||
    FuriString* info_str;
 | 
					 | 
				
			||||||
    info_str = furi_string_alloc();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    widget_add_icon_element(widget, 0, 3, &I_NFC_dolphin_emulation_47x61);
 | 
					 | 
				
			||||||
    widget_add_string_element(widget, 57, 13, AlignLeft, AlignTop, FontPrimary, "Emulating UID");
 | 
					 | 
				
			||||||
    if(strcmp(nfc->dev->dev_name, "") != 0) {
 | 
					 | 
				
			||||||
        furi_string_printf(info_str, "%s", nfc->dev->dev_name);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        for(uint8_t i = 0; i < data->uid_len; i++) {
 | 
					 | 
				
			||||||
            furi_string_cat_printf(info_str, "%02X ", data->uid[i]);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    furi_string_trim(info_str);
 | 
					 | 
				
			||||||
    widget_add_text_box_element(
 | 
					 | 
				
			||||||
        widget, 57, 28, 67, 25, AlignCenter, AlignTop, furi_string_get_cstr(info_str), true);
 | 
					 | 
				
			||||||
    furi_string_free(info_str);
 | 
					 | 
				
			||||||
    if(data_received) {
 | 
					 | 
				
			||||||
        widget_add_button_element(
 | 
					 | 
				
			||||||
            widget, GuiButtonTypeCenter, "Log", nfc_scene_emulate_uid_widget_callback, nfc);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_emulate_uid_on_enter(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Setup Widget
 | 
					 | 
				
			||||||
    nfc_scene_emulate_uid_widget_config(nfc, false);
 | 
					 | 
				
			||||||
    // Setup TextBox
 | 
					 | 
				
			||||||
    TextBox* text_box = nfc->text_box;
 | 
					 | 
				
			||||||
    text_box_set_font(text_box, TextBoxFontHex);
 | 
					 | 
				
			||||||
    text_box_set_focus(text_box, TextBoxFocusEnd);
 | 
					 | 
				
			||||||
    furi_string_reset(nfc->text_box_store);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Set Widget state and view
 | 
					 | 
				
			||||||
    scene_manager_set_scene_state(
 | 
					 | 
				
			||||||
        nfc->scene_manager, NfcSceneEmulateUid, NfcSceneEmulateUidStateWidget);
 | 
					 | 
				
			||||||
    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
 | 
					 | 
				
			||||||
    // Start worker
 | 
					 | 
				
			||||||
    memset(&nfc->dev->dev_data.reader_data, 0, sizeof(NfcReaderRequestData));
 | 
					 | 
				
			||||||
    nfc_worker_start(
 | 
					 | 
				
			||||||
        nfc->worker,
 | 
					 | 
				
			||||||
        NfcWorkerStateUidEmulate,
 | 
					 | 
				
			||||||
        &nfc->dev->dev_data,
 | 
					 | 
				
			||||||
        nfc_emulate_uid_worker_callback,
 | 
					 | 
				
			||||||
        nfc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    nfc_blink_emulate_start(nfc);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool nfc_scene_emulate_uid_on_event(void* context, SceneManagerEvent event) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    NfcReaderRequestData* reader_data = &nfc->dev->dev_data.reader_data;
 | 
					 | 
				
			||||||
    uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmulateUid);
 | 
					 | 
				
			||||||
    bool consumed = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					 | 
				
			||||||
        if(event.event == NfcCustomEventWorkerExit) {
 | 
					 | 
				
			||||||
            // Add data button to widget if data is received for the first time
 | 
					 | 
				
			||||||
            if(!furi_string_size(nfc->text_box_store)) {
 | 
					 | 
				
			||||||
                nfc_scene_emulate_uid_widget_config(nfc, true);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            // Update TextBox data
 | 
					 | 
				
			||||||
            if(furi_string_size(nfc->text_box_store) < NFC_SCENE_EMULATE_UID_LOG_SIZE_MAX) {
 | 
					 | 
				
			||||||
                furi_string_cat_printf(nfc->text_box_store, "R:");
 | 
					 | 
				
			||||||
                for(uint16_t i = 0; i < reader_data->size; i++) {
 | 
					 | 
				
			||||||
                    furi_string_cat_printf(nfc->text_box_store, " %02X", reader_data->data[i]);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                furi_string_push_back(nfc->text_box_store, '\n');
 | 
					 | 
				
			||||||
                text_box_set_text(nfc->text_box, furi_string_get_cstr(nfc->text_box_store));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            memset(reader_data, 0, sizeof(NfcReaderRequestData));
 | 
					 | 
				
			||||||
            consumed = true;
 | 
					 | 
				
			||||||
        } else if(event.event == GuiButtonTypeCenter && state == NfcSceneEmulateUidStateWidget) {
 | 
					 | 
				
			||||||
            view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewTextBox);
 | 
					 | 
				
			||||||
            scene_manager_set_scene_state(
 | 
					 | 
				
			||||||
                nfc->scene_manager, NfcSceneEmulateUid, NfcSceneEmulateUidStateTextBox);
 | 
					 | 
				
			||||||
            consumed = true;
 | 
					 | 
				
			||||||
        } else if(event.event == NfcCustomEventViewExit && state == NfcSceneEmulateUidStateTextBox) {
 | 
					 | 
				
			||||||
            view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
 | 
					 | 
				
			||||||
            scene_manager_set_scene_state(
 | 
					 | 
				
			||||||
                nfc->scene_manager, NfcSceneEmulateUid, NfcSceneEmulateUidStateWidget);
 | 
					 | 
				
			||||||
            consumed = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    } else if(event.type == SceneManagerEventTypeBack) {
 | 
					 | 
				
			||||||
        if(state == NfcSceneEmulateUidStateTextBox) {
 | 
					 | 
				
			||||||
            view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
 | 
					 | 
				
			||||||
            scene_manager_set_scene_state(
 | 
					 | 
				
			||||||
                nfc->scene_manager, NfcSceneEmulateUid, NfcSceneEmulateUidStateWidget);
 | 
					 | 
				
			||||||
            consumed = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return consumed;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_emulate_uid_on_exit(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Stop worker
 | 
					 | 
				
			||||||
    nfc_worker_stop(nfc->worker);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Clear view
 | 
					 | 
				
			||||||
    widget_reset(nfc->widget);
 | 
					 | 
				
			||||||
    text_box_reset(nfc->text_box);
 | 
					 | 
				
			||||||
    furi_string_reset(nfc->text_box_store);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    nfc_blink_stop(nfc);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,46 +0,0 @@
 | 
				
			|||||||
#include "../nfc_i.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum SubmenuIndex {
 | 
					 | 
				
			||||||
    SubmenuIndexInfo,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_emv_menu_submenu_callback(void* context, uint32_t index) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    view_dispatcher_send_custom_event(nfc->view_dispatcher, index);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_emv_menu_on_enter(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    Submenu* submenu = nfc->submenu;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    submenu_add_item(submenu, "Info", SubmenuIndexInfo, nfc_scene_emv_menu_submenu_callback, nfc);
 | 
					 | 
				
			||||||
    submenu_set_selected_item(
 | 
					 | 
				
			||||||
        nfc->submenu, scene_manager_get_scene_state(nfc->scene_manager, NfcSceneEmvMenu));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewMenu);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool nfc_scene_emv_menu_on_event(void* context, SceneManagerEvent event) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    bool consumed = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					 | 
				
			||||||
        if(event.event == SubmenuIndexInfo) {
 | 
					 | 
				
			||||||
            scene_manager_next_scene(nfc->scene_manager, NfcSceneNfcDataInfo);
 | 
					 | 
				
			||||||
            consumed = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        scene_manager_set_scene_state(nfc->scene_manager, NfcSceneEmvMenu, event.event);
 | 
					 | 
				
			||||||
    } else if(event.type == SceneManagerEventTypeBack) {
 | 
					 | 
				
			||||||
        consumed = scene_manager_previous_scene(nfc->scene_manager);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return consumed;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_emv_menu_on_exit(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Clear view
 | 
					 | 
				
			||||||
    submenu_reset(nfc->submenu);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,113 +0,0 @@
 | 
				
			|||||||
#include "../nfc_i.h"
 | 
					 | 
				
			||||||
#include "../helpers/nfc_emv_parser.h"
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_emv_read_success_widget_callback(
 | 
					 | 
				
			||||||
    GuiButtonType result,
 | 
					 | 
				
			||||||
    InputType type,
 | 
					 | 
				
			||||||
    void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    if(type == InputTypeShort) {
 | 
					 | 
				
			||||||
        view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_emv_read_success_on_enter(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    EmvData* emv_data = &nfc->dev->dev_data.emv_data;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Setup Custom Widget view
 | 
					 | 
				
			||||||
    widget_add_button_element(
 | 
					 | 
				
			||||||
        nfc->widget, GuiButtonTypeLeft, "Retry", nfc_scene_emv_read_success_widget_callback, nfc);
 | 
					 | 
				
			||||||
    widget_add_button_element(
 | 
					 | 
				
			||||||
        nfc->widget, GuiButtonTypeRight, "More", nfc_scene_emv_read_success_widget_callback, nfc);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    FuriString* temp_str;
 | 
					 | 
				
			||||||
    if(emv_data->name[0] != '\0') {
 | 
					 | 
				
			||||||
        temp_str = furi_string_alloc_printf("\e#%s\n", emv_data->name);
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
        temp_str = furi_string_alloc_printf("\e#Unknown Bank Card\n");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if(emv_data->number_len) {
 | 
					 | 
				
			||||||
        for(uint8_t i = 0; i < emv_data->number_len; i += 2) {
 | 
					 | 
				
			||||||
            furi_string_cat_printf(
 | 
					 | 
				
			||||||
                temp_str, "%02X%02X ", emv_data->number[i], emv_data->number[i + 1]);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        furi_string_trim(temp_str);
 | 
					 | 
				
			||||||
    } else if(emv_data->aid_len) {
 | 
					 | 
				
			||||||
        furi_string_cat_printf(temp_str, "Can't parse data from app\n");
 | 
					 | 
				
			||||||
        // Parse AID name
 | 
					 | 
				
			||||||
        FuriString* aid_name;
 | 
					 | 
				
			||||||
        aid_name = furi_string_alloc();
 | 
					 | 
				
			||||||
        if(nfc_emv_parser_get_aid_name(
 | 
					 | 
				
			||||||
               nfc->dev->storage, emv_data->aid, emv_data->aid_len, aid_name)) {
 | 
					 | 
				
			||||||
            furi_string_cat_printf(temp_str, "AID: %s", furi_string_get_cstr(aid_name));
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            furi_string_cat_printf(temp_str, "AID: ");
 | 
					 | 
				
			||||||
            for(uint8_t i = 0; i < emv_data->aid_len; i++) {
 | 
					 | 
				
			||||||
                furi_string_cat_printf(temp_str, "%02X", emv_data->aid[i]);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        furi_string_free(aid_name);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Add expiration date
 | 
					 | 
				
			||||||
    if(emv_data->exp_mon) {
 | 
					 | 
				
			||||||
        furi_string_cat_printf(
 | 
					 | 
				
			||||||
            temp_str, "\nExp: %02X/%02X", emv_data->exp_mon, emv_data->exp_year);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // Parse currency code
 | 
					 | 
				
			||||||
    if((emv_data->currency_code)) {
 | 
					 | 
				
			||||||
        FuriString* currency_name;
 | 
					 | 
				
			||||||
        currency_name = furi_string_alloc();
 | 
					 | 
				
			||||||
        if(nfc_emv_parser_get_currency_name(
 | 
					 | 
				
			||||||
               nfc->dev->storage, emv_data->currency_code, currency_name)) {
 | 
					 | 
				
			||||||
            furi_string_cat_printf(temp_str, "\nCur: %s  ", furi_string_get_cstr(currency_name));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        furi_string_free(currency_name);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    // Parse country code
 | 
					 | 
				
			||||||
    if((emv_data->country_code)) {
 | 
					 | 
				
			||||||
        FuriString* country_name;
 | 
					 | 
				
			||||||
        country_name = furi_string_alloc();
 | 
					 | 
				
			||||||
        if(nfc_emv_parser_get_country_name(
 | 
					 | 
				
			||||||
               nfc->dev->storage, emv_data->country_code, country_name)) {
 | 
					 | 
				
			||||||
            furi_string_cat_printf(temp_str, "Reg: %s", furi_string_get_cstr(country_name));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        furi_string_free(country_name);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    notification_message_block(nfc->notifications, &sequence_set_green_255);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    widget_add_text_scroll_element(nfc->widget, 0, 0, 128, 52, furi_string_get_cstr(temp_str));
 | 
					 | 
				
			||||||
    furi_string_free(temp_str);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
bool nfc_scene_emv_read_success_on_event(void* context, SceneManagerEvent event) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
    bool consumed = false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					 | 
				
			||||||
        if(event.event == GuiButtonTypeLeft) {
 | 
					 | 
				
			||||||
            scene_manager_next_scene(nfc->scene_manager, NfcSceneRetryConfirm);
 | 
					 | 
				
			||||||
            consumed = true;
 | 
					 | 
				
			||||||
        } else if(event.event == GuiButtonTypeRight) {
 | 
					 | 
				
			||||||
            scene_manager_next_scene(nfc->scene_manager, NfcSceneEmvMenu);
 | 
					 | 
				
			||||||
            consumed = true;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    } else if(event.type == SceneManagerEventTypeBack) {
 | 
					 | 
				
			||||||
        scene_manager_next_scene(nfc->scene_manager, NfcSceneExitConfirm);
 | 
					 | 
				
			||||||
        consumed = true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return consumed;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
void nfc_scene_emv_read_success_on_exit(void* context) {
 | 
					 | 
				
			||||||
    Nfc* nfc = context;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    notification_message_block(nfc->notifications, &sequence_reset_green);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // Clear view
 | 
					 | 
				
			||||||
    widget_reset(nfc->widget);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,13 +1,13 @@
 | 
				
			|||||||
#include "../nfc_i.h"
 | 
					#include "../nfc_app_i.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nfc_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) {
 | 
					void nfc_scene_exit_confirm_dialog_callback(DialogExResult result, void* context) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
 | 
					    view_dispatcher_send_custom_event(nfc->view_dispatcher, result);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nfc_scene_exit_confirm_on_enter(void* context) {
 | 
					void nfc_scene_exit_confirm_on_enter(void* context) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
    DialogEx* dialog_ex = nfc->dialog_ex;
 | 
					    DialogEx* dialog_ex = nfc->dialog_ex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dialog_ex_set_left_button_text(dialog_ex, "Exit");
 | 
					    dialog_ex_set_left_button_text(dialog_ex, "Exit");
 | 
				
			||||||
@ -22,16 +22,16 @@ void nfc_scene_exit_confirm_on_enter(void* context) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
bool nfc_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) {
 | 
					bool nfc_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
    bool consumed = false;
 | 
					    bool consumed = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if(event.type == SceneManagerEventTypeCustom) {
 | 
					    if(event.type == SceneManagerEventTypeCustom) {
 | 
				
			||||||
        if(event.event == DialogExResultRight) {
 | 
					        if(event.event == DialogExResultRight) {
 | 
				
			||||||
            consumed = scene_manager_previous_scene(nfc->scene_manager);
 | 
					            consumed = scene_manager_previous_scene(nfc->scene_manager);
 | 
				
			||||||
        } else if(event.event == DialogExResultLeft) {
 | 
					        } else if(event.event == DialogExResultLeft) {
 | 
				
			||||||
            if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneReadCardType)) {
 | 
					            if(scene_manager_has_previous_scene(nfc->scene_manager, NfcSceneSelectProtocol)) {
 | 
				
			||||||
                consumed = scene_manager_search_and_switch_to_previous_scene(
 | 
					                consumed = scene_manager_search_and_switch_to_previous_scene(
 | 
				
			||||||
                    nfc->scene_manager, NfcSceneReadCardType);
 | 
					                    nfc->scene_manager, NfcSceneSelectProtocol);
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                consumed = scene_manager_search_and_switch_to_previous_scene(
 | 
					                consumed = scene_manager_search_and_switch_to_previous_scene(
 | 
				
			||||||
                    nfc->scene_manager, NfcSceneStart);
 | 
					                    nfc->scene_manager, NfcSceneStart);
 | 
				
			||||||
@ -45,7 +45,7 @@ bool nfc_scene_exit_confirm_on_event(void* context, SceneManagerEvent event) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void nfc_scene_exit_confirm_on_exit(void* context) {
 | 
					void nfc_scene_exit_confirm_on_exit(void* context) {
 | 
				
			||||||
    Nfc* nfc = context;
 | 
					    NfcApp* nfc = context;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Clean view
 | 
					    // Clean view
 | 
				
			||||||
    dialog_ex_reset(nfc->dialog_ex);
 | 
					    dialog_ex_reset(nfc->dialog_ex);
 | 
				
			||||||
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user