Luu 1 год назад
Родитель
Сommit
9b527bafed
8 измененных файлов с 654 добавлено и 61 удалено
  1. 3 1
      CHANGELOG.md
  2. 58 0
      metroflip.c
  3. 22 1
      metroflip_i.h
  4. 1 0
      scenes/metroflip_scene_config.h
  5. 384 0
      scenes/metroflip_scene_navigo.c
  6. 6 59
      scenes/metroflip_scene_ravkav.c
  7. 3 0
      scenes/metroflip_scene_start.c
  8. 177 0
      scenes/navigo.h

+ 3 - 1
CHANGELOG.md

@@ -4,4 +4,6 @@
 
 
 ## v0.2
 ## v0.2
 
 
-- Update Rav-Kav parsing to show more data such as transaction logs
+- Update Rav-Kav parsing to show more data such as transaction logs
+- Add Navigo parser!
+- Bug fixes

+ 58 - 0
metroflip.c

@@ -126,6 +126,64 @@ void metroflip_app_blink_stop(Metroflip* metroflip) {
     notification_message(metroflip->notifications, &metroflip_app_sequence_blink_stop);
     notification_message(metroflip->notifications, &metroflip_app_sequence_blink_stop);
 }
 }
 
 
+// Calypso
+
+void byte_to_binary(uint8_t byte, char* bits) {
+    for(int i = 7; i >= 0; i--) {
+        bits[7 - i] = (byte & (1 << i)) ? '1' : '0';
+    }
+    bits[8] = '\0';
+}
+
+int binary_to_decimal(const char binary[]) {
+    int decimal = 0;
+    int length = strlen(binary);
+
+    for(int i = 0; i < length; i++) {
+        decimal = decimal * 2 + (binary[i] - '0');
+    }
+
+    return decimal;
+}
+
+void locale_format_datetime_cat(FuriString* out, const DateTime* dt, bool time) {
+    // helper to print datetimes
+    FuriString* s = furi_string_alloc();
+
+    LocaleDateFormat date_format = locale_get_date_format();
+    const char* separator = (date_format == LocaleDateFormatDMY) ? "." : "/";
+    locale_format_date(s, dt, date_format, separator);
+    furi_string_cat(out, s);
+    if (time) {
+        locale_format_time(s, dt, locale_get_time_format(), false);
+        furi_string_cat_printf(out, "  ");
+        furi_string_cat(out, s);
+    }
+
+    furi_string_free(s);
+}
+
+// read file
+uint8_t read_file[5] = {0x94, 0xb2, 0x01, 0x04, 0x1D};
+//                                 ^^^
+//                                 |||
+//                                 FID
+
+// select app
+uint8_t select_app[8] = {0x94, 0xA4, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00};
+//                                                    ^^^^^^^^^
+//                                                    |||||||||
+//                                                    AID: 20XX
+
+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];
+    strncpy(bit_slice, bit_representation + start, end - start + 1);
+    bit_slice[end - start + 1] = '\0';
+    return binary_to_decimal(bit_slice);
+}
+
 extern int32_t metroflip(void* p) {
 extern int32_t metroflip(void* p) {
     UNUSED(p);
     UNUSED(p);
     Metroflip* app = metroflip_alloc();
     Metroflip* app = metroflip_alloc();

+ 22 - 1
metroflip_i.h

@@ -31,6 +31,9 @@ extern const Icon I_RFIDDolphinReceive_97x61;
 #include <lib/nfc/nfc.h>
 #include <lib/nfc/nfc.h>
 #include <nfc/nfc_poller.h>
 #include <nfc/nfc_poller.h>
 #include <nfc/nfc_scanner.h>
 #include <nfc/nfc_scanner.h>
+#include <datetime.h>
+#include <dolphin/dolphin.h>
+#include <locale/locale.h>
 
 
 #include <flipper_application/flipper_application.h>
 #include <flipper_application/flipper_application.h>
 #include <loader/firmware_api/firmware_api.h>
 #include <loader/firmware_api/firmware_api.h>
@@ -114,4 +117,22 @@ void metroflip_app_blink_stop(Metroflip* metroflip);
 #define submenu_add_lockable_item(                                             \
 #define submenu_add_lockable_item(                                             \
     submenu, label, index, callback, callback_context, locked, locked_message) \
     submenu, label, index, callback, callback_context, locked, locked_message) \
     if(!(locked)) submenu_add_item(submenu, label, index, callback, callback_context)
     if(!(locked)) submenu_add_item(submenu, label, index, callback, callback_context)
