浏览代码

Merge metroflip from https://github.com/luu176/Metroflip

Willy-JL 11 月之前
父节点
当前提交
02c8e7e078
共有 100 个文件被更改,包括 4292 次插入158 次删除
  1. 二进制
      metroflip/.DS_Store
  2. 5 3
      metroflip/.github/workflows/main.yml
  3. 12 0
      metroflip/CHANGELOG.md
  4. 16 14
      metroflip/README.md
  5. 49 0
      metroflip/api/calypso/calypso_i.h
  6. 243 75
      metroflip/api/calypso/calypso_util.c
  7. 30 8
      metroflip/api/calypso/calypso_util.h
  8. 400 19
      metroflip/api/calypso/cards/intercode.c
  9. 63 0
      metroflip/api/calypso/cards/intercode.h
  10. 0 10
      metroflip/api/calypso/cards/navigo.h
  11. 276 0
      metroflip/api/calypso/cards/opus.c
  12. 232 0
      metroflip/api/calypso/cards/ravkav.c
  13. 731 0
      metroflip/api/calypso/transit/navigo.c
  14. 39 0
      metroflip/api/calypso/transit/navigo.h
  15. 32 9
      metroflip/api/calypso/transit/navigo_i.h
  16. 14 0
      metroflip/api/calypso/transit/navigo_lists.h
  17. 157 0
      metroflip/api/calypso/transit/opus.c
  18. 59 0
      metroflip/api/calypso/transit/opus_i.h
  19. 324 0
      metroflip/api/calypso/transit/opus_lists.h
  20. 102 0
      metroflip/api/calypso/transit/ravkav.c
  21. 67 0
      metroflip/api/calypso/transit/ravkav_i.h
  22. 64 0
      metroflip/api/calypso/transit/ravkav_lists.h
  23. 138 0
      metroflip/api/metroflip/metroflip_api.h
  24. 9 0
      metroflip/api/metroflip/metroflip_api_interface.h
  25. 27 0
      metroflip/api/metroflip/metroflip_api_table.cpp
  26. 71 0
      metroflip/api/metroflip/metroflip_api_table_i.h
  27. 213 0
      metroflip/api/nfc/mf_classic_key_cache.c
  28. 21 0
      metroflip/api/nfc/mf_classic_key_cache.h
  29. 21 19
      metroflip/app/README.md
  30. 90 1
      metroflip/application.fam
  31. 10 0
      metroflip/files/navigo/stations/metro/stations_1.txt
  32. 12 0
      metroflip/files/navigo/stations/metro/stations_10.txt
  33. 13 0
      metroflip/files/navigo/stations/metro/stations_11.txt
  34. 16 0
      metroflip/files/navigo/stations/metro/stations_12.txt
  35. 8 0
      metroflip/files/navigo/stations/metro/stations_13.txt
  36. 11 0
      metroflip/files/navigo/stations/metro/stations_14.txt
  37. 12 0
      metroflip/files/navigo/stations/metro/stations_15.txt
  38. 13 0
      metroflip/files/navigo/stations/metro/stations_16.txt
  39. 12 0
      metroflip/files/navigo/stations/metro/stations_17.txt
  40. 13 0
      metroflip/files/navigo/stations/metro/stations_18.txt
  41. 13 0
      metroflip/files/navigo/stations/metro/stations_19.txt
  42. 14 0
      metroflip/files/navigo/stations/metro/stations_2.txt
  43. 10 0
      metroflip/files/navigo/stations/metro/stations_20.txt
  44. 10 0
      metroflip/files/navigo/stations/metro/stations_21.txt
  45. 13 0
      metroflip/files/navigo/stations/metro/stations_22.txt
  46. 16 0
      metroflip/files/navigo/stations/metro/stations_23.txt
  47. 13 0
      metroflip/files/navigo/stations/metro/stations_24.txt
  48. 15 0
      metroflip/files/navigo/stations/metro/stations_25.txt
  49. 14 0
      metroflip/files/navigo/stations/metro/stations_26.txt
  50. 7 0
      metroflip/files/navigo/stations/metro/stations_27.txt
  51. 6 0
      metroflip/files/navigo/stations/metro/stations_28.txt
  52. 12 0
      metroflip/files/navigo/stations/metro/stations_29.txt
  53. 12 0
      metroflip/files/navigo/stations/metro/stations_3.txt
  54. 10 0
      metroflip/files/navigo/stations/metro/stations_30.txt
  55. 12 0
      metroflip/files/navigo/stations/metro/stations_31.txt
  56. 9 0
      metroflip/files/navigo/stations/metro/stations_4.txt
  57. 12 0
      metroflip/files/navigo/stations/metro/stations_5.txt
  58. 12 0
      metroflip/files/navigo/stations/metro/stations_6.txt
  59. 10 0
      metroflip/files/navigo/stations/metro/stations_7.txt
  60. 13 0
      metroflip/files/navigo/stations/metro/stations_8.txt
  61. 8 0
      metroflip/files/navigo/stations/metro/stations_9.txt
  62. 2 0
      metroflip/files/navigo/stations/train/stations_1.txt
  63. 1 0
      metroflip/files/navigo/stations/train/stations_10.txt
  64. 1 0
      metroflip/files/navigo/stations/train/stations_1017.txt
  65. 1 0
      metroflip/files/navigo/stations/train/stations_14.txt
  66. 3 0
      metroflip/files/navigo/stations/train/stations_15.txt
  67. 11 0
      metroflip/files/navigo/stations/train/stations_16.txt
  68. 11 0
      metroflip/files/navigo/stations/train/stations_17.txt
  69. 23 0
      metroflip/files/navigo/stations/train/stations_18.txt
  70. 1 0
      metroflip/files/navigo/stations/train/stations_19.txt
  71. 22 0
      metroflip/files/navigo/stations/train/stations_20.txt
  72. 29 0
      metroflip/files/navigo/stations/train/stations_21.txt
  73. 1 0
      metroflip/files/navigo/stations/train/stations_22.txt
  74. 4 0
      metroflip/files/navigo/stations/train/stations_23.txt
  75. 12 0
      metroflip/files/navigo/stations/train/stations_26.txt
  76. 3 0
      metroflip/files/navigo/stations/train/stations_28.txt
  77. 1 0
      metroflip/files/navigo/stations/train/stations_29.txt
  78. 1 0
      metroflip/files/navigo/stations/train/stations_3.txt
  79. 20 0
      metroflip/files/navigo/stations/train/stations_30.txt
  80. 5 0
      metroflip/files/navigo/stations/train/stations_31.txt
  81. 21 0
      metroflip/files/navigo/stations/train/stations_32.txt
  82. 5 0
      metroflip/files/navigo/stations/train/stations_33.txt
  83. 22 0
      metroflip/files/navigo/stations/train/stations_35.txt
  84. 23 0
      metroflip/files/navigo/stations/train/stations_40.txt
  85. 11 0
      metroflip/files/navigo/stations/train/stations_41.txt
  86. 17 0
      metroflip/files/navigo/stations/train/stations_42.txt
  87. 20 0
      metroflip/files/navigo/stations/train/stations_43.txt
  88. 20 0
      metroflip/files/navigo/stations/train/stations_44.txt
  89. 4 0
      metroflip/files/navigo/stations/train/stations_45.txt
  90. 27 0
      metroflip/files/navigo/stations/train/stations_50.txt
  91. 18 0
      metroflip/files/navigo/stations/train/stations_51.txt
  92. 7 0
      metroflip/files/navigo/stations/train/stations_52.txt
  93. 12 0
      metroflip/files/navigo/stations/train/stations_53.txt
  94. 13 0
      metroflip/files/navigo/stations/train/stations_54.txt
  95. 13 0
      metroflip/files/navigo/stations/train/stations_55.txt
  96. 4 0
      metroflip/files/navigo/stations/train/stations_56.txt
  97. 2 0
      metroflip/files/navigo/stations/train/stations_57.txt
  98. 1 0
      metroflip/files/navigo/stations/train/stations_6.txt
  99. 20 0
      metroflip/files/navigo/stations/train/stations_60.txt
  100. 14 0
      metroflip/files/navigo/stations/train/stations_61.txt

二进制
metroflip/.DS_Store


+ 5 - 3
metroflip/.github/workflows/main.yml

@@ -1,11 +1,13 @@
-name: UFBT Build and Test
+name: UFBT Build and Test App
 on:
   push:
     branches:
       - main
+      - dev
   pull_request:
     branches:
       - main
+      - dev
 jobs:
   build:
     name: Build and Test Application
@@ -28,7 +30,7 @@ jobs:
       - name: Build FAP Applications
         run: ufbt faps
       - name: Upload Build Artifacts
-        uses: actions/upload-artifact@v3
+        uses: actions/upload-artifact@v4
         with:
           name: build-output
-          path: build/
+          path: build/

+ 12 - 0
metroflip/CHANGELOG.md

@@ -25,3 +25,15 @@
   - Now doesnt crash when you click the back button while reading
 - Fix Charliecard parser
 
+## v0.5
+
+Big update!
+
+- Custom API Added: A custom API for Metroflip has been introduced for smoother operation and better scalability.
+- Parsers Moved to Plugins: All parsers have been moved to individual plugins, which means all parser-related code is now loaded from the SD card as .fal files, freeing up RAM until a parser is needed.
+- Scene Optimization: All separate scenes have been merged into one unified scene (metroflip_scene_parse.c), simplifying the codebase.
+- RAM Usage Reduced: We’ve reduced RAM usage by over 45%, eliminating those frustrating "out of memory" errors.
+- Navigo Station List: The Navigo station list has been relocated to apps_assets, improving organization and performance.
+- 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).
+

+ 16 - 14
metroflip/README.md

@@ -9,7 +9,7 @@ Metroflip is a multi-protocol metro card reader app for the Flipper Zero, inspir
 Please join the server https://discord.gg/NR5hhbAXqS if you have any questions for me.
 ---
 
-![image](screenshots/Menu-Top.png)
+![Menu-Top-Screenshot](screenshots/Menu-Top.png)
 
 # Setup Instructions
 
@@ -67,19 +67,20 @@ This is a list of metro cards and transit systems that need support or have part
 
 ## ✅ Supported Cards
 
-| **Card / Agency** | **Country / City**                  | **Card Type**     |
-|--------------------|-------------------------------------|-------------------|
-| **Bip!**          | 🇨🇱 Santiago de Chile, Chile        | Mifare Classic    |
-| **Charliecard**    | 🇺🇸 Boston, MA, USA                 | Mifare Classic    |
-| **Clipper**        | 🇺🇸 San Francisco, CA, USA          | Mifare DESFire    |
-| **ITSO**           | 🇬🇧 United Kingdom                 | Mifare DESFire    |
-| **Metromoney**     | 🇬🇪 Tbilisi, Georgia                | Mifare Classic    |
-| **myki**           | 🇦🇺 Melbourne (and surrounds), VIC, Australia | Mifare DESFire |
-| **Navigo**         | 🇫🇷 Paris, France                   | Calypso           |
-| **Opal**           | 🇦🇺 Sydney (and surrounds), NSW, Australia | Mifare DESFire |
-| **Rav-Kav**        | 🇮🇱 Israel                          | Calypso           |
-| **Troika**         | 🇷🇺 Moscow, Russia                  | Mifare Classic    |
-
+| **Card / Agency**  | **Country / City**                          | **Card Type**     |
+|--------------------|---------------------------------------------|-------------------|
+| **Bip!**           | 🇨🇱 Santiago de Chile, Chile                | Mifare Classic    |
+| **Charliecard**    | 🇺🇸 Boston, MA, USA                        | Mifare Classic    |
+| **Clipper**        | 🇺🇸 San Francisco, CA, USA                  | Mifare DESFire    |
+| **ITSO**           | 🇬🇧 United Kingdom                          | Mifare DESFire    |
+| **Metromoney**     | 🇬🇪 Tbilisi, Georgia                        | Mifare Classic    |
+| **myki**           | 🇦🇺 Melbourne (and surrounds), VIC, Australia | Mifare DESFire    |
+| **Navigo**         | 🇫🇷 Paris, France                           | Calypso           |
+| **Opal**           | 🇦🇺 Sydney (and surrounds), NSW, Australia  | Mifare DESFire    |
+| **Opus**           | 🇨🇦 Montreal, QC, Canada                    | Calypso           |
+| **Rav-Kav**        | 🇮🇱 Israel                                  | Calypso           |
+| **SmartRider**     | :australia: Western Australia, Australia   | Mifare Classic    |
+| **Troika**         | 🇷🇺 Moscow, Russia                          | Mifare Classic    |
 
 
 ---
