Merge remote-tracking branch 'origin/release-candidate' into release

This commit is contained in:
Aleksandr Kutuzov 2023-11-01 15:48:39 +09:00
commit e487bcd2f8
544 changed files with 41720 additions and 68223 deletions

1
.github/CODEOWNERS vendored
View File

@ -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

View File

@ -11,6 +11,7 @@ on:
env: env:
DEFAULT_TARGET: f7 DEFAULT_TARGET: f7
FBT_TOOLCHAIN_PATH: /runner/_work FBT_TOOLCHAIN_PATH: /runner/_work
FBT_GIT_SUBMODULE_SHALLOW: 1
jobs: jobs:
main: main:

View File

@ -5,6 +5,7 @@ on:
env: env:
FBT_TOOLCHAIN_PATH: /runner/_work FBT_TOOLCHAIN_PATH: /runner/_work
FBT_GIT_SUBMODULE_SHALLOW: 1
jobs: jobs:
compact: compact:

View File

@ -10,6 +10,7 @@ env:
TARGETS: f7 TARGETS: f7
DEFAULT_TARGET: f7 DEFAULT_TARGET: f7
FBT_TOOLCHAIN_PATH: /runner/_work FBT_TOOLCHAIN_PATH: /runner/_work
FBT_GIT_SUBMODULE_SHALLOW: 1
jobs: jobs:
analyse_c_cpp: analyse_c_cpp:

View File

@ -7,6 +7,7 @@ env:
TARGETS: f7 TARGETS: f7
DEFAULT_TARGET: f7 DEFAULT_TARGET: f7
FBT_TOOLCHAIN_PATH: /opt FBT_TOOLCHAIN_PATH: /opt
FBT_GIT_SUBMODULE_SHALLOW: 1
jobs: jobs:
run_units_on_bench: run_units_on_bench:
@ -31,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
@ -65,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

View File

@ -7,6 +7,7 @@ env:
TARGETS: f7 TARGETS: f7
DEFAULT_TARGET: f7 DEFAULT_TARGET: f7
FBT_TOOLCHAIN_PATH: /opt FBT_TOOLCHAIN_PATH: /opt
FBT_GIT_SUBMODULE_SHALLOW: 1
jobs: jobs:
test_updater_on_bench: test_updater_on_bench:

View File

@ -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

View 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

View File

@ -290,7 +290,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil
return delay_val; return delay_val;
} else if(delay_val < 0) { // Script error } else if(delay_val < 0) { // Script error
bad_usb->st.error_line = bad_usb->st.line_cur - 1; bad_usb->st.error_line = bad_usb->st.line_cur - 1;
FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur - 1U); FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_usb->st.line_cur - 1U);
return SCRIPT_STATE_ERROR; return SCRIPT_STATE_ERROR;
} else { } else {
return (delay_val + bad_usb->defdelay); return (delay_val + bad_usb->defdelay);
@ -329,7 +329,7 @@ static int32_t ducky_script_execute_next(BadUsbScript* bad_usb, File* script_fil
return delay_val; return delay_val;
} else if(delay_val < 0) { } else if(delay_val < 0) {
bad_usb->st.error_line = bad_usb->st.line_cur; bad_usb->st.error_line = bad_usb->st.line_cur;
FURI_LOG_E(WORKER_TAG, "Unknown command at line %u", bad_usb->st.line_cur); FURI_LOG_E(WORKER_TAG, "Unknown command at line %zu", bad_usb->st.line_cur);
return SCRIPT_STATE_ERROR; return SCRIPT_STATE_ERROR;
} else { } else {
return (delay_val + bad_usb->defdelay); return (delay_val + bad_usb->defdelay);

View File

@ -24,10 +24,10 @@ typedef enum {
typedef struct { typedef struct {
BadUsbWorkerState state; BadUsbWorkerState state;
uint16_t line_cur; size_t line_cur;
uint16_t line_nb; size_t line_nb;
uint32_t delay_remain; uint32_t delay_remain;
uint16_t error_line; size_t error_line;
char error[64]; char error[64];
} BadUsbState; } BadUsbState;

View File

@ -82,7 +82,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
canvas_set_font(canvas, FontPrimary); canvas_set_font(canvas, FontPrimary);
canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:"); canvas_draw_str_aligned(canvas, 127, 33, AlignRight, AlignBottom, "ERROR:");
canvas_set_font(canvas, FontSecondary); canvas_set_font(canvas, FontSecondary);
furi_string_printf(disp_str, "line %u", model->state.error_line); furi_string_printf(disp_str, "line %zu", model->state.error_line);
canvas_draw_str_aligned( canvas_draw_str_aligned(
canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); canvas, 127, 46, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
furi_string_reset(disp_str); furi_string_reset(disp_str);
@ -105,7 +105,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
} }
canvas_set_font(canvas, FontBigNumbers); canvas_set_font(canvas, FontBigNumbers);
furi_string_printf( furi_string_printf(
disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
canvas_draw_str_aligned( canvas_draw_str_aligned(
canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
furi_string_reset(disp_str); furi_string_reset(disp_str);
@ -124,7 +124,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
} }
canvas_set_font(canvas, FontBigNumbers); canvas_set_font(canvas, FontBigNumbers);
furi_string_printf( furi_string_printf(
disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
canvas_draw_str_aligned( canvas_draw_str_aligned(
canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
furi_string_reset(disp_str); furi_string_reset(disp_str);
@ -142,7 +142,7 @@ static void bad_usb_draw_callback(Canvas* canvas, void* _model) {
} }
canvas_set_font(canvas, FontBigNumbers); canvas_set_font(canvas, FontBigNumbers);
furi_string_printf( furi_string_printf(
disp_str, "%u", ((model->state.line_cur - 1) * 100) / model->state.line_nb); disp_str, "%zu", ((model->state.line_cur - 1) * 100) / model->state.line_nb);
canvas_draw_str_aligned( canvas_draw_str_aligned(
canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str)); canvas, 114, 40, AlignRight, AlignBottom, furi_string_get_cstr(disp_str));
furi_string_reset(disp_str); furi_string_reset(disp_str);

View File

@ -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"],

View 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;
}

View 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

View 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;
}

View 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

View 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;
}

View 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

View 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);
}
}