-#endif
+#endif
+
+// Calypso
+
+#define Metroflip_POLLER_MAX_BUFFER_SIZE 1024
+
+#define epoch 852073200
+
+void locale_format_datetime_cat(FuriString* out, const DateTime* dt, bool time);
+
+extern uint8_t read_file[5];
+extern uint8_t apdu_success[2];
+extern uint8_t select_app[8];
+
+void byte_to_binary(uint8_t byte, char* bits);
+
+int binary_to_decimal(const char binary[]);
+
+int bit_slice_to_dec(const char* bit_representation, int start, int end);

+ 1 - 0
scenes/metroflip_scene_config.h

@@ -1,5 +1,6 @@
 ADD_SCENE(metroflip, start, Start)
 ADD_SCENE(metroflip, start, Start)
 ADD_SCENE(metroflip, ravkav, RavKav)
 ADD_SCENE(metroflip, ravkav, RavKav)
+ADD_SCENE(metroflip, navigo, Navigo)
 ADD_SCENE(metroflip, charliecard, CharlieCard)
 ADD_SCENE(metroflip, charliecard, CharlieCard)
 ADD_SCENE(metroflip, metromoney, Metromoney)
 ADD_SCENE(metroflip, metromoney, Metromoney)
 ADD_SCENE(metroflip, read_success, ReadSuccess)
 ADD_SCENE(metroflip, read_success, ReadSuccess)

+ 384 - 0
scenes/metroflip_scene_navigo.c

