Просмотр исходного кода

changes

- manage keyfiles
- add basic ovc support
Luu 1 год назад
Родитель
Сommit
6420e3cd6e

+ 3 - 3
api/calypso/calypso_util.c

@@ -77,7 +77,7 @@ void free_calypso_structure(CalypsoApp* structure) {
     free(structure);
 }
 
-int* get_bit_positions(const char* binary_string, int* count) {
+int* get_bitmap_positions(const char* binary_string, int* count) {
     int length = strlen(binary_string);
     int* positions = malloc(length * sizeof(int));
     int pos_index = 0;
@@ -109,7 +109,7 @@ bool is_calypso_subnode_present(
     strncpy(bit_slice, binary_string, bitmap->size);
     bit_slice[bitmap->size] = '\0';
     int count = 0;
-    int* positions = get_bit_positions(bit_slice, &count);
+    int* positions = get_bitmap_positions(bit_slice, &count);
     int offset = bitmap->size;
     for(int i = 0; i < count; i++) {
         CalypsoElement* element = &bitmap->elements[positions[i]];
@@ -185,7 +185,7 @@ int get_calypso_subnode_offset(
         bit_slice[bitmap->size] = '\0';
 
         int count = 0;
-        int* positions = get_bit_positions(bit_slice, &count);
+        int* positions = get_bitmap_positions(bit_slice, &count);
         bool f = false;
 
         int count_offset = bitmap->size;

+ 1 - 1
api/calypso/calypso_util.h

@@ -81,7 +81,7 @@ CalypsoElement make_calypso_container_element(const char* key, int size, Calypso
 
 void free_calypso_structure(CalypsoApp* structure);
 
-int* get_bit_positions(const char* binary_string, int* count);
+int* get_bitmap_positions(const char* binary_string, int* count);
 
 int is_bit_present(int* positions, int count, int bit);
 

+ 71 - 6
metroflip.c

@@ -1,6 +1,12 @@
 
 #include "metroflip_i.h"
 
+struct MfClassicKeyCache {
+    MfClassicDeviceKeys keys;
+    MfClassicKeyType current_key_type;
+    uint8_t current_sector;
+};
+
 static bool metroflip_custom_event_callback(void* context, uint32_t event) {
     furi_assert(context);
     Metroflip* app = context;
@@ -192,11 +198,15 @@ uint8_t select_app[8] = {0x94, 0xA4, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00};
 
 uint8_t apdu_success[2] = {0x90, 0x00};
 
-int bit_slice_to_dec(const char* bit_representation, int start, int end) {
-    char bit_slice[end - start + 2];
+char* bit_slice(const char* bit_representation, int start, int end) {
+    static char bit_slice[32 * 8 + 1];
     strncpy(bit_slice, bit_representation + start, end - start + 1);
     bit_slice[end - start + 1] = '\0';
-    return binary_to_decimal(bit_slice);
+    return bit_slice;
+}
+
+int bit_slice_to_dec(const char* bit_representation, int start, int end) {
+    return binary_to_decimal(bit_slice(bit_representation, start, end));
 }
 
 extern int32_t metroflip(void* p) {
@@ -216,16 +226,24 @@ void dec_to_bits(char dec_representation, char* bit_representation) {
     }
 }
 
-KeyfileManager manage_keyfiles(char uid_str[]) {
+KeyfileManager manage_keyfiles(
+    char uid_str[],
+    const uint8_t* uid,
+    size_t uid_len,
+    MfClassicKeyCache* instance,
+    uint64_t key_mask_a_required,
+    uint64_t key_mask_b_required) {
     Storage* storage = furi_record_open(RECORD_STORAGE);
     File* source = storage_file_alloc(storage);
     char source_path[64];
+    UNUSED(key_mask_b_required);
 
     FURI_LOG_I("TAG", "%s", uid_str);
     size_t source_required_size =
         strlen("/ext/nfc/.cache/") + strlen(uid_str) + strlen(".keys") + 1;
     snprintf(source_path, source_required_size, "/ext/nfc/.cache/%s.keys", uid_str);
     bool cache_file = storage_file_open(source, source_path, FSAM_READ, FSOM_OPEN_EXISTING);
+
     /*-----------------Open assets cache file (if exists)------------*/
 
     File* dest = storage_file_alloc(storage);
@@ -234,7 +252,6 @@ KeyfileManager manage_keyfiles(char uid_str[]) {
         strlen("/ext/nfc/assets/.") + strlen(uid_str) + strlen(".keys") + 1;
     snprintf(dest_path, dest_required_size, "/ext/nfc/assets/.%s.keys", uid_str);
     bool dest_cache_file = storage_file_open(dest, dest_path, FSAM_READ, FSOM_OPEN_EXISTING);
-
     /*-----------------Check cache file------------*/
     if(!cache_file) {
         /*-----------------Check assets cache file------------*/
@@ -268,8 +285,15 @@ KeyfileManager manage_keyfiles(char uid_str[]) {
             return SUCCESSFUL;
         }
     } else {
+        FURI_LOG_I("TAG", "testing 1");
         size_t source_file_length = storage_file_size(source);
-        if(source_file_length > 1216) {
+        FURI_LOG_I("TAG", "testing 2");
+
+        storage_file_close(source);
+        mf_classic_key_cache_load(instance, uid, uid_len);
+
+        if(KEY_MASK_BIT_CHECK(key_mask_a_required, instance->keys.key_a_mask) &&
+           KEY_MASK_BIT_CHECK(key_mask_b_required, instance->keys.key_b_mask)) {
             FURI_LOG_I("TAG", "cache exist, creating assets cache if not already exists");
             storage_file_close(dest);
             storage_file_close(source);
@@ -296,3 +320,44 @@ KeyfileManager manage_keyfiles(char uid_str[]) {
     storage_file_close(source);
     storage_file_close(dest);
 }
+
+void uid_to_string(const uint8_t* uid, size_t uid_len, char* uid_str, size_t max_len) {
+    size_t pos = 0;
+
+    for(size_t i = 0; i < uid_len && pos + 2 < max_len; ++i) {
+        pos += snprintf(&uid_str[pos], max_len - pos, "%02X", uid[i]);
+    }
+
+    uid_str[pos] = '\0'; // Null-terminate the string
+}
+
+void handle_keyfile_case(
+    Metroflip* app,
+    const char* message_title,
+    const char* log_message,
+    FuriString* parsed_data,
+    char card_type[]) {
+    FURI_LOG_I(card_type, log_message);
+    dolphin_deed(DolphinDeedNfcReadSuccess);
+    furi_string_reset(parsed_data);
+
+    furi_string_printf(
+        parsed_data,
+        "\e#%s\n\n"
+        "To read a %s, \nyou need to read \nit in NFC "
+        "app on \nthe flipper, and it\nneeds to show \n32/32 keys and\n"
+        "16/16 sectors read\n"
+        "Here is a guide to \nfollow to read \nMIFARE Classic:\n"
+        "https://flipper.wiki/mifareclassic/\n"
+        "Once completed, Scan again\n\n",
+        message_title,
+        card_type);
+
+    widget_add_text_scroll_element(app->widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+    widget_add_button_element(
+        app->widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+
+    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+    metroflip_app_blink_stop(app);
+}

+ 21 - 2
metroflip_i.h

@@ -46,6 +46,8 @@ extern const Icon I_RFIDDolphinReceive_97x61;
 
 #include "api/calypso/calypso_i.h"
 
+#define KEY_MASK_BIT_CHECK(key_mask_1, key_mask_2) (((key_mask_1) & (key_mask_2)) == (key_mask_1))
+
 typedef struct {
     Gui* gui;
     SceneManager* scene_manager;
@@ -125,7 +127,13 @@ typedef enum {
     MISSING_KEYFILE
 } KeyfileManager;
 
-KeyfileManager manage_keyfiles(char uid_str[]);
+KeyfileManager manage_keyfiles(
+    char uid_str[],
+    const uint8_t* uid,
+    size_t uid_len,
+    MfClassicKeyCache* instance,
+    uint64_t key_mask_a_required,
+    uint64_t key_mask_b_required);
 
 void metroflip_app_blink_start(Metroflip* metroflip);
 void metroflip_app_blink_stop(Metroflip* metroflip);
@@ -138,7 +146,18 @@ void metroflip_app_blink_stop(Metroflip* metroflip);
 
 void metroflip_exit_widget_callback(GuiButtonType result, InputType type, void* context);
 
-///////////////////////////////// Calypso /////////////////////////////////
+void uid_to_string(const uint8_t* uid, size_t uid_len, char* uid_str, size_t max_len);
+
+void handle_keyfile_case(
+    Metroflip* app,
+    const char* message_title,
+    const char* log_message,
+    FuriString* parsed_data,
+    char card_type[]);
+
+char* bit_slice(const char* bit_representation, int start, int end);
+
+///////////////////////////////// Calypso / EN1545 /////////////////////////////////
 
 #define Metroflip_POLLER_MAX_BUFFER_SIZE 1024
 

+ 1 - 0
scenes/metroflip_scene_config.h

@@ -6,6 +6,7 @@ ADD_SCENE(metroflip, clipper, Clipper)
 ADD_SCENE(metroflip, metromoney, Metromoney)
 ADD_SCENE(metroflip, smartrider, Smartrider)
 ADD_SCENE(metroflip, read_success, ReadSuccess)
+ADD_SCENE(metroflip, ovc, OVC)
 ADD_SCENE(metroflip, bip, Bip)
 ADD_SCENE(metroflip, myki, Myki)
 ADD_SCENE(metroflip, troika, Troika)

+ 307 - 0
scenes/metroflip_scene_ovc.c

@@ -0,0 +1,307 @@
+
+#include <flipper_application.h>
+#include "../metroflip_i.h"
+
+#include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
+#include <nfc/protocols/mf_classic/mf_classic.h>
+#include <nfc/protocols/mf_classic/mf_classic_poller.h>
+
+#include <dolphin/dolphin.h>
+#include <bit_lib.h>
+#include <furi_hal.h>
+#include <nfc/nfc.h>
+#include <nfc/nfc_device.h>
+#include <nfc/nfc_listener.h>
+
+typedef struct {
+    int year;
+    int month;
+    int day;
+} OvcBcdDate;
+
+int bcd2int(int bcd) {
+    return ((bcd >> 4) * 10) + (bcd & 0x0F);
+}
+
+#define TAG "Metroflip:Scene:OVC"
+
+uint8_t ovc_sector_num = 0;
+
+OvcBcdDate* ovc_bcd_date_new(int x) {
+    OvcBcdDate* date = malloc(sizeof(OvcBcdDate));
+    if(!date) {
+        FURI_LOG_I(TAG, "Failed to allocate memory");
+        return NULL;
+    }
+
+    date->day = bcd2int((x >> 0) & 0xFF);
+    date->month = bcd2int((x >> 8) & 0xFF);
+    date->year = bcd2int((x >> 16) & 0xFFFF);
+
+    // Check if the year is valid
+    if(date->year == 0) {
+        free(date);
+        return NULL;
+    }
+
+    return date;
+}
+
+static bool ovc_parse(FuriString* parsed_data, const MfClassicData* data) {
+    bool parsed = false;
+
+    do {
+        // Parse data
+        //Card ID (UID in Dec)
+        size_t uid_len = 0;
+        const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
+        uint32_t cardID = bit_lib_bytes_to_num_le(uid, 4);
+        const MfClassicBlock* block1 = &data->block[1]; // basic info block
+        const MfClassicBlock* block2 = &data->block[2]; // basic info block
+        char bit_representation[32 * 8 + 1];
+        bit_representation[0] = '\0';
+        for(size_t i = 0; i < 32; i++) {
+            char bits[9];
+            uint8_t byte = 0;
+
+            if(i < 16) { // First block
+                byte = block1->data[i];
+            } else { // Second block
+                byte = block2->data[i - 16];
+            }
+
+            byte_to_binary(byte, bits);
+            strlcat(bit_representation, bits, sizeof(bit_representation));
+        }
+        bit_representation[32 * 8] = '\0';
+        FURI_LOG_I(TAG, "bit_repr %s", bit_representation);
+        int card_type = bit_slice_to_dec(bit_representation, 145, 151);
+        int days_from_epoch = bit_slice_to_dec(bit_representation, 95, 107);
+        FURI_LOG_I(TAG, "%d", days_from_epoch);
+        int seconds_with_epoch = (days_from_epoch * 3600 * 24) + epoch + 3600;
+
+        furi_string_printf(
+            parsed_data, "\e#Ov-Chipkaart\nCard ID: %lu\nCard Type: %d", cardID, card_type);
+        DateTime dt = {0};
+        datetime_timestamp_to_datetime(seconds_with_epoch, &dt);
+        furi_string_cat_printf(parsed_data, "\nEnd Validity:\n");
+        locale_format_datetime_cat(parsed_data, &dt, true);
+        furi_string_cat_printf(parsed_data, "\n\n");
+
+        const MfClassicBlock* block248 = &data->block[248];
+        memset(bit_representation, 0, sizeof(bit_representation));
+        for(size_t i = 0; i < 16; i++) {
+            char bits[9];
+            uint8_t byte = 0;
+            byte = block248->data[i];
+            byte_to_binary(byte, bits);
+            strlcat(bit_representation, bits, sizeof(bit_representation));
+        }
+        int first_3_bits = bit_slice_to_dec(bit_representation, 0, 3);
+        int credit_slot = (first_3_bits & 1) ? 250 : 249; // False is block 249 True is block 250
+        FURI_LOG_I(TAG, "credit block: %d", credit_slot);
+        const MfClassicBlock* credit_block = &data->block[credit_slot];
+        memset(bit_representation, 0, sizeof(bit_representation));
+        for(size_t i = 0; i < 16; i++) {
+            char bits[9];
+            uint8_t byte = 0;
+            byte = credit_block->data[i];
+            byte_to_binary(byte, bits);
+            strlcat(bit_representation, bits, sizeof(bit_representation));
+        }
+        // credit
+        float credit = bit_slice_to_dec(bit_representation, 78, 92) / 100.0;
+        FURI_LOG_I(TAG, "bit_repr block credit: %s", bit_representation);
+        FURI_LOG_I(TAG, "credit: %f", (double)credit);
+        furi_string_cat_printf(parsed_data, "Credit: %f", (double)credit);
+        // birthdate
+        OvcBcdDate* date = ovc_bcd_date_new(bit_slice_to_dec(bit_representation, 78, 92));
+        if(card_type == 2) {
+            furi_string_cat_printf(
+                parsed_data, "Date: %04d-%02d-%02d\n", date->year, date->month, date->day);
+        }
+        for(int sector = 32; sector < 35; sector++) {
+            memset(bit_representation, 0, sizeof(bit_representation));
+            char bit_representation[240 * 8 + 1]; // 15 x 16 bytes (the whole sector - sector trailer)
+            bit_representation[0] = '\0';
+            for(int block = (16 * (sector - 32) + 128); block < (16 * (sector - 32) + 143);
+                block++) {
+                const MfClassicBlock* current_block = &data->block[block];
+                for(size_t i = 0; i < 16; i++) {
+                    char bits[9];
+                    uint8_t byte = 0;
+                    byte = current_block->data[i];
+
+                    byte_to_binary(byte, bits);
+                    strlcat(bit_representation, bits, sizeof(bit_representation));
+                }
+            }
+            bit_representation[240 * 8] = '\0';
+            FURI_LOG_I(TAG, "sector %d bit repr: %s\n", sector, bit_representation);
+        }
+        parsed = true;
+    } while(false);
+
+    return parsed;
+}
+
+static NfcCommand metroflip_scene_ovc_poller_callback(NfcGenericEvent event, void* context) {
+    furi_assert(context);
+    furi_assert(event.event_data);
+    furi_assert(event.protocol == NfcProtocolMfClassic);
+
+    NfcCommand command = NfcCommandContinue;
+    const MfClassicPollerEvent* mfc_event = event.event_data;
+    Metroflip* app = context;
+    FuriString* parsed_data = furi_string_alloc();
+    Widget* widget = app->widget;
+
+    if(mfc_event->type == MfClassicPollerEventTypeCardDetected) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventCardDetected);
+        command = NfcCommandContinue;
+    } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventCardLost);
+
+        command = NfcCommandStop;
+    } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
+        mfc_event->data->poller_mode.mode = MfClassicPollerModeRead;
+        nfc_device_set_data(
+            app->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(app->poller));
+        nfc_device_get_data(app->nfc_device, NfcProtocolMfClassic);
+        size_t uid_len = 0;
+        const uint8_t* uid = nfc_device_get_uid(app->nfc_device, &uid_len);
+        /*-----------------All of this is to store a keyfile in a permanent way for the user to always access------------*/
+        /*-----------------Open cache file (if exists)------------*/
+
+        char uid_str[uid_len * 2 + 1];
+        uid_to_string(uid, uid_len, uid_str, sizeof(uid_str));
+        uint64_t ovc_key_mask_a_required =
+            1095220854785; // 1111111100000000010000000000000000000001 key mask
+        KeyfileManager manage =
+            manage_keyfiles(uid_str, uid, uid_len, app->mfc_key_cache, ovc_key_mask_a_required, 0);
+        char card_type[] = "OV-Chipkaart";
+        switch(manage) {
+        case MISSING_KEYFILE:
+            handle_keyfile_case(app, "No keys found", "Missing keyfile", parsed_data, card_type);
+            command = NfcCommandStop;
+            break;
+
+        case INCOMPLETE_KEYFILE:
+            handle_keyfile_case(
+                app, "Incomplete keyfile", "incomplete keyfile", parsed_data, card_type);
+            command = NfcCommandStop;
+            break;
+
+        case SUCCESSFUL:
+            FURI_LOG_I(TAG, "success");
+            break;
+        }
+    } else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) {
+        FURI_LOG_I(TAG, "sec_num: %d", ovc_sector_num);
+        MfClassicKey key = {};
+        MfClassicKeyType key_type = MfClassicKeyTypeA;
+        if(mf_classic_key_cache_get_next_key(
+               app->mfc_key_cache, &ovc_sector_num, &key, &key_type)) {
+            mfc_event->data->read_sector_request_data.sector_num = ovc_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(
+            app->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(app->poller));
+
+        dolphin_deed(DolphinDeedNfcReadSuccess);
+        furi_string_reset(app->text_box_store);
+        const MfClassicData* mfc_data = nfc_device_get_data(app->nfc_device, NfcProtocolMfClassic);
+        if(!ovc_parse(parsed_data, mfc_data)) {
+            FURI_LOG_I(TAG, "Unknown card type");
+            furi_string_printf(parsed_data, "\e#Unknown card\n");
+        }
+        widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+        widget_add_button_element(
+            widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+
+        furi_string_free(parsed_data);
+        view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+        metroflip_app_blink_stop(app);
+        UNUSED(ovc_parse);
+        command = NfcCommandStop;
+    } else if(mfc_event->type == MfClassicPollerEventTypeFail) {
+        FURI_LOG_I(TAG, "fail");
+        command = NfcCommandStop;
+    }
+
+    return command;
+}
+
+void metroflip_scene_ovc_on_enter(void* context) {
+    Metroflip* app = context;
+    dolphin_deed(DolphinDeedNfcRead);
+
+    mf_classic_key_cache_reset(app->mfc_key_cache);
+
+    // Setup view
+    Popup* popup = app->popup;
+    popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
+    popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+
+    // Start worker
+    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
+    nfc_scanner_alloc(app->nfc);
+    app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
+    nfc_poller_start(app->poller, metroflip_scene_ovc_poller_callback, app);
+
+    metroflip_app_blink_start(app);
+}
+
+bool metroflip_scene_ovc_on_event(void* context, SceneManagerEvent event) {
+    Metroflip* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == MetroflipCustomEventCardDetected) {
+            Popup* popup = app->popup;
+            popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == MetroflipCustomEventCardLost) {
+            Popup* popup = app->popup;
+            popup_set_header(popup, "Card \n lost", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == MetroflipCustomEventWrongCard) {
+            Popup* popup = app->popup;
+            popup_set_header(popup, "WRONG \n CARD", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == MetroflipCustomEventPollerFail) {
+            Popup* popup = app->popup;
+            popup_set_header(popup, "Failed", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == MetroflipCustomEventPollerSuccess) {
+            scene_manager_next_scene(app->scene_manager, MetroflipSceneReadSuccess);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+void metroflip_scene_ovc_on_exit(void* context) {
+    Metroflip* app = context;
+    widget_reset(app->widget);
+
+    if(app->poller) {
+        nfc_poller_stop(app->poller);
+        nfc_poller_free(app->poller);
+    }
+
+    // Clear view
+    popup_reset(app->popup);
+
+    metroflip_app_blink_stop(app);
+}

+ 13 - 46
scenes/metroflip_scene_smartrider.c

@@ -19,23 +19,13 @@
 #define MAX_BLOCKS          64
 #define MAX_DATE_ITERATIONS 366
 
-uint8_t sector_num = 0;
+uint8_t smartrider_sector_num = 0;
 
 static const uint8_t STANDARD_KEYS[3][6] = {
     {0x20, 0x31, 0xD1, 0xE5, 0x7A, 0x3B},
     {0x4C, 0xA6, 0x02, 0x9F, 0x94, 0x73},
     {0x19, 0x19, 0x53, 0x98, 0xE3, 0x2F}};
 
-void uid_to_string(const uint8_t* uid, size_t uid_len, char* uid_str, size_t max_len) {
-    size_t pos = 0;
-
-    for(size_t i = 0; i < uid_len && pos + 2 < max_len; ++i) {
-        pos += snprintf(&uid_str[pos], max_len - pos, "%02X", uid[i]);
-    }
-
-    uid_str[pos] = '\0'; // Null-terminate the string
-}
-
 typedef struct {
     uint32_t timestamp;
     uint16_t cost;
@@ -258,35 +248,6 @@ static bool smartrider_parse(const NfcDevice* device, FuriString* parsed_data) {
 
 // made with love by jay candel <3
 
-void handle_keyfile_case(
-    Metroflip* app,
-    const char* message_title,
-    const char* log_message,
-    FuriString* parsed_data) {
-    FURI_LOG_I(TAG, log_message);
-    dolphin_deed(DolphinDeedNfcReadSuccess);
-    furi_string_reset(parsed_data);
-
-    furi_string_printf(
-        parsed_data,
-        "\e#%s\n\n"
-        "To read a SmartRider, \nyou need to read \nit in NFC "
-        "app on \nthe flipper, and it\nneeds to show \n32/32 keys and\n"
-        "16/16 sectors read\n"
-        "Here is a guide to \nfollow to read \nMIFARE Classic:\n"
-        "https://flipper.wiki/mifareclassic/\n"
-        "Once completed, Scan again",
-        message_title);
-
-    widget_add_text_scroll_element(app->widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
-
-    widget_add_button_element(
-        app->widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
-
-    view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
-    metroflip_app_blink_stop(app);
-}
-
 static NfcCommand
     metroflip_scene_smartrider_poller_callback(NfcGenericEvent event, void* context) {
     furi_assert(context);
@@ -317,16 +278,21 @@ static NfcCommand
 
         char uid_str[uid_len * 2 + 1];
         uid_to_string(uid, uid_len, uid_str, sizeof(uid_str));
-        KeyfileManager manage = manage_keyfiles(uid_str);
+        uint64_t smartrider_key_mask_a_required = 12299; // 11000000001011
+        KeyfileManager manage = manage_keyfiles(
+            uid_str, uid, uid_len, app->mfc_key_cache, smartrider_key_mask_a_required, 0);
+
+        char card_type[] = "SmartRider";
 
         switch(manage) {
         case MISSING_KEYFILE:
-            handle_keyfile_case(app, "No keys found", "Missing keyfile", parsed_data);
+            handle_keyfile_case(app, "No keys found", "Missing keyfile", parsed_data, card_type);
             command = NfcCommandStop;
             break;
 
         case INCOMPLETE_KEYFILE:
-            handle_keyfile_case(app, "Incomplete keyfile", "incomplete keyfile", parsed_data);
+            handle_keyfile_case(
+                app, "Incomplete keyfile", "incomplete keyfile", parsed_data, card_type);
             command = NfcCommandStop;
             break;
 
@@ -336,11 +302,12 @@ static NfcCommand
             break;
         }
     } else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) {
-        FURI_LOG_I(TAG, "sec_num: %d", sector_num);
+        FURI_LOG_I(TAG, "sec_num: %d", smartrider_sector_num);
         MfClassicKey key = {};
         MfClassicKeyType key_type = MfClassicKeyTypeA;
-        if(mf_classic_key_cache_get_next_key(app->mfc_key_cache, &sector_num, &key, &key_type)) {
-            mfc_event->data->read_sector_request_data.sector_num = sector_num;
+        if(mf_classic_key_cache_get_next_key(
+               app->mfc_key_cache, &smartrider_sector_num, &key, &key_type)) {
+            mfc_event->data->read_sector_request_data.sector_num = smartrider_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;

+ 3 - 0
scenes/metroflip_scene_start.c

@@ -34,6 +34,9 @@ void metroflip_scene_start_on_enter(void* context) {
         metroflip_scene_start_submenu_callback,
         app);
 
+    submenu_add_item(
+        submenu, "OV-Chipkaart", MetroflipSceneOVC, metroflip_scene_start_submenu_callback, app);
+
     submenu_add_item(
         submenu, "myki", MetroflipSceneMyki, metroflip_scene_start_submenu_callback, app);