@@ -89,6 +90,7 @@ This is a list of metro cards and transit systems that need support or have part
 - **Charliecard Parser**: [@zacharyweiss](https://github.com/zacharyweiss)
 - **Rav-Kav Parser**: [@luu176](https://github.com/luu176)
 - **Navigo Parser**: [@luu176](https://github.com/luu176), [@DocSystem](https://github.com/DocSystem)
+- **Opus Parser**: [@DocSystem](https://github.com/DocSystem)
 - **Metromoney Parser**: [@Leptopt1los](https://github.com/Leptopt1los)
 - **Bip! Parser**: [@rbasoalto](https://github.com/rbasoalto), [@gornekich](https://github.com/gornekich)
 - **Clipper Parser**: [@ke6jjj](https://github.com/ke6jjj)

+ 49 - 0
metroflip/api/calypso/calypso_i.h

@@ -0,0 +1,49 @@
+#include "transit/navigo_i.h"
+#include "transit/ravkav_i.h"
+#include "transit/opus_i.h"
+#include <furi.h>
+
+#ifndef CALYPSO_I_H
+#define CALYPSO_I_H
+
+typedef enum {
+    CALYPSO_CARD_MOBIB,
+    CALYPSO_CARD_OPUS,
+    CALYPSO_CARD_VIVA,
+    CALYPSO_CARD_PASSPASS,
+    CALYPSO_CARD_TAM,
+    CALYPSO_CARD_TRANSPOLE,
+    CALYPSO_CARD_OURA,
+    CALYPSO_CARD_NAVIGO,
+    CALYPSO_CARD_KORRIGO,
+    CALYPSO_CARD_TISSEO,
+    CALYPSO_CARD_ENVIBUS,
+    CALYPSO_CARD_GIRONDE,
+    CALYPSO_CARD_RAVKAV,
+    CALYPSO_CARD_UNKNOWN
+} CALYPSO_CARD_TYPE;
+
+typedef struct {
+    NavigoCardData* navigo;
+    OpusCardData* opus;
+    RavKavCardData* ravkav;
+
+    CALYPSO_CARD_TYPE card_type;
+    unsigned int card_number;
+
+    int contracts_count;
+    int events_count;
+    int special_events_count;
+
+    int country_num;
+    int network_num;
+} CalypsoCardData;
+
+typedef struct {
+    CalypsoCardData* card;
+    int page_id;
+    // mutex
+    FuriMutex* mutex;
+} CalypsoContext;
+
+#endif // CALYPSO_I_H

+ 243 - 75
metroflip/api/calypso/calypso_util.c

@@ -1,6 +1,8 @@
+#include "metroflip_i.h"
 #include <stdlib.h>
 #include <string.h>
 #include "calypso_util.h"
+#include "../metroflip/metroflip_api.h"
 
 CalypsoElement make_calypso_final_element(
     const char* key,
@@ -34,27 +36,65 @@ CalypsoElement make_calypso_bitmap_element(const char* key, int size, CalypsoEle
     return bitmap_element;
 }
 
+CalypsoElement
+    make_calypso_container_element(const char* key, int size, CalypsoElement* elements) {
+    CalypsoElement container_element = {};
+
+    container_element.type = CALYPSO_ELEMENT_TYPE_CONTAINER;
+    container_element.container = malloc(sizeof(CalypsoContainerElement));
+    container_element.container->size = size;
+    container_element.container->elements = malloc(size * sizeof(CalypsoElement));
+    for(int i = 0; i < size; i++) {
+        container_element.container->elements[i] = elements[i];
+    }
+    strncpy(container_element.container->key, key, 36);
+
+    return container_element;
+}
+
+CalypsoElement make_calypso_repeater_element(const char* key, int size, CalypsoElement element) {
+    CalypsoElement repeater_element = {};
+
+    repeater_element.type = CALYPSO_ELEMENT_TYPE_REPEATER;
+    repeater_element.repeater = malloc(sizeof(CalypsoRepeaterElement));
+    repeater_element.repeater->size = size;
+    repeater_element.repeater->element = element;
+    strncpy(repeater_element.repeater->key, key, 36);
+
+    return repeater_element;
+}
+
 void free_calypso_element(CalypsoElement* element) {
     if(element->type == CALYPSO_ELEMENT_TYPE_FINAL) {
         free(element->final);
-    } else {
+    } else if(element->type == CALYPSO_ELEMENT_TYPE_BITMAP) {
         for(int i = 0; i < element->bitmap->size; i++) {
             free_calypso_element(&element->bitmap->elements[i]);
         }
         free(element->bitmap->elements);
         free(element->bitmap);
+    } else if(element->type == CALYPSO_ELEMENT_TYPE_CONTAINER) {
+        for(int i = 0; i < element->container->size; i++) {
+            free_calypso_element(&element->container->elements[i]);
+        }
+        free(element->container->elements);
+        free(element->container);
+    } else if(element->type == CALYPSO_ELEMENT_TYPE_REPEATER) {
+        free_calypso_element(&element->repeater->element);
+        free(element->repeater);
     }
 }
 
 void free_calypso_structure(CalypsoApp* structure) {
-    for(int i = 0; i < structure->elements_size; i++) {
-        free_calypso_element(&structure->elements[i]);
+    for(int i = 0; i < structure->container->size; i++) {
+        free_calypso_element(&structure->container->elements[i]);
     }
-    free(structure->elements);
+    free(structure->container->elements);
+    free(structure->container);
     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;
@@ -86,7 +126,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]];
@@ -118,25 +158,25 @@ bool is_calypso_subnode_present(
 
 bool is_calypso_node_present(const char* binary_string, const char* key, CalypsoApp* structure) {
     int offset = 0;
-    for(int i = 0; i < structure->elements_size; i++) {
-        if(structure->elements[i].type == CALYPSO_ELEMENT_TYPE_FINAL) {
-            if(strcmp(structure->elements[i].final->key, key) == 0) {
+    for(int i = 0; i < structure->container->size; i++) {
+        if(structure->container->elements[i].type == CALYPSO_ELEMENT_TYPE_FINAL) {
+            if(strcmp(structure->container->elements[i].final->key, key) == 0) {
                 return true;
             }
-            offset += structure->elements[i].final->size;
+            offset += structure->container->elements[i].final->size;
         } else {
-            if(strcmp(structure->elements[i].bitmap->key, key) == 0) {
+            if(strcmp(structure->container->elements[i].bitmap->key, key) == 0) {
                 return true;
             }
-            int sub_binary_string_size = structure->elements[i].bitmap->size;
+            int sub_binary_string_size = structure->container->elements[i].bitmap->size;
             char bit_slice[sub_binary_string_size + 1];
             strncpy(bit_slice, binary_string, sub_binary_string_size);
             bit_slice[sub_binary_string_size] = '\0';
             if(is_calypso_subnode_present(
-                   binary_string + offset, key, structure->elements[i].bitmap)) {
+                   binary_string + offset, key, structure->container->elements[i].bitmap)) {
                 return true;
             }
-            offset += structure->elements[i].bitmap->size;
+            offset += structure->container->elements[i].bitmap->size;
         }
     }
     return false;
@@ -145,68 +185,92 @@ bool is_calypso_node_present(const char* binary_string, const char* key, Calypso
 int get_calypso_subnode_offset(
     const char* binary_string,
     const char* key,
-    CalypsoBitmapElement* bitmap,
+    CalypsoElement* elem,
     bool* found) {
-    char bit_slice[bitmap->size + 1];
-    strncpy(bit_slice, binary_string, bitmap->size);
-    bit_slice[bitmap->size] = '\0';
+    // recursive function to get the offset of a subnode in a calypso binary string
+    if(elem->type == CALYPSO_ELEMENT_TYPE_FINAL) {
+        if(strcmp(elem->final->key, key) == 0) {
+            *found = true;
+            return 0;
+        }
+        return elem->final->size;
+    } else if(elem->type == CALYPSO_ELEMENT_TYPE_BITMAP) {
+        CalypsoBitmapElement* bitmap = elem->bitmap;
 
-    int count = 0;
-    int* positions = get_bit_positions(bit_slice, &count);
+        char bit_slice[bitmap->size + 1];
+        strncpy(bit_slice, binary_string, bitmap->size);
+        bit_slice[bitmap->size] = '\0';
 
-    int count_offset = bitmap->size;
-    for(int i = 0; i < count; i++) {
-        CalypsoElement element = bitmap->elements[positions[i]];
-        if(element.type == CALYPSO_ELEMENT_TYPE_FINAL) {
-            if(strcmp(element.final->key, key) == 0) {
+        int count = 0;
+        int* positions = get_bitmap_positions(bit_slice, &count);
+        bool f = false;
+
+        int count_offset = bitmap->size;
+        for(int i = 0; i < count; i++) {
+            CalypsoElement element = bitmap->elements[positions[i]];
+            count_offset +=
+                get_calypso_subnode_offset(binary_string + count_offset, key, &element, &f);
+            if(f) {
                 *found = true;
                 free(positions);
                 return count_offset;
             }
-            count_offset += element.final->size;
-        } else {
-            if(strcmp(element.bitmap->key, key) == 0) {
+        }
+
+        free(positions);
+        return count_offset;
+    } else if(elem->type == CALYPSO_ELEMENT_TYPE_CONTAINER) {
+        // same as bitmap but without bitmap at the beginning
+        CalypsoContainerElement* container = elem->container;
+
+        int count_offset = 0;
+        bool f = false;
+        for(int i = 0; i < container->size; i++) {
+            CalypsoElement element = container->elements[i];
+            count_offset +=
+                get_calypso_subnode_offset(binary_string + count_offset, key, &element, &f);
+            if(f) {
                 *found = true;
-                free(positions);
                 return count_offset;
             }
+        }
+
+        return count_offset;
+    } else if(elem->type == CALYPSO_ELEMENT_TYPE_REPEATER) {
+        // same as bitmap but instead of a bitmap, we have the count of how many times to repeat the inner element
+        CalypsoRepeaterElement* repeater = elem->repeater;
+
+        char bit_slice[repeater->size + 1];
+        strncpy(bit_slice, binary_string, repeater->size);
+        bit_slice[repeater->size] = '\0';
+
+        int C = bit_slice_to_dec(bit_slice, 0, repeater->size - 1);
+
+        int count_offset = repeater->size;
+        bool f = false;
+        for(int i = 0; i < C; i++) {
             count_offset += get_calypso_subnode_offset(
-                binary_string + count_offset, key, element.bitmap, found);
-            if(*found) {
-                free(positions);
+                binary_string + count_offset, key, &repeater->element, &f);
+            if(f) {
+                *found = true;
                 return count_offset;
             }
         }
     }
-    free(positions);
-    return count_offset;
+    return 0;
 }
 
 int get_calypso_node_offset(const char* binary_string, const char* key, CalypsoApp* structure) {
-    int count = 0;
-    bool found = false;
-    for(int i = 0; i < structure->elements_size; i++) {
-        if(structure->elements[i].type == CALYPSO_ELEMENT_TYPE_FINAL) {
-            if(strcmp(structure->elements[i].final->key, key) == 0) {
-                return count;
-            }
-            count += structure->elements[i].final->size;
-        } else {
-            if(strcmp(structure->elements[i].bitmap->key, key) == 0) {
-                return count;
-            }
-            int sub_binary_string_size = structure->elements[i].bitmap->size;
-            char bit_slice[sub_binary_string_size + 1];
-            strncpy(bit_slice, binary_string + count, sub_binary_string_size);
-            bit_slice[sub_binary_string_size] = '\0';
-            count += get_calypso_subnode_offset(
-                binary_string + count, key, structure->elements[i].bitmap, &found);
-            if(found) {
-                return count;
-            }
-        }
+    CalypsoElement* element = malloc(sizeof(CalypsoElement));
+    element->type = CALYPSO_ELEMENT_TYPE_CONTAINER;
+    element->container = structure->container;
+    bool found;
+    int offset = get_calypso_subnode_offset(binary_string, key, element, &found);
+    if(!found) {
+        FURI_LOG_E("Metroflip:Scene:Calypso", "Key %s not found in calypso structure", key);
     }
-    return 0;
+    free(element);
+    return offset;
 }
 
 int get_calypso_subnode_size(const char* key, CalypsoElement* element) {
@@ -214,7 +278,7 @@ int get_calypso_subnode_size(const char* key, CalypsoElement* element) {
         if(strcmp(element->final->key, key) == 0) {
             return element->final->size;
         }
-    } else {
+    } else if(element->type == CALYPSO_ELEMENT_TYPE_BITMAP) {
         if(strcmp(element->bitmap->key, key) == 0) {
             return element->bitmap->size;
         }
@@ -224,28 +288,132 @@ int get_calypso_subnode_size(const char* key, CalypsoElement* element) {
                 return size;
             }
         }
+    } else if(element->type == CALYPSO_ELEMENT_TYPE_CONTAINER) {
+        if(strcmp(element->container->key, key) == 0) {
+            return element->container->size;
+        }
+        for(int i = 0; i < element->container->size; i++) {
+            int size = get_calypso_subnode_size(key, &element->container->elements[i]);
+            if(size != 0) {
+                return size;
+            }
+        }
+    } else if(element->type == CALYPSO_ELEMENT_TYPE_REPEATER) {
+        if(strcmp(element->repeater->key, key) == 0) {
+            return element->repeater->size;
+        }
+        int size = get_calypso_subnode_size(key, &element->repeater->element);
+        if(size != 0) {
+            return size;
+        }
     }
     return 0;
 }
 
 int get_calypso_node_size(const char* key, CalypsoApp* structure) {
-    for(int i = 0; i < structure->elements_size; i++) {
-        if(structure->elements[i].type == CALYPSO_ELEMENT_TYPE_FINAL) {
-            if(strcmp(structure->elements[i].final->key, key) == 0) {
-                return structure->elements[i].final->size;
-            }
-        } else {
-            if(strcmp(structure->elements[i].bitmap->key, key) == 0) {
-                return structure->elements[i].bitmap->size;
-            }
-            for(int j = 0; j < structure->elements[i].bitmap->size; j++) {
-                int size =
-                    get_calypso_subnode_size(key, &structure->elements[i].bitmap->elements[j]);
-                if(size != 0) {
-                    return size;
-                }
-            }
+    CalypsoElement* element = malloc(sizeof(CalypsoElement));
+    element->type = CALYPSO_ELEMENT_TYPE_CONTAINER;
+    element->container = structure->container;
+    int count = get_calypso_subnode_size(key, element);
+    free(element);
+    return count;
+}
+
+CALYPSO_CARD_TYPE guess_card_type(int country_num, int network_num) {
+    switch(country_num) {
+    case 56:
+        switch(network_num) {
+        case 1:
+            return CALYPSO_CARD_MOBIB;
+        default:
+            return CALYPSO_CARD_UNKNOWN;
+        }
+    case 124:
+        switch(network_num) {
+        case 1:
+            return CALYPSO_CARD_OPUS;
+        default:
+            return CALYPSO_CARD_UNKNOWN;
+        }
+    case 131:
+        return CALYPSO_CARD_VIVA;
+    case 250:
+        switch(network_num) {
+        case 0:
+            return CALYPSO_CARD_PASSPASS;
+        case 64:
+            return CALYPSO_CARD_TAM; // Montpellier
+        case 149:
+            return CALYPSO_CARD_TRANSPOLE; // Lille
+        case 502:
+            return CALYPSO_CARD_OURA;
+        case 901:
+            return CALYPSO_CARD_NAVIGO;
+        case 908:
+            return CALYPSO_CARD_KORRIGO;
+        case 916:
+            return CALYPSO_CARD_TISSEO;
+        case 920:
+            return CALYPSO_CARD_ENVIBUS;
+        case 921:
+            return CALYPSO_CARD_GIRONDE;
+        default:
+            return CALYPSO_CARD_UNKNOWN;
         }
+    case 376:
+        return CALYPSO_CARD_RAVKAV;
+    default:
+        return CALYPSO_CARD_UNKNOWN;
+    }
+}
+
+const char* get_country_string(int country_num) {
+    switch(country_num) {
+    case 56:
+        return "Belgium";
+    case 124:
+        return "Canada";
+    case 131:
+        return "Portugal";
+    case 250:
+        return "France";
+    case 376:
+        return "Israel";
+    default: {
+        char* country = malloc(4 * sizeof(char));
+        snprintf(country, 4, "%d", country_num);
+        return country;
+    }
+    }
+}
+
+const char* get_network_string(CALYPSO_CARD_TYPE card_type) {
+    switch(card_type) {
+    case CALYPSO_CARD_MOBIB:
+        return "Mobib";
+    case CALYPSO_CARD_OPUS:
+        return "Opus";
+    case CALYPSO_CARD_VIVA:
+        return "Viva";
+    case CALYPSO_CARD_PASSPASS:
+        return "PassPass";
+    case CALYPSO_CARD_TAM:
+        return "TAM";
+    case CALYPSO_CARD_OURA:
+        return "Oura";
+    case CALYPSO_CARD_NAVIGO:
+        return "IDFM";
+    case CALYPSO_CARD_KORRIGO:
+        return "KorriGo";
+    case CALYPSO_CARD_TISSEO:
+        return "Tisseo";
+    case CALYPSO_CARD_ENVIBUS:
+        return "Envibus";
+    case CALYPSO_CARD_GIRONDE:
+        return "Gironde";
+    case CALYPSO_CARD_RAVKAV:
+        return "Rav-Kav";
+    default:
+        return "Unknown";
     }
-    return 0;
 }

+ 30 - 8
metroflip/api/calypso/calypso_util.h

@@ -1,10 +1,14 @@
 #include <stdbool.h>
+#include "calypso_i.h"
 
 #ifndef CALYPSO_UTIL_H
 #define CALYPSO_UTIL_H
 
 typedef enum {
+    CALYPSO_APP_ENV_HOLDER,
     CALYPSO_APP_CONTRACT,
+    CALYPSO_APP_EVENT,
+    CALYPSO_APP_COUNTER,
 } CalypsoAppType;
 
 typedef enum {
@@ -20,21 +24,28 @@ typedef enum {
     CALYPSO_FINAL_TYPE_NETWORK_ID,
     CALYPSO_FINAL_TYPE_TRANSPORT_TYPE,
     CALYPSO_FINAL_TYPE_CARD_STATUS,
+    CALYPSO_FINAL_TYPE_STRING,
 } CalypsoFinalType;
 
 typedef enum {
+    CALYPSO_ELEMENT_TYPE_REPEATER,
+    CALYPSO_ELEMENT_TYPE_CONTAINER,
     CALYPSO_ELEMENT_TYPE_BITMAP,
     CALYPSO_ELEMENT_TYPE_FINAL
 } CalypsoElementType;
 
 typedef struct CalypsoFinalElement_t CalypsoFinalElement;
 typedef struct CalypsoBitmapElement_t CalypsoBitmapElement;
+typedef struct CalypsoContainerElement_t CalypsoContainerElement;
+typedef struct CalypsoRepeaterElement_t CalypsoRepeaterElement;
 
 typedef struct {
     CalypsoElementType type;
     union {
         CalypsoFinalElement* final;
         CalypsoBitmapElement* bitmap;
+        CalypsoContainerElement* container;
+        CalypsoRepeaterElement* repeater;
     };
 } CalypsoElement;
 
@@ -51,10 +62,21 @@ struct CalypsoBitmapElement_t {
     CalypsoElement* elements;
 };
 
+struct CalypsoContainerElement_t {
+    char key[36];
+    int size;
+    CalypsoElement* elements;
+};
+
+struct CalypsoRepeaterElement_t {
+    char key[36];
+    int size;
+    CalypsoElement element;
+};
+
 typedef struct {
     CalypsoAppType type;
-    CalypsoElement* elements;
-    int elements_size;
+    CalypsoContainerElement* container;
 } CalypsoApp;
 
 CalypsoElement make_calypso_final_element(
@@ -65,16 +87,16 @@ CalypsoElement make_calypso_final_element(
 
 CalypsoElement make_calypso_bitmap_element(const char* key, int size, CalypsoElement* elements);
 
-void free_calypso_structure(CalypsoApp* structure);
+CalypsoElement make_calypso_container_element(const char* key, int size, CalypsoElement* elements);
 
-int* get_bit_positions(const char* binary_string, int* count);
+CalypsoElement make_calypso_repeater_element(const char* key, int size, CalypsoElement element);
 
-int is_bit_present(int* positions, int count, int bit);
+int* get_bitmap_positions(const char* binary_string, int* count);
 
-bool is_calypso_node_present(const char* binary_string, const char* key, CalypsoApp* structure);
+int is_bit_present(int* positions, int count, int bit);
 
-int get_calypso_node_offset(const char* binary_string, const char* key, CalypsoApp* structure);
+// Calypso known Card types
 
-int get_calypso_node_size(const char* key, CalypsoApp* structure);
+const char* get_country_string(int country_num);
 
 #endif // CALYPSO_UTIL_H

+ 400 - 19
metroflip/api/calypso/cards/navigo.c → metroflip/api/calypso/cards/intercode.c

@@ -1,19 +1,189 @@
 #include <stdlib.h>
-#include "navigo.h"
+#include "intercode.h"
+#include "../../metroflip/metroflip_api.h"
 
-CalypsoApp* get_navigo_contract_structure() {
-    CalypsoApp* NavigoContractStructure = malloc(sizeof(CalypsoApp));
-    if(!NavigoContractStructure) {
+CalypsoApp* get_intercode_structure_env_holder() {
+    CalypsoApp* IntercodeEnvHolderStructure = malloc(sizeof(CalypsoApp));
+    if(!IntercodeEnvHolderStructure) {
+        return NULL;
+    }
+
+    int app_elements_count = 3;
+
+    IntercodeEnvHolderStructure->type = CALYPSO_APP_ENV_HOLDER;
+    IntercodeEnvHolderStructure->container = malloc(sizeof(CalypsoContainerElement));
+    IntercodeEnvHolderStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    IntercodeEnvHolderStructure->container->size = app_elements_count;
+
+    IntercodeEnvHolderStructure->container->elements[0] = make_calypso_final_element(
+        "EnvApplicationVersionNumber",
+        6,
+        "Numéro de version de l’application Billettique",
+        CALYPSO_FINAL_TYPE_NUMBER);
+    IntercodeEnvHolderStructure->container->elements[1] = make_calypso_bitmap_element(
+        "Env",
+        7,
+        (CalypsoElement[]){
+            make_calypso_final_element(
+                "EnvNetworkId", 24, "Identification du réseau", CALYPSO_FINAL_TYPE_NUMBER),
+            make_calypso_final_element(
+                "EnvApplicationIssuerId",
+                8,
+                "Identification de l’émetteur de l’application",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EnvApplicationValidityEndDate",
+                14,
+                "Date de fin de validité de l’application",
+                CALYPSO_FINAL_TYPE_DATE),
+            make_calypso_final_element(
+                "EnvPayMethod", 11, "Code mode de paiement", CALYPSO_FINAL_TYPE_PAY_METHOD),
+            make_calypso_final_element(
+                "EnvAuthenticator",
+                16,
+                "Code de contrôle de l’intégrité des données",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EnvSelectList",
+                32,
+                "Bitmap de tableau de paramètre multiple",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_container_element(
+                "EnvData",
+                2,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "EnvDataCardStatus", 1, "Statut de la carte", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EnvData2", 0, "Données complémentaires", CALYPSO_FINAL_TYPE_UNKNOWN),
+                }),
+        });
+    IntercodeEnvHolderStructure->container->elements[2] = make_calypso_bitmap_element(
+        "Holder",
+        8,
+        (CalypsoElement[]){
+            make_calypso_bitmap_element(
+                "HolderName",
+                2,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "HolderSurname", 85, "Nom du porteur", CALYPSO_FINAL_TYPE_STRING),
+                    make_calypso_final_element(
+                        "HolderForename",
+                        85,
+                        "Prénom de naissance du porteur",
+                        CALYPSO_FINAL_TYPE_STRING),
+                }),
+            make_calypso_bitmap_element(
+                "HolderBirth",
+                2,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "HolderBirthDate", 32, "Date de naissance", CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "HolderBirthPlace",
+                        115,
+                        "Lieu de naissance (23 caractères)",
+                        CALYPSO_FINAL_TYPE_STRING),
+                }),
+            make_calypso_final_element(
+                "HolderBirthName",
+                85,
+                "Nom de naissance du porteur (17 caractères)",
+                CALYPSO_FINAL_TYPE_STRING),
+            make_calypso_final_element(
+                "HolderIdNumber", 32, "Identifiant Porteur", CALYPSO_FINAL_TYPE_NUMBER),
+            make_calypso_final_element(
+                "HolderCountryAlpha", 24, "Pays du titulaire", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "HolderCompany", 32, "Société du titulaire", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_bitmap_element(
+                "HolderProfiles",
+                4,
+                (CalypsoElement[]){
+                    make_calypso_bitmap_element(
+                        "HolderProfileBitmap",
+                        3,
+                        (CalypsoElement[]){
+                            make_calypso_final_element(
+                                "HolderNetworkId", 24, "Réseau", CALYPSO_FINAL_TYPE_UNKNOWN),
+                            make_calypso_final_element(
+                                "HolderProfileNumber",
+                                8,
+                                "Numéro du statut",
+                                CALYPSO_FINAL_TYPE_NUMBER),
+                            make_calypso_final_element(
+                                "HolderProfileDate",
+                                14,
+                                "Date de fin de validité du statut",
+                                CALYPSO_FINAL_TYPE_DATE),
+                        }),
+                }),
+            make_calypso_bitmap_element(
+                "HolderData",
+                12,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "HolderDataCardStatus", 4, "Type de carte", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataTeleReglement", 4, "Télérèglement", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataResidence", 17, "Ville du domicile", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataCommercialID", 6, "Produit carte", CALYPSO_FINAL_TYPE_NUMBER),
+                    make_calypso_final_element(
+                        "HolderDataWorkPlace", 17, "Lieu de travail", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataStudyPlace", 17, "Lieu d'étude", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataSaleDevice",
+                        16,
+                        "Numéro logique de SAM",
+                        CALYPSO_FINAL_TYPE_NUMBER),
+                    make_calypso_final_element(
+                        "HolderDataAuthenticator", 16, "Signature", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "HolderDataProfileStartDate1",
+                        14,
+                        "Date de début de validité du statut",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "HolderDataProfileStartDate2",
+                        14,
+                        "Date de début de validité du statut",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "HolderDataProfileStartDate3",
+                        14,
+                        "Date de début de validité du statut",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "HolderDataProfileStartDate4",
+                        14,
+                        "Date de début de validité du statut",
+                        CALYPSO_FINAL_TYPE_DATE),
+                }),
+        });
+
+    return IntercodeEnvHolderStructure;
+}
+
+CalypsoApp* get_intercode_structure_contract() {
+    CalypsoApp* IntercodeContractStructure = malloc(sizeof(CalypsoApp));
+    if(!IntercodeContractStructure) {
         return NULL;
     }
 
     int app_elements_count = 1;
 
-    NavigoContractStructure->type = CALYPSO_APP_CONTRACT;
-    NavigoContractStructure->elements = malloc(app_elements_count * sizeof(CalypsoElement));
-    NavigoContractStructure->elements_size = app_elements_count;
+    IntercodeContractStructure->type = CALYPSO_APP_CONTRACT;
+    IntercodeContractStructure->container = malloc(sizeof(CalypsoContainerElement));
+    IntercodeContractStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    IntercodeContractStructure->container->size = app_elements_count;
 
-    NavigoContractStructure->elements[0] = make_calypso_bitmap_element(
+    IntercodeContractStructure->container->elements[0] = make_calypso_bitmap_element(
         "Contract",
         20,
         (CalypsoElement[]){
@@ -243,28 +413,30 @@ CalypsoApp* get_navigo_contract_structure() {
                 "ContractData(0..255)", 0, "Données complémentaires", CALYPSO_FINAL_TYPE_UNKNOWN),
         });
 
-    return NavigoContractStructure;
+    return IntercodeContractStructure;
 }
 
-CalypsoApp* get_navigo_event_structure() {
-    CalypsoApp* NavigoEventStructure = malloc(sizeof(CalypsoApp));
-    if(!NavigoEventStructure) {
+CalypsoApp* get_intercode_structure_event() {
+    CalypsoApp* IntercodeEventStructure = malloc(sizeof(CalypsoApp));
+    if(!IntercodeEventStructure) {
         return NULL;
     }
 
     int app_elements_count = 3;
 
-    NavigoEventStructure->type = CALYPSO_APP_CONTRACT;
-    NavigoEventStructure->elements = malloc(app_elements_count * sizeof(CalypsoElement));
-    NavigoEventStructure->elements_size = app_elements_count;
+    IntercodeEventStructure->type = CALYPSO_APP_EVENT;
+    IntercodeEventStructure->container = malloc(sizeof(CalypsoContainerElement));
+    IntercodeEventStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    IntercodeEventStructure->container->size = app_elements_count;
 
-    NavigoEventStructure->elements[0] = make_calypso_final_element(
+    IntercodeEventStructure->container->elements[0] = make_calypso_final_element(
         "EventDateStamp", 14, "Date de l’événement", CALYPSO_FINAL_TYPE_DATE);
 
-    NavigoEventStructure->elements[1] = make_calypso_final_element(
+    IntercodeEventStructure->container->elements[1] = make_calypso_final_element(
         "EventTimeStamp", 11, "Heure de l’événement", CALYPSO_FINAL_TYPE_TIME);
 
-    NavigoEventStructure->elements[2] = make_calypso_bitmap_element(
+    IntercodeEventStructure->container->elements[2] = make_calypso_bitmap_element(
         "EventBitmap",
         28,
         (CalypsoElement[]){
@@ -375,5 +547,214 @@ CalypsoApp* get_navigo_event_structure() {
                 }),
         });
 
-    return NavigoEventStructure;
+    return IntercodeEventStructure;
+}
+
+CalypsoApp* get_intercode_structure_counter() {
+    CalypsoApp* IntercodeCounterStructure = malloc(sizeof(CalypsoApp));
+    if(!IntercodeCounterStructure) {
+        return NULL;
+    }
+
+    int app_elements_count = 2;
+
+    IntercodeCounterStructure->type = CALYPSO_APP_COUNTER;
+    IntercodeCounterStructure->container = malloc(sizeof(CalypsoContainerElement));
+    IntercodeCounterStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    IntercodeCounterStructure->container->size = app_elements_count;
+
+    IntercodeCounterStructure->container->elements[0] = make_calypso_final_element(
+        "CounterContractCount", 6, "Nombre de titres du carnet", CALYPSO_FINAL_TYPE_NUMBER);
+
+    IntercodeCounterStructure->container->elements[1] = make_calypso_final_element(
+        "CounterRelativeFirstStamp15mn",
+        18,
+        "Temps relatif de la première validation (au quart d'heure près)",
+        CALYPSO_FINAL_TYPE_NUMBER);
+
+    return IntercodeCounterStructure;
+}
+
+const char* get_intercode_string_transition_type(int transition) {
+    switch(transition) {
+    case 0x1:
+        return "Entry (First validation)";
+    case 0x2:
+        return "Exit";
+    case 0x3:
+        return "Validation";
+    case 0x4:
+        return "Inspection";
+    case 0x5:
+        return "Test validation";
+    case 0x6:
+        return "Entry (Interchange)";
+    case 0x7:
+        return "Exit (Interchange)";
+    case 0x9:
+        return "Validation cancelled";
+    case 0xA:
+        return "Entry (Public road)";
+    case 0xB:
+        return "Exit (Public road)";
+    case 0xD:
+        return "Distribution";
+    case 0xF:
+        return "Invalidation";
+    default: {
+        char* transition_str = malloc(6 * sizeof(char));
+        snprintf(transition_str, 6, "%d", transition);
+        return transition_str;
+    }
+    }
+}
+
+const char* get_intercode_string_transport_type(int type) {
+    switch(type) {
+    case URBAN_BUS:
+        return "Urban Bus";
+    case INTERURBAN_BUS:
+        return "Interurban Bus";
+    case METRO:
+        return "Metro";
+    case TRAM:
+        return "Tram";
+    case COMMUTER_TRAIN:
+        return "Train";
+    case PARKING:
+        return "Parking";
+    default:
+        return "Unknown";
+    }
+}
+
+const char* get_intercode_string_pay_method(int pay_method) {
+    switch(pay_method) {
+    case 0x30:
+        return "Apple Pay/Google Pay";
+    case 0x80:
+        return "Debit PME";
+    case 0x90:
+        return "Cash";
+    case 0xA0:
+        return "Mobility Check";
+    case 0xB3:
+        return "Payment Card";
+    case 0xA4:
+        return "Check";
+    case 0xA5:
+        return "Vacation Check";
+    case 0xB7:
+        return "Telepayment";
+    case 0xD0:
+        return "Remote Payment";
+    case 0xD7:
+        return "Voucher, Prepayment, Exchange Voucher, Travel Voucher";
+    case 0xD9:
+        return "Discount Voucher";
+    default:
+        return "Unknown";
+    }
+}
+
+const char* get_intercode_string_event_result(int result) {
+    switch(result) {
+    case 0x0:
+        return "OK";
+    case 0x7:
+        return "Transfer / Dysfunction";
+    case 0x8:
+        return "Disabled due to fraud";
+    case 0x9:
+        return "Disabled due to monetary fraud";
+    case 0xA:
+        return "Invalidation impossible";
+    case 0x30:
+        return "Double validation (Entry)";
+    case 0x31:
+        return "Invalid zone";
+    case 0x32:
+        return "Contract expired";
+    case 0x33:
+        return "Double validation (Exit)";
+    default: {
+        char* result_str = malloc(6 * sizeof(char));
+        if(!result_str) {
+            return "Unknown";
+        }
+        snprintf(result_str, 6, "%d", result);
+        return result_str;
+    }
+    }
+}
+
+const char* get_intercode_string_version(int version) {
+    // version is a 6 bits int
+    // if the first 3 bits are 000, it's a 1.x version
+    // if the first 3 bits are 001, it's a 2.x version
+    // else, it's unknown
+    int major = (version >> 3) & 0x07;
+    if(major == 0) {
+        return "Intercode I";
+    } else if(major == 1) {
+        return "Intercode II";
+    }
+    return "Unknown";
+}
+
+int get_intercode_string_subversion(int version) {
+    // subversion is a 3 bits int
+    return version & 0x07;
+}
+
+const char* get_intercode_string_holder_type(int card_status) {
+    // b3 -> RFU
+    // b2 -> linked to an organization
+    // b1..b0 -> personalization status (0: anonymous, 1: identified, 2: personalized, 3: networkSpecific)
+    int status = card_status & 0x03;
+    switch(status) {
+    case 0:
+        return "Anonymous";
+    case 1:
+        return "Identified";
+    case 2:
+        return "Personalized";
+    case 3:
+        return "Network Specific";
+    default:
+        return "Unknown";
+    }
+}
+
+bool is_intercode_string_holder_linked(int card_status) {
+    // b3 -> RFU
+    // b2 -> linked to an organization
+    // b1..b0 -> personalization status (0: anonymous, 1: identified, 2: personalized, 3: networkSpecific)
+    return card_status & 0x04;
+}
+
+const char* get_intercode_string_contract_status(int status) {
+    switch(status) {
+    case 0x0:
+        return "Valid (never used)";
+    case 0x1:
+        return "Valid (used)";
+    case 0x3:
+        return "Renewal required";
+    case 0xD:
+        return "Not validable";
+    case 0x13:
+        return "Blocked";
+    case 0x3F:
+        return "Suspended";
+    case 0x58:
+        return "Invalid";
+    case 0x7F:
+        return "Refunded";
+    case 0xFF:
+        return "Erasable";
+    default:
+        return "Unknown";
+    }
 }

+ 63 - 0
metroflip/api/calypso/cards/intercode.h

@@ -0,0 +1,63 @@
+#include "../calypso_util.h"
+
+#ifndef INTERCODE_STRUCTURES_H
+#define INTERCODE_STRUCTURES_H
+
+const char* get_intercode_string_transition_type(int transition);
+
+const char* get_intercode_string_transport_type(int type);
+
+const char* get_intercode_string_pay_method(int pay_method);
+
+const char* get_intercode_string_event_result(int result);
+
+const char* get_intercode_string_version(int version);
+
+int get_intercode_string_subversion(int version);
+
+const char* get_intercode_string_holder_type(int card_status);
+
+bool is_intercode_string_holder_linked(int card_status);
+
+const char* get_intercode_string_contract_status(int status);
+
+typedef enum {
+    URBAN_BUS = 1,
+    INTERURBAN_BUS = 2,
+    METRO = 3,
+    TRAM = 4,
+    COMMUTER_TRAIN = 5,
+    WATERBORNE_VEHICLE = 6,
+    TOLL = 7,
+    PARKING = 8,
+    TAXI = 9,
+    HIGH_SPEED_TRAIN = 10,
+    RURAL_BUS = 11,
+    EXPRESS_COMMUTER_TRAIN = 12,
+    PARA_TRANSIT = 13,
+    SELF_DRIVE_VEHICLE = 14,
+    COACH = 15,
+    LOCOMOTIVE = 16,
+    POWERED_MOTOR_VEHICLE = 17,
+    TRAILER = 18,
+    REGIONAL_TRAIN = 19,
+    INTER_CITY = 20,
+    FUNICULAR = 21,
+    CABLE_CAR = 22,
+    SELF_SERVICE_BICYCLE = 23,
+    CAR_SHARING = 24,
+    CAR_POOLING = 25,
+} INTERCODE_TRANSPORT_TYPE;
+
+typedef enum {
+    ENTRY = 1,
+    EXIT = 2,
+    PASSAGE = 3,
+    CHECKPOINT_INSPECTION = 4,
+    AUTONOMOUS = 5,
+    INTERCHANGE = 6,
+    VALIDATION = 7,
+    PRESENCE_DETECTED = 8,
+} INTERCODE_USER_ACTION;
+
+#endif // INTERCODE_STRUCTURES_H

+ 0 - 10
metroflip/api/calypso/cards/navigo.h

@@ -1,10 +0,0 @@
-#include "../calypso_util.h"
-
-#ifndef NAVIGO_STRUCTURES_H
-#define NAVIGO_STRUCTURES_H
-
-CalypsoApp* get_navigo_contract_structure();
-
-CalypsoApp* get_navigo_event_structure();
-
-#endif // NAVIGO_STRUCTURES_H

+ 276 - 0
metroflip/api/calypso/cards/opus.c

@@ -0,0 +1,276 @@
+#include <stdlib.h>
+#include "../../metroflip/metroflip_api.h"
+
+CalypsoApp* get_opus_contract_structure() {
+    CalypsoApp* OpusContractStructure = malloc(sizeof(CalypsoApp));
+
+    if(!OpusContractStructure) {
+        return NULL;
+    }
+
+    int app_elements_count = 1;
+
+    OpusContractStructure->type = CALYPSO_APP_CONTRACT;
+    OpusContractStructure->container = malloc(sizeof(CalypsoContainerElement));
+    OpusContractStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    OpusContractStructure->container->size = app_elements_count;
+
+    OpusContractStructure->container->elements[0] = make_calypso_bitmap_element(
+        "Contract",
+        7,
+        (CalypsoElement[]){
+            make_calypso_final_element(
+                "ContractProvider",
+                8,
+                "Acteur ou groupe d’acteurs ayant dèfini et assurant le service pour le contrat",
+                CALYPSO_FINAL_TYPE_SERVICE_PROVIDER),
+            make_calypso_final_element(
+                "ContractTariff", 16, "Code tarif du contrat", CALYPSO_FINAL_TYPE_TARIFF),
+            make_calypso_bitmap_element(
+                "ContractValidityInfoBitmap",
+                2,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "ContractValidityStartDate",
+                        14,
+                        "Date de début de validité du contrat",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "ContractValidityEndDate",
+                        14,
+                        "Date de fin de validité du contrat",
+                        CALYPSO_FINAL_TYPE_DATE),
+                }),
+            make_calypso_bitmap_element(
+                "ContractData",
+                9,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "ContractDataSaleAgent",
+                        8,
+                        "Acteur ayant effectué la dernière vente sur le contrat",
+                        CALYPSO_FINAL_TYPE_SERVICE_PROVIDER),
+                    make_calypso_final_element(
+                        "ContractDataSaleSecureDevice",
+                        32,
+                        "Numéro du SAM utilisé pour charger le contrat",
+                        CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "ContractDataSaleDate",
+                        14,
+                        "Date de chargement initial du contrat",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "ContractDataSaleTime",
+                        11,
+                        "Heure de chargement initial du contrat",
+                        CALYPSO_FINAL_TYPE_TIME),
+                    make_calypso_final_element(
+                        "ContractDataReloadDate",
+                        14,
+                        "Date de rechargement du contrat",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "ContractDataValidityLimitDate",
+                        14,
+                        "Date limite pour une première utilisation du contrat",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "ContractDataEndInhibitionDate",
+                        14,
+                        "Date jusqu’à laquelle la prèsence du contrat dans une liste de suspension est ignorée",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "ContractDataInhibition", 1, "Contrat invalide", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "ContractDataUsed",
+                        1,
+                        "Contrat déjà validé au moins une fois",
+                        CALYPSO_FINAL_TYPE_UNKNOWN),
+                }),
+            make_calypso_final_element(
+                "ContractUnknownE", 0, "Unknown E", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "ContractUnknownF", 0, "Unknown F", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "ContractUnknownG", 0, "Unknown G", CALYPSO_FINAL_TYPE_UNKNOWN),
+        });
+
+    return OpusContractStructure;
+}
+
+CalypsoApp* get_opus_event_structure() {
+    CalypsoApp* OpusEventStructure = malloc(sizeof(CalypsoApp));
+
+    if(!OpusEventStructure) {
+        return NULL;
+    }
+
+    int app_elements_count = 4;
+
+    OpusEventStructure->type = CALYPSO_APP_EVENT;
+    OpusEventStructure->container = malloc(sizeof(CalypsoContainerElement));
+    OpusEventStructure->container->elements = malloc(app_elements_count * sizeof(CalypsoElement));
+    OpusEventStructure->container->size = app_elements_count;
+
+    OpusEventStructure->container->elements[0] =
+        make_calypso_final_element("EventDateStamp", 14, "Event date", CALYPSO_FINAL_TYPE_DATE);
+    OpusEventStructure->container->elements[1] =
+        make_calypso_final_element("EventTimeStamp", 11, "Event time", CALYPSO_FINAL_TYPE_TIME);
+    OpusEventStructure->container->elements[2] =
+        make_calypso_final_element("EventUnknownX", 19, "Unknown X", CALYPSO_FINAL_TYPE_NUMBER);
+    OpusEventStructure->container->elements[3] = make_calypso_bitmap_element(
+        "Event",
+        9,
+        (CalypsoElement[]){
+            make_calypso_final_element("EventUnknownA", 8, "Unknown A", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventResult", 8, "Code Résultat", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventServiceProvider",
+                8,
+                "Identité de l’exploitant",
+                CALYPSO_FINAL_TYPE_SERVICE_PROVIDER),
+            make_calypso_final_element(
+                "EventLocationId", 16, "Lieu de l’événement", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventRouteNumber", 16, "Référence de la ligne", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventUnknownD", 16, "Unknown D", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventUnknownE", 16, "Unknown E", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventContractPointer",
+                5,
+                "Référence du contrat concerné",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_bitmap_element(
+                "EventData",
+                7,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "EventDataDateFirstStamp",
+                        14,
+                        "Date de la première montée",
+                        CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "EventDataTimeFirstStamp",
+                        11,
+                        "Heure de la première montée",
+                        CALYPSO_FINAL_TYPE_TIME),
+                    make_calypso_final_element(
+                        "EventDataSimulation", 1, "Simulation", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EventDataRouteDirection", 4, "Sens", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EventUnknownG", 4, "Unknown G", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EventUnknownH", 4, "Unknown H", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EventUnknownI", 4, "Unknown I", CALYPSO_FINAL_TYPE_UNKNOWN),
+                }),
+        });
+
+    return OpusEventStructure;
+}
+
+CalypsoApp* get_opus_env_holder_structure() {
+    CalypsoApp* OpusEnvHolderStructure = malloc(sizeof(CalypsoApp));
+    if(!OpusEnvHolderStructure) {
+        return NULL;
+    }
+
+    int app_elements_count = 3;
+
+    OpusEnvHolderStructure->type = CALYPSO_APP_ENV_HOLDER;
+    OpusEnvHolderStructure->container = malloc(sizeof(CalypsoContainerElement));
+    OpusEnvHolderStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    OpusEnvHolderStructure->container->size = app_elements_count;
+
+    OpusEnvHolderStructure->container->elements[0] = make_calypso_final_element(
+        "EnvApplicationVersionNumber",
+        6,
+        "Numéro de version de l’application Billettique",
+        CALYPSO_FINAL_TYPE_NUMBER);
+    OpusEnvHolderStructure->container->elements[1] = make_calypso_bitmap_element(
+        "Env",
+        7,
+        (CalypsoElement[]){
+            make_calypso_final_element(
+                "EnvNetworkId", 24, "Identification du réseau", CALYPSO_FINAL_TYPE_NUMBER),
+            make_calypso_final_element(
+                "EnvApplicationIssuerId",
+                8,
+                "Identification de l’émetteur de l’application",
+                CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EnvApplicationValidityEndDate",
+                14,
+                "Date de fin de validité de l’application",
+                CALYPSO_FINAL_TYPE_DATE),
+            make_calypso_bitmap_element(
+                "EnvData",
+                4,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "EnvDataCardStatus", 1, "Statut de la carte", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EnvData2", 0, "Données complémentaires", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EnvData_CardUtilisation",
+                        1,
+                        "Utilisation de la carte",
+                        CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EnvData4", 0, "Données complémentaires", CALYPSO_FINAL_TYPE_UNKNOWN),
+                }),
+            make_calypso_final_element("EnvUnknownA", 0, "Unknown A", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element("EnvUnknownB", 0, "Unknown B", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element("EnvUnknownC", 0, "Unknown C", CALYPSO_FINAL_TYPE_UNKNOWN),
+        });
+
+    OpusEnvHolderStructure->container->elements[2] = make_calypso_bitmap_element(
+        "Holder",
+        8,
+        (CalypsoElement[]){
+            make_calypso_bitmap_element(
+                "HolderBirthBitmap",
+                2,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "HolderBirthDate", 32, "Date de naissance", CALYPSO_FINAL_TYPE_DATE),
+                    make_calypso_final_element(
+                        "HolderBirthUnknownA", 0, "Unknown A", CALYPSO_FINAL_TYPE_UNKNOWN),
+                }),
+            make_calypso_repeater_element(
+                "HolderProfilesList",
+                4,
+                make_calypso_bitmap_element(
+                    "HolderProfile",
+                    3,
+                    (CalypsoElement[]){
+                        make_calypso_final_element(
+                            "HolderProfileNumber", 6, "Numéro de profil", CALYPSO_FINAL_TYPE_NUMBER),
+                        make_calypso_final_element(
+                            "HolderProfileDate", 14, "Date de profil", CALYPSO_FINAL_TYPE_DATE),
+                        make_calypso_final_element(
+                            "HolderProfileUnknownA", 0, "Unknown A", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    })),
+            make_calypso_final_element(
+                "HolderData_Language", 6, "Langue de l'utilisateur", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "HolderUnknownA", 0, "Unknown A", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "HolderUnknownB", 0, "Unknown B", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "HolderUnknownC", 0, "Unknown C", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "HolderUnknownD", 0, "Unknown D", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "HolderUnknownE", 0, "Unknown E", CALYPSO_FINAL_TYPE_UNKNOWN),
+        });
+
+    return OpusEnvHolderStructure;
+}

+ 232 - 0
metroflip/api/calypso/cards/ravkav.c

@@ -0,0 +1,232 @@
+#include <stdlib.h>
+#include "../../metroflip/metroflip_api.h"
+
+CalypsoApp* get_ravkav_contract_structure() {
+    /*
+    En1545FixedInteger("Version", 3),
+    En1545FixedInteger.date(CONTRACT_START),
+    En1545FixedInteger(CONTRACT_PROVIDER, 8),
+    En1545FixedInteger(CONTRACT_TARIFF, 11),
+    En1545FixedInteger.date(CONTRACT_SALE),
+    En1545FixedInteger(CONTRACT_SALE_DEVICE, 12),
+    En1545FixedInteger("ContractSaleNumber", 10),
+    En1545FixedInteger(CONTRACT_INTERCHANGE, 1),
+    En1545Bitmap(
+            En1545FixedInteger(CONTRACT_UNKNOWN_A, 5),
+            En1545FixedInteger(CONTRACT_RESTRICT_CODE, 5),
+            En1545FixedInteger("ContractRestrictDuration", 6),
+            En1545FixedInteger.date(CONTRACT_END),
+            En1545FixedInteger(CONTRACT_DURATION, 8),
+            En1545FixedInteger(CONTRACT_UNKNOWN_B, 32),
+            En1545FixedInteger(CONTRACT_UNKNOWN_C, 6),
+            En1545FixedInteger(CONTRACT_UNKNOWN_D, 32),
+            En1545FixedInteger(CONTRACT_UNKNOWN_E, 32)
+    )
+    */
+    CalypsoApp* RavKavContractStructure = malloc(sizeof(CalypsoApp));
+
+    if(!RavKavContractStructure) {
+        return NULL;
+    }
+
+    int app_elements_count = 9;
+
+    RavKavContractStructure->type = CALYPSO_APP_CONTRACT;
+    RavKavContractStructure->container = malloc(sizeof(CalypsoContainerElement));
+    RavKavContractStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    RavKavContractStructure->container->size = app_elements_count;
+
+    RavKavContractStructure->container->elements[0] =
+        make_calypso_final_element("ContractVersion", 3, "Version", CALYPSO_FINAL_TYPE_NUMBER);
+    RavKavContractStructure->container->elements[1] =
+        make_calypso_final_element("ContractStartDate", 14, "Start Date", CALYPSO_FINAL_TYPE_DATE);
+    RavKavContractStructure->container->elements[2] =
+        make_calypso_final_element("ContractProvider", 8, "Provider", CALYPSO_FINAL_TYPE_UNKNOWN);
+    RavKavContractStructure->container->elements[3] =
+        make_calypso_final_element("ContractTariff", 11, "Tariff", CALYPSO_FINAL_TYPE_TARIFF);
+    RavKavContractStructure->container->elements[4] =
+        make_calypso_final_element("ContractSaleDate", 14, "Sale Date", CALYPSO_FINAL_TYPE_DATE);
+    RavKavContractStructure->container->elements[5] = make_calypso_final_element(
+        "ContractSaleDevice", 12, "Sale Device", CALYPSO_FINAL_TYPE_UNKNOWN);
+    RavKavContractStructure->container->elements[6] = make_calypso_final_element(
+        "ContractSaleNumber", 10, "Sale Number", CALYPSO_FINAL_TYPE_UNKNOWN);
+    RavKavContractStructure->container->elements[7] = make_calypso_final_element(
+        "ContractInterchange", 1, "Contract Interchange", CALYPSO_FINAL_TYPE_UNKNOWN);
+    RavKavContractStructure->container->elements[8] = make_calypso_bitmap_element(
+        "Contract",
+        9,
+        (CalypsoElement[]){
+            make_calypso_final_element(
+                "ContractUnknownA", 5, "Unknown A", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "ContractRestrictCode", 5, "Restrict Code", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "ContractRestrictDuration", 6, "Restrict Duration", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element("ContractEndDate", 14, "End Date", CALYPSO_FINAL_TYPE_DATE),
+            make_calypso_final_element(
+                "ContractDuration", 8, "Duration", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "ContractUnknownB", 32, "Unknown B", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "ContractUnknownC", 6, "Unknown C", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "ContractUnknownD", 32, "Unknown D", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "ContractUnknownE", 32, "Unknown E", CALYPSO_FINAL_TYPE_UNKNOWN),
+
+        });
+
+    return RavKavContractStructure;
+}
+
+CalypsoApp* get_ravkav_env_holder_structure() {
+    /*
+    En1545FixedInteger(ENV_VERSION_NUMBER, 3),
+    En1545FixedInteger(ENV_NETWORK_ID, 20),
+    En1545FixedInteger(ENV_UNKNOWN_A, 26),
+    En1545FixedInteger.date(ENV_APPLICATION_ISSUE),
+    En1545FixedInteger.date(ENV_APPLICATION_VALIDITY_END),
+    En1545FixedInteger("PayMethod", 3),
+    En1545FixedInteger.dateBCD(HOLDER_BIRTH_DATE),
+    En1545FixedHex(ENV_UNKNOWN_B, 44),
+    En1545FixedInteger(HOLDER_ID_NUMBER, 30)
+    */
+
+    CalypsoApp* RavKavEnvHolderStructure = malloc(sizeof(CalypsoApp));
+    if(!RavKavEnvHolderStructure) {
+        return NULL;
+    }
+
+    int app_elements_count = 8;
+
+    RavKavEnvHolderStructure->type = CALYPSO_APP_ENV_HOLDER;
+    RavKavEnvHolderStructure->container = malloc(sizeof(CalypsoContainerElement));
+    RavKavEnvHolderStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    RavKavEnvHolderStructure->container->size = app_elements_count;
+
+    RavKavEnvHolderStructure->container->elements[0] = make_calypso_final_element(
+        "EnvApplicationVersionNumber",
+        3,
+        "Numéro de version de l’application Billettique",
+        CALYPSO_FINAL_TYPE_NUMBER);
+    RavKavEnvHolderStructure->container->elements[1] = make_calypso_final_element(
+        "EnvCountry", 12, "Identification du réseau", CALYPSO_FINAL_TYPE_NUMBER);
+    RavKavEnvHolderStructure->container->elements[2] =
+        make_calypso_final_element("EnvIssuer", 8, "Issuer", CALYPSO_FINAL_TYPE_NUMBER);
+    RavKavEnvHolderStructure->container->elements[3] = make_calypso_final_element(
+        "EnvApplicationNumber", 26, "Application Number", CALYPSO_FINAL_TYPE_NUMBER);
+    RavKavEnvHolderStructure->container->elements[4] =
+        make_calypso_final_element("EnvDateOfIssue", 14, "Date of issue", CALYPSO_FINAL_TYPE_DATE);
+    RavKavEnvHolderStructure->container->elements[5] =
+        make_calypso_final_element("EnvEndValidity", 14, "End Validity", CALYPSO_FINAL_TYPE_DATE);
+    RavKavEnvHolderStructure->container->elements[6] =
+        make_calypso_final_element("   ", 3, "Payment method", CALYPSO_FINAL_TYPE_PAY_METHOD),
+    RavKavEnvHolderStructure->container->elements[7] = make_calypso_final_element(
+        "EnvBCDDate", 32, "Birth Date", CALYPSO_FINAL_TYPE_DATE); // might do this one later
+
+    return RavKavEnvHolderStructure;
+}
+
+CalypsoApp* get_ravkav_event_structure() {
+    /*
+    En1545FixedInteger("EventVersion", 3),
+    En1545FixedInteger(EVENT_SERVICE_PROVIDER, 8),
+    En1545FixedInteger(EVENT_CONTRACT_POINTER, 4),
+    En1545FixedInteger(EVENT_CODE, 8),
+    En1545FixedInteger.dateTime(EVENT),
+    En1545FixedInteger("EventTransferFlag", 1),
+    En1545FixedInteger.dateTime(EVENT_FIRST_STAMP),
+    En1545FixedInteger("EventContractPrefs", 32),
+    En1545Bitmap(
+            En1545FixedInteger(EVENT_LOCATION_ID, 16),
+            En1545FixedInteger(EVENT_ROUTE_NUMBER, 16),
+            En1545FixedInteger("StopEnRoute", 8),
+            En1545FixedInteger(EVENT_UNKNOWN_A, 12),
+            En1545FixedInteger(EVENT_VEHICLE_ID, 14),
+            En1545FixedInteger(EVENT_UNKNOWN_B, 4),
+            En1545FixedInteger(EVENT_UNKNOWN_C, 8)
+    ),
+    En1545Bitmap(
+            En1545Container(
+                    En1545FixedInteger("RouteSystem", 10),
+                    En1545FixedInteger("FareCode", 8),
+                    En1545FixedInteger(EVENT_PRICE_AMOUNT, 16)
+            ),
+            En1545FixedInteger(EVENT_UNKNOWN_D, 32),
+            En1545FixedInteger(EVENT_UNKNOWN_E, 32)
+    )
+    */
+    //  EventTime EventVehicle
+    CalypsoApp* RavKavEventStructure = malloc(sizeof(CalypsoApp));
+
+    if(!RavKavEventStructure) {
+        return NULL;
+    }
+
+    int app_elements_count = 11;
+
+    RavKavEventStructure->type = CALYPSO_APP_EVENT;
+    RavKavEventStructure->container = malloc(sizeof(CalypsoContainerElement));
+    RavKavEventStructure->container->elements =
+        malloc(app_elements_count * sizeof(CalypsoElement));
+    RavKavEventStructure->container->size = app_elements_count;
+
+    RavKavEventStructure->container->elements[0] =
+        make_calypso_final_element("EventVersion", 3, "Version Number", CALYPSO_FINAL_TYPE_NUMBER);
+    RavKavEventStructure->container->elements[1] = make_calypso_final_element(
+        "EventServiceProvider", 8, "Service provider", CALYPSO_FINAL_TYPE_UNKNOWN);
+    RavKavEventStructure->container->elements[2] = make_calypso_final_element(
+        "EventContractID", 4, "Contract ID", CALYPSO_FINAL_TYPE_UNKNOWN);
+    RavKavEventStructure->container->elements[3] =
+        make_calypso_final_element("EventAreaID", 4, "Area ID", CALYPSO_FINAL_TYPE_UNKNOWN);
+    RavKavEventStructure->container->elements[4] =
+        make_calypso_final_element("EventType", 4, "Event Type", CALYPSO_FINAL_TYPE_UNKNOWN);
+    RavKavEventStructure->container->elements[5] =
+        make_calypso_final_element("EventTime", 30, "Event Time", CALYPSO_FINAL_TYPE_DATE);
+    RavKavEventStructure->container->elements[6] = make_calypso_final_element(
+        "EventInterchangeFlag", 1, "Event Interchange flag", CALYPSO_FINAL_TYPE_UNKNOWN);
+    RavKavEventStructure->container->elements[7] = make_calypso_final_element(
+        "EventFirstTime", 30, "Event First Time", CALYPSO_FINAL_TYPE_DATE);
+    RavKavEventStructure->container->elements[8] =
+        make_calypso_final_element("EventUnknownA", 32, "Unknown A", CALYPSO_FINAL_TYPE_UNKNOWN);
+    RavKavEventStructure->container->elements[9] = make_calypso_bitmap_element(
+        "Location",
+        7,
+        (CalypsoElement[]){
+            make_calypso_final_element(
+                "EventLocationID", 16, "Location ID", CALYPSO_FINAL_TYPE_NUMBER),
+            make_calypso_final_element(
+                "EVentRouteNumber", 16, "Route Number", CALYPSO_FINAL_TYPE_NUMBER),
+            make_calypso_final_element(
+                "EventStopEnRoute", 8, "Stop En Route", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventUnknownB", 12, "Unknown B", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventVehicleID", 14, "Vehicle ID", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element("EventUnknownC", 4, "Unknown C", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventUnknownD", 8, "Unknown D", CALYPSO_FINAL_TYPE_UNKNOWN)});
+    RavKavEventStructure->container->elements[10] = make_calypso_bitmap_element(
+        "EventExtension",
+        3,
+        (CalypsoElement[]){
+            make_calypso_container_element(
+                "EventData",
+                3,
+                (CalypsoElement[]){
+                    make_calypso_final_element(
+                        "EventRouteSystem", 10, "Route System", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EventfareCode", 8, "fare Code", CALYPSO_FINAL_TYPE_UNKNOWN),
+                    make_calypso_final_element(
+                        "EventDebitAmount", 16, "Debit Amount", CALYPSO_FINAL_TYPE_UNKNOWN),
+                }),
+            make_calypso_final_element(
+                "EventUnknownE", 32, "Unknown C", CALYPSO_FINAL_TYPE_UNKNOWN),
+            make_calypso_final_element(
+                "EventUnknownF", 32, "Unknown D", CALYPSO_FINAL_TYPE_UNKNOWN),
+        });
+    return RavKavEventStructure;
+}

+ 731 - 0
metroflip/api/calypso/transit/navigo.c

@@ -0,0 +1,731 @@
+#include "navigo.h"
+#include "navigo_lists.h"
+#include "../../../metroflip_i.h"
+#include "../../metroflip/metroflip_api.h"
+
+const char* get_navigo_service_provider(int provider) {
+    switch(provider) {
+    case NAVIGO_PROVIDER_SNCF:
+        return "SNCF";
+    case NAVIGO_PROVIDER_RATP:
+        return "RATP";
+    case 4:
+    case 10:
+        return "IDF Mobilites";
+    case NAVIGO_PROVIDER_ORA:
+        return "ORA";
+    case NAVIGO_PROVIDER_VEOLIA_CSO:
+        return "CSO (VEOLIA)";
+    case NAVIGO_PROVIDER_VEOLIA_RBUS:
+        return "R'Bus (VEOLIA)";
+    case NAVIGO_PROVIDER_PHEBUS:
+        return "Phebus";
+    case NAVIGO_PROVIDER_RATP_VEOLIA_NANTERRE:
+        return "RATP (Veolia Transport Nanterre)";
+    default: {
+        char* provider_str = malloc(6 * sizeof(char));
+        snprintf(provider_str, 6, "%d", provider);
+        return provider_str;
+    }
+    }
+}
+
+const char* get_navigo_tariff(int tariff) {
+    switch(tariff) {
+    case 0x0000:
+        return "Navigo Mois";
+    case 0x0001:
+        return "Navigo Semaine";
+    case 0x0002:
+        return "Navigo Annuel";
+    case 0x0003:
+        return "Navigo Jour";
+    case 0x0004:
+        return "Imagine R Junior";
+    case 0x0005:
+        return "Imagine R Etudiant";
+    case 0x000D:
+        return "Navigo Jeunes Week-end";
+    case 0x0015:
+        return "Paris-Visite"; // Theoric
+    case 0x1000:
+        return "Navigo Liberte+";
+    case 0x4000:
+        return "Navigo Mois 75%%";
+    case 0x4001:
+        return "Navigo Semaine 75%%";
+    case 0x4015:
+        return "Paris-Visite (Enfant)"; // Theoric
+    case 0x5000:
+        return "Tickets T+";
+    case 0x5004:
+        return "Tickets OrlyBus"; // Theoric
+    case 0x5005:
+        return "Tickets RoissyBus"; // Theoric
+    case 0x5006:
+        return "Bus-Tram"; // Theoric
+    case 0x5008:
+        return "Metro-Train-RER"; // Theoric
+    case 0x500b:
+        return "Paris <> Aeroports"; // Theoric
+    case 0x5010:
+        return "Tickets T+ (Reduit)"; // Theoric
+    case 0x5016:
+        return "Bus-Tram (Reduit)"; // Theoric
+    case 0x5018:
+        return "Metro-Train-RER (Reduit)"; // Theoric
+    case 0x501b:
+        return "Paris <> Aeroports (Reduit)"; // Theoric
+    case 0x8003:
+        return "Navigo Solidarite Gratuit";
+    default: {
+        char* tariff_str = malloc(6 * sizeof(char));
+        snprintf(tariff_str, 6, "%d", tariff);
+        return tariff_str;
+    }
+    }
+}
+
+const char* get_zones(int* zones) {
+    if(zones[0] && zones[4]) {
+        return "All Zones (1-5)";
+    } else if(zones[0] && zones[3]) {
+        return "Zones 1-4";
+    } else if(zones[0] && zones[2]) {
+        return "Zones 1-3";
+    } else if(zones[0] && zones[1]) {
+        return "Zones 1-2";
+    } else if(zones[0]) {
+        return "Zone 1";
+    } else if(zones[1] && zones[4]) {
+        return "Zones 2-5";
+    } else if(zones[1] && zones[3]) {
+        return "Zones 2-4";
+    } else if(zones[1] && zones[2]) {
+        return "Zones 2-3";
+    } else if(zones[1]) {
+        return "Zone 2";
+    } else if(zones[2] && zones[4]) {
+        return "Zones 3-5";
+    } else if(zones[2] && zones[3]) {
+        return "Zones 3-4";
+    } else if(zones[2]) {
+        return "Zone 3";
+    } else if(zones[3] && zones[4]) {
+        return "Zones 4-5";
+    } else if(zones[3]) {
+        return "Zone 4";
+    } else if(zones[4]) {
+        return "Zone 5";
+    } else {
+        return "Unknown";
+    }
+}
+
+char* get_token(char* psrc, const char* delimit, void* psave) {
+    static char sret[512];
+    register char* ptr = psave;
+    memset(sret, 0, sizeof(sret));
+
+    if(psrc != NULL) strcpy(ptr, psrc);
+    if(ptr == NULL) return NULL;
+
+    int i = 0, nlength = strlen(ptr);
+    for(i = 0; i < nlength; i++) {
+        if(ptr[i] == delimit[0]) break;
+        if(ptr[i] == delimit[1]) {
+            ptr = NULL;
+            break;
+        }
+        sret[i] = ptr[i];
+    }
+    if(ptr != NULL) strcpy(ptr, &ptr[i + 1]);
+
+    return sret;
+}
+
+char* get_navigo_station(
+    int station_group_id,
+    int station_id,
+    int station_sub_id,
+    int transport_type) {
+    switch(transport_type) {
+    case COMMUTER_TRAIN: {
+        if(station_group_id < 77 && station_id < 19) {
+            char* file_path = malloc(256 * sizeof(char));
+            if(!file_path) {
+                return "Unknown";
+            }
+            snprintf(
+                file_path,
+                256,
+                APP_ASSETS_PATH("navigo/stations/train/stations_%d.txt"),
+                station_group_id);
+            const char* sncf_stations_path = file_path;
+            Storage* storage = furi_record_open(RECORD_STORAGE);
+
+            Stream* stream = file_stream_alloc(storage);
+            FuriString* line = furi_string_alloc();
+
+            char* found_station_name = NULL;
+
+            if(file_stream_open(stream, sncf_stations_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+                while(stream_read_line(stream, line)) {
+                    // file is in csv format: station_id,station_sub_id,station_name
+                    // search for the station
+                    furi_string_replace_all(line, "\r", "");
+                    furi_string_replace_all(line, "\n", "");
+                    const char* string_line = furi_string_get_cstr(line);
+                    char* string_line_copy = strdup(string_line);
+                    if(!string_line_copy) {
+                        return "Unknown";
+                    }
+                    int line_station_id = atoi(get_token(string_line_copy, ",", string_line_copy));
+                    int line_station_sub_id =
+                        atoi(get_token(string_line_copy, ",", string_line_copy));
+                    if(line_station_id == station_id && line_station_sub_id == station_sub_id) {
+                        found_station_name =
+                            strdup(get_token(string_line_copy, ",", string_line_copy));
+                        free(string_line_copy);
+                        break;
+                    }
+                    free(string_line_copy);
+                }
+            } else {
+                FURI_LOG_E("Metroflip:Scene:Calypso", "Failed to open train stations file");
+            }
+
+            furi_string_free(line);
+            file_stream_close(stream);
+            stream_free(stream);
+            free(file_path);
+
+            if(found_station_name) {
+                return found_station_name;
+            }
+        }
+        // cast station_group_id-station_id-station_sub_id to a string
+        char* station = malloc(12 * sizeof(char));
+        if(!station) {
+            return "Unknown";
+        }
+        snprintf(station, 10, "%d-%d-%d", station_group_id, station_id, station_sub_id);
+        return station;
+    }
+    case TRAM: {
+        char* file_path = malloc(256 * sizeof(char));
+        if(!file_path) {
+            return "Unknown";
+        }
+        snprintf(
+            file_path,
+            256,
+            APP_ASSETS_PATH("navigo/stations/tram/stations_%d.txt"),
+            station_group_id);
+        const char* sncf_stations_path = file_path;
+        Storage* storage = furi_record_open(RECORD_STORAGE);
+
+        Stream* stream = file_stream_alloc(storage);
+        FuriString* line = furi_string_alloc();
+
+        char* found_station_name = NULL;
+
+        if(file_stream_open(stream, sncf_stations_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+            while(stream_read_line(stream, line)) {
+                // file is in csv format: station_id,station_sub_id,station_name
+                // search for the station
+                furi_string_replace_all(line, "\r", "");
+                furi_string_replace_all(line, "\n", "");
+                const char* string_line = furi_string_get_cstr(line);
+                char* string_line_copy = strdup(string_line);
+                if(!string_line_copy) {
+                    return "Unknown";
+                }
+                int line_station_id = atoi(get_token(string_line_copy, ",", string_line_copy));
+                int line_station_sub_id = atoi(get_token(string_line_copy, ",", string_line_copy));
+                if(line_station_id == station_id && line_station_sub_id == station_sub_id) {
+                    found_station_name =
+                        strdup(get_token(string_line_copy, ",", string_line_copy));
+                    free(string_line_copy);
+                    break;
+                }
+                free(string_line_copy);
+            }
+        } else {
+            FURI_LOG_E("Metroflip:Scene:Calypso", "Failed to open tram stations file");
+        }
+
+        furi_string_free(line);
+        file_stream_close(stream);
+        stream_free(stream);
+        free(file_path);
+
+        if(found_station_name) {
+            return found_station_name;
+        }
+
+        // cast station_group_id-station_id-station_sub_id to a string
+        char* station = malloc(12 * sizeof(char));
+        if(!station) {
+            return "Unknown";
+        }
+        if(station_sub_id != 0) {
+            snprintf(station, 10, "%d-%d-%d", station_group_id, station_id, station_sub_id);
+        } else if(station_id != 0) {
+            snprintf(station, 10, "%d-%d", station_group_id, station_id);
+        } else {
+            snprintf(station, 10, "%d", station_group_id);
+        }
+        return station;
+    }
+    case METRO: {
+        if(station_group_id < 32 && station_id < 16) {
+            char* file_path = malloc(256 * sizeof(char));
+            if(!file_path) {
+                return "Unknown";
+            }
+            snprintf(
+                file_path,
+                256,
+                APP_ASSETS_PATH("navigo/stations/metro/stations_%d.txt"),
+                station_group_id);
+            const char* ratp_stations_path = file_path;
+            Storage* storage = furi_record_open(RECORD_STORAGE);
+
+            Stream* stream = file_stream_alloc(storage);
+            FuriString* line = furi_string_alloc();
+
+            char* found_station_name = NULL;
+
+            if(file_stream_open(stream, ratp_stations_path, FSAM_READ, FSOM_OPEN_EXISTING)) {
+                while(stream_read_line(stream, line)) {
+                    // file is in csv format: station_id,station_name
+                    // search for the station
+                    furi_string_replace_all(line, "\r", "");
+                    furi_string_replace_all(line, "\n", "");
+                    const char* string_line = furi_string_get_cstr(line);
+                    char* string_line_copy = strdup(string_line);
+                    if(!string_line_copy) {
+                        return "Unknown";
+                    }
+                    int line_station_id = atoi(get_token(string_line_copy, ",", string_line_copy));
+                    if(line_station_id == station_id) {
+                        found_station_name =
+                            strdup(get_token(string_line_copy, ",", string_line_copy));
+                        free(string_line_copy);
+                        break;
+                    }
+                    free(string_line_copy);
+                }
+            } else {
+                FURI_LOG_E("Metroflip:Scene:Calypso", "Failed to open metro stations file");
+            }
+
+            furi_string_free(line);
+            file_stream_close(stream);
+            stream_free(stream);
+            free(file_path);
+
+            if(found_station_name) {
+                return found_station_name;
+            }
+        }
+        // cast station_group_id-station_id to a string
+        char* station = malloc(12 * sizeof(char));
+        if(!station) {
+            return "Unknown";
+        }
+        if(station_sub_id != 0) {
+            snprintf(station, 10, "%d-%d-%d", station_group_id, station_id, station_sub_id);
+        } else if(station_id != 0) {
+            snprintf(station, 10, "%d-%d", station_group_id, station_id);
+        } else {
+            snprintf(station, 10, "%d", station_group_id);
+        }
+        return station;
+    }
+    default: {
+        // cast station_group_id-station_id to a string
+        char* station = malloc(12 * sizeof(char));
+        if(!station) {
+            return "Unknown";
+        }
+        if(station_sub_id != 0) {
+            snprintf(station, 10, "%d-%d-%d", station_group_id, station_id, station_sub_id);
+        } else if(station_id != 0) {
+            snprintf(station, 10, "%d-%d", station_group_id, station_id);
+        } else {
+            snprintf(station, 10, "%d", station_group_id);
+        }
+        return station;
+    }
+    }
+}
+
+char* get_navigo_train_sector(int station_group_id) {
+    // group id is in format XY where X is the sector
+    const char* station_name = NAVIGO_SNCF_SECTORS_LIST[station_group_id / 10];
+    return strdup(station_name);
+}
+
+const char* get_navigo_tram_line(int route_number) {
+    switch(route_number) {
+    case 1:
+    case 13:
+        return "T3a";
+    case 9:
+        return "T9";
+    case 16:
+        return "T6";
+    case 18:
+        return "T8";
+    default: {
+        char* line = malloc(5 * sizeof(char));
+        if(!line) {
+            return "Unknown";
+        }
+        snprintf(line, 5, "?%d?", route_number);
+        return line;
+    }
+    }
+}
+
+void show_navigo_event_info(
+    NavigoCardEvent* event,
+    NavigoCardContract* contracts,
+    FuriString* parsed_data) {
+    if(event->used_contract == 0) {
+        furi_string_cat_printf(parsed_data, "No event data\n");
+        return;
+    }
+    int navigo_station_type = event->transport_type;
+    char* station = get_navigo_station(
+        event->station_group_id, event->station_id, event->station_sub_id, navigo_station_type);
+    char* sector = NULL;
+    if(navigo_station_type == COMMUTER_TRAIN || navigo_station_type == TRAM) {
+        sector = get_navigo_train_sector(event->station_group_id);
+    } else {
+        sector = get_navigo_station(event->station_group_id, 0, 0, navigo_station_type);
+    }
+
+    if(event->transport_type == URBAN_BUS || event->transport_type == INTERURBAN_BUS ||
+       event->transport_type == METRO || event->transport_type == TRAM) {
+        if(event->route_number_available) {
+            if(event->transport_type == METRO && event->route_number == 103) {
+                furi_string_cat_printf(
+                    parsed_data,
+                    "%s 3 bis\n%s\n",
+                    get_intercode_string_transport_type(event->transport_type),
+                    get_intercode_string_transition_type(event->transition));
+            } else if(event->transport_type == TRAM) {
+                furi_string_cat_printf(
+                    parsed_data,
+                    "%s %s\n%s\n",
+                    get_intercode_string_transport_type(event->transport_type),
+                    get_navigo_tram_line(event->route_number),
+                    get_intercode_string_transition_type(event->transition));
+            } else {
+                furi_string_cat_printf(
+                    parsed_data,
+                    "%s %d\n%s\n",
+                    get_intercode_string_transport_type(event->transport_type),
+                    event->route_number,
+                    get_intercode_string_transition_type(event->transition));
+            }
+        } else {
+            furi_string_cat_printf(
+                parsed_data,
+                "%s\n%s\n",
+                get_intercode_string_transport_type(event->transport_type),
+                get_intercode_string_transition_type(event->transition));
+        }
+        furi_string_cat_printf(
+            parsed_data,
+            "Transporter: %s\n",
+            get_navigo_service_provider(event->service_provider));
+        furi_string_cat_printf(parsed_data, "Station: %s\nSector: %s\n", station, sector);
+        if(event->location_gate_available) {
+            furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
+        }
+        if(event->device_available) {
+            if(event->transport_type == URBAN_BUS || event->transport_type == INTERURBAN_BUS) {
+                const char* side = event->side == 0 ? "right" : "left";
+                furi_string_cat_printf(parsed_data, "Door: %d\nSide: %s\n", event->door, side);
+            } else {
+                furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
+            }
+        }
+        if(event->mission_available) {
+            furi_string_cat_printf(parsed_data, "Mission: %d\n", event->mission);
+        }
+        if(event->vehicle_id_available) {
+            furi_string_cat_printf(parsed_data, "Vehicle: %d\n", event->vehicle_id);
+        }
+        if(event->used_contract_available) {
+            furi_string_cat_printf(
+                parsed_data,
+                "Contract: %d - %s\n",
+                event->used_contract,
+                get_navigo_tariff(contracts[event->used_contract - 1].tariff));
+        }
+        locale_format_datetime_cat(parsed_data, &event->date, true);
+        furi_string_cat_printf(parsed_data, "\n");
+    } else if(event->transport_type == COMMUTER_TRAIN) {
+        if(event->route_number_available) {
+            furi_string_cat_printf(
+                parsed_data,
+                "RER %c\n%s\n",
+                (65 + event->route_number - 16),
+                get_intercode_string_transition_type(event->transition));
+        } else {
+            furi_string_cat_printf(
+                parsed_data,
+                "%s\n%s\n",
+                get_intercode_string_transport_type(event->transport_type),
+                get_intercode_string_transition_type(event->transition));
+        }
+        furi_string_cat_printf(
+            parsed_data,
+            "Transporter: %s\n",
+            get_navigo_service_provider(event->service_provider));
+        furi_string_cat_printf(parsed_data, "Station: %s\nSector: %s\n", station, sector);
+        if(event->location_gate_available) {
+            furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
+        }
+        if(event->device_available) {
+            if(event->service_provider == NAVIGO_PROVIDER_SNCF) {
+                furi_string_cat_printf(parsed_data, "Device: %d\n", event->device & 0xFF);
+            } else {
+                furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
+            }
+        }
+        if(event->mission_available) {
+            furi_string_cat_printf(parsed_data, "Mission: %d\n", event->mission);
+        }
+        if(event->vehicle_id_available) {
+            furi_string_cat_printf(parsed_data, "Vehicle: %d\n", event->vehicle_id);
+        }
+        if(event->used_contract_available) {
+            furi_string_cat_printf(
+                parsed_data,
+                "Contract: %d - %s\n",
+                event->used_contract,
+                get_navigo_tariff(contracts[event->used_contract - 1].tariff));
+        }
+        locale_format_datetime_cat(parsed_data, &event->date, true);
+        furi_string_cat_printf(parsed_data, "\n");
+    } else {
+        furi_string_cat_printf(
+            parsed_data,
+            "%s - %s\n",
+            get_intercode_string_transport_type(event->transport_type),
+            get_intercode_string_transition_type(event->transition));
+        furi_string_cat_printf(
+            parsed_data,
+            "Transporter: %s\n",
+            get_navigo_service_provider(event->service_provider));
+        furi_string_cat_printf(parsed_data, "Station: %s\nSector: %s\n", station, sector);
+        if(event->location_gate_available) {
+            furi_string_cat_printf(parsed_data, "Gate: %d\n", event->location_gate);
+        }
+        if(event->device_available) {
+            furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
+        }
+        if(event->mission_available) {
+            furi_string_cat_printf(parsed_data, "Mission: %d\n", event->mission);
+        }
+        if(event->vehicle_id_available) {
+            furi_string_cat_printf(parsed_data, "Vehicle: %d\n", event->vehicle_id);
+        }
+        if(event->used_contract_available) {
+            furi_string_cat_printf(
+                parsed_data,
+                "Contract: %d - %s\n",
+                event->used_contract,
+                get_navigo_tariff(contracts[event->used_contract - 1].tariff));
+        }
+        locale_format_datetime_cat(parsed_data, &event->date, true);
+        furi_string_cat_printf(parsed_data, "\n");
+    }
+
+    free(station);
+    free(sector);
+}
+
+void show_navigo_special_event_info(NavigoCardSpecialEvent* event, FuriString* parsed_data) {
+    int navigo_station_type = event->transport_type;
+    char* station = get_navigo_station(
+        event->station_group_id, event->station_id, event->station_sub_id, navigo_station_type);
+    char* sector = NULL;
+    if(navigo_station_type == COMMUTER_TRAIN || navigo_station_type == TRAM) {
+        sector = get_navigo_train_sector(event->station_group_id);
+    } else {
+        sector = get_navigo_station(event->station_group_id, 0, 0, navigo_station_type);
+    }
+
+    if(event->transport_type == URBAN_BUS || event->transport_type == INTERURBAN_BUS ||
+       event->transport_type == METRO || event->transport_type == TRAM) {
+        if(event->route_number_available) {
+            if(event->transport_type == METRO && event->route_number == 103) {
+                furi_string_cat_printf(
+                    parsed_data,
+                    "%s 3 bis\n%s\n",
+                    get_intercode_string_transport_type(event->transport_type),
+                    get_intercode_string_transition_type(event->transition));
+            } else if(event->transport_type == TRAM) {
+                furi_string_cat_printf(
+                    parsed_data,
+                    "%s %s\n%s\n",
+                    get_intercode_string_transport_type(event->transport_type),
+                    get_navigo_tram_line(event->route_number),
+                    get_intercode_string_transition_type(event->transition));
+            } else {
+                furi_string_cat_printf(
+                    parsed_data,
+                    "%s %d\n%s\n",
+                    get_intercode_string_transport_type(event->transport_type),
+                    event->route_number,
+                    get_intercode_string_transition_type(event->transition));
+            }
+        } else {
+            furi_string_cat_printf(
+                parsed_data,
+                "%s\n%s\n",
+                get_intercode_string_transport_type(event->transport_type),
+                get_intercode_string_transition_type(event->transition));
+        }
+        furi_string_cat_printf(
+            parsed_data, "Result: %s\n", get_intercode_string_event_result(event->result));
+        furi_string_cat_printf(
+            parsed_data,
+            "Transporter: %s\n",
+            get_navigo_service_provider(event->service_provider));
+        furi_string_cat_printf(parsed_data, "Station: %s\nSector: %s\n", station, sector);
+        if(event->device_available) {
+            furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
+        }
+        locale_format_datetime_cat(parsed_data, &event->date, true);
+        furi_string_cat_printf(parsed_data, "\n");
+    } else if(event->transport_type == COMMUTER_TRAIN) {
+        if(event->route_number_available) {
+            furi_string_cat_printf(
+                parsed_data,
+                "RER %c\n%s\n",
+                (65 + event->route_number - 16),
+                get_intercode_string_transition_type(event->transition));
+        } else {
+            furi_string_cat_printf(
+                parsed_data,
+                "%s\n%s\n",
+                get_intercode_string_transport_type(event->transport_type),
+                get_intercode_string_transition_type(event->transition));
+        }
+        furi_string_cat_printf(
+            parsed_data, "Result: %s\n", get_intercode_string_event_result(event->result));
+        furi_string_cat_printf(
+            parsed_data,
+            "Transporter: %s\n",
+            get_navigo_service_provider(event->service_provider));
+        furi_string_cat_printf(parsed_data, "Station: %s\nSector: %s\n", station, sector);
+        if(event->device_available) {
+            if(event->service_provider == NAVIGO_PROVIDER_SNCF) {
+                furi_string_cat_printf(parsed_data, "Device: %d\n", event->device & 0xFF);
+            } else {
+                furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
+            }
+        }
+        locale_format_datetime_cat(parsed_data, &event->date, true);
+        furi_string_cat_printf(parsed_data, "\n");
+    } else {
+        furi_string_cat_printf(
+            parsed_data,
+            "%s - %s\n",
+            get_intercode_string_transport_type(event->transport_type),
+            get_intercode_string_transition_type(event->transition));
+        furi_string_cat_printf(
+            parsed_data, "Result: %s\n", get_intercode_string_event_result(event->result));
+        furi_string_cat_printf(
+            parsed_data,
+            "Transporter: %s\n",
+            get_navigo_service_provider(event->service_provider));
+        furi_string_cat_printf(parsed_data, "Station: %s\nSector: %s\n", station, sector);
+        if(event->device_available) {
+            furi_string_cat_printf(parsed_data, "Device: %d\n", event->device);
+        }
+        locale_format_datetime_cat(parsed_data, &event->date, true);
+        furi_string_cat_printf(parsed_data, "\n");
+    }
+
+    free(station);
+    free(sector);
+}
+
+void show_navigo_contract_info(NavigoCardContract* contract, FuriString* parsed_data) {
+    // Core type and ticket info
+    furi_string_cat_printf(parsed_data, "Type: %s\n", get_navigo_tariff(contract->tariff));
+    if(contract->counter_present) {
+        furi_string_cat_printf(parsed_data, "Remaining Tickets: %d\n", contract->counter.count);
+        furi_string_cat_printf(parsed_data, "Last load: %d\n", contract->counter.last_load);
+    }
+
+    // Validity period
+    furi_string_cat_printf(parsed_data, "Valid from: ");
+    locale_format_datetime_cat(parsed_data, &contract->start_date, false);
+    furi_string_cat_printf(parsed_data, "\n");
+    if(contract->end_date_available) {
+        furi_string_cat_printf(parsed_data, "to: ");
+        locale_format_datetime_cat(parsed_data, &contract->end_date, false);
+        furi_string_cat_printf(parsed_data, "\n");
+    }
+
+    // Serial number (if available)
+    if(contract->serial_number_available) {
+        furi_string_cat_printf(parsed_data, "TCN Number: %d\n", contract->serial_number);
+    }
+    if(contract->price_amount_available) {
+        furi_string_cat_printf(parsed_data, "Amount: %.2f EUR\n", contract->price_amount);
+    }
+    if(contract->pay_method_available) {
+        furi_string_cat_printf(
+            parsed_data,
+            "Payment Method: %s\n",
+            get_intercode_string_pay_method(contract->pay_method));
+    }
+    if(contract->zones_available) {
+        furi_string_cat_printf(parsed_data, "%s\n", get_zones(contract->zones));
+    }
+    furi_string_cat_printf(parsed_data, "Sold on: ");
+    locale_format_datetime_cat(parsed_data, &contract->sale_date, false);
+    furi_string_cat_printf(parsed_data, "\n");
+    furi_string_cat_printf(
+        parsed_data, "Sales Agent: %s\n", get_navigo_service_provider(contract->sale_agent));
+    furi_string_cat_printf(parsed_data, "Sales Terminal: %d\n", contract->sale_device);
+    furi_string_cat_printf(
+        parsed_data, "Status: %s\n", get_intercode_string_contract_status(contract->status));
+    furi_string_cat_printf(parsed_data, "Authenticity Code: %d\n", contract->authenticator);
+}
+
+void show_navigo_environment_info(
+    NavigoCardEnv* environment,
+    NavigoCardHolder* holder,
+    FuriString* parsed_data) {
+    furi_string_cat_printf(
+        parsed_data, "Card status: %s\n", get_intercode_string_holder_type(holder->card_status));
+    if(is_intercode_string_holder_linked(holder->card_status)) {
+        furi_string_cat_printf(parsed_data, "Linked to an organization\n");
+    }
+    furi_string_cat_printf(
+        parsed_data,
+        "App Version: %s - v%d\n",
+        get_intercode_string_version(environment->app_version),
+        get_intercode_string_subversion(environment->app_version));
+    furi_string_cat_printf(
+        parsed_data, "Country: %s\n", get_country_string(environment->country_num));
+    furi_string_cat_printf(
+        parsed_data,
+        "Network: %s\n",
+        get_network_string(guess_card_type(environment->country_num, environment->network_num)));
+    furi_string_cat_printf(parsed_data, "End of validity:\n");
+    locale_format_datetime_cat(parsed_data, &environment->end_dt, false);
+    furi_string_cat_printf(parsed_data, "\n");
+}

+ 39 - 0
metroflip/api/calypso/transit/navigo.h

@@ -0,0 +1,39 @@
+#include "../calypso_util.h"
+#include "../cards/intercode.h"
+#include "navigo_i.h"
+#include <datetime.h>
+#include <stdbool.h>
+#include <furi.h>
+#include <storage/storage.h>
+#include <toolbox/stream/stream.h>
+#include <toolbox/stream/file_stream.h>
+
+#ifndef NAVIGO_H
+#define NAVIGO_H
+
+const char* get_navigo_type(int type);
+
+char* get_navigo_station(
+    int station_group_id,
+    int station_id,
+    int station_sub_id,
+    int service_provider);
+
+const char* get_navigo_sncf_train_line(int station_group_id);
+
+const char* get_navigo_sncf_station(int station_group_id, int station_id);
+
+const char* get_navigo_tram_line(int route_number);
+
+typedef enum {
+    NAVIGO_PROVIDER_SNCF = 2,
+    NAVIGO_PROVIDER_RATP = 3,
+    NAVIGO_PROVIDER_IDFM = 4,
+    NAVIGO_PROVIDER_ORA = 8,
+    NAVIGO_PROVIDER_VEOLIA_CSO = 115,
+    NAVIGO_PROVIDER_VEOLIA_RBUS = 116,
+    NAVIGO_PROVIDER_PHEBUS = 156,
+    NAVIGO_PROVIDER_RATP_VEOLIA_NANTERRE = 175
+} NAVIGO_SERVICE_PROVIDER;
+
+#endif // NAVIGO_H

+ 32 - 9
metroflip/scenes/navigo_structs.h → metroflip/api/calypso/transit/navigo_i.h

@@ -1,6 +1,8 @@
 #include <datetime.h>
 #include <stdbool.h>
-#include <furi.h>
+
+#ifndef NAVIGO_I_H
+#define NAVIGO_I_H
 
 typedef struct {
     int transport_type;
@@ -8,6 +10,7 @@ typedef struct {
     int service_provider;
     int station_group_id;
     int station_id;
+    int station_sub_id;
     int location_gate;
     bool location_gate_available;
     int device;
@@ -25,6 +28,21 @@ typedef struct {
     DateTime date;
 } NavigoCardEvent;
 
+typedef struct {
+    int transport_type;
+    int transition;
+    int result;
+    int service_provider;
+    int station_group_id;
+    int station_id;
+    int station_sub_id;
+    int device;
+    bool device_available;
+    int route_number;
+    bool route_number_available;
+    DateTime date;
+} NavigoCardSpecialEvent;
+
 typedef struct {
     int app_version;
     int country_num;
@@ -37,6 +55,13 @@ typedef struct {
     int commercial_id;
 } NavigoCardHolder;
 
+typedef struct {
+    int count;
+    int relative_first_stamp_15mn;
+    int struct_number;
+    int last_load;
+} NavigoCardContractCounter;
+
 typedef struct {
     int tariff;
     int serial_number;
@@ -55,19 +80,17 @@ typedef struct {
     int sale_device;
     int status;
     int authenticator;
+    NavigoCardContractCounter counter;
+    bool counter_present;
+    bool present;
 } NavigoCardContract;
 
 typedef struct {
     NavigoCardEnv environment;
     NavigoCardHolder holder;
-    NavigoCardContract contracts[2];
+    NavigoCardContract contracts[4];
     NavigoCardEvent events[3];
-    int ticket_count;
+    NavigoCardSpecialEvent special_events[3];
 } NavigoCardData;
 
-typedef struct {
-    NavigoCardData* card;
-    int page_id;
-    // mutex
-    FuriMutex* mutex;
-} NavigoContext;
+#endif // NAVIGO_I_H

+ 14 - 0
metroflip/api/calypso/transit/navigo_lists.h

@@ -0,0 +1,14 @@
+#ifndef NAVIGO_LISTS_H
+#define NAVIGO_LISTS_H
+
+static const char* NAVIGO_SNCF_SECTORS_LIST[8] = {
+    "Intramuros",
+    "RATP",
+    "Est",
+    "St Lazare",
+    "Sud-Est + Austerlitz",
+    "Nord",
+    "Montparnasse",
+    "Unknown"};
+
+#endif

+ 157 - 0
metroflip/api/calypso/transit/opus.c

@@ -0,0 +1,157 @@
+#include "opus_lists.h"
+#include "../../../metroflip_i.h"
+#include "../../metroflip/metroflip_api.h"
+#include "../calypso_util.h"
+#include "opus_i.h"
+
+const char* get_opus_service_provider(int provider) {
+    switch(provider) {
+    case 0x01:
+    case 0x02:
+        return "STM";
+    case 0x03:
+        return "RTL";
+    case 0x04:
+        return "RTM";
+    case 0x05:
+        return "RTC";
+    case 0x06:
+        return "STL";
+    case 0x10:
+        return "STLevis";
+    case 0x20:
+        return "Chrono";
+    default: {
+        char* provider_str = malloc(10 * sizeof(char));
+        if(!provider_str) {
+            return "Unknown";
+        }
+        snprintf(provider_str, 10, "0x%02X", provider);
+        return provider_str;
+    }
+    }
+}
+
+const char* get_opus_transport_type(int location_id) {
+    switch(location_id) {
+    case 0x65:
+        return "Bus";
+    case 0xc9:
+        return "Metro";
+    default: {
+        char* location_str = malloc(9 * sizeof(char));
+        if(!location_str) {
+            return "Unknown";
+        }
+        snprintf(location_str, 9, "0x%02X", location_id);
+        return location_str;
+    }
+    }
+}
+
+const char* get_opus_transport_line(int route_number) {
+    if(OPUS_LINES_LIST[route_number]) {
+        return OPUS_LINES_LIST[route_number];
+    } else {
+        // Return hex
+        char* route_str = malloc(9 * sizeof(char));
+        if(!route_str) {
+            return "Unknown";
+        }
+        snprintf(route_str, 9, "0x%02X", route_number);
+        return route_str;
+    }
+}
+
+const char* get_opus_tariff(int tariff) {
+    switch(tariff) {
+    case 0xb1:
+        return "Monthly";
+    case 0xb2:
+    case 0xc9:
+        return "Weekly";
+    case 0x1c7:
+        return "Single Trips";
+    case 0xa34:
+        return "Monthly Student";
+    case 0xa3e:
+        return "Weekly";
+    default: {
+        char* tariff_str = malloc(9 * sizeof(char));
+        if(!tariff_str) {
+            return "Unknown";
+        }
+        snprintf(tariff_str, 9, "0x%02X", tariff);
+        return tariff_str;
+    }
+    }
+}
+
+void show_opus_event_info(
+    OpusCardEvent* event,
+    OpusCardContract* contracts,
+    FuriString* parsed_data) {
+    UNUSED(contracts);
+    furi_string_cat_printf(
+        parsed_data,
+        "%s %s\n",
+        get_opus_transport_type(event->location_id),
+        get_opus_transport_line(event->route_number));
+    furi_string_cat_printf(
+        parsed_data, "Transporter: %s\n", get_opus_service_provider(event->service_provider));
+    furi_string_cat_printf(parsed_data, "Result: %d\n", event->result);
+    furi_string_cat_printf(
+        parsed_data,
+        "Contract: %d - %s\n",
+        event->used_contract,
+        get_opus_tariff(contracts[event->used_contract - 1].tariff));
+    furi_string_cat_printf(parsed_data, "Simulation: %s\n", event->simulation ? "true" : "false");
+    furi_string_cat_printf(parsed_data, "Date: ");
+    locale_format_datetime_cat(parsed_data, &event->date, true);
+    furi_string_cat_printf(parsed_data, "\nFirst stamp: ");
+    locale_format_datetime_cat(parsed_data, &event->first_stamp_date, true);
+    furi_string_cat_printf(parsed_data, "\n");
+}
+
+void show_opus_contract_info(OpusCardContract* contract, FuriString* parsed_data) {
+    furi_string_cat_printf(parsed_data, "Type: %s\n", get_opus_tariff(contract->tariff));
+    furi_string_cat_printf(
+        parsed_data, "Provider: %s\n", get_opus_service_provider(contract->provider));
+    furi_string_cat_printf(parsed_data, "Valid from: ");
+    locale_format_datetime_cat(parsed_data, &contract->start_date, false);
+    furi_string_cat_printf(parsed_data, "\nto: ");
+    locale_format_datetime_cat(parsed_data, &contract->end_date, false);
+    furi_string_cat_printf(parsed_data, "\nSold on: ");
+    locale_format_datetime_cat(parsed_data, &contract->sale_date, true);
+    furi_string_cat_printf(
+        parsed_data, "\nSales Agent: %s\n", get_opus_service_provider(contract->sale_agent));
+    if(contract->inhibition) {
+        furi_string_cat_printf(parsed_data, "Contract inhibited\n");
+    }
+    furi_string_cat_printf(parsed_data, "Used: %s\n", contract->used ? "true" : "false");
+}
+
+void show_opus_environment_info(
+    OpusCardEnv* environment,
+    OpusCardHolder* holder,
+    FuriString* parsed_data) {
+    furi_string_cat_printf(parsed_data, "App Version: %d\n", environment->app_version);
+    furi_string_cat_printf(
+        parsed_data, "Country: %s\n", get_country_string(environment->country_num));
+    furi_string_cat_printf(
+        parsed_data,
+        "Network: %s\n",
+        get_network_string(guess_card_type(environment->country_num, environment->network_num)));
+    furi_string_cat_printf(parsed_data, "End of validity:\n");
+    locale_format_datetime_cat(parsed_data, &environment->end_dt, false);
+    furi_string_cat_printf(parsed_data, "\nIssuer: %d\n", environment->issuer_id);
+    furi_string_cat_printf(
+        parsed_data, "Card status: %s\n", environment->card_status ? "true" : "false");
+    furi_string_cat_printf(
+        parsed_data,
+        "Card utilisation: %s\n",
+        environment->card_utilisation ? "Used" : "Not used");
+    furi_string_cat_printf(parsed_data, "Holder birth date: ");
+    locale_format_datetime_cat(parsed_data, &holder->birth_date, false);
+    furi_string_cat_printf(parsed_data, "\n");
+}

+ 59 - 0
metroflip/api/calypso/transit/opus_i.h

@@ -0,0 +1,59 @@
+#include <datetime.h>
+#include <stdbool.h>
+
+#ifndef OPUS_I_H
+#define OPUS_I_H
+
+typedef struct {
+    int service_provider;
+    int result;
+    int route_number;
+    int route_direction;
+    int location_id;
+    int used_contract;
+    bool simulation;
+    DateTime date;
+    DateTime first_stamp_date;
+} OpusCardEvent;
+
+typedef struct {
+    int app_version;
+    int country_num;
+    int network_num;
+    int issuer_id;
+    bool card_status;
+    bool card_utilisation;
+    DateTime end_dt;
+} OpusCardEnv;
+
+typedef struct {
+    int number;
+    DateTime date;
+} OpusCardHolderProfile;
+
+typedef struct {
+    DateTime birth_date;
+    OpusCardHolderProfile profiles[4];
+    int language;
+} OpusCardHolder;
+
+typedef struct {
+    int provider;
+    int tariff;
+    DateTime start_date;
+    DateTime end_date;
+    int sale_agent;
+    DateTime sale_date;
+    bool inhibition;
+    bool used;
+    bool present;
+} OpusCardContract;
+
+typedef struct {
+    OpusCardEnv environment;
+    OpusCardHolder holder;
+    OpusCardContract contracts[4];
+    OpusCardEvent events[3];
+} OpusCardData;
+
+#endif // OPUS_I_H

+ 324 - 0
metroflip/api/calypso/transit/opus_lists.h

@@ -0,0 +1,324 @@
+/*
+<bus id="2" logo="stm_orange" name="METRO Ligne orange" />
+    <bus id="1" logo="stm_vert" name="METRO Ligne verte" />
+    <bus id="3" logo="stm_jaune" name="METRO Ligne jaune" />
+    <bus id="4" logo="stm_bleue" name="METRO Ligne bleue" />
+    <bus id="" logo="stm_local" name="10" />
+    <bus id="" logo="stm_local" name="11" />
+    <bus id="" logo="stm_local" name="12" />
+    <bus id="" logo="stm_local" name="13" />
+    <bus id="" logo="stm_local" name="14" />
+    <bus id="" logo="stm_local" name="15" />
+    <bus id="" logo="stm_local" name="16" />
+    <bus id="" logo="stm_local" name="17" />
+    <bus id="13" logo="stm_local" name="18" />
+    <bus id="" logo="stm_local" name="19" />
+    <bus id="" logo="stm_local" name="21" />
+    <bus id="" logo="stm_local" name="22" />
+    <bus id="15" logo="stm_local" name="24" />
+    <bus id="16" logo="stm_local" name="25" />
+    <bus id="231" logo="stm_local" name="26" />
+    <bus id="17" logo="stm_local" name="27" />
+    <bus id="18" logo="stm_local" name="28" />
+    <bus id="" logo="stm_local" name="29" />
+    <bus id="" logo="stm_local" name="30" />
+    <bus id="" logo="stm_local" name="31" />
+    <bus id="22" logo="stm_local" name="32" />
+    <bus id="23" logo="stm_local" name="33" />
+    <bus id="24" logo="stm_local" name="34" />
+    <bus id="" logo="stm_local" name="36" />
+    <bus id="26" logo="stm_local" name="37" />
+    <bus id="" logo="stm_local" name="39" />
+    <bus id="" logo="stm_local" name="40" />
+    <bus id="226" logo="stm_local" name="41" />
+    <bus id="29" logo="stm_local" name="43" />
+    <bus id="30" logo="stm_local" name="44" />
+    <bus id="31" logo="stm_local" name="45" />
+    <bus id="" logo="stm_local" name="46" />
+    <bus id="33" logo="stm_local" name="47" />
+    <bus id="34" logo="stm_local" name="48" />
+    <bus id="35" logo="stm_local" name="49" />
+    <bus id="36" logo="stm_local" name="51" />
+    <bus id="" logo="stm_local" name="52" />
+    <bus id="" logo="stm_local" name="53" />
+    <bus id="" logo="stm_local" name="54" />
+    <bus id="40" logo="stm_local" name="55" />
+    <bus id="" logo="stm_local" name="56" />
+    <bus id="" logo="stm_local" name="57" />
+    <bus id="" logo="stm_local" name="58" />
+    <bus id="44" logo="stm_local" name="61" />
+    <bus id="" logo="stm_local" name="63" />
+    <bus id="46" logo="stm_local" name="64" />
+    <bus id="" logo="stm_local" name="66" />
+    <bus id="48" logo="stm_local" name="67" />
+    <bus id="49" logo="stm_local" name="68" />
+    <bus id="50" logo="stm_local" name="69" />
+    <bus id="51" logo="stm_local" name="70" />
+    <bus id="" logo="stm_local" name="71" />
+    <bus id="" logo="stm_local" name="72" />
+    <bus id="" logo="stm_local" name="73" />
+    <bus id="" logo="stm_local" name="74" />
+    <bus id="" logo="stm_local" name="75" />
+    <bus id="" logo="stm_local" name="76" />
+    <bus id="" logo="stm_local" name="77" />
+    <bus id="" logo="stm_local" name="78" />
+    <bus id="59" logo="stm_local" name="80" />
+    <bus id="60" logo="stm_local" name="85" />
+    <bus id="61" logo="stm_local" name="86" />
+    <bus id="63" logo="stm_local" name="90" />
+    <bus id="" logo="stm_local" name="92" />
+    <bus id="65" logo="stm_local" name="93" />
+    <bus id="" logo="stm_local" name="94" />
+    <bus id="" logo="stm_local" name="95" />
+    <bus id="68" logo="stm_local" name="97" />
+    <bus id="" logo="stm_local" name="99" />
+    <bus id="" logo="stm_local" name="100" />
+    <bus id="" logo="stm_local" name="101" />
+    <bus id="" logo="stm_local" name="102" />
+    <bus id="73" logo="stm_local" name="103" />
+    <bus id="74" logo="stm_local" name="104" />
+    <bus id="75" logo="stm_local" name="105" />
+    <bus id="" logo="stm_local" name="106" />
+    <bus id="77" logo="stm_local" name="107" />
+    <bus id="" logo="stm_local" name="108" />
+    <bus id="79" logo="stm_local" name="109" />
+    <bus id="80" logo="stm_local" name="110" />
+    <bus id="81" logo="stm_local" name="112" />
+    <bus id="82" logo="stm_local" name="113" />
+    <bus id="" logo="stm_local" name="115" />
+    <bus id="" logo="stm_local" name="116" />
+    <bus id="" logo="stm_local" name="117" />
+    <bus id="" logo="stm_local" name="119" />
+    <bus id="87" logo="stm_local" name="121" />
+    <bus id="" logo="stm_local" name="123" />
+    <bus id="89" logo="stm_local" name="124" />
+    <bus id="90" logo="stm_local" name="125" />
+    <bus id="" logo="stm_local" name="126" />
+    <bus id="92" logo="stm_local" name="128" />
+    <bus id="93" logo="stm_local" name="129" />
+    <bus id="94" logo="stm_local" name="131" />
+    <bus id="" logo="stm_local" name="135" />
+    <bus id="234" logo="stm_local" name="136" />
+    <bus id="97" logo="stm_local" name="138" />
+    <bus id="98" logo="stm_local" name="139" />
+    <bus id="99" logo="stm_local" name="140" />
+    <bus id="100" logo="stm_local" name="141" />
+    <bus id="" logo="stm_local" name="144" />
+    <bus id="103" logo="stm_local" name="146" />
+    <bus id="" logo="stm_local" name="150" />
+    <bus id="" logo="stm_local" name="160" />
+    <bus id="108" logo="stm_local" name="161" />
+    <bus id="109" logo="stm_local" name="162" />
+    <bus id="110" logo="stm_local" name="164" />
+    <bus id="111" logo="stm_local" name="165" />
+    <bus id="" logo="stm_local" name="166" />
+    <bus id="113" logo="stm_local" name="168" />
+    <bus id="" logo="stm_local" name="170" />
+    <bus id="116" logo="stm_local" name="171" />
+    <bus id="" logo="stm_local" name="174" />
+    <bus id="" logo="stm_local" name="175" />
+    <bus id="120" logo="stm_local" name="177" />
+    <bus id="" logo="stm_local" name="178" />
+    <bus id="121" logo="stm_local" name="179" />
+    <bus id="" logo="stm_local" name="180" />
+    <bus id="" logo="stm_local" name="183" />
+    <bus id="126" logo="stm_local" name="185" />
+    <bus id="" logo="stm_local" name="186" />
+    <bus id="128" logo="stm_local" name="187" />
+    <bus id="" logo="stm_local" name="188" />
+    <bus id="130" logo="stm_local" name="189" />
+    <bus id="132" logo="stm_local" name="191" />
+    <bus id="134" logo="stm_local" name="192" />
+    <bus id="134" logo="stm_local" name="193" />
+    <bus id="136" logo="stm_local" name="195" />
+    <bus id="" logo="stm_local" name="196" />
+    <bus id="138" logo="stm_local" name="197" />
+    <bus id="" logo="stm_local" name="200" />
+    <bus id="141" logo="stm_local" name="201" />
+    <bus id="" logo="stm_local" name="202" />
+    <bus id="" logo="stm_local" name="203" />
+    <bus id="" logo="stm_local" name="204" />
+    <bus id="145" logo="stm_local" name="205" />
+    <bus id="" logo="stm_local" name="206" />
+    <bus id="147" logo="stm_local" name="207" />
+    <bus id="148" logo="stm_local" name="208" />
+    <bus id="149" logo="stm_local" name="209" />
+    <bus id="151" logo="stm_local" name="211" />
+    <bus id="" logo="stm_local" name="212" />
+    <bus id="" logo="stm_local" name="213" />
+    <bus id="" logo="stm_local" name="215" />
+    <bus id="155" logo="stm_local" name="216" />
+    <bus id="156" logo="stm_local" name="217" />
+    <bus id="" logo="stm_local" name="218" />
+    <bus id="" logo="stm_local" name="219" />
+    <bus id="" logo="stm_local" name="220" />
+    <bus id="" logo="stm_local" name="225" />
+    <bus id="" logo="stm_night" name="350" />
+    <bus id="" logo="stm_night" name="353" />
+    <bus id="" logo="stm_night" name="354" />
+    <bus id="" logo="stm_night" name="355" />
+    <bus id="" logo="stm_night" name="356" />
+    <bus id="" logo="stm_night" name="357" />
+    <bus id="" logo="stm_night" name="358" />
+    <bus id="" logo="stm_night" name="359" />
+    <bus id="" logo="stm_night" name="360" />
+    <bus id="" logo="stm_night" name="361" />
+    <bus id="" logo="stm_night" name="362" />
+    <bus id="" logo="stm_night" name="363" />
+    <bus id="" logo="stm_night" name="364" />
+    <bus id="" logo="stm_night" name="365" />
+    <bus id="" logo="stm_night" name="368" />
+    <bus id="" logo="stm_night" name="369" />
+    <bus id="" logo="stm_night" name="370" />
+    <bus id="" logo="stm_night" name="371" />
+    <bus id="" logo="stm_night" name="372" />
+    <bus id="" logo="stm_night" name="376" />
+    <bus id="" logo="stm_night" name="378" />
+    <bus id="" logo="stm_night" name="380" />
+    <bus id="" logo="stm_night" name="382" />
+    <bus id="237" logo="stm_express" name="401" />
+    <bus id="261" logo="stm_express" name="405" />
+    <bus id="" logo="stm_express" name="406" />
+    <bus id="" logo="stm_express" name="407" />
+    <bus id="" logo="stm_express" name="409" />
+    <bus id="165" logo="stm_express" name="410" />
+    <bus id="" logo="stm_express" name="411" />
+    <bus id="242" logo="stm_express" name="419" />
+    <bus id="" logo="stm_express" name="420" />
+    <bus id="" logo="stm_express" name="425" />
+    <bus id="218" logo="stm_express" name="427" />
+    <bus id="" logo="stm_express" name="428" />
+    <bus id="" logo="stm_express" name="430" />
+    <bus id="" logo="stm_express" name="432" />
+    <bus id="" logo="stm_express" name="435" />
+    <bus id="" logo="stm_express" name="439" />
+    <bus id="" logo="stm_express" name="440" />
+    <bus id="" logo="stm_express" name="444" />
+    <bus id="" logo="stm_express" name="445" />
+    <bus id="" logo="stm_express" name="448" />
+    <bus id="" logo="stm_express" name="449" />
+    <bus id="" logo="stm_express" name="460" />
+    <bus id="" logo="stm_express" name="465" />
+    <bus id="211" logo="stm_express" name="467" />
+    <bus id="" logo="stm_express" name="468" />
+    <bus id="" logo="stm_express" name="469" />
+    <bus id="169" logo="stm_express" name="470" />
+    <bus id="" logo="stm_express" name="475" />
+    <bus id="" logo="stm_express" name="480" />
+    <bus id="" logo="stm_express" name="485" />
+    <bus id="" logo="stm_express" name="486" />
+    <bus id="253" logo="stm_express" name="487" />
+    <bus id="" logo="stm_express" name="491" />
+    <bus id="255" logo="stm_express" name="495" />
+    <bus id="256" logo="stm_express" name="496" />
+    <bus id="257" logo="stm_shuttle" name="715" />
+    <bus id="219" logo="stm_shuttle" name="747" />
+    <bus id="" logo="stm_shuttle" name="777" />
+    <bus id="" logo="stm_local" name="252" />
+    <bus id="" logo="stm_local" name="253" />
+    <bus id="" logo="stm_local" name="254" />
+    <bus id="" logo="stm_local" name="256" />
+    <bus id="" logo="stm_local" name="257" />
+    <bus id="" logo="stm_local" name="258" />
+    <bus id="" logo="stm_local" name="259" />
+    <bus id="" logo="stm_local" name="260" />
+    <bus id="" logo="stm_local" name="262" />
+    <bus id="" logo="stm_local" name="263" />
+*/
+
+#ifndef OPUS_LISTS_H
+#define OPUS_LISTS_H
+
+static const char* OPUS_LINES_LIST[512] = {
+    [1] = "Green", [2] = "Orange",    [3] = "Yellow", [4] = "Blue",
+
+    [13] = "18",
+
+    [15] = "24",   [16] = "25",       [231] = "26",   [17] = "27",   [18] = "28",
+
+    [22] = "32",   [23] = "33",       [24] = "34",
+
+    [26] = "37",
+
+    [226] = "41",  [29] = "43",       [30] = "44",    [31] = "45",
+
+    [33] = "47",   [34] = "48",       [35] = "49",    [36] = "51",
+
+    [40] = "55",
+
+    [44] = "61",
+
+    [46] = "64",
+
+    [48] = "67",   [49] = "68",       [50] = "69",    [51] = "70",
+
+    [59] = "80",   [60] = "85",       [61] = "86",    [63] = "90",
+
+    [65] = "93",
+
+    [68] = "97",
+
+    [73] = "103",  [74] = "104",      [75] = "105",
+
+    [77] = "107",
+
+    [79] = "109",  [80] = "110",      [81] = "112",   [82] = "113",
+
+    [87] = "121",  [89] = "124",      [90] = "125",
+
+    [92] = "128",  [93] = "129",      [94] = "131",
+
+    [234] = "136", [97] = "138",      [98] = "139",   [99] = "140",  [100] = "141",
+
+    [103] = "146",
+
+    [108] = "161", [109] = "162",     [110] = "164",  [111] = "165",
+
+    [113] = "168",
+
+    [116] = "171",
+
+    [120] = "177", [121] = "179",
+
+    [126] = "185",
+
+    [128] = "187",
+
+    [130] = "189",
+
+    [132] = "191", [134] = "192/193",
+
+    [136] = "195",
+
+    [138] = "197",
+
+    [141] = "201",
+
+    [145] = "205",
+
+    [147] = "207", [148] = "208",     [149] = "209",
+
+    [151] = "211",
+
+    [155] = "216", [156] = "217",
+
+    [237] = "401", [261] = "405",
+
+    [165] = "410",
+
+    [242] = "419",
+
+    [218] = "427",
+
+    [211] = "467",
+
+    [169] = "470",
+
+    [253] = "487",
+
+    [255] = "495", [256] = "496",     [257] = "715",
+
+    [219] = "747",
+};
+
+#endif // OPUS_LISTS_H

+ 102 - 0
metroflip/api/calypso/transit/ravkav.c

@@ -0,0 +1,102 @@
+#include "ravkav_lists.h"
+#include "../../../metroflip_i.h"
+#include "../../metroflip/metroflip_api.h"
+#include "../calypso_util.h"
+#include "ravkav_i.h"
+
+const char* get_ravkav_issuer(int issuer) {
+    if(RAVKAV_ISSUERS_LIST[issuer]) {
+        return RAVKAV_ISSUERS_LIST[issuer];
+    } else {
+        // Return hex
+        char* issuer_str = malloc(9 * sizeof(char));
+        if(!issuer_str) {
+            return "Unknown";
+        }
+        snprintf(issuer_str, 9, "0x%02X", issuer);
+        return issuer_str;
+    }
+}
+
+void show_ravkav_contract_info(RavKavCardContract* contract, FuriString* parsed_data) {
+    // Core contract validity period
+    furi_string_cat_printf(parsed_data, "Valid from: ");
+    locale_format_datetime_cat(parsed_data, &contract->start_date, false);
+    if(contract->end_date_available) {
+        furi_string_cat_printf(parsed_data, "\nto: ");
+        locale_format_datetime_cat(parsed_data, &contract->end_date, false);
+    }
+    furi_string_cat_printf(parsed_data, "\n");
+
+    // Issuer information
+    furi_string_cat_printf(parsed_data, "Issuer: %s\n", get_ravkav_issuer(contract->provider));
+
+    // Sale details
+    furi_string_cat_printf(parsed_data, "Sold on: ");
+    locale_format_datetime_cat(parsed_data, &contract->sale_date, false);
+    furi_string_cat_printf(parsed_data, "\nSale device: %d\n", contract->sale_device);
+    furi_string_cat_printf(parsed_data, "Sale number: %d\n", contract->sale_number);
+
+    // Restriction details
+    if(contract->restrict_code_available) {
+        furi_string_cat_printf(parsed_data, "Restriction code: %d\n", contract->restrict_code);
+    }
+    if(contract->restrict_duration_available) {
+        furi_string_cat_printf(
+            parsed_data, "Restriction duration: \n%d minutes\n", contract->restrict_duration);
+    }
+
+    // Additional metadata
+    furi_string_cat_printf(parsed_data, "Contract version: %d\n", contract->version);
+    furi_string_cat_printf(parsed_data, "Journey interchanges flag: %d\n", contract->interchange);
+}
+
+void show_ravkav_environment_info(RavKavCardEnv* environment, FuriString* parsed_data) {
+    // Validity information
+    furi_string_cat_printf(parsed_data, "End of validity:\n");
+    locale_format_datetime_cat(parsed_data, &environment->end_dt, false);
+    furi_string_cat_printf(parsed_data, "\n");
+
+    // Issue date
+    furi_string_cat_printf(parsed_data, "Issue date:\n");
+    locale_format_datetime_cat(parsed_data, &environment->issue_dt, false);
+    furi_string_cat_printf(parsed_data, "\n");
+
+    // Application details
+    furi_string_cat_printf(parsed_data, "App version: %d\n", environment->app_version);
+    furi_string_cat_printf(parsed_data, "App number: %d\n", environment->app_num);
+
+    // Payment and network details
+    furi_string_cat_printf(parsed_data, "Pay method: %d\n", environment->pay_method);
+}
+
+void show_ravkav_event_info(RavKavCardEvent* event, FuriString* parsed_data) {
+    // Essential details first
+    if(event->location_id_available) {
+        furi_string_cat_printf(parsed_data, "Location:\n%d\n", event->location_id);
+    }
+    furi_string_cat_printf(parsed_data, "Time:\n");
+    locale_format_datetime_cat(parsed_data, &event->time, true);
+    furi_string_cat_printf(parsed_data, "\n");
+    furi_string_cat_printf(parsed_data, "Service provider: %d\n", event->service_provider);
+
+    // Fare and route-related information
+    if(event->fare_code_available) {
+        furi_string_cat_printf(parsed_data, "Fare Code: %d\n", event->fare_code);
+    }
+    if(event->route_number_available) {
+        furi_string_cat_printf(parsed_data, "Route Number: %d\n", event->route_number);
+    }
+    if(event->debit_amount_available) {
+        furi_string_cat_printf(parsed_data, "Debit Amount: %.2f\n", (double)event->debit_amount);
+    }
+    if(event->stop_en_route_available) {
+        furi_string_cat_printf(parsed_data, "Stop en Route: %d\n", event->stop_en_route);
+    }
+
+    // Remaining event metadata
+    furi_string_cat_printf(parsed_data, "Area ID: %d\n", event->area_id);
+    furi_string_cat_printf(parsed_data, "Contract ID: %d\n", event->contract_id);
+    furi_string_cat_printf(parsed_data, "Event Type: %d\n", event->type);
+    furi_string_cat_printf(parsed_data, "Event Interchange Flag: %d\n", event->interchange_flag);
+}

+ 67 - 0
metroflip/api/calypso/transit/ravkav_i.h

@@ -0,0 +1,67 @@
+#include <datetime.h>
+#include <stdbool.h>
+
+#ifndef RAVKAV_I_H
+#define RAVKAV_I_H
+
+typedef struct {
+    int version;
+    int provider;
+    int tariff;
+    int sale_device;
+    int sale_number;
+    DateTime start_date;
+    DateTime sale_date;
+    int interchange;
+
+    //bitmap elements
+    int restrict_code;
+    int restrict_duration;
+    DateTime end_date;
+    bool restrict_code_available;
+    bool restrict_duration_available;
+    bool end_date_available;
+
+    bool present;
+} RavKavCardContract;
+
+typedef struct {
+    int event_version;
+    int service_provider;
+    int contract_id;
+    int area_id;
+    int type;
+    DateTime time;
+    int interchange_flag;
+
+    int location_id;
+    bool location_id_available;
+    int route_number;
+    bool route_number_available;
+    int stop_en_route;
+    bool stop_en_route_available;
+    int vehicle_id;
+    bool vehicle_id_available;
+
+    int fare_code;
+    bool fare_code_available;
+    float debit_amount;
+    bool debit_amount_available;
+} RavKavCardEvent;
+
+typedef struct {
+    int app_version;
+    int network_num;
+    int app_num;
+    int pay_method;
+    DateTime end_dt;
+    DateTime issue_dt;
+} RavKavCardEnv;
+
+typedef struct {
+    RavKavCardEnv environment;
+    RavKavCardContract contracts[4];
+    RavKavCardEvent events[3];
+} RavKavCardData;
+
+#endif // RAVKAV_I_H

+ 64 - 0
metroflip/api/calypso/transit/ravkav_lists.h

@@ -0,0 +1,64 @@
+#ifndef RAVKAV_LISTS_H
+#define RAVKAV_LISTS_H
+
+static const char* RAVKAV_ISSUERS_LIST[300] = {
+    [0] = "undefined",
+    [1] = "Service Center (Postal Bank)",
+    [2] = "Israel Railways",
+    [3] = "Egged",
+    [4] = "Egged Transport",
+    [5] = "Dan",
+    [6] = "NTT (United Bus Services Nazareth)",
+    [7] = "NTT (Nazareth Travel & Tourism)",
+    [8] = "GB Tours",
+    [9] = "Omni Nazrin Express (transferred to Nativ Express)",
+    [10] = "Eilot Regional Council",
+    [11] = "Illit (merged with Kavim)",
+    [12] = "undefined 12",
+    [13] = "undefined 13",
+    [14] = "Nativ Express",
+    [15] = "Metropoline",
+    [16] = "Superbus",
+    [17] = "Connex Veolia (transferred to Afikim)",
+    [18] = "Kavim",
+    [19] = "Metrodan (defunct, transferred to Dan Be\'er Sheva)",
+    [20] = "Carmelit",
+    [21] = "CityPass",
+    [22] = "Tel Aviv Light Rail operator",
+    [23] = "Galim (Narkis Gal)",
+    [24] = "Golan Regional Council",
+    [25] = "Afikim",
+    [26] = "undefined 26",
+    [27] = "undefined 27",
+    [28] = "undefined 28",
+    [29] = "undefined 29",
+    [30] = "Dan North",
+    [31] = "Dan South",
+    [32] = "Dan Be\'er Sheva",
+    [33] = "undefined 33",
+    [34] = "undefined 34",
+    [35] = "undefined 35",
+    [36] = "undefined 36",
+    [37] = "undefined 37",
+    [38] = "undefined 38",
+    [39] = "undefined 39",
+    [40] = "undefined 40",
+    [41] = "East Jerusalem operators",
+    [42] = "Jerusalem - Ramallah united",
+    [43] = "(obsolete) Jerusalem - Mahane Shuafat",
+    [44] = "Jerusalem - Anata united",
+    [45] = "Jerusalem - Isawiya united",
+    [46] = "(obsolete) Jabal Al Muhbar Ltd",
+    [47] = "Jerusalem - Mount of Olives",
+    [48] = "(obsolete) Abu-Tor Siluan bus company",
+    [49] = "Jerusalem - Isawiya - Mahane Shuafat united",
+    [50] = "South Jerusalem united",
+    [51] = "Jerusalem - Sur Baher united",
+    [52] = "(obsolete) Ras-al Amud Izriya - Abu-Dis",
+    [53] = "(obsolete) Jerusalem - A. Swahra united",
+
+    [98] = "Tel Aviv Service Taxi Lines 4, 2 and 5",
+    [240] = "[Technical] Service Center - Contract restores",
+    [250] = "Israel Defence Forces"};
+
+#endif // RAVKAV_LISTS_H

+ 138 - 0
metroflip/api/metroflip/metroflip_api.h

@@ -0,0 +1,138 @@
+/**
+ * @file metroflip_api.h
+ * @brief Application API example.
+ *
+ * This file contains an API that is internally implemented by the application
+ * It is also exposed to plugins to allow them to use the application's API.
+ */
+#pragma once
+
+#include <stdint.h>
+#include "../../metroflip_i.h"
+#include <gui/gui.h>
+#include <gui/modules/widget_elements/widget_element.h>
+#include "../calypso/calypso_i.h"
+#include "../calypso/calypso_util.h"
+#include <datetime.h>
+#include <stdbool.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// metroflip
+
+void metroflip_exit_widget_callback(GuiButtonType result, InputType type, void* context);
+
+void metroflip_app_blink_start(Metroflip* metroflip);
+
+void metroflip_app_blink_stop(Metroflip* metroflip);
+
+int bit_slice_to_dec(const char* bit_representation, int start, int end);
+
+void byte_to_binary(uint8_t byte, char* bits);
+
+bool mf_classic_key_cache_load(MfClassicKeyCache* instance, const uint8_t* uid, size_t uid_len);
+
+bool mf_classic_key_cache_get_next_key(
+    MfClassicKeyCache* instance,
+    uint8_t* sector_num,
+    MfClassicKey* key,
+    MfClassicKeyType* key_type);
+
+void mf_classic_key_cache_reset(MfClassicKeyCache* instance);
+
+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 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[]);
+
+extern uint8_t read_file[5];
+extern uint8_t apdu_success[2];
+extern uint8_t select_app[8];
+
+/*****  calypso *****/
+int get_calypso_node_offset(const char* binary_string, const char* key, CalypsoApp* structure);
+const char* get_network_string(CALYPSO_CARD_TYPE card_type);
+void metroflip_back_button_widget_callback(GuiButtonType result, InputType type, void* context);
+void metroflip_next_button_widget_callback(GuiButtonType result, InputType type, void* context);
+bool is_calypso_node_present(const char* binary_string, const char* key, CalypsoApp* structure);
+int get_calypso_node_size(const char* key, CalypsoApp* structure);
+void free_calypso_structure(CalypsoApp* structure);
+CALYPSO_CARD_TYPE guess_card_type(int country_num, int network_num);
+
+// intercode
+
+CalypsoApp* get_intercode_structure_env_holder();
+
+CalypsoApp* get_intercode_structure_contract();
+
+CalypsoApp* get_intercode_structure_event();
+
+CalypsoApp* get_intercode_structure_counter();
+
+//navigo
+
+void show_navigo_event_info(
+    NavigoCardEvent* event,
+    NavigoCardContract* contracts,
+    FuriString* parsed_data);
+
+void show_navigo_special_event_info(NavigoCardSpecialEvent* event, FuriString* parsed_data);
+
+void show_navigo_contract_info(NavigoCardContract* contract, FuriString* parsed_data);
+
+void show_navigo_environment_info(
+    NavigoCardEnv* environment,
+    NavigoCardHolder* holder,
+    FuriString* parsed_data);
+
+// opus
+
+CalypsoApp* get_opus_contract_structure();
+
+CalypsoApp* get_opus_event_structure();
+
+CalypsoApp* get_opus_env_holder_structure();
+
+void show_opus_event_info(
+    OpusCardEvent* event,
+    OpusCardContract* contracts,
+    FuriString* parsed_data);
+
+void show_opus_contract_info(OpusCardContract* contract, FuriString* parsed_data);
+
+void show_opus_environment_info(
+    OpusCardEnv* environment,
+    OpusCardHolder* holder,
+    FuriString* parsed_data);
+
+//ravkav
+
+CalypsoApp* get_ravkav_contract_structure();
+
+CalypsoApp* get_ravkav_event_structure();
+
+CalypsoApp* get_ravkav_env_holder_structure();
+
+void show_ravkav_event_info(RavKavCardEvent* event, FuriString* parsed_data);
+
+void show_ravkav_contract_info(RavKavCardContract* contract, FuriString* parsed_data);
+
+void show_ravkav_environment_info(RavKavCardEnv* environment, FuriString* parsed_data);
+/*******************/
+#ifdef __cplusplus
+}
+#endif

+ 9 - 0
metroflip/api/metroflip/metroflip_api_interface.h

@@ -0,0 +1,9 @@
+#pragma once
+
+#include <flipper_application/api_hashtable/api_hashtable.h>
+
+/* 
+ * Resolver interface with private application's symbols. 
+ * Implementation is contained in metroflip_api_table.c
+ */
+extern const ElfApiInterface* const metroflip_api_interface;

+ 27 - 0
metroflip/api/metroflip/metroflip_api_table.cpp

@@ -0,0 +1,27 @@
+#include <flipper_application/api_hashtable/api_hashtable.h>
+#include <flipper_application/api_hashtable/compilesort.hpp>
+
+/* 
+ * This file contains an implementation of a symbol table 
+ * with private app's symbols. It is used by composite API resolver
+ * to load plugins that use internal application's APIs.
+ */
+#include "metroflip_api_table_i.h"
+
+static_assert(!has_hash_collisions(metroflip_api_table), "Detected API method hash collision!");
+
+constexpr HashtableApiInterface applicaton_hashtable_api_interface{
+    {
+        .api_version_major = 0,
+        .api_version_minor = 0,
+        /* generic resolver using pre-sorted array */
+        .resolver_callback = &elf_resolve_from_hashtable,
+    },
+    /* pointers to application's API table boundaries */
+    metroflip_api_table.cbegin(),
+    metroflip_api_table.cend(),
+};
+
+/* Casting to generic resolver to use in Composite API resolver */
+extern "C" const ElfApiInterface* const metroflip_api_interface =
+    &applicaton_hashtable_api_interface;

+ 71 - 0
metroflip/api/metroflip/metroflip_api_table_i.h

@@ -0,0 +1,71 @@
+#include "metroflip_api.h"
+#include "../../metroflip_i.h"
+
+/* 
+ * A list of app's private functions and objects to expose for plugins.
+ * It is used to generate a table of symbols for import resolver to use.
+ * TBD: automatically generate this table from app's header files
+ */
+static constexpr auto metroflip_api_table = sort(create_array_t<sym_entry>(
+    // metroflip stuff
+    API_METHOD(metroflip_exit_widget_callback, void, (GuiButtonType, InputType, void*)),
+    API_METHOD(metroflip_app_blink_start, void, (Metroflip*)),
+    API_METHOD(metroflip_app_blink_stop, void, (Metroflip*)),
+    API_METHOD(bit_slice_to_dec, int, (const char*, int, int)),
+    API_METHOD(byte_to_binary, void, (uint8_t, char*)),
+    API_VARIABLE(read_file, uint8_t[5]),
+    API_VARIABLE(apdu_success, uint8_t[2]),
+    API_VARIABLE(select_app, uint8_t[8]),
+    API_METHOD(mf_classic_key_cache_load, bool, (MfClassicKeyCache*, const uint8_t*, size_t)),
+    API_METHOD(
+        mf_classic_key_cache_get_next_key,
+        bool,
+        (MfClassicKeyCache*, uint8_t*, MfClassicKey*, MfClassicKeyType*)),
+    API_METHOD(mf_classic_key_cache_reset, void, (MfClassicKeyCache*)),
+    API_METHOD(
+        manage_keyfiles,
+        KeyfileManager,
+        (char[], const uint8_t*, size_t, MfClassicKeyCache*, uint64_t, uint64_t)),
+    API_METHOD(uid_to_string, void, (const uint8_t*, size_t, char*, size_t)),
+    API_METHOD(
+        handle_keyfile_case,
+        void,
+        (Metroflip*, const char*, const char*, FuriString*, char[])),
+
+    // calypso
+    API_METHOD(get_calypso_node_offset, int, (const char*, const char*, CalypsoApp*)),
+    API_METHOD(get_network_string, const char*, (CALYPSO_CARD_TYPE)),
+    API_METHOD(metroflip_back_button_widget_callback, void, (GuiButtonType, InputType, void*)),
+    API_METHOD(metroflip_next_button_widget_callback, void, (GuiButtonType, InputType, void*)),
+    API_METHOD(is_calypso_node_present, bool, (const char*, const char*, CalypsoApp*)),
+    API_METHOD(get_calypso_node_size, int, (const char*, CalypsoApp*)),
+    API_METHOD(free_calypso_structure, void, (CalypsoApp*)),
+    API_METHOD(guess_card_type, CALYPSO_CARD_TYPE, (int, int)),
+
+    // intercode
+    API_METHOD(get_intercode_structure_env_holder, CalypsoApp*, ()),
+    API_METHOD(get_intercode_structure_contract, CalypsoApp*, ()),
+    API_METHOD(get_intercode_structure_event, CalypsoApp*, ()),
+    API_METHOD(get_intercode_structure_counter, CalypsoApp*, ()),
+
+    // navigo
+    API_METHOD(show_navigo_event_info, void, (NavigoCardEvent*, NavigoCardContract*, FuriString*)),
+    API_METHOD(show_navigo_special_event_info, void, (NavigoCardSpecialEvent*, FuriString*)),
+    API_METHOD(show_navigo_contract_info, void, (NavigoCardContract*, FuriString*)),
+    API_METHOD(show_navigo_environment_info, void, (NavigoCardEnv*, NavigoCardHolder*, FuriString*)),
+
+    // opus
+    API_METHOD(get_opus_contract_structure, CalypsoApp*, ()),
+    API_METHOD(get_opus_event_structure, CalypsoApp*, ()),
+    API_METHOD(get_opus_env_holder_structure, CalypsoApp*, ()),
+    API_METHOD(show_opus_event_info, void, (OpusCardEvent*, OpusCardContract*, FuriString*)),
+    API_METHOD(show_opus_contract_info, void, (OpusCardContract*, FuriString*)),
+    API_METHOD(show_opus_environment_info, void, (OpusCardEnv*, OpusCardHolder*, FuriString*)),
+
+    // ravkav
+    API_METHOD(get_ravkav_contract_structure, CalypsoApp*, ()),
+    API_METHOD(get_ravkav_event_structure, CalypsoApp*, ()),
+    API_METHOD(get_ravkav_env_holder_structure, CalypsoApp*, ()),
+    API_METHOD(show_ravkav_event_info, void, (RavKavCardEvent*, FuriString*)),
+    API_METHOD(show_ravkav_contract_info, void, (RavKavCardContract*, FuriString*)),
+    API_METHOD(show_ravkav_environment_info, void, (RavKavCardEnv*, FuriString*))));

+ 213 - 0
metroflip/api/nfc/mf_classic_key_cache.c

@@ -0,0 +1,213 @@
+#include "mf_classic_key_cache.h"
+
+#include <furi/furi.h>
+#include <storage/storage.h>
+#include "../metroflip/metroflip_api.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(void) {
+    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_cache_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;
+}

+ 21 - 0
metroflip/api/nfc/mf_classic_key_cache.h

@@ -0,0 +1,21 @@
+#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);
+
+void mf_classic_key_cache_free(MfClassicKeyCache* instance);
+
+void mf_classic_key_cache_load_from_data(MfClassicKeyCache* instance, const MfClassicData* data);
+
+bool mf_classic_key_cache_save(MfClassicKeyCache* instance, const MfClassicData* data);
+
+#ifdef __cplusplus
+}
+#endif

+ 21 - 19
metroflip/app/README.md

@@ -1,5 +1,5 @@
 # Metroflip
-Metroflip is a multi-protocol metro card reader app for the Flipper Zero, inspired by the Metrodroid project. It enables the parsing and analysis of metro cards from transit systems around the world, providing a proof-of-concept for exploring transit card data in a portable format.
+Metroflip is a multi-protocol metro card reader app for the Flipper Zero, inspired by the Metrodroid project. It enables the parsing and analysis of metro cards from transit systems around the world, providing a proof-of-concept for exploring transit card data in a portable format. 
 
 # Author
 luu176
@@ -9,29 +9,31 @@ luu176
 This is a list of metro cards and transit systems that are supported.
 
 ## Supported Cards
-- **Rav-Kav**  
-- **Navigo**  
-- **Charliecard**  
-- **Metromoney**  
 - **Bip!**  
+- **Charliecard**  
 - **Clipper**  
-- **Troika**  
+- **ITSO**  
+- **Metromoney**  
 - **Myki**  
+- **Navigo**  
 - **Opal**  
-- **ITSO**
+- **Opus**  
+- **Rav-Kav**  
+- **Troika**  
 
 More coming soon! 
 
 ## Credits:
-- **App Author**: luu176
-- **Charliecard Parser**: zacharyweiss
-- **Rav-Kav Parser**: luu176
-- **Navigo Parser**: luu176, DocSystem
-- **Metromoney Parser**: Leptopt1los
-- **Bip! Parser**: rbasoaltor & gornekich
-- **Clipper Parser**: ke6jjj
-- **Troika Parser**: gornekich
-- **Myki Parser**: gornekich
-- **Opal parser**: gornekich
-- **ITSO parser**: gsp8181, hedger, gornekich
-- **Info Slaves**: Equip, TheDingo8MyBaby
+- **App Author**: luu176  
+- **Special Thanks**: willyjl
+- **Bip! Parser**: rbasoalto, gornekich  
+- **Charliecard Parser**: zacharyweiss  
+- **Clipper Parser**: ke6jjj  
+- **Info Slaves**: equipter, TheDingo8MyBaby  
+- **ITSO Parser**: gsp8181, hedger, gornekich  
+- **Metromoney Parser**: Leptopt1los  
+- **Myki Parser**: gornekich  
+- **Navigo Parser**: luu176, DocSystem  
+- **Opal Parser**: gornekich  
+- **Rav-Kav Parser**: luu176  
+- **Troika Parser**: gornekich  

+ 90 - 1
metroflip/application.fam

@@ -5,9 +5,98 @@ App(
     entry_point="metroflip",
     stack_size=2 * 1024,
     fap_category="NFC",
-    fap_version="0.4",
+    fap_version="0.5",
     fap_icon="icon.png",
     fap_description="An implementation of metrodroid on the flipper",
     fap_author="luu176",
     fap_icon_assets="images",  # Image assets to compile for this application
+    fap_file_assets="files",
+)
+
+App(
+    appid="charliecard_plugin",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="charliecard_plugin_ep",
+    requires=["metroflip"],
+    sources=["scenes/plugins/charliecard.c"],
+    fal_embedded=True,
+)
+
+App(
+    appid="calypso_plugin",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="calypso_plugin_ep",
+    requires=["metroflip"],
+    sources=["scenes/plugins/calypso.c"],
+    fal_embedded=True,
+)
+
+App(
+    appid="metromoney_plugin",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="metromoney_plugin_ep",
+    requires=["metroflip"],
+    sources=["scenes/plugins/metromoney.c"],
+)
+
+App(
+    appid="bip_plugin",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="bip_plugin_ep",
+    requires=["metroflip"],
+    sources=["scenes/plugins/bip.c"],
+)
+
+App(
+    appid="clipper_plugin",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="clipper_plugin_ep",
+    requires=["metroflip"],
+    sources=["scenes/plugins/clipper.c"],
+    fal_embedded=True,
+)
+
+App(
+    appid="itso_plugin",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="itso_plugin_ep",
+    requires=["metroflip"],
+    sources=["scenes/plugins/itso.c"],
+    fal_embedded=True,
+)
+
+App(
+    appid="myki_plugin",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="myki_plugin_ep",
+    requires=["metroflip"],
+    sources=["scenes/plugins/myki.c"],
+    fal_embedded=True,
+)
+
+App(
+    appid="opal_plugin",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="opal_plugin_ep",
+    requires=["metroflip"],
+    sources=["scenes/plugins/opal.c"],
+    fal_embedded=True,
+)
+
+App(
+    appid="smartrider_plugin",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="smartrider_plugin_ep",
+    requires=["metroflip"],
+    sources=["scenes/plugins/smartrider.c"],
+    fal_embedded=True,
+)
+
+App(
+    appid="troika_plugin",
+    apptype=FlipperAppType.PLUGIN,
+    entry_point="troika_plugin_ep",
+    requires=["metroflip"],
+    sources=["scenes/plugins/troika.c"],
+    fal_embedded=True,
 )

+ 10 - 0
metroflip/files/navigo/stations/metro/stations_1.txt

@@ -0,0 +1,10 @@
+0,Cite
+1,Saint-Michel
+4,Odeon
+6,Maubert - Mutualite
+8,Chatelet
+9,Les Halles
+12,Louvre - Rivoli
+13,Pont Neuf
+14,Cite
+15,Hotel de Ville

+ 12 - 0
metroflip/files/navigo/stations/metro/stations_10.txt

@@ -0,0 +1,12 @@
+0,Invalides
+1,Champs-Elysees - Clemenceau
+2,Concorde
+3,Madeleine
+4,Bir-Hakeim
+7,Ecole Militaire
+8,La Tour-Maubourg
+9,Invalides
+11,Saint-Denis - Universite
+12,Varenne
+13,Assemblee Nationale
+14,Solferino

+ 13 - 0
metroflip/files/navigo/stations/metro/stations_11.txt

@@ -0,0 +1,13 @@
+0,Sentier
+1,Tuileries
+2,Palais Royal - Musee du Louvre
+3,Pyramides
+4,Bourse
+6,Grands Boulevards
+7,Richelieu - Drouot
+8,Bonne Nouvelle
+10,Strasbourg - Saint-Denis
+11,Chateau d'Eau
+13,Sentier
+14,Reaumur - Sebastopol
+15,Etienne Marcel

+ 16 - 0
metroflip/files/navigo/stations/metro/stations_12.txt

@@ -0,0 +1,16 @@
+0,Ile Saint-Louis
+1,Faidherbe - Chaligny
+2,Reuilly - Diderot
+3,Montgallet
+4,Censier - Daubenton
+5,Place Monge
+6,Cardinal Lemoine
+7,Jussieu
+8,Sully - Morland
+9,Pont Marie (Cite des Arts)
+10,Saint-Paul (Le Marais)
+11,Bastille
+12,Bastille
+13,Chemin Vert
+14,Breguet-Sabin
+15,Ledru-Rollin

+ 8 - 0
metroflip/files/navigo/stations/metro/stations_13.txt

@@ -0,0 +1,8 @@
+0,Daumesnil
+1,Porte Doree
+3,Porte de Charenton
+7,Bercy
+8,Dugommier
+10,Michel Bizot
+11,Daumesnil
+12,Bel-Air

+ 11 - 0
metroflip/files/navigo/stations/metro/stations_14.txt

@@ -0,0 +1,11 @@
+0,Italie
+2,Porte de Choisy
+3,Porte d'Italie
+8,Maison Blanche
+9,Maison Blanche
+10,Tolbiac
+11,Nationale
+12,Campo-Formio
+13,Les Gobelins
+14,Place d'Italie
+15,Corvisart

+ 12 - 0
metroflip/files/navigo/stations/metro/stations_15.txt

@@ -0,0 +1,12 @@
+0,Denfert
+1,Cour Saint-Emilion
+2,Porte d'Orleans
+3,Bibliotheque Francois Mitterrand
+4,Mouton-Duvernet
+5,Alesia
+6,Olympiades
+7,Mairie de Montrouge
+8,Glaciere
+9,Saint-Jacques
+10,Raspail
+14,Denfert-Rochereau

+ 13 - 0
metroflip/files/navigo/stations/metro/stations_16.txt

@@ -0,0 +1,13 @@
+0,Felix Faure
+1,Falguiere
+2,Pasteur
+3,Volontaires
+4,Vaugirard
+5,Convention
+6,Porte de Versailles
+9,Balard
+10,Lourmel
+11,Boucicaut
+12,Felix Faure
+13,Charles Michels
+14,Javel - Andre Citroen

+ 12 - 0
metroflip/files/navigo/stations/metro/stations_17.txt

@@ -0,0 +1,12 @@
+0,Passy
+2,Porte Dauphine
+4,La Motte-Picquet - Grenelle
+5,Commerce
+6,Avenue Emile Zola
+7,Dupleix
+8,Passy
+9,Ranelagh
+11,La Muette
+13,Rue de la Pompe
+14,Boissiere
+15,Trocadero

+ 13 - 0
metroflip/files/navigo/stations/metro/stations_18.txt

@@ -0,0 +1,13 @@
+0,Etoile
+1,Iena
+3,Alma - Marceau
+4,Miromesnil
+5,Saint-Philippe-du-Roule
+7,Franklin D. Roosevelt
+8,George V
+9,Kleber
+10,Victor Hugo
+11,Argentine
+12,Charles de Gaulle - Etoile
+14,Ternes
+15,Courcelles

+ 13 - 0
metroflip/files/navigo/stations/metro/stations_19.txt

@@ -0,0 +1,13 @@
+0,Clichy - Saint Ouen
+1,Mairie de Clichy
+2,Gabriel Peri
+3,Les Agnettes
+4,Asnieres - Gennevilliers Les Courtilles
+5,Saint-Ouen
+6,Mairie de Saint-Ouen
+7,Saint-Denis - Pleyel
+10,Garibaldi
+11,Mairie de Saint-Ouen
+13,Carrefour Pleyel
+14,Saint-Denis - Porte de Paris
+15,Basilique de Saint-Denis

+ 14 - 0
metroflip/files/navigo/stations/metro/stations_2.txt

@@ -0,0 +1,14 @@
+0,Rennes
+1,Hotel de Ville
+2,Cambronne
+3,Sevres-Lecourbe
+4,Segur
+6,Saint-Francois-Xavier
+7,Duroc
+8,Vaneau
+9,Sevres - Babylone
+10,Rue du Bac
+11,Rennes
+12,Saint-Sulpice
+14,Mabillon
+15,Saint-Germain-des-Pres

+ 10 - 0
metroflip/files/navigo/stations/metro/stations_20.txt

@@ -0,0 +1,10 @@
+0,Montmartre
+1,Porte de Clignancourt
+5,Mairie d'Aubervilliers
+6,Porte de la Chapelle
+7,Marx Dormoy
+8,Front Populaire
+9,Marcadet - Poissonniers
+10,Simplon
+11,Jules Joffrin
+12,Lamarck - Caulaincourt

+ 10 - 0
metroflip/files/navigo/stations/metro/stations_21.txt

@@ -0,0 +1,10 @@
+0,Lafayette
+1,Chaussee d'Antin - La Fayette
+2,Le Peletier
+3,Cadet
+4,Chateau Rouge
+7,Barbes - Rochechouart
+8,Gare du Nord
+9,Gare de l'Est
+10,Poissonniere
+11,Chateau Landon

+ 13 - 0
metroflip/files/navigo/stations/metro/stations_22.txt

@@ -0,0 +1,13 @@
+0,Buttes Chaumont
+1,Porte de Pantin
+2,Ourcq
+4,Corentin Cariou
+6,Crimee
+8,Riquet
+9,La Chapelle
+10,Louis Blanc
+11,Stalingrad
+12,Jaures
+13,Laumiere
+14,Bolivar
+15,Colonel Fabien

+ 16 - 0
metroflip/files/navigo/stations/metro/stations_23.txt

@@ -0,0 +1,16 @@
+0,Belleville
+1,Rosny-Bois-Perrier
+2,Porte des Lilas
+3,Mairie des Lilas
+4,Porte de Bagnolet
+5,Gallieni
+6,Coteaux Beauclair
+7,Serge Gainsbourg
+8,Place des Fetes
+9,Botzaris
+10,Danube
+11,Pre-Saint-Gervais
+12,Romainville-Carnot
+13,Buttes Chaumont
+14,Jourdain
+15,Telegraphe

+ 13 - 0
metroflip/files/navigo/stations/metro/stations_24.txt

@@ -0,0 +1,13 @@
+0,Pere Lachaise
+1,Voltaire
+2,Charonne
+4,Pere Lachaise
+5,Menilmontant
+6,Rue Saint-Maur
+7,Philippe Auguste
+8,Saint-Fargeau
+9,Pelleport
+10,Gambetta
+12,Belleville
+13,Couronnes
+14,Pyrenees

+ 15 - 0
metroflip/files/navigo/stations/metro/stations_25.txt

@@ -0,0 +1,15 @@
+0,Charenton
+2,Croix de Chavaux
+3,Mairie de Montreuil
+4,Maisons-Alfort - Les Juilliottes
+5,Creteil - L'Echat
+6,Creteil - Universite
+7,Creteil - Prefecture
+8,Saint-Mande
+9,Pointe du Lac
+10,Berault
+11,Chateau de Vincennes
+12,Liberte
+13,Charenton - Ecoles
+14,Ecole Veterinaire de Maisons-Alfort
+15,Maisons-Alfort - Stade

+ 14 - 0
metroflip/files/navigo/stations/metro/stations_26.txt

@@ -0,0 +1,14 @@
+0,Ivry - Villejuif
+3,Porte d'Ivry
+4,Pierre et Marie Curie
+5,Mairie d'Ivry
+6,Le Kremlin-Bicetre
+7,Villejuif Leo Lagrange
+8,Villejuif Paul Vaillant-Couturier
+9,Villejuif - Louis Aragon
+10,Hopital Bicetre
+11,Villejuif - Gustave Roussy
+12,L'Hay-les-Roses
+13,Chevilly-Larue  (Marche International )
+14,Thiais - Orly  (Pont de Rungis )
+15,Aeroport d'Orly

+ 7 - 0
metroflip/files/navigo/stations/metro/stations_27.txt

@@ -0,0 +1,7 @@
+0,Vanves
+2,Porte de Vanves
+3,Barbara
+4,Bagneux - Lucie Aubrac
+7,Malakoff - Plateau de Vanves
+8,Malakoff - Rue Etienne Dolet
+9,Chatillon-Montrouge

+ 6 - 0
metroflip/files/navigo/stations/metro/stations_28.txt

@@ -0,0 +1,6 @@
+0,Issy
+2,Corentin Celton
+3,Mairie d'Issy
+8,Marcel Sembat
+9,Billancourt
+10,Pont de Sevres

+ 12 - 0
metroflip/files/navigo/stations/metro/stations_29.txt

@@ -0,0 +1,12 @@
+0,Levallois
+4,Boulogne Jean Jaures
+5,Antony
+5,Boulogne Pont de Saint-Cloud
+8,Les Sablons
+9,Pont de Neuilly
+10,Esplanade de la Defense
+11,La Defense (Grande Arche)
+12,Porte de Champerret
+13,Louise Michel
+14,Anatole France
+15,Pont de Levallois - Becon

+ 12 - 0
metroflip/files/navigo/stations/metro/stations_3.txt

@@ -0,0 +1,12 @@
+0,Villette
+1,Aime Cesaire
+4,Porte de la Villette
+5,Aubervilliers-Pantin Quatre Chemins
+6,Fort d'Aubervilliers
+7,La Courneuve - 8 Mai 1945
+9,Hoche
+10,Eglise de Pantin
+11,Bobigny-Pantin - Raymond Queneau
+12,Bobigny Pablo Picasso
+13,Montreuil - Hopital
+14,La Dhuys

+ 10 - 0
metroflip/files/navigo/stations/metro/stations_30.txt

@@ -0,0 +1,10 @@
+0,Pereire
+1,Porte Maillot
+4,Wagram
+5,Pereire
+6,Pont Cardinet
+7,Porte de Clichy
+8,Brochant
+9,Porte de Clichy
+12,Guy Moquet
+13,Porte de Saint-Ouen

+ 12 - 0
metroflip/files/navigo/stations/metro/stations_31.txt

@@ -0,0 +1,12 @@
+0,Pigalle
+2,Funiculaire de Montmartre
+4,Anvers
+5,Abbesses
+6,Pigalle
+7,Blanche
+8,Trinite - d'Estienne d'Orves
+9,Notre-Dame-de-Lorette
+10,Saint-Georges
+12,Rome
+13,Place de Clichy
+14,La Fourche

+ 9 - 0
metroflip/files/navigo/stations/metro/stations_4.txt

@@ -0,0 +1,9 @@
+0,Montparnasse
+2,Pernety
+3,Plaisance
+4,Gaite
+6,Edgar Quinet
+7,Vavin
+8,Montparnasse Bienvenue
+12,Saint-Placide
+14,Notre-Dame des Champs

+ 12 - 0
metroflip/files/navigo/stations/metro/stations_5.txt

@@ -0,0 +1,12 @@
+0,Nation
+2,Robespierre
+3,Porte de Montreuil
+4,Maraichers
+5,Buzenval
+6,Rue des Boulets
+7,Porte de Vincennes
+9,Picpus
+10,Nation
+11,Nation
+12,Avron
+13,Alexandre Dumas

+ 12 - 0
metroflip/files/navigo/stations/metro/stations_6.txt

@@ -0,0 +1,12 @@
+0,Saint-Lazare
+1,Malesherbes
+2,Monceau
+3,Villiers
+4,Quatre Septembre
+5,Opera
+7,Havre-Caumartin
+8,Saint-Lazare
+9,Saint-Lazare
+10,Saint-Augustin
+12,Europe
+13,Liege

+ 10 - 0
metroflip/files/navigo/stations/metro/stations_7.txt

@@ -0,0 +1,10 @@
+0,Auteuil
+3,Porte de Saint-Cloud
+7,Porte d'Auteuil
+8,Eglise d'Auteuil
+9,Michel-Ange - Auteuil
+10,Michel-Ange - Molitor
+11,Chardon Lagache
+12,Mirabeau
+14,Exelmans
+15,Jasmin

+ 13 - 0
metroflip/files/navigo/stations/metro/stations_8.txt

@@ -0,0 +1,13 @@
+0,Republique
+1,Rambuteau
+3,Arts et Metiers
+4,Jacques Bonsergent
+5,Goncourt
+6,Temple
+7,Republique
+10,Oberkampf
+11,Parmentier
+12,Filles du Calvaire
+13,Saint-Sebastien - Froissart
+14,Richard-Lenoir
+15,Saint-Ambroise

+ 8 - 0
metroflip/files/navigo/stations/metro/stations_9.txt

@@ -0,0 +1,8 @@
+0,Austerlitz
+1,Quai de la Gare
+2,Chevaleret
+4,Saint-Marcel
+7,Gare d'Austerlitz
+8,Gare de Lyon
+10,Quai de la Rapee
+14,Gare de Lyon

+ 2 - 0
metroflip/files/navigo/stations/train/stations_1.txt

@@ -0,0 +1,2 @@
+7,0,Luxembourg
+10,0,Chatelet les Halles

+ 1 - 0
metroflip/files/navigo/stations/train/stations_10.txt

@@ -0,0 +1 @@
+9,0,Invalides

+ 1 - 0
metroflip/files/navigo/stations/train/stations_1017.txt

@@ -0,0 +1 @@
+24,15,Orry-la-Ville - Coye

+ 1 - 0
metroflip/files/navigo/stations/train/stations_14.txt

@@ -0,0 +1 @@
+4,0,Cite Universitaire

+ 3 - 0
metroflip/files/navigo/stations/train/stations_15.txt

@@ -0,0 +1,3 @@
+12,0,Port Royal
+13,0,Denfert-Rochereau
+13,1,Denfert-Rochereau

+ 11 - 0
metroflip/files/navigo/stations/train/stations_16.txt

@@ -0,0 +1,11 @@
+1,1,Nation
+2,1,Vincennes
+2,2,Fontenay-sous-Bois
+3,1,Nogent-sur-Marne
+3,2,Joinville-le-Pont
+4,1,Saint-Maur - Creteil
+5,1,Le Parc de Saint-Maur
+6,1,Champigny
+7,1,La Varenne - Chennevieres
+8,1,Sucy - Bonneuil
+8,2,Boissy-Saint-Leger

+ 11 - 0
metroflip/files/navigo/stations/train/stations_17.txt

@@ -0,0 +1,11 @@
+1,1,Charles de Gaulle - Etoile
+4,1,La Defense
+4,2,La Defense
+5,1,Nanterre Prefecture
+5,2,Nanterre Universite
+5,3,Nanterre Ville
+6,1,Rueil-Malmaison
+8,1,Chatou - Croissy
+9,1,Le Vesinet - Centre
+9,2,Le Vesinet - Le Pecq
+9,3,Saint-Germain-en-Laye

+ 23 - 0
metroflip/files/navigo/stations/train/stations_18.txt

@@ -0,0 +1,23 @@
+2,1,Gentilly
+2,2,Laplace
+2,3,Arcueil - Cachan
+3,1,Bagneux
+3,2,Bourg-la-Reine
+4,1,Parc de Sceaux
+4,2,La Croix de Berny
+5,1,Antony
+5,2,Fontaine Michalon
+5,3,Les Baconnets
+6,1,Massy - Verrieres
+6,2,Massy - Palaiseau
+6,3,Massy - Palaiseau
+7,1,Palaiseau
+7,2,Palaiseau - Villebon
+8,1,Lozere
+9,1,Le Guichet
+9,2,Orsay Ville
+10,1,Bures-sur-Yvette
+10,2,La Hacquiniere
+10,3,Gif-sur-Yvette
+10,4,Courcelle-sur-Yvette
+10,5,Saint-Remy-les-Chevreuse

+ 1 - 0
metroflip/files/navigo/stations/train/stations_19.txt

@@ -0,0 +1 @@
+5,0,Saint-Ouen

+ 22 - 0
metroflip/files/navigo/stations/train/stations_20.txt

@@ -0,0 +1,22 @@
+1,1,Gare de l'Est
+1,7,Rosa Parks
+4,1,Pantin
+5,1,Noisy-le-Sec
+6,1,Bondy
+7,1,Le Raincy - Villemomble - Montfermeil
+7,2,Gagny
+9,1,Le Chenay Gagny
+9,2,Le Chenay Gagny
+9,3,Chelles - Gournay
+10,1,Vaires - Torcy
+11,1,Lagny - Thorigny
+13,1,Esbly
+14,1,Meaux
+15,1,Trilport
+15,2,Changis - Saint-Jean
+15,3,Isles - Armentieres - Congis
+15,4,Nanteuil - Saacy
+16,1,Lizy-sur-Ourcq
+16,2,Crouy-sur-Ourcq
+16,3,La Ferte-sous-Jouarre
+16,4,Nanteuil - Saacy

+ 29 - 0
metroflip/files/navigo/stations/train/stations_21.txt

@@ -0,0 +1,29 @@
+5,1,Rosny Bois Perrier
+5,2,Rosny-sous-Bois
+5,3,Val de Fontenay
+6,1,Nogent - Le Perreux
+7,1,Les Boullereaux Champigny
+7,2,Les Boullereaux Champigny
+8,1,Villiers-sur-Marne - Le Plessis-Trevise
+9,1,Les Yvris Noisy-le-Grand
+10,1,Emerainville - Pontault-Combault
+10,2,Roissy-en-Brie
+11,1,Ozoir-la-Ferriere
+12,1,Gretz-Armainvilliers
+12,2,Tournan
+15,1,Marles-en-Brie
+15,2,Mortcerf
+15,3,Guerard - La Celle-sur-Morin
+15,4,Faremoutiers - Pommeuse
+15,5,Mouroux
+15,6,Verneuil-l'Etang
+15,7,Mormant
+16,1,Coulommiers
+16,2,Gare de Chailly Boissy le Chatel
+16,3,Saint-Simeon
+16,5,Nangis
+17,1,Longueville
+17,2,Sainte-Colombe Septveilles
+17,3,Champbenoist Poigny
+17,4,Provins
+17,5,La Ferte-Gaucher

+ 1 - 0
metroflip/files/navigo/stations/train/stations_22.txt

@@ -0,0 +1 @@
+9,0,Gare du Nord

+ 4 - 0
metroflip/files/navigo/stations/train/stations_23.txt

@@ -0,0 +1,4 @@
+13,2,Couilly - Saint-Germain - Quincy
+13,3,Montry - Conde
+14,1,Villiers Montbarbin
+14,2,Crecy-la-Chapelle

+ 12 - 0
metroflip/files/navigo/stations/train/stations_26.txt

@@ -0,0 +1,12 @@
+5,1,Val de Fontenay
+6,1,Neuilly-Plaisance
+6,2,Bry-sur-Marne
+7,1,Noisy-le-Grand - Mont d'Est
+8,1,Noisy - Champs
+10,1,Noisiel
+10,2,Lognes
+10,3,Torcy
+11,1,Bussy-Saint-Georges
+12,1,Val d'Europe
+13,1,Marne-la-Vallee Chessy
+13,2,Marne-la-Vallee Chessy

+ 3 - 0
metroflip/files/navigo/stations/train/stations_28.txt

@@ -0,0 +1,3 @@
+4,1,Sceaux
+4,2,Fontenay-aux-Roses
+4,3,Robinson

+ 1 - 0
metroflip/files/navigo/stations/train/stations_29.txt

@@ -0,0 +1 @@
+7,1,Aeroport d'Orly

+ 1 - 0
metroflip/files/navigo/stations/train/stations_3.txt

@@ -0,0 +1 @@
+1,1,Saint-Michel Notre-Dame

+ 20 - 0
metroflip/files/navigo/stations/train/stations_30.txt

@@ -0,0 +1,20 @@
+1,1,Gare Saint-Lazare
+3,1,Pont Cardinet
+4,1,Clichy - Levallois
+4,2,Asnieres-sur-Seine
+4,3,Becon les Bruyeres
+4,4,Courbevoie
+4,5,La Defense
+4,8,La Defense
+5,1,Puteaux
+5,2,Suresnes Mont Valerien
+7,1,Le Val d'Or
+7,2,Saint-Cloud
+7,3,Garches - Marnes-la-Coquette
+8,1,Vaucresson
+9,1,La Celle-Saint-Cloud
+9,2,Bougival
+9,3,Louveciennes
+9,4,Marly-le-Roi
+10,1,L'Etang-la-Ville
+10,2,Saint-Nom-la-Breteche Foret de Marly

+ 5 - 0
metroflip/files/navigo/stations/train/stations_31.txt

@@ -0,0 +1,5 @@
+7,1,Sevres - Ville-d'Avray
+7,2,Chaville Rive Droite
+7,3,Viroflay Rive Droite
+8,1,Montreuil
+8,2,Versailles Rive Droite

+ 21 - 0
metroflip/files/navigo/stations/train/stations_32.txt

@@ -0,0 +1,21 @@
+5,1,Les Vallees
+5,2,La Garenne-Colombes
+5,4,Nanterre Universite
+5,11,Nanterre-La-Folie
+7,1,Houilles - Carrieres-sur-Seine
+7,2,Sartrouville
+9,1,Maisons-Laffitte
+10,1,Acheres Grand Cormier
+10,2,Poissy
+11,1,Villennes-sur-Seine
+12,1,Vernouillet - Verneuil
+12,2,Les Clairieres de Verneuil
+13,1,Les Mureaux
+13,2,Aubergenville Elisabethville
+14,1,Epone - Mezieres
+15,1,Mantes Station
+15,2,Mantes-la-Jolie
+16,1,Mantes Station
+16,2,Mantes-la-Jolie
+16,3,Rosny-sur-Seine
+16,4,Bonnieres

+ 5 - 0
metroflip/files/navigo/stations/train/stations_33.txt

@@ -0,0 +1,5 @@
+10,1,Acheres Ville
+11,1,Neuville Universite
+11,2,Cergy Prefecture
+12,1,Cergy Saint-Christophe
+12,2,Cergy le Haut

+ 22 - 0
metroflip/files/navigo/stations/train/stations_35.txt

@@ -0,0 +1,22 @@
+4,1,Bois-Colombes
+5,1,Colombes
+5,2,Le Stade
+6,1,Argenteuil
+8,1,Val d'Argenteuil
+8,2,Cormeilles-en-Parisis
+9,1,La Frette - Montigny
+9,2,Herblay
+10,1,Conflans-Sainte-Honorine
+10,2,Conflans Fin d'Oise
+11,1,Maurecourt
+11,2,Andresy
+11,3,Chanteloup-les-Vignes
+12,1,Triel-sur-Seine
+12,2,Vaux-sur-Seine
+13,1,Thun le Paradis
+13,2,Meulan - Hardricourt
+14,1,Juziers
+14,2,Gargenville
+15,1,Issou - Porcheville
+15,2,Limay
+16,2,Breval

+ 23 - 0
metroflip/files/navigo/stations/train/stations_40.txt

@@ -0,0 +1,23 @@
+1,1,Gare de Lyon
+1,3,Paris-Bercy Bourgogne - Pays d'Auvergne
+5,1,Maisons-Alfort - Alfortville
+5,2,Le Vert de Maisons
+6,2,Creteil Pompadour
+7,1,Villeneuve Triage
+8,1,Villeneuve-Saint-Georges
+9,1,Vigneux-sur-Seine
+9,2,Juvisy
+10,1,Viry-Chatillon
+10,2,Ris-Orangis
+11,1,Grand Bourg
+11,2,Evry - Val de Seine
+12,1,Corbeil-Essonnes
+12,2,Moulin Galant
+12,3,Mennecy
+13,1,Ballancourt
+14,1,La Ferte-Alais
+16,1,Boutigny
+16,2,Maisse
+17,1,Buno - Gironville
+17,2,Boigneville
+17,4,Boigneville

+ 11 - 0
metroflip/files/navigo/stations/train/stations_41.txt

@@ -0,0 +1,11 @@
+0,1,Saint-Michel Notre-Dame
+0,2,Musee d'Orsay
+1,1,Gare d'Austerlitz
+3,1,Bibliotheque Francois Mitterrand
+4,1,Ivry-sur-Seine
+4,2,Vitry-sur-Seine
+5,1,Les Ardoines
+5,2,Choisy-le-Roi
+7,1,Villeneuve-le-Roi
+8,1,Ablon
+9,1,Athis-Mons

+ 17 - 0
metroflip/files/navigo/stations/train/stations_42.txt

@@ -0,0 +1,17 @@
+9,1,Savigny-sur-Orge
+9,3,Epinay-sur-Orge
+10,1,Sainte-Genevieve-des-Bois
+11,1,Saint-Michel-sur-Orge
+12,1,Bretigny
+12,3,Marolles-en-Hurepoix
+13,1,Bouray
+13,2,Lardy
+14,1,Gare de Chamarande
+14,2,Etrechy
+15,1,Etampes
+15,2,Saint-Martin d'Etampes
+16,1,Etampes
+16,2,Saint-Martin d'Etampes
+17,1,Guillerval
+17,2,Monnerville
+17,3,Angerville

+ 20 - 0
metroflip/files/navigo/stations/train/stations_43.txt

@@ -0,0 +1,20 @@
+9,1,Montgeron - Crosne
+9,2,Montgeron - Crosne
+9,3,Yerres
+10,1,Brunoy
+10,2,Brunoy
+11,1,Boussy-Saint-Antoine
+11,2,Combs-la-Ville - Quincy
+12,1,Lieusaint - Moissy
+13,1,Savigny-le-Temple - Nandy
+13,2,Cesson
+15,1,Le Mee
+15,2,Melun
+16,1,Livry-sur-Seine
+16,2,Chartrettes
+16,3,Fontaine-le-Port
+17,1,Hericy
+17,2,Vulaines-sur-Seine - Samoreau
+17,3,Champagne-sur-Seine
+17,4,Vernou-sur-Seine
+17,5,La Grande-Paroisse

+ 20 - 0
metroflip/files/navigo/stations/train/stations_44.txt

@@ -0,0 +1,20 @@
+12,1,Essonnes Robinson
+12,2,Villabe
+13,1,Le Plessis Chenet
+13,2,Le Coudray-Montceaux
+13,3,Saint-Fargeau
+14,1,Ponthierry - Pringy
+14,2,Boissise-le-Roi
+15,1,Vosves
+16,1,Bois-le-Roi
+16,2,Le Coudray-Montceaux
+17,1,Fontainebleau - Avon
+17,2,Thomery
+17,3,Moret - Veneux-les-Sablons
+17,4,Montigny-sur-Loing
+17,5,Bourron-Marlotte - Grez
+17,6,Nemours-Saint-Pierre
+17,7,Bagneaux-sur-Loing
+17,8,Souppes - Chateau-Landon
+17,9,Saint-Mammes
+17,10,Montereau

+ 4 - 0
metroflip/files/navigo/stations/train/stations_45.txt

@@ -0,0 +1,4 @@
+10,1,Grigny Centre
+11,1,Orangis Bois de l'Epine
+11,2,Evry - Courcouronnes
+12,2,Le Bras de Fer

+ 27 - 0
metroflip/files/navigo/stations/train/stations_50.txt

@@ -0,0 +1,27 @@
+0,1,Haussmann Saint-Lazare
+1,1,Gare du Nord
+1,6,Magenta
+4,1,Stade de France Saint-Denis
+5,1,Saint-Denis
+5,2,Epinay - Villetaneuse
+5,4,Saint-Denis
+6,1,La Barre Ormesson
+7,1,Enghien-les-Bains
+7,2,Champ de Courses d'Enghien
+7,3,Champ de Courses d'Enghien
+8,1,Ermont - Eaubonne
+8,3,Gros Noyer Saint-Prix
+8,4,Ermont Halte
+9,1,Saint-Leu-la-Foret
+9,2,Vaucelles
+9,3,Taverny
+10,1,Bessancourt
+10,2,Frepillon
+10,3,Mery-sur-Oise
+11,1,Meriel
+11,2,Valmondois
+12,1,L'Isle-Adam - Parmain
+12,2,Champagne-sur-Oise
+12,3,Persan - Beaumont
+12,4,Persan - Beaumont
+12,5,Bruyeres-sur-Oise

+ 18 - 0
metroflip/files/navigo/stations/train/stations_51.txt

@@ -0,0 +1,18 @@
+4,1,La Plaine Stade de France
+4,2,La Courneuve - Aubervilliers
+4,3,La Courneuve - Aubervilliers
+4,5,La Plaine Stade de France
+5,1,Le Bourget
+5,2,Le Bourget
+7,1,Drancy
+7,2,Le Blanc-Mesnil
+8,1,Aulnay-sous-Bois
+9,1,Sevran - Livry
+9,2,Sevran - Livry
+9,3,Vert Galant
+10,1,Villeparisis - Mitry-le-Neuf
+11,1,Mitry - Claye
+11,2,Mitry - Claye
+11,3,Compans
+12,1,Thieux - Nantouillet
+12,2,Dammartin - Juilly - Saint-Mard

+ 7 - 0
metroflip/files/navigo/stations/train/stations_52.txt

@@ -0,0 +1,7 @@
+6,1,Pierrefitte - Stains
+7,1,Garges - Sarcelles
+8,1,Villiers-le-Bel - Gonesse - Arnouville
+10,1,Goussainville
+10,2,Les Noues
+10,3,Louvres
+11,1,Survilliers - Fosses

+ 12 - 0
metroflip/files/navigo/stations/train/stations_53.txt

@@ -0,0 +1,12 @@
+6,1,Deuil - Montmagny
+7,1,Groslay
+8,1,Sarcelles - Saint-Brice
+9,1,Ecouen - Ezanville
+9,2,Domont
+10,1,Bouffemont - Moisselles
+10,2,Montsoult - Maffliers
+11,1,Villaines
+11,2,Belloy - Saint-Martin
+11,3,Viarmes
+11,4,Seugy
+11,5,Luzarches

+ 13 - 0
metroflip/files/navigo/stations/train/stations_54.txt

@@ -0,0 +1,13 @@
+8,1,Cernay
+9,1,Franconville - Le Plessis-Bouchard
+9,2,Montigny - Beauchamp
+10,1,Pierrelaye
+11,1,Saint-Ouen-l'Aumone Liesse
+11,2,Pontoise
+11,6,Saint-Ouen-l'Aumone
+12,1,Osny
+12,2,Boissy-l'Aillerie
+15,1,Montgeroult - Courcelles
+15,2,Us
+15,3,Santeuil - Le Perchay
+15,4,Chars

+ 13 - 0
metroflip/files/navigo/stations/train/stations_55.txt

@@ -0,0 +1,13 @@
+0,1,Neuilly Porte Maillot
+0,2,Avenue Foch
+0,3,Avenue Henri Martin
+0,4,Boulainvilliers
+0,5,Avenue du President Kennedy Maison de Radio France
+0,12,Neuilly Porte Maillot
+1,1,Pereire Levallois
+2,1,Porte de Clichy
+3,1,Saint-Ouen
+4,1,Les Gresillons
+5,1,Gennevilliers
+6,1,Epinay-sur-Seine
+7,1,Saint-Gratien

+ 4 - 0
metroflip/files/navigo/stations/train/stations_56.txt

@@ -0,0 +1,4 @@
+11,1,Gare Epluches
+11,2,Pont Petit
+11,3,Chaponval
+11,4,Auvers-sur-Oise

+ 2 - 0
metroflip/files/navigo/stations/train/stations_57.txt

@@ -0,0 +1,2 @@
+11,1,Presles - Courcelles
+12,1,Nointel - Mours

+ 1 - 0
metroflip/files/navigo/stations/train/stations_6.txt

@@ -0,0 +1 @@
+6,0,Auber

+ 20 - 0
metroflip/files/navigo/stations/train/stations_60.txt

@@ -0,0 +1,20 @@
+1,1,Gare Montparnasse
+4,1,Vanves - Malakoff
+4,2,Clamart
+5,1,Meudon
+5,2,Bellevue
+5,3,Sevres Rive Gauche
+6,1,Chaville Rive Gauche
+6,2,Viroflay Rive Gauche
+6,3,Viroflay Rive Gauche
+7,1,Versailles Chantiers
+10,1,Saint-Cyr
+11,1,Saint-Quentin en Yvelines - Montigny-le-Bretonneux
+11,2,Trappes
+12,1,La Verriere
+12,2,La Verriere
+12,3,Coignieres
+13,1,Les Essarts-le-Roi
+14,1,Le Perray
+14,2,Rambouillet
+15,1,Gazeran

+ 14 - 0
metroflip/files/navigo/stations/train/stations_61.txt

@@ -0,0 +1,14 @@
+10,1,Fontenay-le-Fleury
+11,1,Villepreux - Les Clayes
+12,1,Plaisir - Les Clayes
+12,2,Plaisir - Grignon
+13,1,Beynes
+13,2,Mareil-sur-Mauldre
+13,3,Maule
+13,4,Nezel - Aulnay
+15,1,Gare de Villiers Neauphle Pontchartrain
+15,2,Montfort-l'Amaury - Mere
+15,3,Garancieres - La Queue
+15,4,Orgerus - Behoust
+15,5,Tacoignieres - Richebourg
+16,1,Houdan

部分文件因为文件数量过多而无法显示