@@ -0,0 +1,384 @@
+#include "../metroflip_i.h"
+#include <datetime.h>
+#include <dolphin/dolphin.h>
+#include <locale/locale.h>
+#include "navigo.h"
+
+#include <nfc/protocols/iso14443_4b/iso14443_4b_poller.h>
+
+#define TAG "Metroflip:Scene:Navigo"
+
+void metroflip_navigo_widget_callback(
+    GuiButtonType result,
+    InputType type,
+    void* context) {
+    Metroflip* app = context;
+    UNUSED(result);
+
+    if(type == InputTypeShort) {
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+    }
+}
+
+static NfcCommand metroflip_scene_navigo_poller_callback(NfcGenericEvent event, void* context) {
+    furi_assert(event.protocol == NfcProtocolIso14443_4b);
+    NfcCommand next_command = NfcCommandContinue;
+    MetroflipPollerEventType stage = MetroflipPollerEventTypeStart;
+
+    Metroflip* app = context;
+    FuriString* parsed_data = furi_string_alloc();
+    Widget* widget = app->widget;
+    furi_string_reset(app->text_box_store);
+
+    const Iso14443_4bPollerEvent* iso14443_4b_event = event.event_data;
+
+    Iso14443_4bPoller* iso14443_4b_poller = event.instance;
+
+    BitBuffer* tx_buffer = bit_buffer_alloc(Metroflip_POLLER_MAX_BUFFER_SIZE);
+    BitBuffer* rx_buffer = bit_buffer_alloc(Metroflip_POLLER_MAX_BUFFER_SIZE);
+
+    if(iso14443_4b_event->type == Iso14443_4bPollerEventTypeReady) {
+        if(stage == MetroflipPollerEventTypeStart) {
+            nfc_device_set_data(
+                app->nfc_device, NfcProtocolIso14443_4b, nfc_poller_get_data(app->poller));
+
+            Iso14443_4bError error;
+            size_t response_length = 0;
+
+            do {
+
+                // Select app for contracts
+                select_app[6] = 32;
+                bit_buffer_reset(tx_buffer);
+                bit_buffer_append_bytes(tx_buffer, select_app, sizeof(select_app));
+                error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
+                if(error != Iso14443_4bErrorNone) {
+                    FURI_LOG_I(TAG, "Select File: iso14443_4b_poller_send_block error %d", error);
+                    stage = MetroflipPollerEventTypeFail;
+                    view_dispatcher_send_custom_event(
+                        app->view_dispatcher, MetroflipCustomEventPollerFail);
+                    break;
+                }
+
+                // Check the response after selecting app
+                response_length = bit_buffer_get_size_bytes(rx_buffer);
+                if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
+                   bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
+                    FURI_LOG_I(
+                        TAG,
+                        "Select profile app failed: %02x%02x",
+                        bit_buffer_get_byte(rx_buffer, response_length - 2),
+                        bit_buffer_get_byte(rx_buffer, response_length - 1));
+                    stage = MetroflipPollerEventTypeFail;
+                    view_dispatcher_send_custom_event(
+                        app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
+                    break;
+                }
+
+
+                // read file 1
+                read_file[2] = 1;
+                bit_buffer_reset(tx_buffer);
+                bit_buffer_append_bytes(tx_buffer, read_file, sizeof(read_file));
+                error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
+                if(error != Iso14443_4bErrorNone) {
+                    FURI_LOG_I(TAG, "Read File: iso14443_4b_poller_send_block error %d", error);
+                    stage = MetroflipPollerEventTypeFail;
+                    view_dispatcher_send_custom_event(
+                        app->view_dispatcher, MetroflipCustomEventPollerFail);
+                    break;
+                }
+
+                // Check the response after reading the file
+                response_length = bit_buffer_get_size_bytes(rx_buffer);
+                if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
+                   bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
+                    FURI_LOG_I(
+                        TAG,
+                        "Read file failed: %02x%02x",
+                        bit_buffer_get_byte(rx_buffer, response_length - 2),
+                        bit_buffer_get_byte(rx_buffer, response_length - 1));
+                    stage = MetroflipPollerEventTypeFail;
+                    view_dispatcher_send_custom_event(
+                        app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
+                    break;
+                }
+                char bit_representation
+                    [response_length * 8 + 1]; 
+                bit_representation[0] = '\0'; 
+                for(size_t i = 0; i < response_length; i++) {
+                    char bits[9]; 
+                    uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
+                    byte_to_binary(byte, bits);
+                    strcat(bit_representation, bits); 
+                }
+                int start = 55, end = 70;
+                float decimal_value = bit_slice_to_dec(bit_representation, start, end);
+                float balance = decimal_value / 100;
+                furi_string_printf(parsed_data, "\e#Navigo:\n\n");
+                furi_string_cat_printf(parsed_data, "\e#Contract 1:\n");
+                furi_string_cat_printf(parsed_data, "Balance: %.2f EUR\n", (double)balance);
+                start = 80, end = 93;
+                decimal_value = bit_slice_to_dec(bit_representation, start, end);
+                uint64_t start_date_timestamp = (decimal_value * 24 * 3600) + (float)epoch + 3600;
+                DateTime start_dt = {0};
+                datetime_timestamp_to_datetime(start_date_timestamp, &start_dt);
+                furi_string_cat_printf(parsed_data, "\nStart Date:\n");
+                locale_format_datetime_cat(parsed_data, &start_dt, false);
+                furi_string_cat_printf(parsed_data, "\n");
+
+                // Select app for environment
+                select_app[6] = 1;
+                bit_buffer_reset(tx_buffer);
+                bit_buffer_append_bytes(tx_buffer, select_app, sizeof(select_app));
+                error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
+                if(error != Iso14443_4bErrorNone) {
+                    FURI_LOG_I(TAG, "Select File: iso14443_4b_poller_send_block error %d", error);
+                    stage = MetroflipPollerEventTypeFail;
+                    view_dispatcher_send_custom_event(
+                        app->view_dispatcher, MetroflipCustomEventPollerFail);
+                    break;
+                }
+
+                // Check the response after selecting app
+                response_length = bit_buffer_get_size_bytes(rx_buffer);
+                if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
+                   bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
+                    FURI_LOG_I(
+                        TAG,
+                        "Select profile app failed: %02x%02x",
+                        bit_buffer_get_byte(rx_buffer, response_length - 2),
+                        bit_buffer_get_byte(rx_buffer, response_length - 1));
+                    stage = MetroflipPollerEventTypeFail;
+                    view_dispatcher_send_custom_event(
+                        app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
+                    break;
+                }
+
+
+                // read file 1
+                read_file[2] = 1;
+                bit_buffer_reset(tx_buffer);
+                bit_buffer_append_bytes(tx_buffer, read_file, sizeof(read_file));
+                error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
+                if(error != Iso14443_4bErrorNone) {
+                    FURI_LOG_I(TAG, "Read File: iso14443_4b_poller_send_block error %d", error);
+                    stage = MetroflipPollerEventTypeFail;
+                    view_dispatcher_send_custom_event(
+                        app->view_dispatcher, MetroflipCustomEventPollerFail);
+                    break;
+                }
+
+                // Check the response after reading the file
+                response_length = bit_buffer_get_size_bytes(rx_buffer);
+                if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
+                   bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
+                    FURI_LOG_I(
+                        TAG,
+                        "Read file failed: %02x%02x",
+                        bit_buffer_get_byte(rx_buffer, response_length - 2),
+                        bit_buffer_get_byte(rx_buffer, response_length - 1));
+                    stage = MetroflipPollerEventTypeFail;
+                    view_dispatcher_send_custom_event(
+                        app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
+                    break;
+                }
+                char environment_bit_representation[response_length * 8 + 1]; 
+                environment_bit_representation[0] = '\0'; 
+                for(size_t i = 0; i < response_length; i++) {
+                    char bits[9]; 
+                    uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
+                    byte_to_binary(byte, bits);
+                    strcat(environment_bit_representation, bits); 
+                }
+                start = 45;
+                end = 58;
+                decimal_value = bit_slice_to_dec(environment_bit_representation, start, end);
+                uint64_t end_validity_timestamp = (decimal_value * 24 * 3600) + (float)epoch + 3600;
+                DateTime end_dt = {0};
+                datetime_timestamp_to_datetime(end_validity_timestamp, &end_dt);
+                furi_string_cat_printf(parsed_data, "\nEnd Validity:\n");
+                locale_format_datetime_cat(parsed_data, &end_dt, false);
+                furi_string_cat_printf(parsed_data, "\n\n");
+
+                // Select app for events
+                select_app[6] = 16;
+                bit_buffer_reset(tx_buffer);
+                bit_buffer_append_bytes(tx_buffer, select_app, sizeof(select_app));
+                error = iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
+                if(error != Iso14443_4bErrorNone) {
+                    FURI_LOG_I(TAG, "Select File: iso14443_4b_poller_send_block error %d", error);
+                    stage = MetroflipPollerEventTypeFail;
+                    view_dispatcher_send_custom_event(
+                        app->view_dispatcher, MetroflipCustomEventPollerFail);
+                    break;
+                }
+
+                // Check the response after selecting app
+                response_length = bit_buffer_get_size_bytes(rx_buffer);
+                if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
+                   bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
+                    FURI_LOG_I(
+                        TAG,
+                        "Select events app failed: %02x%02x",
+                        bit_buffer_get_byte(rx_buffer, response_length - 2),
+                        bit_buffer_get_byte(rx_buffer, response_length - 1));
+                    stage = MetroflipPollerEventTypeFail;
+                    view_dispatcher_send_custom_event(
+                        app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
+                    break;
+                }
+
+                furi_string_cat_printf(parsed_data, "\e#Events:\n");
+                // Now send the read command
+                for(size_t i = 1; i < 4; i++) {
+                    read_file[2] = i;
+                    bit_buffer_reset(tx_buffer);
+                    bit_buffer_append_bytes(tx_buffer, read_file, sizeof(read_file));
+                    error =
+                        iso14443_4b_poller_send_block(iso14443_4b_poller, tx_buffer, rx_buffer);
+                    if(error != Iso14443_4bErrorNone) {
+                        FURI_LOG_I(
+                            TAG, "Read File: iso14443_4b_poller_send_block error %d", error);
+                        stage = MetroflipPollerEventTypeFail;
+                        view_dispatcher_send_custom_event(
+                            app->view_dispatcher, MetroflipCustomEventPollerFail);
+                        break;
+                    }
+
+                    // Check the response after reading the file
+                    response_length = bit_buffer_get_size_bytes(rx_buffer);
+                    if(bit_buffer_get_byte(rx_buffer, response_length - 2) != apdu_success[0] ||
+                       bit_buffer_get_byte(rx_buffer, response_length - 1) != apdu_success[1]) {
+                        FURI_LOG_I(
+                            TAG,
+                            "Read file failed: %02x%02x",
+                            bit_buffer_get_byte(rx_buffer, response_length - 2),
+                            bit_buffer_get_byte(rx_buffer, response_length - 1));
+                        stage = MetroflipPollerEventTypeFail;
+                        view_dispatcher_send_custom_event(
+                            app->view_dispatcher, MetroflipCustomEventPollerFileNotFound);
+                        break;
+                    }
+                    char event_bit_representation
+                        [response_length * 8 + 1]; 
+                    event_bit_representation[0] = '\0'; 
+                    for(size_t i = 0; i < response_length; i++) {
+                        char bits[9]; 
+                        uint8_t byte = bit_buffer_get_byte(rx_buffer, i);
+                        byte_to_binary(byte, bits);
+                        strcat(event_bit_representation, bits); 
+                    }
+                    furi_string_cat_printf(parsed_data, "\nEvent 0%d:\n", i);
+                    int start = 53, end = 60;
+                    int decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
+                    int transport_type = decimal_value >> 4;
+                    int transition = decimal_value & 15;
+                    furi_string_cat_printf(parsed_data, "%s - %s\n", TRANSPORT_LIST[transport_type], TRANSITION_LIST[transition]);
+                    start = 69, end = 84;
+                    decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
+                    int line_id = (decimal_value >> 9) - 1;
+                    int station_id = ((decimal_value >> 4) & 31) - 1;
+                    furi_string_cat_printf(parsed_data, "Line: %s\nStation: %s\n", METRO_LIST[line_id].name, METRO_LIST[line_id].stations[station_id]);
+                    start = 61, end = 68;
+                    decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
+                    furi_string_cat_printf(parsed_data, "Provider: %s\n", SERVICE_PROVIDERS[decimal_value]);
+                    start = 0, end = 13;
+                    decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
+                    uint64_t date_timestamp = (decimal_value * 24 * 3600) + epoch + 3600;
+                    DateTime dt = {0};
+                    datetime_timestamp_to_datetime(date_timestamp, &dt);
+                    furi_string_cat_printf(parsed_data, "Time: ");
+                    locale_format_datetime_cat(parsed_data, &dt, false);
+                    start = 14, end = 24;
+                    decimal_value = bit_slice_to_dec(event_bit_representation, start, end);
+                    furi_string_cat_printf(parsed_data, " %02d:%02d:%02d\n\n", ((decimal_value*60) / 3600), (((decimal_value*60) % 3600)/60), (((decimal_value*60) % 3600)%60));
+                }
+
+                widget_add_text_scroll_element(
+                    widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+                widget_add_button_element(
+                    widget,
+                    GuiButtonTypeRight,
+                    "Exit",
+                    metroflip_navigo_widget_callback,
+                    app);
+
+                furi_string_free(parsed_data);
+                view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+                metroflip_app_blink_stop(app);
+                stage = MetroflipPollerEventTypeSuccess;
+                next_command = NfcCommandStop;
+                } while(false);
+
+
+
+            if(stage != MetroflipPollerEventTypeSuccess) {
+                next_command = NfcCommandStop;
+            }
+        }
+    }
+    bit_buffer_free(tx_buffer);
+    bit_buffer_free(rx_buffer);
+
+    return next_command;
+}
+
+void metroflip_scene_navigo_on_enter(void* context) {
+    Metroflip* app = context;
+    dolphin_deed(DolphinDeedNfcRead);
+
+    // 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, NfcProtocolIso14443_4b);
+    nfc_poller_start(app->poller, metroflip_scene_navigo_poller_callback, app);
+
+    metroflip_app_blink_start(app);
+}
+
+bool metroflip_scene_navigo_on_event(void* context, SceneManagerEvent event) {
+    Metroflip* app = context;
+    bool consumed = false;
+
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == MetroflipPollerEventTypeCardDetect) {
+            Popup* popup = app->popup;
+            popup_set_header(popup, "Scanning..", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == MetroflipCustomEventPollerFileNotFound) {
+            Popup* popup = app->popup;
+            popup_set_header(popup, "Read Error,\n wrong card", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == MetroflipCustomEventPollerFail) {
+            Popup* popup = app->popup;
+            popup_set_header(popup, "Error, try\n again", 68, 30, AlignLeft, AlignTop);
+            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_navigo_on_exit(void* context) {
+    Metroflip* app = context;
+
+    if(app->poller) {
+        nfc_poller_stop(app->poller);
+        nfc_poller_free(app->poller);
+    }
+    metroflip_app_blink_stop(app);
+    widget_reset(app->widget);
+
+    // Clear view
+    popup_reset(app->popup);
+}

+ 6 - 59
scenes/metroflip_scene_ravkav.c

@@ -1,63 +1,10 @@
 #include "../metroflip_i.h"
 #include "../metroflip_i.h"
-#include <datetime.h>
-#include <dolphin/dolphin.h>
-#include <locale/locale.h>
 
 
 #include <nfc/protocols/iso14443_4b/iso14443_4b_poller.h>
 #include <nfc/protocols/iso14443_4b/iso14443_4b_poller.h>
 
 
-#define Metroflip_POLLER_MAX_BUFFER_SIZE 1024
-
 #define TAG "Metroflip:Scene:RavKav"
 #define TAG "Metroflip:Scene:RavKav"
 
 
-#define epoch_ravkav 852073200
-
-uint8_t apdu_success[] = {0x90, 0x00};
-
-// read file
-uint8_t read_file[] = {0x94, 0xb2, 0x01, 0x04, 0x1D};
-//                                 ^^^
-//                                 |||
-//                                 FID
-
-// select app
-uint8_t select_app[] = {0x94, 0xA4, 0x00, 0x00, 0x02, 0x20, 0x00, 0x00};
-//                                                    ^^^^^^^^^
-//                                                    |||||||||
-//                                                    AID: 20XX
-void locale_format_datetime_cat(FuriString* out, const DateTime* dt) {
-    // helper to print datetimes
-    FuriString* s = furi_string_alloc();
-
-    LocaleDateFormat date_format = locale_get_date_format();
-    const char* separator = (date_format == LocaleDateFormatDMY) ? "." : "/";
-    locale_format_date(s, dt, date_format, separator);
-    furi_string_cat(out, s);
-    locale_format_time(s, dt, locale_get_time_format(), false);
-    furi_string_cat_printf(out, "  ");
-    furi_string_cat(out, s);
-
-    furi_string_free(s);
-}
-
-void byte_to_binary(uint8_t byte, char* bits) {
-    for(int i = 7; i >= 0; i--) {
-        bits[7 - i] = (byte & (1 << i)) ? '1' : '0';
-    }
-    bits[8] = '\0';
-}
-
-int binary_to_decimal(const char binary[]) {
-    int decimal = 0;
-    int length = strlen(binary);
-
-    for(int i = 0; i < length; i++) {
-        decimal = decimal * 2 + (binary[i] - '0');
-    }
-
-    return decimal;
-}
-
-void metroflip_charliecard_ravkav_widget_callback(
+void metroflip_ravkav_widget_callback(
     GuiButtonType result,
     GuiButtonType result,
     InputType type,
     InputType type,
     void* context) {
     void* context) {
@@ -239,11 +186,11 @@ static NfcCommand metroflip_scene_ravkav_poller_callback(NfcGenericEvent event,
                 strncpy(bit_slice, bit_representation + start, end - start + 1);
                 strncpy(bit_slice, bit_representation + start, end - start + 1);
                 bit_slice[end - start + 1] = '\0';
                 bit_slice[end - start + 1] = '\0';
                 int decimal_value = binary_to_decimal(bit_slice);
                 int decimal_value = binary_to_decimal(bit_slice);
-                uint64_t result_timestamp = decimal_value + epoch_ravkav + (3600 * 3);
+                uint64_t result_timestamp = decimal_value + epoch + (3600 * 3);
                 DateTime dt = {0};
                 DateTime dt = {0};
                 datetime_timestamp_to_datetime(result_timestamp, &dt);
                 datetime_timestamp_to_datetime(result_timestamp, &dt);
                 furi_string_cat_printf(parsed_data, "\nActivation date:\n");
                 furi_string_cat_printf(parsed_data, "\nActivation date:\n");
-                locale_format_datetime_cat(parsed_data, &dt);
+                locale_format_datetime_cat(parsed_data, &dt, true);
                 furi_string_cat_printf(parsed_data, "\n\n");
                 furi_string_cat_printf(parsed_data, "\n\n");
 
 
                 // Select app for events
                 // Select app for events
@@ -318,11 +265,11 @@ static NfcCommand metroflip_scene_ravkav_poller_callback(NfcGenericEvent event,
                     strncpy(bit_slice, bit_representation + start, end - start + 1);
                     strncpy(bit_slice, bit_representation + start, end - start + 1);
                     bit_slice[end - start + 1] = '\0';
                     bit_slice[end - start + 1] = '\0';
                     int decimal_value = binary_to_decimal(bit_slice);
                     int decimal_value = binary_to_decimal(bit_slice);
-                    uint64_t result_timestamp = decimal_value + epoch_ravkav + (3600 * 3);
+                    uint64_t result_timestamp = decimal_value + epoch + (3600 * 3);
                     DateTime dt = {0};
                     DateTime dt = {0};
                     datetime_timestamp_to_datetime(result_timestamp, &dt);
                     datetime_timestamp_to_datetime(result_timestamp, &dt);
                     furi_string_cat_printf(parsed_data, "\nEvent 0%d:\n", i);
                     furi_string_cat_printf(parsed_data, "\nEvent 0%d:\n", i);
-                    locale_format_datetime_cat(parsed_data, &dt);
+                    locale_format_datetime_cat(parsed_data, &dt, true);
                     furi_string_cat_printf(parsed_data, "\n\n");
                     furi_string_cat_printf(parsed_data, "\n\n");
                 }
                 }
 
 
@@ -333,7 +280,7 @@ static NfcCommand metroflip_scene_ravkav_poller_callback(NfcGenericEvent event,
                     widget,
                     widget,
                     GuiButtonTypeRight,
                     GuiButtonTypeRight,
                     "Exit",
                     "Exit",
-                    metroflip_charliecard_ravkav_widget_callback,
+                    metroflip_ravkav_widget_callback,
                     app);
                     app);
 
 
                 furi_string_free(parsed_data);
                 furi_string_free(parsed_data);

+ 3 - 0
scenes/metroflip_scene_start.c

@@ -14,6 +14,9 @@ void metroflip_scene_start_on_enter(void* context) {
     submenu_add_item(
     submenu_add_item(
         submenu, "Rav-Kav", MetroflipSceneRavKav, metroflip_scene_start_submenu_callback, app);
         submenu, "Rav-Kav", MetroflipSceneRavKav, metroflip_scene_start_submenu_callback, app);
 
 
+    submenu_add_item(
+        submenu, "Navigo", MetroflipSceneNavigo, metroflip_scene_start_submenu_callback, app);
+
     submenu_add_item(
     submenu_add_item(
         submenu,
         submenu,
         "CharlieCard",
         "CharlieCard",

+ 177 - 0
scenes/navigo.h

@@ -0,0 +1,177 @@
+#ifndef METRO_LIST_H
+#define METRO_LIST_H
+
+typedef struct {
+    const char* name;
+    const char* stations[14];  
+} MetroLine;
+
+#ifndef NAVIGO_H
+#define NAVIGO_H
+
+// Service Providers
+static const char* SERVICE_PROVIDERS[] = { 
+    [2] = "SNCF",  
+    [3] = "RATP"
+};
+
+// Transport Types
+static const char* TRANSPORT_LIST[] = {  
+    [1] = "Urban Bus",  
+    [2] = "Interurban Bus",  
+    [3] = "Metro",  
+    [4] = "Tram",  
+    [5] = "Train",  
+    [8] = "Parking"
+};
+
+// Transition Types
+static const char* TRANSITION_LIST[] = {  
+    [1] = "Entry",  
+    [2] = "Exit",  
+    [4] = "Inspection",  
+    [6] = "Interchange (entry)",  
+    [7] = "Interchange (exit)"
+};
+
+#endif // NAVIGO_H
+
+
+const MetroLine METRO_LIST[] = {
+    [0] = {
+        "Cite", {
+            "Saint-Michel", "Odeon", "Cluny - La Sorbonne", "Maubert - Mutualite", "Luxembourg", 
+            "Chatelet", "Les Halles", "Les Halles", "Louvre - Rivoli", "Pont Neuf", "Cite", "Hotel de Ville"
+        }
+    },
+    [1] = {
+        "Rennes", {
+            "Cambronne", "Sevres - Lecourbe", "Segur", "Saint-Francois-Xavier", "Duroc", 
+            "Vaneau", "Sevres - Babylone", "Rue du Bac", "Rennes", "Saint-Sulpice", "Mabillon", "Saint-Germain-des-Pres"
+        }
+    },
+    [2] = {
+        "Villette", {
+            "Porte de la Villette", "Aubervilliers - Pantin - Quatre Chemins", "Fort d'Aubervilliers", 
+            "La Courneuve - 8 Mai 1945", "Hoche", "Eglise de Pantin", "Bobigny - Pantin - Raymond Queneau", 
+            "Bobigny - Pablo Picasso"
+        }
+    },
+    [3] = {
+        "Montparnasse", {
+            "Pernety", "Plaisance", "Gaite", "Edgar Quinet", "Vavin", "Montparnasse - Bienvenue", 
+            "Saint-Placide", "Notre-Dame-des-Champs"
+        }
+    },
+    [4] = {
+        "Nation", {
+            "Robespierre", "Porte de Montreuil", "Maraichers", "Buzenval", "Rue des Boulets", "Porte de Vincennes", 
+            "Picpus", "Nation", "Avron", "Alexandre Dumas"
+        }
+    },
+    [5] = {
+        "Saint-Lazare", {
+            "Malesherbes", "Monceau", "Villiers", "Quatre-Septembre", "Opera", "Auber", 
+            "Havre - Caumartin", "Saint-Lazare", "Saint-Lazare", "Saint-Augustin", "Europe", "Liege"
+        }
+    },
+    [6] = {
+        "Auteuil", {
+            "Porte de Saint-Cloud", "Porte d'Auteuil", "Eglise d'Auteuil", "Michel-Ange - Auteuil", 
+            "Michel-Ange - Molitor", "Chardon-Lagache", "Mirabeau", "Exelmans", "Jasmin"
+        }
+    },
+    [7] = {
+        "Republique", {
+            "Rambuteau", "Arts et Metiers", "Jacques Bonsergent", "Goncourt", "Temple", "Republique", 
+            "Oberkampf", "Parmentier", "Filles du Calvaire", "Saint-Sebastien - Froissart", "Richard-Lenoir", "Saint-Ambroise"
+        }
+    },
+    [8] = {
+        "Austerlitz", {
+            "Quai de la Gare", "Chevaleret", "Saint-Marcel", "Gare d'Austerlitz", "Gare de Lyon", "Quai de la Rapee"
+        }
+    },
+    [9] = {
+        "Invalides", {
+            "Champs-Elysees - Clemenceau", "Concorde", "Madeleine", "Bir-Hakeim", "Ecole Militaire", 
+            "La Tour-Maubourg", "Invalides", "Saint-Denis - Universite", "Varenne", "Assemblee nationale", "Solferino"
+        }
+    },
+    [10] = {
+        "Sentier", {
+            "Tuileries", "Palais Royal - Musee du Louvre", "Pyramides", "Bourse", "Grands Boulevards", 
+            "Richelieu - Drouot", "Bonne Nouvelle", "Strasbourg - Saint-Denis", "Chateau d'Eau", "Sentier", 
+            "Reaumur - Sebastopol", "Etienne Marcel"
+        }
+    },
+    [11] = {
+        "Ile Saint-Louis", {
+            "Faidherbe - Chaligny", "Reuilly - Diderot", "Montgallet", "Censier - Daubenton", "Place Monge", 
+            "Cardinal Lemoine", "Jussieu", "Sully - Morland", "Pont Marie", "Saint-Paul", "Bastille", "Chemin Vert", 
+            "Breguet - Sabin", "Ledru-Rollin"
+        }
+    },
+    [12] = {
+        "Daumesnil", {
+            "Porte Doree", "Porte de Charenton", "Bercy", "Dugommier", "Michel Bizot", "Daumesnil", "Bel-Air"
+        }
+    },
+    [13] = {
+        "Italie", {
+            "Porte de Choisy", "Porte d'Italie", "Cite universitaire", "Maison Blanche", "Tolbiac", "Nationale", 
+            "Campo-Formio", "Les Gobelins", "Place d'Italie", "Corvisart"
+        }
+    },
+    [14] = {
+        "Denfert", {
+            "Cour Saint-Emilion", "Porte d'Orleans", "Bibliotheque Francois Mitterrand", "Mouton-Duvernet", "Alesia", 
+            "Olympiades", "Glaciere", "Saint-Jacques", "Raspail", "Denfert-Rochereau"
+        }
+    },
+    [15] = {
+        "Felix Faure", {
+            "Falguiere", "Pasteur", "Volontaires", "Vaugirard", "Convention", "Porte de Versailles", 
+            "Balard", "Lourmel", "Boucicaut", "Felix Faure", "Charles Michels", "Javel - Andre Citroen"
+        }
+    },
+    [16] = {
+        "Passy", {
+            "Porte Dauphine", "La Motte-Picquet - Grenelle", "Commerce", "Avenue Emile Zola", "Dupleix", 
+            "Passy", "Ranelagh", "La Muette", "Rue de la Pompe", "Boissiere", "Trocadero"
+        }
+    },
+    [17] = {
+        "Etoile", {
+            "Iena", "Alma - Marceau", "Miromesnil", "Saint-Philippe du Roule", "Franklin D. Roosevelt", 
+            "George V", "Kleber", "Victor Hugo", "Argentine", "Charles de Gaulle - Etoile", "Ternes", "Courcelles"
+        }
+    },
+    [18] = {
+        "Clichy - Saint Ouen", {
+            "Mairie de Clichy", "Gabriel Peri", "Les Agnettes", "Asnieres - Gennevilliers - Les Courtilles", 
+            "La Chapelle", "Garibaldi", "Mairie de Saint-Ouen", "Carrefour Pleyel", "Saint-Denis - Porte de Paris", 
+            "Basilique de Saint-Denis"
+        }
+    },
+    [19] = {
+        "Montmartre", {
+            "Porte de Clignancourt", "Porte de la Chapelle", "Marx Dormoy", "Marcadet - Poissonniers", "Simplon", 
+            "Jules Joffrin", "Lamarck - Caulaincourt"
+        }
+    },
+    [20] = {
+        "Lafayette", {
+            "Chaussee d'Antin - La Fayette", "Le Peletier", "Cadet", "Chateau Rouge", "Barbes - Rochechouart", 
+            "Gare du Nord", "Gare de l'Est", "Poissonniere", "Chateau-Landon"
+        }
+    },
+    [21] = {
+        "Buttes Chaumont", {
+            "Porte de Pantin", "Ourcq", "Corentin Cariou", "Crimee", "Riquet", "La Chapelle", 
+            "Belleville", "Botzaris", "Pelleport", "Place des Fetes", "Cimetiere du Pere Lachaise"
+        }
+    }
+};
+
+#endif // METRO_LIST_H