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

Merge pull request #46 from luu176/main

v0.7 beta
Luu 11 месяцев назад
Родитель
Сommit
ac4922c34e

+ 5 - 0
CHANGELOG.md

@@ -37,3 +37,8 @@ Big update!
 - Unified Calypso Parser: A new unified Calypso parser has been introduced (thanks to DocSystem), streamlining Calypso card support.
 - RavKav Moved to Calypso Parser: RavKav has been moved to the new unified Calypso parser (credit to luu176).
 
+## v0.6
+
+- Added a load mode and a save mode to store card info
+- Fixed a major bug due to API symbol not existing
+

+ 4 - 0
api/calypso/transit/ravkav.c

@@ -20,6 +20,10 @@ const char* get_ravkav_issuer(int issuer) {
 
 void show_ravkav_contract_info(RavKavCardContract* contract, FuriString* parsed_data) {
     // Core contract validity period
+    if(contract->balance != 0.0f) {
+        furi_string_cat_printf(parsed_data, "Balance: %.2f ILS\n", (double)contract->balance);
+    }
+
     furi_string_cat_printf(parsed_data, "Valid from: ");
     locale_format_datetime_cat(parsed_data, &contract->start_date, false);
     if(contract->end_date_available) {

+ 1 - 0
api/calypso/transit/ravkav_i.h

@@ -23,6 +23,7 @@ typedef struct {
     bool end_date_available;
 
     bool present;
+    float balance;
 } RavKavCardContract;
 
 typedef struct {

+ 9 - 0
application.fam

@@ -100,3 +100,12 @@ App(
     sources=["scenes/plugins/troika.c"],
     fal_embedded=True,
 )
+
+App(
+    appid="gocard_plugin",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="gocard_plugin_ep",
+    requires=["metroflip"],
+    sources=["scenes/plugins/gocard.c"],
+    fal_embedded=True,
+)

+ 1 - 1
manifest.yml

@@ -15,7 +15,7 @@ screenshots:
 short_description: 'An implementation of Metrodroid on the Flipper Zero'
 sourcecode:
   location:
-    commit_sha: ac4a57a8fc31cc47195239cad1515ce141fb440c
+    commit_sha: c4e1ed2304ad96a3565f83a58b3c7f8529f9d651
     origin: https://github.com/luu176/Metroflip
     subdir:
   type: git

+ 2 - 1
metroflip.c

@@ -157,7 +157,8 @@ void metroflip_exit_widget_callback(GuiButtonType result, InputType type, void*
     UNUSED(result);
 
     if(type == InputTypeShort) {
-        scene_manager_next_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
     }
 }
 

+ 35 - 0
scenes/keys.c

@@ -33,6 +33,12 @@ const MfClassicKeyPair metromoney_1k_verify_key[] = {
     {.a = 0x9C616585E26D},
 };
 
+const uint8_t gocard_verify_data[1][14] = {
+    {0x16, 0x18, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x5A, 0x5B, 0x20, 0x21, 0x22, 0x23}};
+
+const uint8_t gocard_verify_data2[1][14] = {
+    {0x16, 0x18, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x01, 0x01}};
+
 static bool charliecard_verify(Nfc* nfc, MfClassicData* mfc_data, bool data_loaded) {
     bool verified = false;
     FURI_LOG_I(TAG, "verifying charliecard..");
@@ -244,6 +250,33 @@ static bool troika_verify(Nfc* nfc, MfClassicData* mfc_data, bool data_loaded) {
            troika_verify_type(nfc, mfc_data, data_loaded, MfClassicType4k);
 }
 
+static bool gocard_verify(MfClassicData* mfc_data, bool data_loaded) {
+    bool verified = false;
+    FURI_LOG_I(TAG, "verifying charliecard..");
+    do {
+        if(data_loaded) {
+            uint8_t* buffer = &mfc_data->block[1].data[1];
+            size_t buffer_size = 14;
+
+            if(memcmp(buffer, gocard_verify_data[0], buffer_size) == 0) {
+                FURI_LOG_I(TAG, "Match!");
+            } else {
+                FURI_LOG_I(TAG, "No match.");
+                if(memcmp(buffer, gocard_verify_data2[0], buffer_size) == 0) {
+                    FURI_LOG_I(TAG, "Match!");
+                } else {
+                    FURI_LOG_I(TAG, "No match.");
+                    break;
+                }
+            }
+
+            verified = true;
+        }
+    } while(false);
+
+    return verified;
+}
+
 CardType determine_card_type(Nfc* nfc, MfClassicData* mfc_data, bool data_loaded) {
     FURI_LOG_I(TAG, "checking keys..");
     UNUSED(bip_verify);
@@ -258,6 +291,8 @@ CardType determine_card_type(Nfc* nfc, MfClassicData* mfc_data, bool data_loaded
         return CARD_TYPE_TROIKA;
     } else if(charliecard_verify(nfc, mfc_data, data_loaded)) {
         return CARD_TYPE_CHARLIECARD;
+    } else if(gocard_verify(mfc_data, data_loaded)) {
+        return CARD_TYPE_GOCARD;
     } else {
         FURI_LOG_I(TAG, "its unknown");
         return CARD_TYPE_UNKNOWN;

+ 3 - 1
scenes/keys.h

@@ -9,6 +9,7 @@ typedef enum {
     CARD_TYPE_CHARLIECARD,
     CARD_TYPE_SMARTRIDER,
     CARD_TYPE_TROIKA,
+    CARD_TYPE_GOCARD,
     CARD_TYPE_UNKNOWN
 } CardType;
 
@@ -28,9 +29,10 @@ typedef struct {
 
 extern const MfClassicKeyPair troika_1k_keys[16];
 extern const MfClassicKeyPair troika_4k_keys[40];
-extern const uint8_t SMARTRIDER_STANDARD_KEYS[3][6];
 extern const MfClassicKeyPair charliecard_1k_keys[16];
 extern const MfClassicKeyPair bip_1k_keys[16];
 extern const MfClassicKeyPair metromoney_1k_keys[16];
+extern const uint8_t gocard_verify_data[1][14];
+extern const uint8_t gocard_verify_data2[1][14];
 
 #endif // KEYS_H

+ 7 - 2
scenes/metroflip_scene_load.c

@@ -60,6 +60,10 @@ void metroflip_scene_load_on_enter(void* context) {
                         app->card_type = "troika";
                         FURI_LOG_I(TAG, "Detected: Troika\n");
                         break;
+                    case CARD_TYPE_GOCARD:
+                        app->card_type = "gocard";
+                        FURI_LOG_I(TAG, "Detected: go card\n");
+                        break;
                     case CARD_TYPE_UNKNOWN:
                         app->card_type = "unknown";
                         //popup_set_header(popup, "Unsupported\n card", 58, 31, AlignLeft, AlignTop);
@@ -118,9 +122,10 @@ void metroflip_scene_load_on_enter(void* context) {
             app->card_type = furi_string_get_cstr(card_type);
             has_card_type = false;
         }
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
         scene_manager_next_scene(app->scene_manager, MetroflipSceneParse);
     } else {
-        scene_manager_next_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
     }
     furi_string_free(file_path);
     furi_record_close(RECORD_STORAGE);
@@ -133,7 +138,7 @@ bool metroflip_scene_load_on_event(void* context, SceneManagerEvent event) {
     // If they don't select any file in the brwoser and press back button,
     // the data is not loaded
     if(!app->data_loaded) {
-        scene_manager_next_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
     }
     consumed = true;
 

+ 51 - 6
scenes/plugins/calypso.c

@@ -321,6 +321,8 @@ void metroflip_next_button_widget_callback(GuiButtonType result, InputType type,
             ctx->page_id = 0;
             scene_manager_search_and_switch_to_previous_scene(
                 app->scene_manager, MetroflipSceneStart);
+            scene_manager_set_scene_state(
+                app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
             return;
         }
         if(ctx->page_id < 10) {
@@ -352,6 +354,8 @@ void metroflip_next_button_widget_callback(GuiButtonType result, InputType type,
                 ctx->page_id = 0;
                 scene_manager_search_and_switch_to_previous_scene(
                     app->scene_manager, MetroflipSceneStart);
+                scene_manager_set_scene_state(
+                    app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
                 return;
             }
             ctx->page_id += 1;
@@ -359,6 +363,8 @@ void metroflip_next_button_widget_callback(GuiButtonType result, InputType type,
             ctx->page_id = 0;
             scene_manager_search_and_switch_to_previous_scene(
                 app->scene_manager, MetroflipSceneStart);
+            scene_manager_set_scene_state(
+                app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
             return;
         }
 
@@ -1783,8 +1789,17 @@ static NfcCommand calypso_poller_callback(NfcGenericEvent event, void* context)
                     if(card->card_type == CALYPSO_CARD_RAVKAV) {
                         card->ravkav = malloc(sizeof(RavKavCardData));
 
+                        // Prepare calypso structure
+
+                        CalypsoApp* RavKavContractStructure = get_ravkav_contract_structure();
+                        if(!RavKavContractStructure) {
+                            FURI_LOG_E(TAG, "Failed to load RavKav Contract structure");
+                            break;
+                        }
+
+                        //get balance
                         error = select_new_app(
-                            0x20, 0x20, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
+                            0x20, 0x2A, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
                         if(error != 0) {
                             FURI_LOG_E(TAG, "Failed to select app for contracts");
                             break;
@@ -1793,15 +1808,44 @@ static NfcCommand calypso_poller_callback(NfcGenericEvent event, void* context)
                         // Check the response after selecting app
                         if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
                             FURI_LOG_E(
-                                TAG, "Failed to check response after selecting app for contracts");
+                                TAG, "Failed to check response after selecting app for counter");
                             break;
                         }
 
-                        // Prepare calypso structure
+                        error = read_new_file(
+                            1, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
+                        if(error != 0) {
+                            FURI_LOG_E(TAG, "Failed to read counter %d", 1);
+                            break;
+                        }
 
-                        CalypsoApp* RavKavContractStructure = get_ravkav_contract_structure();
-                        if(!RavKavContractStructure) {
-                            FURI_LOG_E(TAG, "Failed to load RavKav Contract structure");
+                        // Check the response after reading the file
+                        if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
+                            FURI_LOG_E(
+                                TAG, "Failed to check response after reading counter %d", 1);
+                            break;
+                        }
+
+                        uint32_t value = 0;
+                        for(uint8_t i = 0; i < 3; i++) {
+                            value = (value << 8) | bit_buffer_get_byte(rx_buffer, i);
+                        }
+                        float result = value / 100.0f;
+                        FURI_LOG_I(TAG, "Value: %.2f ILS", (double)result);
+
+                        card->ravkav->contracts[0].balance = result;
+
+                        error = select_new_app(
+                            0x20, 0x20, tx_buffer, rx_buffer, iso14443_4b_poller, app, &stage);
+                        if(error != 0) {
+                            FURI_LOG_E(TAG, "Failed to select app for contracts");
+                            break;
+                        }
+
+                        // Check the response after selecting app
+                        if(check_response(rx_buffer, app, &stage, &response_length) != 0) {
+                            FURI_LOG_E(
+                                TAG, "Failed to check response after selecting app for contracts");
                             break;
                         }
 
@@ -2477,6 +2521,7 @@ static bool calypso_on_event(Metroflip* app, SceneManagerEvent event) {
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
     }
 

+ 1 - 0
scenes/plugins/charliecard.c

@@ -1320,6 +1320,7 @@ static bool charliecard_on_event(Metroflip* app, SceneManagerEvent event) {
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
     }
 

+ 1 - 0
scenes/plugins/clipper.c

@@ -671,6 +671,7 @@ static bool clipper_on_event(Metroflip* app, SceneManagerEvent event) {
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
     }
 

+ 184 - 0
scenes/plugins/gocard.c

@@ -0,0 +1,184 @@
+
+#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>
+#include "../../api/metroflip/metroflip_api.h"
+#include "../../metroflip_plugins.h"
+
+#define TAG "Metroflip:Scene:gocard"
+
+unsigned short byteArrayToIntReversed(unsigned int dec1, unsigned int dec2) {
+    unsigned char byte1 = (unsigned char)dec1;
+    unsigned char byte2 = (unsigned char)dec2;
+    return ((unsigned short)byte2 << 8) | byte1;
+}
+
+static bool gocard_parse(FuriString* parsed_data, const MfClassicData* data) {
+    bool parsed = false;
+
+    do {
+        // Verify key
+        //const uint8_t ticket_sector_number = 1;
+        //const uint8_t ticket_block_number = 1;
+
+        //const MfClassicSectorTrailer* sec_tr =
+        //    mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number);
+
+        //const uint64_t key =
+        //    bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
+        ///if(key != gocard_1k_keys[ticket_sector_number].a) break;
+        //FURI_LOG_D(TAG, "passed key check");
+        // Parse data
+        //const uint8_t start_block_num =
+        //    mf_classic_get_first_block_num_of_sector(ticket_sector_number);
+
+        //const uint8_t* block_start_ptr =
+        //    &data->block[start_block_num + ticket_block_number].data[0];
+
+        //uint32_t balance = bit_lib_bytes_to_num_le(block_start_ptr, 4) - 100;
+
+        //uint32_t balance_lari = balance / 100;
+        //uint8_t balance_tetri = balance % 100;
+
+        int balance_slot = 4;
+
+        if(data->block[balance_slot].data[13] <= data->block[balance_slot + 1].data[13])
+            balance_slot++;
+
+        unsigned short balancecents = byteArrayToIntReversed(
+            data->block[balance_slot].data[2], data->block[balance_slot].data[3]);
+
+        // Check if the sign flag is set in 'balance'
+        if((balancecents & 0x8000) == 0x8000) {
+            balancecents = balancecents & 0x7fff; // Clear the sign flag.
+            balancecents *= -1; // Negate the balance.
+        }
+        // Otherwise, check the sign flag in data->block[4].data[1]
+        else if((data->block[balance_slot].data[1] & 0x80) == 0x80) {
+            // seq_go uses a sign flag in an adjacent byte.
+            balancecents *= -1;
+        }
+
+        double balance = balancecents / 100.0;
+        furi_string_printf(parsed_data, "\e#Go card\nValue: A$%.2f\n", balance);
+
+        parsed = true;
+    } while(false);
+
+    return parsed;
+}
+
+static void gocard_on_enter(Metroflip* app) {
+    dolphin_deed(DolphinDeedNfcRead);
+
+    app->sec_num = 0;
+
+    if(app->data_loaded) {
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+        FlipperFormat* ff = flipper_format_file_alloc(storage);
+        if(flipper_format_file_open_existing(ff, app->file_path)) {
+            MfClassicData* mfc_data = mf_classic_alloc();
+            mf_classic_load(mfc_data, ff, 2);
+            FuriString* parsed_data = furi_string_alloc();
+            Widget* widget = app->widget;
+
+            furi_string_reset(app->text_box_store);
+            if(!gocard_parse(parsed_data, mfc_data)) {
+                furi_string_reset(app->text_box_store);
+                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);
+            widget_add_button_element(
+                widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
+            mf_classic_free(mfc_data);
+            furi_string_free(parsed_data);
+            view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+        }
+        flipper_format_free(ff);
+    } else {
+        // Setup view
+        Popup* popup = app->popup;
+        popup_set_header(popup, "unsupported", 68, 30, AlignLeft, AlignTop);
+        popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+    }
+}
+
+static bool gocard_on_event(Metroflip* app, SceneManagerEvent event) {
+    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.type == SceneManagerEventTypeBack) {
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+static void gocard_on_exit(Metroflip* app) {
+    widget_reset(app->widget);
+
+    if(app->poller && !app->data_loaded) {
+        nfc_poller_stop(app->poller);
+        nfc_poller_free(app->poller);
+    }
+
+    // Clear view
+    popup_reset(app->popup);
+
+    metroflip_app_blink_stop(app);
+}
+
+/* Actual implementation of app<>plugin interface */
+static const MetroflipPlugin gocard_plugin = {
+    .card_name = "gocard",
+    .plugin_on_enter = gocard_on_enter,
+    .plugin_on_event = gocard_on_event,
+    .plugin_on_exit = gocard_on_exit,
+
+};
+
+/* Plugin descriptor to comply with basic plugin specification */
+static const FlipperAppPluginDescriptor gocard_plugin_descriptor = {
+    .appid = METROFLIP_SUPPORTED_CARD_PLUGIN_APP_ID,
+    .ep_api_version = METROFLIP_SUPPORTED_CARD_PLUGIN_API_VERSION,
+    .entry_point = &gocard_plugin,
+};
+
+/* Plugin entry point - must return a pointer to const descriptor  */
+const FlipperAppPluginDescriptor* gocard_plugin_ep(void) {
+    return &gocard_plugin_descriptor;
+}

+ 1 - 0
scenes/plugins/itso.c

@@ -217,6 +217,7 @@ static bool itso_on_event(Metroflip* app, SceneManagerEvent event) {
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
     }
 

+ 1 - 0
scenes/plugins/metromoney.c

@@ -209,6 +209,7 @@ static bool metromoney_on_event(Metroflip* app, SceneManagerEvent event) {
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
     }
 

+ 1 - 0
scenes/plugins/myki.c

@@ -200,6 +200,7 @@ static bool myki_on_event(Metroflip* app, SceneManagerEvent event) {
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
     }
 

+ 1 - 0
scenes/plugins/opal.c

@@ -314,6 +314,7 @@ static bool opal_on_event(Metroflip* app, SceneManagerEvent event) {
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
     }
 

+ 1 - 0
scenes/plugins/smartrider.c

@@ -402,6 +402,7 @@ static bool smartrider_on_event(Metroflip* app, SceneManagerEvent event) {
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
     }
 

+ 1 - 0
scenes/plugins/troika.c

@@ -306,6 +306,7 @@ static bool troika_on_event(Metroflip* app, SceneManagerEvent event) {
         }
     } else if(event.type == SceneManagerEventTypeBack) {
         scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
         consumed = true;
     }