View 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

View File

@ -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;

View 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;
}

View 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

View 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,
},
};

View File

@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_felica;

View File

@ -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]);
}
}
}

View File

@ -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);

View File

@ -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,
},
};

View File

@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_3a;

View File

@ -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);
}

View File

@ -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);

View File

@ -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,
},
};

View File

@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_3b;

View File

@ -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);

View File

@ -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]);
}
}

View File

@ -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);

View File

@ -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,
},
};

View File

@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_4a;

View File

@ -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);

View File

@ -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);
}

View File

@ -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);

View File

@ -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,
},
};

View File

@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_iso14443_4b;

View File

@ -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);
}

View File

@ -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);

View File

@ -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,
},
};

View File

@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_iso15693_3;

View File

@ -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);
}
}
}

View File

@ -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);

View File

@ -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, &sector_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,
},
};

View File

@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_mf_classic;

View File

@ -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, &sectors_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]);
}
}
}

View File

@ -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);

View File

@ -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,
},
};

View File

@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_mf_desfire;

View File

@ -0,0 +1,266 @@
#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;
const uint32_t total_size = simple_array_get_count(data->data);
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:
record_size = MF_DESFIRE_VALUE_SIZE;
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++) {
const uint32_t size_offset = rec * record_size;
const uint32_t size_remaining = total_size > size_offset ? total_size - size_offset : 0;
if(size_remaining < record_size) {
furi_string_cat_printf(
str, "record %lu (partial %lu of %lu)\n", rec, size_remaining, record_size);
record_size = size_remaining;
} else {
furi_string_cat_printf(str, "record %lu\n", rec);
}
for(uint32_t 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');
}
}

View File

@ -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);

View File

@ -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,
},
};

View File

@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_mf_ultralight;

View File

@ -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]);
}
}
}

View File

@ -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);

View File

@ -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,
},
};

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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 */
};

View File

@ -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[];

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View 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,
},
};

View File

@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_slix;

View File

@ -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);
}

View File

@ -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);

View File

@ -0,0 +1,108 @@
#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_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,
},
};

View File

@ -0,0 +1,5 @@
#pragma once
#include "../nfc_protocol_support_base.h"
extern const NfcProtocolSupportBase nfc_protocol_support_st25tb;

View File

@ -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]);
}
}
}

View File

@ -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);

View File

@ -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;
}

View File

@ -1,3 +0,0 @@
#pragma once
typedef struct Nfc Nfc;

View 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;
}

View 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;

View 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);

View File

@ -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);

View File

@ -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);

View 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;
}

View 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;
}

View File

@ -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;

View 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, &timestamp);
// 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, &timestamp, locale_get_date_format(), "-");
furi_string_cat(parsed_data, timestamp_str);
furi_string_cat(parsed_data, " at ");
locale_format_time(timestamp_str, &timestamp, 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;
}

View 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;
}

View 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;
}

View 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;
}

View File

@ -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)

View File

@ -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);
} }

View File

@ -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);
} }

View File

@ -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);

View 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);
}

View File

@ -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);
}

View File

@ -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);
}

Some files were not shown because too many files have changed in this diff Show More