zinongli 11 месяцев назад
Родитель
Сommit
f65c69dc60
97 измененных файлов с 2492 добавлено и 0 удалено
  1. BIN
      .DS_Store
  2. BIN
      api/.DS_Store
  3. 48 0
      api/suica/suica_assets.h
  4. 59 0
      api/suica/suica_loading.h
  5. 18 0
      api/suica/suica_structs.h
  6. 65 0
      api/suica/suica_structs_i.h
  7. 1 0
      files/suica/line_0x00.txt
  8. 36 0
      files/suica/line_0x01.txt
  9. 10 0
      files/suica/line_0x1D.txt
  10. 8 0
      files/suica/line_0x82.txt
  11. 50 0
      files/suica/line_0xD5.txt
  12. 6 0
      files/suica/line_0xD6.txt
  13. 65 0
      files/suica/line_0xE3.txt
  14. 45 0
      files/suica/line_0xE4.txt
  15. 30 0
      files/suica/line_0xE5.txt
  16. 14 0
      files/suica/line_0xE6.txt
  17. 19 0
      files/suica/line_0xE7.txt
  18. 20 0
      files/suica/line_0xEF.txt
  19. 27 0
      files/suica/line_0xF0.txt
  20. 21 0
      files/suica/line_0xF1.txt
  21. 26 0
      files/suica/line_0xF2.txt
  22. 14 0
      files/suica/line_0xF3.txt
  23. 11 0
      files/suica/line_0xFA.txt
  24. 419 0
      files/suica/stations.txt
  25. BIN
      images/suica/Suica_AsakusaA.png
  26. BIN
      images/suica/Suica_BigStar.png
  27. BIN
      images/suica/Suica_ChiyodaC.png
  28. BIN
      images/suica/Suica_CrackingEgg.png
  29. BIN
      images/suica/Suica_DashLine.png
  30. BIN
      images/suica/Suica_EmptyArrowDown.png
  31. BIN
      images/suica/Suica_EmptyArrowRight.png
  32. BIN
      images/suica/Suica_FilledArrowDown.png
  33. BIN
      images/suica/Suica_FilledArrowRight.png
  34. BIN
      images/suica/Suica_GinzaG.png
  35. BIN
      images/suica/Suica_HanzomonZ.png
  36. BIN
      images/suica/Suica_HibiyaH.png
  37. BIN
      images/suica/Suica_JRLogo.png
  38. BIN
      images/suica/Suica_KeikyuKK.png
  39. BIN
      images/suica/Suica_KeikyuLogo.png
  40. BIN
      images/suica/Suica_MarunouchiHonanchoMb.png
  41. BIN
      images/suica/Suica_MarunouchiM.png
  42. BIN
      images/suica/Suica_MinusSign0.png
  43. BIN
      images/suica/Suica_MinusSign1.png
  44. BIN
      images/suica/Suica_MinusSign2.png
  45. BIN
      images/suica/Suica_MinusSign3.png
  46. BIN
      images/suica/Suica_MinusSign4.png
  47. BIN
      images/suica/Suica_MinusSign5.png
  48. BIN
      images/suica/Suica_MinusSign6.png
  49. BIN
      images/suica/Suica_MinusSign7.png
  50. BIN
      images/suica/Suica_MinusSign8.png
  51. BIN
      images/suica/Suica_MinusSign9.png
  52. BIN
      images/suica/Suica_MitaI.png
  53. BIN
      images/suica/Suica_NambokuN.png
  54. BIN
      images/suica/Suica_Nothing.png
  55. BIN
      images/suica/Suica_OedoE.png
  56. BIN
      images/suica/Suica_PenguinHappyBirthday.png
  57. BIN
      images/suica/Suica_PenguinTodaysVIP.png
  58. BIN
      images/suica/Suica_PlusSign1.png
  59. BIN
      images/suica/Suica_PlusSign2.png
  60. BIN
      images/suica/Suica_PlusSign3.png
  61. BIN
      images/suica/Suica_PlusStar.png
  62. BIN
      images/suica/Suica_QuestionMarkBig.png
  63. BIN
      images/suica/Suica_QuestionMarkSmall.png
  64. BIN
      images/suica/Suica_RinkaiR.png
  65. BIN
      images/suica/Suica_ShinjukuS.png
  66. BIN
      images/suica/Suica_ShopPin.png
  67. BIN
      images/suica/Suica_SmallStar.png
  68. BIN
      images/suica/Suica_StoreFan1.png
  69. BIN
      images/suica/Suica_StoreFan2.png
  70. BIN
      images/suica/Suica_StoreFrame.png
  71. BIN
      images/suica/Suica_StoreLightningHorizontal.png
  72. BIN
      images/suica/Suica_StoreLightningVertical.png
  73. BIN
      images/suica/Suica_StoreP1Counter.png
  74. BIN
      images/suica/Suica_StoreReceiptDashLine.png
  75. BIN
      images/suica/Suica_StoreReceiptFrame1.png
  76. BIN
      images/suica/Suica_StoreReceiptFrame2.png
  77. BIN
      images/suica/Suica_StoreSlidingDoor.png
  78. BIN
      images/suica/Suica_TWRLogo.png
  79. BIN
      images/suica/Suica_ToeiLogo.png
  80. BIN
      images/suica/Suica_TokyoMetroLogo.png
  81. BIN
      images/suica/Suica_TokyoMonorailLogo.png
  82. BIN
      images/suica/Suica_TozaiT.png
  83. BIN
      images/suica/Suica_VendingCan1.png
  84. BIN
      images/suica/Suica_VendingCan2.png
  85. BIN
      images/suica/Suica_VendingCan3.png
  86. BIN
      images/suica/Suica_VendingCan4.png
  87. BIN
      images/suica/Suica_VendingFlap1.png
  88. BIN
      images/suica/Suica_VendingFlap2.png
  89. BIN
      images/suica/Suica_VendingFlap3.png
  90. BIN
      images/suica/Suica_VendingFlapHollow.png
  91. BIN
      images/suica/Suica_VendingMachine.png
  92. BIN
      images/suica/Suica_VendingPage2Full.png
  93. BIN
      images/suica/Suica_YenKanji.png
  94. BIN
      images/suica/Suica_YenSign.png
  95. BIN
      images/suica/Suica_YurakuchoY.png
  96. BIN
      scenes/.DS_Store
  97. 1480 0
      scenes/plugins/suica.c


+ 48 - 0
api/suica/suica_assets.h

@@ -0,0 +1,48 @@
+#include <datetime.h>
+#include <stdbool.h>
+#include <furi.h>
+
+#include "suica_structs_i.h"
+
+
+#define SUICA_RAILWAY_NUM 21 // Don't count Unknown
+
+#define SUICA_RAILWAY_UNKNOWN_NAME "Unknown"
+// Railway
+
+static const Railway RailwaysList[] = {
+    // Japan Railway East JRE
+    {0x01, {0, 0}, "Keihin Tohoku", 14, SuicaJR, "JK", 0},
+    {0x01, {0, 0}, "Tokaido Main", 21, SuicaJR, "JT", 0},
+    {0x1D, {0, 0}, "Negishi", 10, SuicaJR, "JK", 0},
+
+    // Tokyo Waterfront Area Rapid Transit TWR
+    {0x82, {0, 0}, "Rinkai", 8, SuicaTWR, "R", &I_Suica_RinkaiR},
+
+    // Tokyo Monorail
+    {0xFA, {0, 0}, "Tokyo Monorail", 11, SuicaTokyoMonorail, "MO", 0},
+
+    // Keikyu
+    {0xD5, {0, 0}, "Keikyu Main", 50, SuicaKeikyu, "KK", &I_Suica_KeikyuKK},
+    {0xD6, {0, 0}, "Keikyu Airport", 6, SuicaKeikyu, "KK", &I_Suica_KeikyuKK},
+
+    // Tokyo Metro
+    {0xE3, {0, 0}, "Ginza", 19, SuicaTokyoMetro, "G", &I_Suica_GinzaG},
+    {0xE3, {1, 0}, "Chiyoda", 20, SuicaTokyoMetro, "C", &I_Suica_ChiyodaC},
+    {0xE3, {1, 1}, "Yurakucho", 24, SuicaTokyoMetro, "Y", &I_Suica_YurakuchoY},
+    {0xE4, {1, 0}, "Hibiya", 21, SuicaTokyoMetro, "H", &I_Suica_HibiyaH},
+    {0xE4, {2, 1}, "Tozai", 23, SuicaTokyoMetro, "T", &I_Suica_TozaiT},
+    {0xE5, {0, 1}, "Marunouchi", 25, SuicaTokyoMetro, "M", &I_Suica_MarunouchiM},
+    {0xE5, {-5, 1}, "M Honancho", 4, SuicaTokyoMetro, "Mb", &I_Suica_MarunouchiHonanchoMb},
+    {0xE6, {2, 1}, "Hanzomon", 14, SuicaTokyoMetro, "Z", &I_Suica_HanzomonZ},
+    {0xE7, {0, 1}, "Namboku", 19, SuicaTokyoMetro, "N", &I_Suica_NambokuN},
+    
+    // Toei
+    {0xEF, {0, 0}, "Asakusa", 20, SuicaToei, "A", &I_Suica_AsakusaA},
+    {0xF0, {4, 0}, "Mita", 27, SuicaToei, "I", &I_Suica_MitaI},
+    {0xF1, {2, 0}, "Shinjuku", 21, SuicaToei, "S", &I_Suica_ShinjukuS},
+    {0xF2, {3, 0}, "Oedo", 26, SuicaToei, "E", &I_Suica_OedoE},
+    {0xF3, {3, 0}, "Oedo", 14, SuicaToei, "E", &I_Suica_OedoE},
+    // Unknown
+    {0x00, {0, 0}, SUICA_RAILWAY_UNKNOWN_NAME, 1, SuicaRailwayTypeMax, "??", &I_Suica_QuestionMarkBig}
+};

+ 59 - 0
api/suica/suica_loading.h

@@ -0,0 +1,59 @@
+#include "../../metroflip_i.h"
+#include <lib/nfc/protocols/felica/felica.h>
+#include "suica_structs_i.h"
+
+void suica_add_entry(SuicaHistoryViewModel* model, const uint8_t* entry) {
+    if(model->size <= 0) {
+        model->travel_history =
+            (uint8_t*)malloc(3 * FELICA_DATA_BLOCK_SIZE); // Each entry is 16 bytes
+        model->size = 0;
+        model->capacity = 3;
+    }
+    // Check if resizing is needed
+    if(model->size == model->capacity) {
+        size_t new_capacity = model->capacity * 2; // Double the capacity
+        uint8_t* new_data =
+            (uint8_t*)realloc(model->travel_history, new_capacity * FELICA_DATA_BLOCK_SIZE);
+        model->travel_history = new_data;
+        model->capacity = new_capacity;
+    }
+
+    // Copy the 16-byte entry to the next slot
+    for(size_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) {
+        model->travel_history[(model->size * FELICA_DATA_BLOCK_SIZE) + i] = entry[i];
+    }
+
+    model->size++;
+}
+
+void load_suica_data(void* context, FlipperFormat* format) {
+    Metroflip* app = (Metroflip*)context;
+    app->suica_context = malloc(sizeof(SuicaContext));
+    app->suica_context->view_history = view_alloc();
+    view_set_context(app->suica_context->view_history, app);
+    view_allocate_model(
+        app->suica_context->view_history, ViewModelTypeLockFree, sizeof(SuicaHistoryViewModel));
+    SuicaHistoryViewModel* model = view_get_model(app->suica_context->view_history);
+
+    uint8_t* byte_array_buffer = (uint8_t*)malloc(FELICA_DATA_BLOCK_SIZE);
+            FuriString* entry_preamble = furi_string_alloc();
+    // Read the travel history entries
+    for(uint8_t i = 0; i < SUICA_MAX_HISTORY_ENTRIES; i++) {
+        furi_string_printf(entry_preamble, "Travel %02X", i);
+        // For every line in the flipper format file
+        // We read the entire line's hex and store it in the byte_array_buffer
+        if(!flipper_format_read_hex(
+               format,
+               furi_string_get_cstr(entry_preamble),
+               byte_array_buffer,
+               FELICA_DATA_BLOCK_SIZE))
+            break;
+        uint8_t block_data[16] = {0};
+        for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) {
+            block_data[j] = byte_array_buffer[j];
+        }
+        suica_add_entry(model, block_data);
+    }
+    furi_string_free(entry_preamble);
+    free(byte_array_buffer);
+}

+ 18 - 0
api/suica/suica_structs.h

@@ -0,0 +1,18 @@
+#include <datetime.h>
+#include <stdbool.h>
+#include <furi.h>
+
+#define SUICA_MAX_HISTORY_ENTRIES 0x15
+typedef enum {
+    SuicaHistoryNull,
+    SuicaHistoryBus,
+    SuicaHistoryPosAndTaxi,
+    SuicaHistoryVendingMachine,
+    SuicaHistoryHappyBirthday,
+    SuicaHistoryTrain
+} SuicaHistoryType;
+
+typedef struct {
+    View* view_history;
+    FuriTimer* timer;
+} SuicaContext;

+ 65 - 0
api/suica/suica_structs_i.h

@@ -0,0 +1,65 @@
+#include <datetime.h>
+#include <stdbool.h>
+#include <furi.h>
+
+typedef enum {
+    SuicaKeikyu,
+    SuicaTokyoMetro,
+    SuicaToei,
+    SuicaJR,
+    SuicaTWR,
+    SuicaTokyoMonorail,
+    SuicaRailwayTypeMax,
+} SuicaRailwayCompany;
+
+typedef enum {
+    SuicaBalanceAdd,
+    SuicaBalanceSub,
+    SuicaBalanceEqual,
+} SuicaBalanceChangeSign;
+
+typedef struct {
+    uint8_t station_code;
+    uint8_t station_number;
+    FuriString* name;
+    FuriString* jr_header;
+} Station;
+
+typedef struct {
+    uint8_t line_code;
+    int logo_offset[2];
+    const char* long_name;
+    uint8_t station_num;
+    SuicaRailwayCompany type;
+    const char* short_name;
+    const Icon* logo_icon;
+} Railway;
+
+typedef struct {
+    Railway entry_line;
+    Station entry_station;
+    Railway exit_line;
+    Station exit_station;
+    uint8_t year;
+    uint8_t month;
+    uint8_t day;
+    uint8_t hour;
+    uint8_t minute;
+    uint16_t balance;
+    uint8_t history_type;
+    uint8_t area_code;
+    uint16_t previous_balance;
+    uint16_t balance_change;
+    SuicaBalanceChangeSign balance_sign;
+    uint8_t* shop_code;
+} SuicaHistory;
+
+typedef struct {
+    uint8_t entry; // Which entry we are currently viewing
+    uint8_t page; // Which vertial page we are on
+    uint8_t* travel_history; // Dynamic array for raw 16-byte entries
+    size_t size; // Number of entries currently stored
+    size_t capacity; // Allocated capacity
+    uint8_t animator_tick; // Counter for the animations
+    SuicaHistory history; // Current history entry
+} SuicaHistoryViewModel;

+ 1 - 0
files/suica/line_0x00.txt

@@ -0,0 +1 @@
+0x00,0x00,UnknownLine,Unknown,0,0

+ 36 - 0
files/suica/line_0x01.txt

@@ -0,0 +1,36 @@
+0x01,0x01,Tokaido Main,Tokyo,1,TYO
+0x01,0x03,Tokaido Main,Shimbashi,2,SMB
+0x01,0x07,Tokaido Main,Shinagawa,3,SGW
+0x01,0x0D,Tokaido Main,Kawasaki,4,KWS
+0x01,0x12,Tokaido Main,Yokohama,5,YHM
+0x01,0x16,Tokaido Main,Totsuka,6,TTK
+0x01,0x18,Tokaido Main,Ofuna,7,OFN
+0x01,0x19,Tokaido Main,Fujisawa,8,0
+0x01,0x1A,Tokaido Main,Tsujido,9,0
+0x01,0x1B,Tokaido Main,Chigasaki,10,0
+0x01,0x1D,Tokaido Main,Hiratsuka,11,0
+0x01,0x1E,Tokaido Main,Oiso,12,0
+0x01,0x20,Tokaido Main,Ninomiya,13,0
+0x01,0x21,Tokaido Main,Kozu,14,0
+0x01,0x22,Tokaido Main,Kamonomiya,15,0
+0x01,0x23,Tokaido Main,Odawara,16,0
+0x01,0x25,Tokaido Main,Hayakawa,17,0
+0x01,0x26,Tokaido Main,Nebukawa,18,0
+0x01,0x28,Tokaido Main,Manazuru,19,0
+0x01,0x29,Tokaido Main,Yugawara,20,0
+0x01,0x2B,Tokaido Main,Atami,21,0
+,,,,,
+0x01,0x01,Keihin Tohoku,Tokyo,26,TYO
+0x01,0x02,Keihin Tohoku,Yurakucho,25,0
+0x01,0x03,Keihin Tohoku,Shimbashi,24,SMB
+0x01,0x04,Keihin Tohoku,Hamamatsucho,23,HMC
+0x01,0x06,Keihin Tohoku,Tamachi,22,0
+0x01,0x07,Keihin Tohoku,Shinagawa,20,SGW
+0x01,0x08,Keihin Tohoku,Oimachi,19,0
+0x01,0x09,Keihin Tohoku,Omori,18,0
+0x01,0x0B,Keihin Tohoku,Kamata,17,0
+0x01,0x0D,Keihin Tohoku,Kawasaki,16,KWS
+0x01,0x0E,Keihin Tohoku,Tsurumi,15,0
+0x01,0x10,Keihin Tohoku,Shin-koyasu,14,0
+0x01,0x11,Keihin Tohoku,Higashi-kanagawa,13,0
+0x01,0x12,Keihin Tohoku,Yokohama,12,YHM

+ 10 - 0
files/suica/line_0x1D.txt

@@ -0,0 +1,10 @@
+0x1D,0x04,Negishi,Hongodai,2,0
+0x1D,0x06,Negishi,Konandai,3,0
+0x1D,0x07,Negishi,Yokodai,4,0
+0x1D,0x08,Negishi,Shin-sugita,5,0
+0x1D,0x09,Negishi,Isogo,6,0
+0x1D,0x0A,Negishi,Negishi,7,0
+0x1D,0x0C,Negishi,Yamate,8,0
+0x1D,0x0D,Negishi,Ishikawacho,9,0
+0x1D,0x0E,Negishi,Kannai,10,0
+0x1D,0x0F,Negishi,Sakuragicho,11,0

+ 8 - 0
files/suica/line_0x82.txt

@@ -0,0 +1,8 @@
+0x82,0x01,Rinkai,Shin-kiba,1,0
+0x82,0x03,Rinkai,Shinonome,2,0
+0x82,0x04,Rinkai,Kokusai-tenjijo,3,0
+0x82,0x05,Rinkai,Tokyo Teleport,4,0
+0x82,0x06,Rinkai,Tennozu Isle,5,0
+0x82,0x07,Rinkai,Shinagawa Seaside,6,0
+0x82,0x08,Rinkai,Oimachi,7,0
+0x82,0x0A,Rinkai,Osaki,8,0

+ 50 - 0
files/suica/line_0xD5.txt

@@ -0,0 +1,50 @@
+0xD5,0x01,Keikyu Main,Sengakuji,7,0
+0xD5,0x02,Keikyu Main,Shinagawa,1,0
+0xD5,0x03,Keikyu Main,Kita-Shinagawa,2,0
+0xD5,0x04,Keikyu Main,Shimbamba,3,0
+0xD5,0x06,Keikyu Main,Aomono-yokocho,4,0
+0xD5,0x07,Keikyu Main,Samezu,5,0
+0xD5,0x08,Keikyu Main,Tachiaigawa,6,0
+0xD5,0x09,Keikyu Main,Omorikaigan,7,0
+0xD5,0x0A,Keikyu Main,Heiwajima,8,0
+0xD5,0x0B,Keikyu Main,Omorimachi,9,0
+0xD5,0x0C,Keikyu Main,Umeyashiki,10,0
+0xD5,0x0D,Keikyu Main,Keikyu Kamata,11,0
+0xD5,0x0E,Keikyu Main,Zoshiki,18,0
+0xD5,0x0F,Keikyu Main,Rokugodote,19,0
+0xD5,0x10,Keikyu Main,Keikyu Kawasaki,20,0
+0xD5,0x11,Keikyu Main,Hatcho-nawate,27,0
+0xD5,0x12,Keikyu Main,Tsurumi-ichiba,28,0
+0xD5,0x13,Keikyu Main,Keikyu Tsurumi,29,0
+0xD5,0x14,Keikyu Main,Kagetsu-sojiji,30,0
+0xD5,0x15,Keikyu Main,Namamugi,31,0
+0xD5,0x16,Keikyu Main,Keikyu Shinkoyasu,32,0
+0xD5,0x17,Keikyu Main,Koyasu,33,0
+0xD5,0x18,Keikyu Main,Kanagawa-shimmachi,34,0
+0xD5,0x19,Keikyu Main,KK Higashi-kanagawa,35,0
+0xD5,0x1A,Keikyu Main,Kanagawa,36,0
+0xD5,0x1B,Keikyu Main,Yokohama,37,0
+0xD5,0x1C,Keikyu Main,Tobe,38,0
+0xD5,0x1D,Keikyu Main,Hinodecho,39,0
+0xD5,0x1E,Keikyu Main,Koganecho,40,0
+0xD5,0x1F,Keikyu Main,Minamiota,41,0
+0xD5,0x20,Keikyu Main,Idogaya,42,0
+0xD5,0x21,Keikyu Main,Gumyoji,43,0
+0xD5,0x22,Keikyu Main,Kamiooka,44,0
+0xD5,0x24,Keikyu Main,Byobugaura,45,0
+0xD5,0x25,Keikyu Main,Sugita,46,0
+0xD5,0x27,Keikyu Main,Keikyu Tomioka,47,0
+0xD5,0x28,Keikyu Main,Nokendai,48,0
+0xD5,0x29,Keikyu Main,Kanazawa-bunko,49,0
+0xD5,0x2A,Keikyu Main,Kanazawa-hakkei,50,0
+0xD5,0x2B,Keikyu Main,Oppama,54,0
+0xD5,0x2C,Keikyu Main,Keikyu Taura,55,0
+0xD5,0x2E,Keikyu Main,Anjinzuka,56,0
+0xD5,0x2F,Keikyu Main,Hemi,57,0
+0xD5,0x30,Keikyu Main,Shioiri,58,0
+0xD5,0x31,Keikyu Main,Yokosuka-chuo,59,0
+0xD5,0x32,Keikyu Main,Kenritsudaigaku,60,0
+0xD5,0x33,Keikyu Main,Horinouchi,61,0
+0xD5,0x34,Keikyu Main,Keikyu Otsu,62,0
+0xD5,0x35,Keikyu Main,Maborikaigan,63,0
+0xD5,0x36,Keikyu Main,Uraga,64,0

+ 6 - 0
files/suica/line_0xD6.txt

@@ -0,0 +1,6 @@
+0xD6,0x01,Keikyu Airport,Keikyu Kamata,11,0
+0xD6,0x02,Keikyu Airport,Kojiya,12,0
+0xD6,0x03,Keikyu Airport,Otorii,13,0
+0xD6,0x04,Keikyu Airport,Anamori-Inari,14,0
+0xD6,0x05,Keikyu Airport,Tenkubashi,15,0
+0xD6,0x06,Keikyu Airport,Haneda-Airport,16,0

+ 65 - 0
files/suica/line_0xE3.txt

@@ -0,0 +1,65 @@
+0xE3,0x2C,Ginza,Asakusa,19,0
+0xE3,0x2D,Ginza,Tawaramachi,18,0
+0xE3,0x2E,Ginza,Inaricho,17,0
+0xE3,0x2F,Ginza,Ueno,16,0
+0xE3,0x30,Ginza,Ueno-hirokoji,15,0
+0xE3,0x31,Ginza,Suehirocho,14,0
+0xE3,0x32,Ginza,Kanda,13,0
+0xE3,0x33,Ginza,Mitsukoshimae,12,0
+0xE3,0x34,Ginza,Nihombashi,11,0
+0xE3,0x35,Ginza,Kyobashi,10,0
+0xE3,0x36,Ginza,Ginza,9,0
+0xE3,0x37,Ginza,Shimbashi,8,0
+0xE3,0x38,Ginza,Toranomon,7,0
+0xE3,0x39,Ginza,Tameike-sannō,6,0
+0xE3,0x3A,Ginza,Akasaka-mitsuke,5,0
+0xE3,0x3B,Ginza,Aoyama-itchōme,4,0
+0xE3,0x3C,Ginza,Gaiemmae,3,0
+0xE3,0x3D,Ginza,Omote-sandō,2,0
+0xE3,0x3E,Ginza,Shibuya,1,0
+,,,,,
+0xE3,0x5E,Chiyoda,Yoyogi-uehara,1,0
+0xE3,0x5D,Chiyoda,Yoyogi-koen,2,0
+0xE3,0x5C,Chiyoda,Meiji-jingumae,3,0
+0xE3,0x5B,Chiyoda,Omotesando,4,0
+0xE3,0x59,Chiyoda,Nogizaka,5,0
+0xE3,0x58,Chiyoda,Akasaka,6,0
+0xE3,0x57,Chiyoda,Kokkai-gijidomae,7,0
+0xE3,0x56,Chiyoda,Kasumigaseki,8,0
+0xE3,0x55,Chiyoda,Hibiya,9,0
+0xE3,0x54,Chiyoda,Nijubashimae,10,0
+0xE3,0x53,Chiyoda,Otemachi,11,0
+0xE3,0x51,Chiyoda,Shin-ochanomizu,12,0
+0xE3,0x50,Chiyoda,Yushima,13,0
+0xE3,0x4F,Chiyoda,Nezu,14,0
+0xE3,0x4E,Chiyoda,Sendagi,15,0
+0xE3,0x4D,Chiyoda,Nishi-Nippori,16,0
+0xE3,0x4C,Chiyoda,Machiya,17,0
+0xE3,0x4A,Chiyoda,Kita-senju,18,0
+0xE3,0x48,Chiyoda,Ayase,19,0
+0xE3,0x47,Chiyoda,Kita-Ayase,20,0
+,,,,,
+0xE3,0x65,Yurakucho,Wakoshi,1,0
+0xE3,0x66,Yurakucho,Chikatetsu-narimasu,2,0
+0xE3,0x68,Yurakucho,Chikatetsu-akatsuka,3,0
+0xE3,0x6A,Yurakucho,Heiwadai,4,0
+0xE3,0x6B,Yurakucho,Hikawadai,5,0
+0xE3,0x6D,Yurakucho,Kotake-mukaihara,6,0
+0xE3,0x6E,Yurakucho,Senkawa,7,0
+0xE3,0x6F,Yurakucho,Kanamecho,8,0
+0xE3,0x70,Yurakucho,Ikebukuro,9,0
+0xE3,0x71,Yurakucho,Higashi-ikebukuro,10,0
+0xE3,0x72,Yurakucho,Gokokuji,11,0
+0xE3,0x73,Yurakucho,Edogawabashi,12,0
+0xE3,0x75,Yurakucho,Iidabashi,13,0
+0xE3,0x76,Yurakucho,Ichigaya,14,0
+0xE3,0x77,Yurakucho,Kojimachi,15,0
+0xE3,0x78,Yurakucho,Nagatacho,16,0
+0xE3,0x79,Yurakucho,Sakuradamon,17,0
+0xE3,0x7A,Yurakucho,Yurakucho,18,0
+0xE3,0x7B,Yurakucho,Ginza-itchome,19,0
+0xE3,0x7C,Yurakucho,Shintomicho,20,0
+0xE3,0x7D,Yurakucho,Tsukishima,21,0
+0xE3,0x7E,Yurakucho,Toyosu,22,0
+0xE3,0x7F,Yurakucho,Tatsumi,23,0
+0xE3,0x80,Yurakucho,Shin-kiba,24,0

+ 45 - 0
files/suica/line_0xE4.txt

@@ -0,0 +1,45 @@
+0xE4,0x25,Hibiya,Kita-senju,22,0
+0xE4,0x27,Hibiya,Minami-senju,21,0
+0xE4,0x28,Hibiya,Minowa,20,0
+0xE4,0x29,Hibiya,Iriya,19,0
+0xE4,0x2A,Hibiya,Ueno,18,0
+0xE4,0x2B,Hibiya,Naka-okachimachi,17,0
+0xE4,0x2C,Hibiya,Akihabara,16,0
+0xE4,0x2D,Hibiya,Kodenmacho,15,0
+0xE4,0x2E,Hibiya,Ningyocho,14,0
+0xE4,0x2F,Hibiya,Kayabacho,13,0
+0xE4,0x30,Hibiya,Hatchobori,12,0
+0xE4,0x31,Hibiya,Tsukiji,11,0
+0xE4,0x32,Hibiya,Higashi-ginza,10,0
+0xE4,0x33,Hibiya,Ginza,9,0
+0xE4,0x34,Hibiya,Hibiya,8,0
+0xE4,0x35,Hibiya,Kasumigaseki,7,0
+0xE4,0x37,Hibiya,Kamiyacho,5,0
+0xE4,0x39,Hibiya,Roppongi,4,0
+0xE4,0x3B,Hibiya,Hiro-o,3,0
+0xE4,0x3D,Hibiya,Ebisu,2,0
+0xE4,0x3E,Hibiya,Naka-meguro,1,0
+,,,,,
+0xE4,0x41,Tozai,Nakano,1,0
+0xE4,0x43,Tozai,Ochiai,2,0
+0xE4,0x45,Tozai,Takadanobaba,3,0
+0xE4,0x47,Tozai,Waseda,4,0
+0xE4,0x48,Tozai,Kagurazaka,5,0
+0xE4,0x49,Tozai,Iidabashi,6,0
+0xE4,0x4A,Tozai,Kudanshita,7,0
+0xE4,0x4B,Tozai,Takebashi,8,0
+0xE4,0x4C,Tozai,Otemachi,9,0
+0xE4,0x4D,Tozai,Nihombashi,10,0
+0xE4,0x4E,Tozai,Kayabacho,11,0
+0xE4,0x50,Tozai,Monzen-nakacho,12,0
+0xE4,0x51,Tozai,Kiba,13,0
+0xE4,0x52,Tozai,Toyocho,14,0
+0xE4,0x53,Tozai,Minami-sunamachi,15,0
+0xE4,0x54,Tozai,Nishi-kasai,16,0
+0xE4,0x55,Tozai,Kasai,17,0
+0xE4,0x57,Tozai,Urayasu,18,0
+0xE4,0x58,Tozai,Minami-gyotoku,19,0
+0xE4,0x59,Tozai,Gyotoku,20,0
+0xE4,0x5A,Tozai,Myoden,21,0
+0xE4,0x5C,Tozai,Baraki-nakayama,22,0
+0xE4,0x5E,Tozai,Nishi-funabashi,23,0

+ 30 - 0
files/suica/line_0xE5.txt

@@ -0,0 +1,30 @@
+0xE5,0x21,Marunouchi,Ikebukuro,25,0
+0xE5,0x23,Marunouchi,Shin-otsuka,24,0
+0xE5,0x24,Marunouchi,Myogadani,23,0
+0xE5,0x26,Marunouchi,Korakuen,22,0
+0xE5,0x27,Marunouchi,Hongo-sanchome,21,0
+0xE5,0x28,Marunouchi,Ochanomizu,20,0
+0xE5,0x29,Marunouchi,Awajicho,19,0
+0xE5,0x2A,Marunouchi,Otemachi,18,0
+0xE5,0x2B,Marunouchi,Tokyo,17,0
+0xE5,0x2C,Marunouchi,Ginza,16,0
+0xE5,0x2D,Marunouchi,Kasumigaseki,15,0
+0xE5,0x2E,Marunouchi,Kokkai-gijidomae,14,0
+0xE5,0x2F,Marunouchi,Akasaka-mitsuke,13,0
+0xE5,0x30,Marunouchi,Yotsuya,12,0
+0xE5,0x31,Marunouchi,Yotsuya-sanchome,11,0
+0xE5,0x32,Marunouchi,Shinjuku-gyoemmae,10,0
+0xE5,0x33,Marunouchi,Shinjuku-sanchome,9,0
+0xE5,0x34,Marunouchi,Shinjuku,8,0
+0xE5,0x35,Marunouchi,Nishi-shinjuku,7,0
+0xE5,0x36,Marunouchi,Nakano-sakaue,6,0
+0xE5,0x37,Marunouchi,Shin-nakano,5,0
+0xE5,0x38,Marunouchi,Higashi-koenji,4,0
+0xE5,0x39,Marunouchi,Shin-koenji,3,0
+0xE5,0x3A,Marunouchi,Minami-asagaya,2,0
+0xE5,0x3C,Marunouchi,Ogikubo,1,0
+,,,,,
+0xE5,0x41,M Honancho,Nakano-sakaue,6,0
+0xE5,0x42,M Honancho,Nakano-shimbashi,5,0
+0xE5,0x43,M Honancho,Nakano-fujimicho,4,0
+0xE5,0x44,M Honancho,Honancho,3,0

+ 14 - 0
files/suica/line_0xE6.txt

@@ -0,0 +1,14 @@
+0xE6,0x21,Hanzomon,Shibuya,1,0
+0xE6,0x22,Hanzomon,Omotesando,2,0
+0xE6,0x23,Hanzomon,Aoyama-itchome,3,0
+0xE6,0x26,Hanzomon,Nagatacho,4,0
+0xE6,0x27,Hanzomon,Hanzomon,5,0
+0xE6,0x28,Hanzomon,Kudanshita,6,0
+0xE6,0x29,Hanzomon,Jimbocho,7,0
+0xE6,0x2B,Hanzomon,Otemachi,8,0
+0xE6,0x2C,Hanzomon,Mitsukoshimae,9,0
+0xE6,0x2D,Hanzomon,Suitengumae,10,0
+0xE6,0x2F,Hanzomon,Kiyosumi-shirakawa,11,0
+0xE6,0x31,Hanzomon,Sumiyoshi,12,0
+0xE6,0x33,Hanzomon,Kinshicho,13,0
+0xE6,0x35,Hanzomon,Oshiage,14,0

+ 19 - 0
files/suica/line_0xE7.txt

@@ -0,0 +1,19 @@
+0xE7,0x24,Namboku,Meguro,1,0
+0xE7,0x25,Namboku,Shirokanedai,2,0
+0xE7,0x26,Namboku,Shirokane-takanawa,3,0
+0xE7,0x28,Namboku,Azabu-juban,4,0
+0xE7,0x29,Namboku,Roppongi-itchome,5,0
+0xE7,0x2A,Namboku,Tameike-sanno,6,0
+0xE7,0x2C,Namboku,Nagatacho,7,0
+0xE7,0x2E,Namboku,Yotsuya,8,0
+0xE7,0x30,Namboku,Ichigaya,9,0
+0xE7,0x32,Namboku,Iidabashi,10,0
+0xE7,0x33,Namboku,Korakuen,11,0
+0xE7,0x34,Namboku,Todaimae,12,0
+0xE7,0x35,Namboku,Hon-komagome,13,0
+0xE7,0x36,Namboku,Komagome,14,0
+0xE7,0x37,Namboku,Nishigahara,15,0
+0xE7,0x38,Namboku,Oji,16,0
+0xE7,0x39,Namboku,Oji-kamiya,17,0
+0xE7,0x3B,Namboku,Shimo,18,0
+0xE7,0x3C,Namboku,Akabane-iwabuchi,19,0

+ 20 - 0
files/suica/line_0xEF.txt

@@ -0,0 +1,20 @@
+0xEF,0x01,Asakusa,Oshiage,20,0
+0xEF,0x02,Asakusa,Honjo-azumabashi,19,0
+0xEF,0x03,Asakusa,Asakusa,18,0
+0xEF,0x04,Asakusa,Kuramae,17,0
+0xEF,0x05,Asakusa,Asakusabashi,16, 0
+0xEF,0x06,Asakusa,Higashi-nihombashi,15,0
+0xEF,0x07,Asakusa,Ningyocho,14,0
+0xEF,0x08,Asakusa,Nihombashi,13,0
+0xEF,0x09,Asakusa,Takaracho,12,0
+0xEF,0x0A,Asakusa,Higashi-ginza,11,0
+0xEF,0x0B,Asakusa,Shimbashi,10,0
+0xEF,0x0C,Asakusa,Daimon,9,0
+0xEF,0x0D,Asakusa,Mita,8,0
+0xEF,0x0E,Asakusa,Sengakuji,7,0
+0xEF,0x0F,Asakusa,Takanawadai,6,0
+0xEF,0x10,Asakusa,Gotanda,5,0
+0xEF,0x11,Asakusa,Togoshi,4,0
+0xEF,0x12,Asakusa,Nakanobu,3,0
+0xEF,0x13,Asakusa,Magome,2,0
+0xEF,0x14,Asakusa,Nishi-magome,1,0

+ 27 - 0
files/suica/line_0xF0.txt

@@ -0,0 +1,27 @@
+0xF0,0x27,Mita,Nishi-takashimadaira,27,0
+0xF0,0x28,Mita,Shin-takashimadaira,26,0
+0xF0,0x29,Mita,Takashimadaira,25,0
+0xF0,0x2A,Mita,Nishidai,24,0
+0xF0,0x2B,Mita,Hasune,23,0
+0xF0,0x2C,Mita,Shimura-sanchome,22,0
+0xF0,0x2D,Mita,Shimura-sakaue,21,0
+0xF0,0x2E,Mita,Motohasunuma,20,0
+0xF0,0x2F,Mita,Itabashi-honcho,19,0
+0xF0,0x30,Mita,Itabashikuyakushomae,18,0
+0xF0,0x31,Mita,Shin-itabashi,17,0
+0xF0,0x32,Mita,Nishi-sugamo,16,0
+0xF0,0x33,Mita,Sugamo,15,0
+0xF0,0x34,Mita,Sengoku,14,0
+0xF0,0x35,Mita,Hakusan,13,0
+0xF0,0x36,Mita,Kasuga,12,0
+0xF0,0x37,Mita,Suidobashi,11,0
+0xF0,0x38,Mita,Jimbocho,10,0
+0xF0,0x39,Mita,Otemachi,9,0
+0xF0,0x3A,Mita,Hibiya,8,0
+0xF0,0x3B,Mita,Uchisaiwaicho,7,0
+0xF0,0x3C,Mita,Onarimon,6,0
+0xF0,0x3D,Mita,Shibakoen,5,0
+0xF0,0x3E,Mita,Mita,4,0
+0xF0,0x40,Mita,Shirokane-takanawa,3,0
+0xF0,0x42,Mita,Shirokanedai,2,0
+0xF0,0x44,Mita,Meguro,1,0

+ 21 - 0
files/suica/line_0xF1.txt

@@ -0,0 +1,21 @@
+0xF1,0x01,Shinjuku,Shinjuku,1,0
+0xF1,0x02,Shinjuku,Shinjuku-sanchome,2,0
+0xF1,0x03,Shinjuku,Akebonobashi,3,0
+0xF1,0x04,Shinjuku,Ichigaya,4,0
+0xF1,0x05,Shinjuku,Kudanshita,5,0
+0xF1,0x06,Shinjuku,Jimbocho,6,0
+0xF1,0x07,Shinjuku,Ogawamachi,7,0
+0xF1,0x08,Shinjuku,Iwamotocho,8,0
+0xF1,0x09,Shinjuku,Bakuro-yokoyama,9,0
+0xF1,0x0A,Shinjuku,Hamacho,10,0
+0xF1,0x0B,Shinjuku,Morishita,11,0
+0xF1,0x0C,Shinjuku,Kikukawa,12,0
+0xF1,0x0D,Shinjuku,Sumiyoshi,13,0
+0xF1,0x0E,Shinjuku,Nishi-ojima,14,0
+0xF1,0x0F,Shinjuku,Ojima,15,0
+0xF1,0x11,Shinjuku,Higashi-ojima,16,0
+0xF1,0x13,Shinjuku,Funabori,17,0
+0xF1,0x15,Shinjuku,Ichinoe,18,0
+0xF1,0x17,Shinjuku,Mizue,19,0
+0xF1,0x19,Shinjuku,Shinozaki,20,0
+0xF1,0x1B,Shinjuku,Motoyawata,21,0

+ 26 - 0
files/suica/line_0xF2.txt

@@ -0,0 +1,26 @@
+0xF2,0x04,Oedo,Morishita,13,0
+0xF2,0x06,Oedo,Kiyosumi-shirakawa,14,0
+0xF2,0x08,Oedo,Monzen-nakacho,15,0
+0xF2,0x0A,Oedo,Tsukishima,16,0
+0xF2,0x0C,Oedo,Kachidoki,17,0
+0xF2,0x0E,Oedo,Tsukiji-shijo,18,0
+0xF2,0x10,Oedo,Shiodome,19,0
+0xF2,0x12,Oedo,Daimon,20,0
+0xF2,0x14,Oedo,Akabanebashi,21,0
+0xF2,0x16,Oedo,Azabu-juban,22,0
+0xF2,0x18,Oedo,Roppongi,23,0
+0xF2,0x1A,Oedo,Aoyama-itchome,24,0
+0xF2,0x1C,Oedo,Kokuritsu-kyogijo,25,0
+0xF2,0x1E,Oedo,Yoyogi,26,0
+0xF2,0x20,Oedo,Shinjuku,27,0
+0xF2,0x22,Oedo,Tochomae,28,0
+0xF2,0x24,Oedo,Nishi-shinjuku-gochome,29,0
+0xF2,0x26,Oedo,Nakano-sakaue,30,0
+0xF2,0x28,Oedo,Higashi-nakano,31,0
+0xF2,0x2A,Oedo,Nakai,32,0
+0xF2,0x2C,Oedo,Ochiai-minami-nagasaki,33,0
+0xF2,0x2E,Oedo,Shin-egota,34,0
+0xF2,0x30,Oedo,Nerima,35,0
+0xF2,0x32,Oedo,Toshimaen,36,0
+0xF2,0x34,Oedo,Nerima-kasugacho,37,0
+0xF2,0x36,Oedo,Hikarigaoka,38,0

+ 14 - 0
files/suica/line_0xF3.txt

@@ -0,0 +1,14 @@
+0xF3,0x03,Oedo,Morishita,13,0
+0xF3,0x05,Oedo,Ryogoku,12,0
+0xF3,0x07,Oedo,Kuramae,11,0
+0xF3,0x09,Oedo,Shin-okachimachi,10,0
+0xF3,0x0B,Oedo,Ueno-okachimachi,9,0
+0xF3,0x0D,Oedo,Hongo-sanchome,8,0
+0xF3,0x0F,Oedo,Kasuga,7,0
+0xF3,0x11,Oedo,Iidabashi,6,0
+0xF3,0x13,Oedo,Ushigome-kagurazaka,5,0
+0xF3,0x15,Oedo,Ushigome-yanagicho,4,0
+0xF3,0x17,Oedo,Wakamatsu-kawada,3,0
+0xF3,0x19,Oedo,Higashi-shinjuku,2,0
+0xF3,0x1B,Oedo,Shinjuku-nishiguchi,1,0
+0xF3,0x1D,Oedo,Tochomae,28,0

+ 11 - 0
files/suica/line_0xFA.txt

@@ -0,0 +1,11 @@
+0xFA,0x01,Tokyo Monorail,Monorail Hamamatsucho,1,0
+0xFA,0x03,Tokyo Monorail,Tennozu Isle,2,0
+0xFA,0x04,Tokyo Monorail,Oi Keibajo Mae,3,0
+0xFA,0x05,Tokyo Monorail,Ryutsu Center,4,0
+0xFA,0x06,Tokyo Monorail,Showajima,5,0
+0xFA,0x07,Tokyo Monorail,Seibijo,6,0
+0xFA,0x09,Tokyo Monorail,Tenkubashi,7,0
+0xFA,0x0A,Tokyo Monorail,Haneda Airport T3,8,0
+0xFA,0x0B,Tokyo Monorail,Shinseibijo,9,0
+0xFA,0x0C,Tokyo Monorail,Haneda Airport T1,10,0
+0xFA,0x0D,Tokyo Monorail,Haneda Airport T2,11,0

+ 419 - 0
files/suica/stations.txt

@@ -0,0 +1,419 @@
+0x01,0x01,Tokaido Main,Tokyo,1,TYO
+0x01,0x03,Tokaido Main,Shimbashi,2,SMB
+0x01,0x07,Tokaido Main,Shinagawa,3,SGW
+0x01,0x0D,Tokaido Main,Kawasaki,4,KWS
+0x01,0x12,Tokaido Main,Yokohama,5,YHM
+0x01,0x16,Tokaido Main,Totsuka,6,TTK
+0x01,0x18,Tokaido Main,Ofuna,7,OFN
+0x01,0x19,Tokaido Main,Fujisawa,8,0
+0x01,0x1A,Tokaido Main,Tsujido,9,0
+0x01,0x1B,Tokaido Main,Chigasaki,10,0
+0x01,0x1D,Tokaido Main,Hiratsuka,11,0
+0x01,0x1E,Tokaido Main,Oiso,12,0
+0x01,0x20,Tokaido Main,Ninomiya,13,0
+0x01,0x21,Tokaido Main,Kozu,14,0
+0x01,0x22,Tokaido Main,Kamonomiya,15,0
+0x01,0x23,Tokaido Main,Odawara,16,0
+0x01,0x25,Tokaido Main,Hayakawa,17,0
+0x01,0x26,Tokaido Main,Nebukawa,18,0
+0x01,0x28,Tokaido Main,Manazuru,19,0
+0x01,0x29,Tokaido Main,Yugawara,20,0
+0x01,0x2B,Tokaido Main,Atami,21,0
+,,,,,
+0x01,0x01,Keihin Tohoku,Tokyo,26,TYO
+0x01,0x02,Keihin Tohoku,Yurakucho,25,0
+0x01,0x03,Keihin Tohoku,Shimbashi,24,SMB
+0x01,0x04,Keihin Tohoku,Hamamatsucho,23,HMC
+0x01,0x06,Keihin Tohoku,Tamachi,22,0
+0x01,0x07,Keihin Tohoku,Shinagawa,20,SGW
+0x01,0x08,Keihin Tohoku,Oimachi,19,0
+0x01,0x09,Keihin Tohoku,Omori,18,0
+0x01,0x0B,Keihin Tohoku,Kamata,17,0
+0x01,0x0D,Keihin Tohoku,Kawasaki,16,KWS
+0x01,0x0E,Keihin Tohoku,Tsurumi,15,0
+0x01,0x10,Keihin Tohoku,Shin-koyasu,14,0
+0x01,0x11,Keihin Tohoku,Higashi-kanagawa,13,0
+0x01,0x12,Keihin Tohoku,Yokohama,12,YHM
+,,,,,
+0x1D,0x04,Negishi,Hongodai,2,0
+0x1D,0x06,Negishi,Konandai,3,0
+0x1D,0x07,Negishi,Yokodai,4,0
+0x1D,0x08,Negishi,Shin-sugita,5,0
+0x1D,0x09,Negishi,Isogo,6,0
+0x1D,0x0A,Negishi,Negishi,7,0
+0x1D,0x0C,Negishi,Yamate,8,0
+0x1D,0x0D,Negishi,Ishikawacho,9,0
+0x1D,0x0E,Negishi,Kannai,10,0
+0x1D,0x0F,Negishi,Sakuragicho,11,0
+,,,,,
+0x82,0x01,Rinkai,Shin-kiba,1,0
+0x82,0x03,Rinkai,Shinonome,2,0
+0x82,0x04,Rinkai,Kokusai-tenjijo,3,0
+0x82,0x05,Rinkai,Tokyo Teleport,4,0
+0x82,0x06,Rinkai,Tennozu Isle,5,0
+0x82,0x07,Rinkai,Shinagawa Seaside,6,0
+0x82,0x08,Rinkai,Oimachi,7,0
+0x82,0x0A,Rinkai,Osaki,8,0
+,,,,,
+0xFA,0x01,Tokyo Monorail,Monorail Hamamatsucho,1,0
+0xFA,0x03,Tokyo Monorail,Tennozu Isle,2,0
+0xFA,0x04,Tokyo Monorail,Oi Keibajo Mae,3,0
+0xFA,0x05,Tokyo Monorail,Ryutsu Center,4,0
+0xFA,0x06,Tokyo Monorail,Showajima,5,0
+0xFA,0x07,Tokyo Monorail,Seibijo,6,0
+0xFA,0x09,Tokyo Monorail,Tenkubashi,7,0
+0xFA,0x0A,Tokyo Monorail,Haneda Airport T3,8,0
+0xFA,0x0B,Tokyo Monorail,Shinseibijo,9,0
+0xFA,0x0C,Tokyo Monorail,Haneda Airport T1,10,0
+0xFA,0x0D,Tokyo Monorail,Haneda Airport T2,11,0
+,,,,,
+0xD6,0x01,Keikyu Airport,Keikyu Kamata,11,0
+0xD6,0x02,Keikyu Airport,Kojiya,12,0
+0xD6,0x03,Keikyu Airport,Otorii,13,0
+0xD6,0x04,Keikyu Airport,Anamori-Inari,14,0
+0xD6,0x05,Keikyu Airport,Tenkubashi,15,0
+0xD6,0x06,Keikyu Airport,Haneda-Airport,16,0
+,,,,,
+0xD5,0x01,Keikyu Main,Sengakuji,7,0
+0xD5,0x02,Keikyu Main,Shinagawa,1,0
+0xD5,0x03,Keikyu Main,Kita-Shinagawa,2,0
+0xD5,0x04,Keikyu Main,Shimbamba,3,0
+0xD5,0x06,Keikyu Main,Aomono-yokocho,4,0
+0xD5,0x07,Keikyu Main,Samezu,5,0
+0xD5,0x08,Keikyu Main,Tachiaigawa,6,0
+0xD5,0x09,Keikyu Main,Omorikaigan,7,0
+0xD5,0x0A,Keikyu Main,Heiwajima,8,0
+0xD5,0x0B,Keikyu Main,Omorimachi,9,0
+0xD5,0x0C,Keikyu Main,Umeyashiki,10,0
+0xD5,0x0D,Keikyu Main,Keikyu Kamata,11,0
+0xD5,0x0E,Keikyu Main,Zoshiki,18,0
+0xD5,0x0F,Keikyu Main,Rokugodote,19,0
+0xD5,0x10,Keikyu Main,Keikyu Kawasaki,20,0
+0xD5,0x11,Keikyu Main,Hatcho-nawate,27,0
+0xD5,0x12,Keikyu Main,Tsurumi-ichiba,28,0
+0xD5,0x13,Keikyu Main,Keikyu Tsurumi,29,0
+0xD5,0x14,Keikyu Main,Kagetsu-sojiji,30,0
+0xD5,0x15,Keikyu Main,Namamugi,31,0
+0xD5,0x16,Keikyu Main,Keikyu Shinkoyasu,32,0
+0xD5,0x17,Keikyu Main,Koyasu,33,0
+0xD5,0x18,Keikyu Main,Kanagawa-shimmachi,34,0
+0xD5,0x19,Keikyu Main,KK Higashi-kanagawa,35,0
+0xD5,0x1A,Keikyu Main,Kanagawa,36,0
+0xD5,0x1B,Keikyu Main,Yokohama,37,0
+0xD5,0x1C,Keikyu Main,Tobe,38,0
+0xD5,0x1D,Keikyu Main,Hinodecho,39,0
+0xD5,0x1E,Keikyu Main,Koganecho,40,0
+0xD5,0x1F,Keikyu Main,Minamiota,41,0
+0xD5,0x20,Keikyu Main,Idogaya,42,0
+0xD5,0x21,Keikyu Main,Gumyoji,43,0
+0xD5,0x22,Keikyu Main,Kamiooka,44,0
+0xD5,0x24,Keikyu Main,Byobugaura,45,0
+0xD5,0x25,Keikyu Main,Sugita,46,0
+0xD5,0x27,Keikyu Main,Keikyu Tomioka,47,0
+0xD5,0x28,Keikyu Main,Nokendai,48,0
+0xD5,0x29,Keikyu Main,Kanazawa-bunko,49,0
+0xD5,0x2A,Keikyu Main,Kanazawa-hakkei,50,0
+0xD5,0x2B,Keikyu Main,Oppama,54,0
+0xD5,0x2C,Keikyu Main,Keikyu Taura,55,0
+0xD5,0x2E,Keikyu Main,Anjinzuka,56,0
+0xD5,0x2F,Keikyu Main,Hemi,57,0
+0xD5,0x30,Keikyu Main,Shioiri,58,0
+0xD5,0x31,Keikyu Main,Yokosuka-chuo,59,0
+0xD5,0x32,Keikyu Main,Kenritsudaigaku,60,0
+0xD5,0x33,Keikyu Main,Horinouchi,61,0
+0xD5,0x34,Keikyu Main,Keikyu Otsu,62,0
+0xD5,0x35,Keikyu Main,Maborikaigan,63,0
+0xD5,0x36,Keikyu Main,Uraga,64,0
+,,,,,
+0xE3,0x2C,Ginza,Asakusa,19,0
+0xE3,0x2D,Ginza,Tawaramachi,18,0
+0xE3,0x2E,Ginza,Inaricho,17,0
+0xE3,0x2F,Ginza,Ueno,16,0
+0xE3,0x30,Ginza,Ueno-hirokoji,15,0
+0xE3,0x31,Ginza,Suehirocho,14,0
+0xE3,0x32,Ginza,Kanda,13,0
+0xE3,0x33,Ginza,Mitsukoshimae,12,0
+0xE3,0x34,Ginza,Nihombashi,11,0
+0xE3,0x35,Ginza,Kyobashi,10,0
+0xE3,0x36,Ginza,Ginza,9,0
+0xE3,0x37,Ginza,Shimbashi,8,0
+0xE3,0x38,Ginza,Toranomon,7,0
+0xE3,0x39,Ginza,Tameike-sannō,6,0
+0xE3,0x3A,Ginza,Akasaka-mitsuke,5,0
+0xE3,0x3B,Ginza,Aoyama-itchōme,4,0
+0xE3,0x3C,Ginza,Gaiemmae,3,0
+0xE3,0x3D,Ginza,Omote-sandō,2,0
+0xE3,0x3E,Ginza,Shibuya,1,0
+,,,,,
+0xE3,0x5E,Chiyoda,Yoyogi-uehara,1,0
+0xE3,0x5D,Chiyoda,Yoyogi-koen,2,0
+0xE3,0x5C,Chiyoda,Meiji-jingumae,3,0
+0xE3,0x5B,Chiyoda,Omotesando,4,0
+0xE3,0x59,Chiyoda,Nogizaka,5,0
+0xE3,0x58,Chiyoda,Akasaka,6,0
+0xE3,0x57,Chiyoda,Kokkai-gijidomae,7,0
+0xE3,0x56,Chiyoda,Kasumigaseki,8,0
+0xE3,0x55,Chiyoda,Hibiya,9,0
+0xE3,0x54,Chiyoda,Nijubashimae,10,0
+0xE3,0x53,Chiyoda,Otemachi,11,0
+0xE3,0x51,Chiyoda,Shin-ochanomizu,12,0
+0xE3,0x50,Chiyoda,Yushima,13,0
+0xE3,0x4F,Chiyoda,Nezu,14,0
+0xE3,0x4E,Chiyoda,Sendagi,15,0
+0xE3,0x4D,Chiyoda,Nishi-Nippori,16,0
+0xE3,0x4C,Chiyoda,Machiya,17,0
+0xE3,0x4A,Chiyoda,Kita-senju,18,0
+0xE3,0x48,Chiyoda,Ayase,19,0
+0xE3,0x47,Chiyoda,Kita-Ayase,20,0
+,,,,,
+0xE3,0x65,Yurakucho,Wakoshi,1,0
+0xE3,0x66,Yurakucho,Chikatetsu-narimasu,2,0
+0xE3,0x68,Yurakucho,Chikatetsu-akatsuka,3,0
+0xE3,0x6A,Yurakucho,Heiwadai,4,0
+0xE3,0x6B,Yurakucho,Hikawadai,5,0
+0xE3,0x6D,Yurakucho,Kotake-mukaihara,6,0
+0xE3,0x6E,Yurakucho,Senkawa,7,0
+0xE3,0x6F,Yurakucho,Kanamecho,8,0
+0xE3,0x70,Yurakucho,Ikebukuro,9,0
+0xE3,0x71,Yurakucho,Higashi-ikebukuro,10,0
+0xE3,0x72,Yurakucho,Gokokuji,11,0
+0xE3,0x73,Yurakucho,Edogawabashi,12,0
+0xE3,0x75,Yurakucho,Iidabashi,13,0
+0xE3,0x76,Yurakucho,Ichigaya,14,0
+0xE3,0x77,Yurakucho,Kojimachi,15,0
+0xE3,0x78,Yurakucho,Nagatacho,16,0
+0xE3,0x79,Yurakucho,Sakuradamon,17,0
+0xE3,0x7A,Yurakucho,Yurakucho,18,0
+0xE3,0x7B,Yurakucho,Ginza-itchome,19,0
+0xE3,0x7C,Yurakucho,Shintomicho,20,0
+0xE3,0x7D,Yurakucho,Tsukishima,21,0
+0xE3,0x7E,Yurakucho,Toyosu,22,0
+0xE3,0x7F,Yurakucho,Tatsumi,23,0
+0xE3,0x80,Yurakucho,Shin-kiba,24,0
+,,,,,
+0xE4,0x25,Hibiya,Kita-senju,22,0
+0xE4,0x27,Hibiya,Minami-senju,21,0
+0xE4,0x28,Hibiya,Minowa,20,0
+0xE4,0x29,Hibiya,Iriya,19,0
+0xE4,0x2A,Hibiya,Ueno,18,0
+0xE4,0x2B,Hibiya,Naka-okachimachi,17,0
+0xE4,0x2C,Hibiya,Akihabara,16,0
+0xE4,0x2D,Hibiya,Kodenmacho,15,0
+0xE4,0x2E,Hibiya,Ningyocho,14,0
+0xE4,0x2F,Hibiya,Kayabacho,13,0
+0xE4,0x30,Hibiya,Hatchobori,12,0
+0xE4,0x31,Hibiya,Tsukiji,11,0
+0xE4,0x32,Hibiya,Higashi-ginza,10,0
+0xE4,0x33,Hibiya,Ginza,9,0
+0xE4,0x34,Hibiya,Hibiya,8,0
+0xE4,0x35,Hibiya,Kasumigaseki,7,0
+0xE4,0x37,Hibiya,Kamiyacho,5,0
+0xE4,0x39,Hibiya,Roppongi,4,0
+0xE4,0x3B,Hibiya,Hiro-o,3,0
+0xE4,0x3D,Hibiya,Ebisu,2,0
+0xE4,0x3E,Hibiya,Naka-meguro,1,0
+,,,,,
+0xE4,0x41,Tozai,Nakano,1,0
+0xE4,0x43,Tozai,Ochiai,2,0
+0xE4,0x45,Tozai,Takadanobaba,3,0
+0xE4,0x47,Tozai,Waseda,4,0
+0xE4,0x48,Tozai,Kagurazaka,5,0
+0xE4,0x49,Tozai,Iidabashi,6,0
+0xE4,0x4A,Tozai,Kudanshita,7,0
+0xE4,0x4B,Tozai,Takebashi,8,0
+0xE4,0x4C,Tozai,Otemachi,9,0
+0xE4,0x4D,Tozai,Nihombashi,10,0
+0xE4,0x4E,Tozai,Kayabacho,11,0
+0xE4,0x50,Tozai,Monzen-nakacho,12,0
+0xE4,0x51,Tozai,Kiba,13,0
+0xE4,0x52,Tozai,Toyocho,14,0
+0xE4,0x53,Tozai,Minami-sunamachi,15,0
+0xE4,0x54,Tozai,Nishi-kasai,16,0
+0xE4,0x55,Tozai,Kasai,17,0
+0xE4,0x57,Tozai,Urayasu,18,0
+0xE4,0x58,Tozai,Minami-gyotoku,19,0
+0xE4,0x59,Tozai,Gyotoku,20,0
+0xE4,0x5A,Tozai,Myoden,21,0
+0xE4,0x5C,Tozai,Baraki-nakayama,22,0
+0xE4,0x5E,Tozai,Nishi-funabashi,23,0
+,,,,,
+0xE5,0x21,Marunouchi,Ikebukuro,25,0
+0xE5,0x23,Marunouchi,Shin-otsuka,24,0
+0xE5,0x24,Marunouchi,Myogadani,23,0
+0xE5,0x26,Marunouchi,Korakuen,22,0
+0xE5,0x27,Marunouchi,Hongo-sanchome,21,0
+0xE5,0x28,Marunouchi,Ochanomizu,20,0
+0xE5,0x29,Marunouchi,Awajicho,19,0
+0xE5,0x2A,Marunouchi,Otemachi,18,0
+0xE5,0x2B,Marunouchi,Tokyo,17,0
+0xE5,0x2C,Marunouchi,Ginza,16,0
+0xE5,0x2D,Marunouchi,Kasumigaseki,15,0
+0xE5,0x2E,Marunouchi,Kokkai-gijidomae,14,0
+0xE5,0x2F,Marunouchi,Akasaka-mitsuke,13,0
+0xE5,0x30,Marunouchi,Yotsuya,12,0
+0xE5,0x31,Marunouchi,Yotsuya-sanchome,11,0
+0xE5,0x32,Marunouchi,Shinjuku-gyoemmae,10,0
+0xE5,0x33,Marunouchi,Shinjuku-sanchome,9,0
+0xE5,0x34,Marunouchi,Shinjuku,8,0
+0xE5,0x35,Marunouchi,Nishi-shinjuku,7,0
+0xE5,0x36,Marunouchi,Nakano-sakaue,6,0
+0xE5,0x37,Marunouchi,Shin-nakano,5,0
+0xE5,0x38,Marunouchi,Higashi-koenji,4,0
+0xE5,0x39,Marunouchi,Shin-koenji,3,0
+0xE5,0x3A,Marunouchi,Minami-asagaya,2,0
+0xE5,0x3C,Marunouchi,Ogikubo,1,0
+,,,,,
+0xE5,0x41,M Honancho,Nakano-sakaue,6,0
+0xE5,0x42,M Honancho,Nakano-shimbashi,5,0
+0xE5,0x43,M Honancho,Nakano-fujimicho,4,0
+0xE5,0x44,M Honancho,Honancho,3,0
+,,,,,
+0xE6,0x21,Hanzomon,Shibuya,1,0
+0xE6,0x22,Hanzomon,Omotesando,2,0
+0xE6,0x23,Hanzomon,Aoyama-itchome,3,0
+0xE6,0x26,Hanzomon,Nagatacho,4,0
+0xE6,0x27,Hanzomon,Hanzomon,5,0
+0xE6,0x28,Hanzomon,Kudanshita,6,0
+0xE6,0x29,Hanzomon,Jimbocho,7,0
+0xE6,0x2B,Hanzomon,Otemachi,8,0
+0xE6,0x2C,Hanzomon,Mitsukoshimae,9,0
+0xE6,0x2D,Hanzomon,Suitengumae,10,0
+0xE6,0x2F,Hanzomon,Kiyosumi-shirakawa,11,0
+0xE6,0x31,Hanzomon,Sumiyoshi,12,0
+0xE6,0x33,Hanzomon,Kinshicho,13,0
+0xE6,0x35,Hanzomon,Oshiage,14,0
+,,,,,
+0xE7,0x24,Namboku,Meguro,1,0
+0xE7,0x25,Namboku,Shirokanedai,2,0
+0xE7,0x26,Namboku,Shirokane-takanawa,3,0
+0xE7,0x28,Namboku,Azabu-juban,4,0
+0xE7,0x29,Namboku,Roppongi-itchome,5,0
+0xE7,0x2A,Namboku,Tameike-sanno,6,0
+0xE7,0x2C,Namboku,Nagatacho,7,0
+0xE7,0x2E,Namboku,Yotsuya,8,0
+0xE7,0x30,Namboku,Ichigaya,9,0
+0xE7,0x32,Namboku,Iidabashi,10,0
+0xE7,0x33,Namboku,Korakuen,11,0
+0xE7,0x34,Namboku,Todaimae,12,0
+0xE7,0x35,Namboku,Hon-komagome,13,0
+0xE7,0x36,Namboku,Komagome,14,0
+0xE7,0x37,Namboku,Nishigahara,15,0
+0xE7,0x38,Namboku,Oji,16,0
+0xE7,0x39,Namboku,Oji-kamiya,17,0
+0xE7,0x3B,Namboku,Shimo,18,0
+0xE7,0x3C,Namboku,Akabane-iwabuchi,19,0
+,,,,,
+0xEF,0x01,Asakusa,Oshiage,20,0
+0xEF,0x02,Asakusa,Honjo-azumabashi,19,0
+0xEF,0x03,Asakusa,Asakusa,18,0
+0xEF,0x04,Asakusa,Kuramae,17,0
+0xEF,0x05,Asakusa,Asakusabashi,16, 0
+0xEF,0x06,Asakusa,Higashi-nihombashi,15,0
+0xEF,0x07,Asakusa,Ningyocho,14,0
+0xEF,0x08,Asakusa,Nihombashi,13,0
+0xEF,0x09,Asakusa,Takaracho,12,0
+0xEF,0x0A,Asakusa,Higashi-ginza,11,0
+0xEF,0x0B,Asakusa,Shimbashi,10,0
+0xEF,0x0C,Asakusa,Daimon,9,0
+0xEF,0x0D,Asakusa,Mita,8,0
+0xEF,0x0E,Asakusa,Sengakuji,7,0
+0xEF,0x0F,Asakusa,Takanawadai,6,0
+0xEF,0x10,Asakusa,Gotanda,5,0
+0xEF,0x11,Asakusa,Togoshi,4,0
+0xEF,0x12,Asakusa,Nakanobu,3,0
+0xEF,0x13,Asakusa,Magome,2,0
+0xEF,0x14,Asakusa,Nishi-magome,1,0
+,,,,,
+0xF0,0x27,Mita,Nishi-takashimadaira,27,0
+0xF0,0x28,Mita,Shin-takashimadaira,26,0
+0xF0,0x29,Mita,Takashimadaira,25,0
+0xF0,0x2A,Mita,Nishidai,24,0
+0xF0,0x2B,Mita,Hasune,23,0
+0xF0,0x2C,Mita,Shimura-sanchome,22,0
+0xF0,0x2D,Mita,Shimura-sakaue,21,0
+0xF0,0x2E,Mita,Motohasunuma,20,0
+0xF0,0x2F,Mita,Itabashi-honcho,19,0
+0xF0,0x30,Mita,Itabashikuyakushomae,18,0
+0xF0,0x31,Mita,Shin-itabashi,17,0
+0xF0,0x32,Mita,Nishi-sugamo,16,0
+0xF0,0x33,Mita,Sugamo,15,0
+0xF0,0x34,Mita,Sengoku,14,0
+0xF0,0x35,Mita,Hakusan,13,0
+0xF0,0x36,Mita,Kasuga,12,0
+0xF0,0x37,Mita,Suidobashi,11,0
+0xF0,0x38,Mita,Jimbocho,10,0
+0xF0,0x39,Mita,Otemachi,9,0
+0xF0,0x3A,Mita,Hibiya,8,0
+0xF0,0x3B,Mita,Uchisaiwaicho,7,0
+0xF0,0x3C,Mita,Onarimon,6,0
+0xF0,0x3D,Mita,Shibakoen,5,0
+0xF0,0x3E,Mita,Mita,4,0
+0xF0,0x40,Mita,Shirokane-takanawa,3,0
+0xF0,0x42,Mita,Shirokanedai,2,0
+0xF0,0x44,Mita,Meguro,1,0
+,,,,,
+0xF1,0x01,Shinjuku,Shinjuku,1,0
+0xF1,0x02,Shinjuku,Shinjuku-sanchome,2,0
+0xF1,0x03,Shinjuku,Akebonobashi,3,0
+0xF1,0x04,Shinjuku,Ichigaya,4,0
+0xF1,0x05,Shinjuku,Kudanshita,5,0
+0xF1,0x06,Shinjuku,Jimbocho,6,0
+0xF1,0x07,Shinjuku,Ogawamachi,7,0
+0xF1,0x08,Shinjuku,Iwamotocho,8,0
+0xF1,0x09,Shinjuku,Bakuro-yokoyama,9,0
+0xF1,0x0A,Shinjuku,Hamacho,10,0
+0xF1,0x0B,Shinjuku,Morishita,11,0
+0xF1,0x0C,Shinjuku,Kikukawa,12,0
+0xF1,0x0D,Shinjuku,Sumiyoshi,13,0
+0xF1,0x0E,Shinjuku,Nishi-ojima,14,0
+0xF1,0x0F,Shinjuku,Ojima,15,0
+0xF1,0x11,Shinjuku,Higashi-ojima,16,0
+0xF1,0x13,Shinjuku,Funabori,17,0
+0xF1,0x15,Shinjuku,Ichinoe,18,0
+0xF1,0x17,Shinjuku,Mizue,19,0
+0xF1,0x19,Shinjuku,Shinozaki,20,0
+0xF1,0x1B,Shinjuku,Motoyawata,21,0
+,,,,,
+0xF2,0x04,Oedo,Morishita,13,0
+0xF2,0x06,Oedo,Kiyosumi-shirakawa,14,0
+0xF2,0x08,Oedo,Monzen-nakacho,15,0
+0xF2,0x0A,Oedo,Tsukishima,16,0
+0xF2,0x0C,Oedo,Kachidoki,17,0
+0xF2,0x0E,Oedo,Tsukiji-shijo,18,0
+0xF2,0x10,Oedo,Shiodome,19,0
+0xF2,0x12,Oedo,Daimon,20,0
+0xF2,0x14,Oedo,Akabanebashi,21,0
+0xF2,0x16,Oedo,Azabu-juban,22,0
+0xF2,0x18,Oedo,Roppongi,23,0
+0xF2,0x1A,Oedo,Aoyama-itchome,24,0
+0xF2,0x1C,Oedo,Kokuritsu-kyogijo,25,0
+0xF2,0x1E,Oedo,Yoyogi,26,0
+0xF2,0x20,Oedo,Shinjuku,27,0
+0xF2,0x22,Oedo,Tochomae,28,0
+0xF2,0x24,Oedo,Nishi-shinjuku-gochome,29,0
+0xF2,0x26,Oedo,Nakano-sakaue,30,0
+0xF2,0x28,Oedo,Higashi-nakano,31,0
+0xF2,0x2A,Oedo,Nakai,32,0
+0xF2,0x2C,Oedo,Ochiai-minami-nagasaki,33,0
+0xF2,0x2E,Oedo,Shin-egota,34,0
+0xF2,0x30,Oedo,Nerima,35,0
+0xF2,0x32,Oedo,Toshimaen,36,0
+0xF2,0x34,Oedo,Nerima-kasugacho,37,0
+0xF2,0x36,Oedo,Hikarigaoka,38,0
+,,,,,
+0xF3,0x03,Oedo,Morishita,13,0
+0xF3,0x05,Oedo,Ryogoku,12,0
+0xF3,0x07,Oedo,Kuramae,11,0
+0xF3,0x09,Oedo,Shin-okachimachi,10,0
+0xF3,0x0B,Oedo,Ueno-okachimachi,9,0
+0xF3,0x0D,Oedo,Hongo-sanchome,8,0
+0xF3,0x0F,Oedo,Kasuga,7,0
+0xF3,0x11,Oedo,Iidabashi,6,0
+0xF3,0x13,Oedo,Ushigome-kagurazaka,5,0
+0xF3,0x15,Oedo,Ushigome-yanagicho,4,0
+0xF3,0x17,Oedo,Wakamatsu-kawada,3,0
+0xF3,0x19,Oedo,Higashi-shinjuku,2,0
+0xF3,0x1B,Oedo,Shinjuku-nishiguchi,1,0
+0xF3,0x1D,Oedo,Tochomae,28,0
+,,,,,
+0x00,0x00,UnknownLine,Unknown,0,0

BIN
images/suica/Suica_AsakusaA.png


BIN
images/suica/Suica_BigStar.png


BIN
images/suica/Suica_ChiyodaC.png


BIN
images/suica/Suica_CrackingEgg.png


BIN
images/suica/Suica_DashLine.png


BIN
images/suica/Suica_EmptyArrowDown.png


BIN
images/suica/Suica_EmptyArrowRight.png


BIN
images/suica/Suica_FilledArrowDown.png


BIN
images/suica/Suica_FilledArrowRight.png


BIN
images/suica/Suica_GinzaG.png


BIN
images/suica/Suica_HanzomonZ.png


BIN
images/suica/Suica_HibiyaH.png


BIN
images/suica/Suica_JRLogo.png


BIN
images/suica/Suica_KeikyuKK.png


BIN
images/suica/Suica_KeikyuLogo.png


BIN
images/suica/Suica_MarunouchiHonanchoMb.png


BIN
images/suica/Suica_MarunouchiM.png


BIN
images/suica/Suica_MinusSign0.png


BIN
images/suica/Suica_MinusSign1.png


BIN
images/suica/Suica_MinusSign2.png


BIN
images/suica/Suica_MinusSign3.png


BIN
images/suica/Suica_MinusSign4.png


BIN
images/suica/Suica_MinusSign5.png


BIN
images/suica/Suica_MinusSign6.png


BIN
images/suica/Suica_MinusSign7.png


BIN
images/suica/Suica_MinusSign8.png


BIN
images/suica/Suica_MinusSign9.png


BIN
images/suica/Suica_MitaI.png


BIN
images/suica/Suica_NambokuN.png


BIN
images/suica/Suica_Nothing.png


BIN
images/suica/Suica_OedoE.png


BIN
images/suica/Suica_PenguinHappyBirthday.png


BIN
images/suica/Suica_PenguinTodaysVIP.png


BIN
images/suica/Suica_PlusSign1.png


BIN
images/suica/Suica_PlusSign2.png


BIN
images/suica/Suica_PlusSign3.png


BIN
images/suica/Suica_PlusStar.png


BIN
images/suica/Suica_QuestionMarkBig.png


BIN
images/suica/Suica_QuestionMarkSmall.png


BIN
images/suica/Suica_RinkaiR.png


BIN
images/suica/Suica_ShinjukuS.png


BIN
images/suica/Suica_ShopPin.png


BIN
images/suica/Suica_SmallStar.png


BIN
images/suica/Suica_StoreFan1.png


BIN
images/suica/Suica_StoreFan2.png


BIN
images/suica/Suica_StoreFrame.png


BIN
images/suica/Suica_StoreLightningHorizontal.png


BIN
images/suica/Suica_StoreLightningVertical.png


BIN
images/suica/Suica_StoreP1Counter.png


BIN
images/suica/Suica_StoreReceiptDashLine.png


BIN
images/suica/Suica_StoreReceiptFrame1.png


BIN
images/suica/Suica_StoreReceiptFrame2.png


BIN
images/suica/Suica_StoreSlidingDoor.png


BIN
images/suica/Suica_TWRLogo.png


BIN
images/suica/Suica_ToeiLogo.png


BIN
images/suica/Suica_TokyoMetroLogo.png


BIN
images/suica/Suica_TokyoMonorailLogo.png


BIN
images/suica/Suica_TozaiT.png


BIN
images/suica/Suica_VendingCan1.png


BIN
images/suica/Suica_VendingCan2.png


BIN
images/suica/Suica_VendingCan3.png


BIN
images/suica/Suica_VendingCan4.png


BIN
images/suica/Suica_VendingFlap1.png


BIN
images/suica/Suica_VendingFlap2.png


BIN
images/suica/Suica_VendingFlap3.png


BIN
images/suica/Suica_VendingFlapHollow.png


BIN
images/suica/Suica_VendingMachine.png


BIN
images/suica/Suica_VendingPage2Full.png


BIN
images/suica/Suica_YenKanji.png


BIN
images/suica/Suica_YenSign.png


BIN
images/suica/Suica_YurakuchoY.png


BIN
scenes/.DS_Store


+ 1480 - 0
scenes/plugins/suica.c

@@ -0,0 +1,1480 @@
+/*
+ * Suica Scene
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "metroflip_i.h"
+#include <flipper_application.h>
+#include "../../metroflip_plugins.h"
+#include "../../api/metroflip/metroflip_api.h"
+#include "../../api/suica/suica_assets.h"
+
+#include <lib/nfc/protocols/felica/felica.h>
+#include <lib/nfc/protocols/felica/felica_poller.h>
+#include <lib/nfc/protocols/felica/felica_poller_i.h>
+#include <lib/nfc/helpers/felica_crc.h>
+#include <lib/bit_lib/bit_lib.h>
+
+#include <applications/services/locale/locale.h>
+#include <datetime.h>
+
+// Probably not needed after upstream include this in their suica_i.h
+#include <toolbox/stream/stream.h>
+#include <toolbox/stream/file_stream.h>
+#define TAG "Suica:Scene:Suica"
+
+#define SUICA_STATION_LIST_PATH         APP_ASSETS_PATH("suica/line_")
+#define SUICA_IC_TYPE_CODE              0x31
+#define SERVICE_CODE_HISTORY_IN_LE      (0x090FU)
+#define SERVICE_CODE_TAPS_LOG_IN_LE     (0x108FU)
+#define BLOCK_COUNT                     1
+#define HISTORY_VIEW_PAGE_NUM           3
+#define TERMINAL_NULL                   0x02
+#define TERMINAL_BUS                    0x05
+#define TERMINAL_TICKET_VENDING_MACHINE 0x12
+#define TERMINAL_TURNSTILE              0x16
+#define TERMINAL_MOBILE_PHONE           0x1B
+#define TERMINAL_IN_CAR_SUPP_MACHINE    0x24
+#define TERMINAL_POS_AND_TAXI           0xC7
+#define TERMINAL_VENDING_MACHINE        0xC8
+#define PROCESSING_CODE_NEW_ISSUE       0x02
+#define ARROW_ANIMATION_FRAME_MS        350
+
+const char* suica_service_names[] = {
+    "Travel History",
+    "Taps Log",
+};
+
+typedef enum {
+    SuicaTrainRideEntry,
+    SuicaTrainRideExit,
+} SuicaTrainRideType;
+
+typedef enum {
+    SuicaRedrawScreen,
+} MetroflipCustomEvent;
+
+static void suica_model_initialize(SuicaHistoryViewModel* model, size_t initial_capacity) {
+    model->travel_history =
+        (uint8_t*)malloc(initial_capacity * FELICA_DATA_BLOCK_SIZE); // Each entry is 16 bytes
+    model->size = 0;
+    model->capacity = initial_capacity;
+    model->entry = 1;
+    model->page = 0;
+    model->animator_tick = 0;
+    model->history.entry_station.name = furi_string_alloc_set("Unknown");
+    model->history.entry_station.jr_header = furi_string_alloc_set("0");
+    model->history.exit_station.name = furi_string_alloc_set("Unknown");
+    model->history.exit_station.jr_header = furi_string_alloc_set("0");
+    model->history.entry_line = RailwaysList[SUICA_RAILWAY_NUM];
+    model->history.exit_line = RailwaysList[SUICA_RAILWAY_NUM];
+}
+
+static void suica_model_initialize_after_load(SuicaHistoryViewModel* model) {
+    model->entry = 1;
+    model->page = 0;
+    model->animator_tick = 0;
+    model->history.entry_station.name = furi_string_alloc_set("Unknown");
+    model->history.entry_station.jr_header = furi_string_alloc_set("0");
+    model->history.exit_station.name = furi_string_alloc_set("Unknown");
+    model->history.exit_station.jr_header = furi_string_alloc_set("0");
+    model->history.entry_line = RailwaysList[SUICA_RAILWAY_NUM];
+    model->history.exit_line = RailwaysList[SUICA_RAILWAY_NUM];
+}
+
+static void suica_add_entry(SuicaHistoryViewModel* model, const uint8_t* entry) {
+    if(model->size <= 0) {
+        suica_model_initialize(model, 3);
+    }
+    // Check if resizing is needed
+    if(model->size == model->capacity) {
+        size_t new_capacity = model->capacity * 2; // Double the capacity
+        uint8_t* new_data =
+            (uint8_t*)realloc(model->travel_history, new_capacity * FELICA_DATA_BLOCK_SIZE);
+        model->travel_history = new_data;
+        model->capacity = new_capacity;
+    }
+
+    // Copy the 16-byte entry to the next slot
+    for(size_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) {
+        model->travel_history[(model->size * FELICA_DATA_BLOCK_SIZE) + i] = entry[i];
+    }
+
+    model->size++;
+}
+
+void suica_parse_train_code(
+    uint8_t line_code,
+    uint8_t station_code,
+    SuicaTrainRideType ride_type,
+    SuicaHistoryViewModel* model) {
+    Storage* storage = furi_record_open(RECORD_STORAGE);
+    Stream* stream = file_stream_alloc(storage);
+    FuriString* line = furi_string_alloc();
+
+    FuriString* line_code_str = furi_string_alloc();
+    FuriString* line_and_station_code_str = furi_string_alloc();
+
+    furi_string_printf(line_code_str, "0x%02X", line_code);
+    furi_string_printf(line_and_station_code_str, "0x%02X,0x%02X", line_code, station_code);
+
+    FuriString* line_candidate = furi_string_alloc_set(SUICA_RAILWAY_UNKNOWN_NAME);
+    FuriString* station_candidate = furi_string_alloc_set(SUICA_RAILWAY_UNKNOWN_NAME);
+    FuriString* station_num_candidate = furi_string_alloc_set("0");
+    FuriString* station_JR_header_candidate = furi_string_alloc_set("0");
+    FuriString* line_copy = furi_string_alloc();
+    size_t line_comma_ind = 0;
+    size_t station_comma_ind = 0;
+    size_t station_num_comma_ind = 0;
+    size_t station_JR_header_comma_ind = 0;
+
+    bool station_found = false;
+    FuriString* file_name = furi_string_alloc();
+    furi_string_printf(file_name, "%s0x%02X.txt", SUICA_STATION_LIST_PATH, line_code);
+    if(file_stream_open(stream, furi_string_get_cstr(file_name), FSAM_READ, FSOM_OPEN_EXISTING)) {
+        while(stream_read_line(stream, line) && !station_found) {
+            // file is in csv format: station_group_id,station_id,station_sub_id,station_name
+            // search for the station
+            furi_string_replace_all(line, "\r", "");
+            furi_string_replace_all(line, "\n", "");
+            furi_string_set(line_copy, line); // 0xD5,0x02,Keikyu Main,Shinagawa,1,0
+
+            if(furi_string_start_with(line, line_code_str)) {
+                // set line name here
+                furi_string_right(line_copy, 10); // Keikyu Main,Shinagawa,1,0
+                furi_string_set(line_candidate, line_copy);
+                line_comma_ind = furi_string_search_char(line_candidate, ',', 0);
+                furi_string_left(line_candidate, line_comma_ind); // Keikyu Main
+                // we cut the line and station code in the line line copy
+                // and we leave only the line name for the line candidate
+                if(furi_string_start_with(line, line_and_station_code_str)) {
+                    furi_string_set(station_candidate, line_copy); // Keikyu Main,Shinagawa,1,0
+                    furi_string_right(station_candidate, line_comma_ind + 1);
+                    station_comma_ind =
+                        furi_string_search_char(station_candidate, ',', 0); // Shinagawa,1,0
+                    furi_string_left(station_candidate, station_comma_ind); //  Shinagawa
+                    station_found = true;
+                    break;
+                }
+            }
+        }
+    } else {
+        FURI_LOG_E("Suica:Scene:Suica", "Failed to open stations.txt");
+    }
+
+    furi_string_set(station_num_candidate, line_copy); // Keikyu Main,Shinagawa,1,0
+    furi_string_right(station_num_candidate, line_comma_ind + station_comma_ind + 2); // 1,0
+    station_num_comma_ind = furi_string_search_char(station_num_candidate, ',', 0);
+    furi_string_left(station_num_candidate, station_num_comma_ind); // 1
+
+    furi_string_set(station_JR_header_candidate, line_copy); // Keikyu Main,Shinagawa,1,0
+    furi_string_right(
+        station_JR_header_candidate,
+        line_comma_ind + station_comma_ind + station_num_comma_ind + 3); // 0
+    station_JR_header_comma_ind = furi_string_search_char(station_JR_header_candidate, ',', 0);
+    furi_string_left(station_JR_header_candidate, station_JR_header_comma_ind); // 0
+
+    switch(ride_type) {
+    case SuicaTrainRideEntry:
+        for(size_t i = 0; i < SUICA_RAILWAY_NUM; i++) {
+            if(furi_string_equal_str(line_candidate, RailwaysList[i].long_name)) {
+                model->history.entry_line = RailwaysList[i];
+                furi_string_set(model->history.entry_station.name, station_candidate);
+                model->history.entry_station.station_number =
+                    atoi(furi_string_get_cstr(station_num_candidate));
+                furi_string_set(
+                    model->history.entry_station.jr_header, station_JR_header_candidate);
+                break;
+            }
+        }
+        break;
+    case SuicaTrainRideExit:
+        for(size_t i = 0; i < SUICA_RAILWAY_NUM; i++) {
+            if(furi_string_equal_str(line_candidate, RailwaysList[i].long_name)) {
+                model->history.exit_line = RailwaysList[i];
+                furi_string_set(model->history.exit_station.name, station_candidate);
+                model->history.exit_station.station_number =
+                    atoi(furi_string_get_cstr(station_num_candidate));
+                furi_string_set(
+                    model->history.exit_station.jr_header, station_JR_header_candidate);
+                break;
+            }
+        }
+        break;
+    default:
+        UNUSED(model);
+        break;
+    }
+
+    furi_string_free(line);
+    furi_string_free(line_copy);
+    furi_string_free(line_code_str);
+    furi_string_free(line_and_station_code_str);
+    furi_string_free(line_candidate);
+    furi_string_free(station_candidate);
+    furi_string_free(station_num_candidate);
+    furi_string_free(station_JR_header_candidate);
+    file_stream_close(stream);
+    stream_free(stream);
+    furi_record_close(RECORD_STORAGE);
+}
+
+static void suica_parse(SuicaHistoryViewModel* my_model) {
+    uint8_t current_block[FELICA_DATA_BLOCK_SIZE];
+    // Parse the current block/entry
+    for(size_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) {
+        current_block[i] = my_model->travel_history[((my_model->entry - 1) * 16) + i];
+    }
+
+    if(((uint8_t)current_block[4] + (uint8_t)current_block[5]) != 0) {
+        my_model->history.year = ((uint8_t)current_block[4] & 0xFE) >> 1;
+        my_model->history.month = (((uint8_t)current_block[4] & 0x01) << 3) |
+                                  (((uint8_t)current_block[5] & 0xE0) >> 5);
+        my_model->history.day = (uint8_t)current_block[5] & 0x1F;
+    } else {
+        my_model->history.year = 0;
+        my_model->history.month = 0;
+        my_model->history.day = 0;
+    }
+    my_model->history.balance = ((uint16_t)current_block[11] << 8) | (uint16_t)current_block[10];
+    // FURI_LOG_I(TAG,"%02X", (uint8_t)current_block[0]);
+    my_model->history.area_code = current_block[15];
+    if((uint8_t)current_block[0] >= TERMINAL_TICKET_VENDING_MACHINE &&
+       (uint8_t)current_block[0] <= TERMINAL_IN_CAR_SUPP_MACHINE) {
+        // Train rides
+        // Will be overwritton is is ticket sale (TERMINAL_TICKET_VENDING_MACHINE)
+        my_model->history.history_type = SuicaHistoryTrain;
+        uint8_t entry_line = current_block[6];
+        uint8_t entry_station = current_block[7];
+        uint8_t exit_line = current_block[8];
+        uint8_t exit_station = current_block[9];
+
+        if((uint8_t)current_block[0] != TERMINAL_MOBILE_PHONE) {
+            suica_parse_train_code(entry_line, entry_station, SuicaTrainRideEntry, my_model);
+        }
+        if((uint8_t)current_block[1] != PROCESSING_CODE_NEW_ISSUE) {
+            suica_parse_train_code(exit_line, exit_station, SuicaTrainRideExit, my_model);
+        }
+
+        if(((uint8_t)current_block[4] + (uint8_t)current_block[5]) != 0) {
+            my_model->history.year = ((uint8_t)current_block[4] & 0xFE) >> 1;
+            my_model->history.month = (((uint8_t)current_block[4] & 0x01) << 3) |
+                                      (((uint8_t)current_block[5] & 0xE0) >> 5);
+            my_model->history.day = (uint8_t)current_block[5] & 0x1F;
+        }
+    }
+    switch((uint8_t)current_block[0]) {
+    case TERMINAL_BUS:
+        // 6 & 7 bus line code
+        // 8 & 9 bus stop code
+        my_model->history.history_type = SuicaHistoryBus;
+        break;
+    case TERMINAL_POS_AND_TAXI:
+    case TERMINAL_VENDING_MACHINE:
+        // 6 & 7 are hour and minute
+        my_model->history.history_type = ((uint8_t)current_block[0] == TERMINAL_POS_AND_TAXI) ?
+                                             SuicaHistoryPosAndTaxi :
+                                             SuicaHistoryVendingMachine;
+        my_model->history.hour = ((uint8_t)current_block[6] & 0xF8) >> 3;
+        my_model->history.minute = (((uint8_t)current_block[6] & 0x07) << 3) |
+                                   (((uint8_t)current_block[7] & 0xE0) >> 5);
+        my_model->history.shop_code = (uint8_t*)malloc(2);
+        my_model->history.shop_code[0] = current_block[8];
+        my_model->history.shop_code[1] = current_block[9];
+        break;
+    case TERMINAL_MOBILE_PHONE:
+        if((uint8_t)current_block[1] == PROCESSING_CODE_NEW_ISSUE) {
+            my_model->history.hour = ((uint8_t)current_block[6] & 0xF8) >> 3;
+            my_model->history.minute = (((uint8_t)current_block[6] & 0x07) << 3) |
+                                       (((uint8_t)current_block[7] & 0xE0) >> 5);
+        }
+        break;
+    case TERMINAL_TICKET_VENDING_MACHINE:
+        my_model->history.history_type = SuicaHistoryHappyBirthday;
+        break;
+    default:
+        if((uint8_t)current_block[0] <= TERMINAL_NULL) {
+            my_model->history.history_type = SuicaHistoryNull;
+        }
+        break;
+    }
+    if((uint8_t)current_block[1] == PROCESSING_CODE_NEW_ISSUE) {
+        my_model->history.history_type = SuicaHistoryHappyBirthday;
+    }
+}
+
+static void suica_draw_train_page_1(
+    Canvas* canvas,
+    SuicaHistory history,
+    SuicaHistoryViewModel* model,
+    bool is_birthday) {
+    // Entry logo
+    switch(history.entry_line.type) {
+    case SuicaKeikyu:
+        canvas_draw_icon(canvas, 2, 11, &I_Suica_KeikyuLogo);
+        break;
+    case SuicaJR:
+        canvas_draw_icon(canvas, 1, 12, &I_Suica_JRLogo);
+        break;
+    case SuicaTokyoMetro:
+        canvas_draw_icon(canvas, 2, 12, &I_Suica_TokyoMetroLogo);
+        break;
+    case SuicaToei:
+        canvas_draw_icon(canvas, 4, 11, &I_Suica_ToeiLogo);
+        break;
+    case SuicaTWR:
+        canvas_draw_icon(canvas, 0, 12, &I_Suica_TWRLogo);
+        break;
+    case SuicaTokyoMonorail:
+        canvas_draw_icon(canvas, 0, 11, &I_Suica_TokyoMonorailLogo);
+        break;
+    case SuicaRailwayTypeMax:
+        canvas_draw_icon(canvas, 5, 11, &I_Suica_QuestionMarkSmall);
+        break;
+    default:
+        break;
+    }
+
+    // Entry Text
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 26, 23, history.entry_line.long_name);
+
+    canvas_set_font(canvas, FontSecondary);
+    canvas_draw_str(canvas, 2, 34, furi_string_get_cstr(history.entry_station.name));
+
+    if(!is_birthday) {
+        // Exit logo
+        switch(history.exit_line.type) {
+        case SuicaKeikyu:
+            canvas_draw_icon(canvas, 2, 39, &I_Suica_KeikyuLogo);
+            break;
+        case SuicaJR:
+            canvas_draw_icon(canvas, 1, 40, &I_Suica_JRLogo);
+            break;
+        case SuicaTokyoMetro:
+            canvas_draw_icon(canvas, 2, 40, &I_Suica_TokyoMetroLogo);
+            break;
+        case SuicaToei:
+            canvas_draw_icon(canvas, 4, 39, &I_Suica_ToeiLogo);
+            break;
+        case SuicaTWR:
+            canvas_draw_icon(canvas, 0, 40, &I_Suica_TWRLogo);
+            break;
+        case SuicaTokyoMonorail:
+            canvas_draw_icon(canvas, 0, 39, &I_Suica_TokyoMonorailLogo);
+            break;
+        case SuicaRailwayTypeMax:
+            canvas_draw_icon(canvas, 5, 39, &I_Suica_QuestionMarkSmall);
+            break;
+        default:
+            break;
+        }
+
+        // Exit Text
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str(canvas, 26, 51, history.exit_line.long_name);
+
+        canvas_set_font(canvas, FontSecondary);
+        canvas_draw_str(canvas, 2, 62, furi_string_get_cstr(history.exit_station.name));
+    } else {
+        // Birthday
+        canvas_draw_icon(canvas, 5, 42, &I_Suica_CrackingEgg);
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str(canvas, 28, 56, "Suica issued");
+    }
+
+    // Separator
+    canvas_draw_icon(canvas, 0, 37, &I_Suica_DashLine);
+
+    // Arrow
+    uint8_t arrow_bits[4] = {0b1000, 0b0100, 0b0010, 0b0001};
+
+    // Arrow
+    if(model->animator_tick > 3) {
+        // 4 steps of animation
+        model->animator_tick = 0;
+    }
+    uint8_t current_arrow_bits = arrow_bits[model->animator_tick];
+    canvas_draw_icon(
+        canvas,
+        110,
+        19,
+        (current_arrow_bits & 0b1000) ? &I_Suica_FilledArrowDown : &I_Suica_EmptyArrowDown);
+    canvas_draw_icon(
+        canvas,
+        110,
+        29,
+        (current_arrow_bits & 0b0100) ? &I_Suica_FilledArrowDown : &I_Suica_EmptyArrowDown);
+    canvas_draw_icon(
+        canvas,
+        110,
+        39,
+        (current_arrow_bits & 0b0010) ? &I_Suica_FilledArrowDown : &I_Suica_EmptyArrowDown);
+    canvas_draw_icon(
+        canvas,
+        110,
+        49,
+        (current_arrow_bits & 0b0001) ? &I_Suica_FilledArrowDown : &I_Suica_EmptyArrowDown);
+}
+
+static void
+    suica_draw_train_page_2(Canvas* canvas, SuicaHistory history, SuicaHistoryViewModel* model) {
+    FuriString* buffer = furi_string_alloc();
+
+    // Entry
+    switch(history.entry_line.type) {
+    case SuicaKeikyu:
+        canvas_draw_disc(canvas, 24, 38, 24);
+        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_disc(canvas, 24, 38, 21);
+        canvas_set_color(canvas, ColorBlack);
+        canvas_set_font(canvas, FontKeyboard);
+        canvas_draw_icon(canvas, 16, 24, history.entry_line.logo_icon);
+        canvas_set_font(canvas, FontBigNumbers);
+        furi_string_printf(buffer, "%02d", history.entry_station.station_number);
+        canvas_draw_str(canvas, 13, 51, furi_string_get_cstr(buffer));
+        break;
+    case SuicaTokyoMonorail:
+        canvas_draw_rbox(canvas, 9, 23, 32, 32, 5);
+        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_box(canvas, 12, 26, 26, 26);
+        canvas_set_color(canvas, ColorBlack);
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(
+            canvas, 25, 38, AlignCenter, AlignBottom, history.entry_line.short_name);
+        canvas_draw_str(canvas, 17, 36, history.entry_line.short_name);
+        canvas_set_font(canvas, FontBigNumbers);
+        furi_string_printf(buffer, "%02d", history.entry_station.station_number);
+        canvas_draw_str(canvas, 14, 51, furi_string_get_cstr(buffer));
+        break;
+    case SuicaJR:
+        if(!furi_string_equal_str(history.entry_station.jr_header, "0")) {
+            canvas_draw_rbox(canvas, 6, 14, 38, 48, 7);
+            canvas_set_color(canvas, ColorWhite);
+            canvas_set_font(canvas, FontPrimary);
+            canvas_draw_str_aligned(
+                canvas,
+                25,
+                24,
+                AlignCenter,
+                AlignBottom,
+                furi_string_get_cstr(history.entry_station.jr_header));
+            canvas_draw_rbox(canvas, 9, 26, 32, 32, 5);
+            canvas_set_color(canvas, ColorBlack);
+            canvas_draw_frame(canvas, 12, 29, 26, 26);
+            canvas_set_font(canvas, FontKeyboard);
+            canvas_draw_str_aligned(
+                canvas, 25, 38, AlignCenter, AlignBottom, history.entry_line.short_name);
+            canvas_set_font(canvas, FontBigNumbers);
+            furi_string_printf(buffer, "%02d", history.entry_station.station_number);
+            canvas_draw_str(canvas, 14, 53, furi_string_get_cstr(buffer));
+        } else {
+            canvas_draw_rframe(canvas, 9, 23, 32, 32, 5);
+            canvas_draw_frame(canvas, 12, 26, 26, 26);
+            canvas_set_font(canvas, FontKeyboard);
+            canvas_draw_str_aligned(
+                canvas, 25, 35, AlignCenter, AlignBottom, history.entry_line.short_name);
+            canvas_set_font(canvas, FontBigNumbers);
+            furi_string_printf(buffer, "%02d", history.entry_station.station_number);
+            canvas_draw_str(canvas, 14, 50, furi_string_get_cstr(buffer));
+        }
+        break;
+    case SuicaTokyoMetro:
+    case SuicaToei:
+        canvas_draw_disc(canvas, 24, 38, 24);
+        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_disc(canvas, 24, 38, 19);
+        canvas_set_color(canvas, ColorBlack);
+        canvas_set_font(canvas, FontBigNumbers);
+        canvas_draw_icon(
+            canvas,
+            17 + history.entry_line.logo_offset[0],
+            22 + history.entry_line.logo_offset[1],
+            history.entry_line.logo_icon);
+        furi_string_printf(buffer, "%02d", history.entry_station.station_number);
+        canvas_draw_str(canvas, 13, 53, furi_string_get_cstr(buffer));
+        break;
+    case SuicaTWR:
+        canvas_draw_circle(canvas, 24, 38, 24);
+        canvas_draw_circle(canvas, 24, 38, 20);
+        canvas_draw_disc(canvas, 24, 38, 18);
+        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_icon(canvas, 20, 23, history.entry_line.logo_icon);
+        canvas_set_font(canvas, FontBigNumbers);
+        furi_string_printf(buffer, "%02d", history.entry_station.station_number);
+        canvas_draw_str(canvas, 13, 53, furi_string_get_cstr(buffer));
+        canvas_set_color(canvas, ColorBlack);
+        break;
+    case SuicaRailwayTypeMax:
+        canvas_draw_circle(canvas, 24, 38, 24);
+        canvas_draw_circle(canvas, 24, 38, 19);
+        canvas_draw_icon(canvas, 14, 22, &I_Suica_QuestionMarkBig);
+        break;
+    default:
+        break;
+    }
+
+    // Exit
+    switch(history.exit_line.type) {
+    case SuicaKeikyu:
+        canvas_draw_disc(canvas, 103, 38, 24);
+        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_disc(canvas, 103, 38, 21);
+        canvas_set_color(canvas, ColorBlack);
+        canvas_draw_icon(canvas, 95, 24, history.exit_line.logo_icon);
+        canvas_set_font(canvas, FontBigNumbers);
+        furi_string_printf(buffer, "%02d", history.exit_station.station_number);
+        canvas_draw_str(canvas, 92, 52, furi_string_get_cstr(buffer));
+        break;
+    case SuicaTokyoMonorail:
+        canvas_draw_rbox(canvas, 86, 23, 32, 32, 5);
+        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_box(canvas, 89, 26, 26, 26);
+        canvas_set_color(canvas, ColorBlack);
+        canvas_set_font(canvas, FontPrimary);
+        canvas_draw_str_aligned(
+            canvas, 101, 35, AlignCenter, AlignBottom, history.exit_line.short_name);
+        canvas_set_font(canvas, FontBigNumbers);
+        furi_string_printf(buffer, "%02d", history.exit_station.station_number);
+        canvas_draw_str(canvas, 91, 51, furi_string_get_cstr(buffer));
+        break;
+    case SuicaJR:
+        if(!furi_string_equal_str(history.exit_station.jr_header, "0")) {
+            canvas_draw_rbox(canvas, 83, 14, 38, 48, 7);
+            canvas_set_color(canvas, ColorWhite);
+            canvas_set_font(canvas, FontPrimary);
+            canvas_draw_str_aligned(
+                canvas,
+                101,
+                24,
+                AlignCenter,
+                AlignBottom,
+                furi_string_get_cstr(history.exit_station.jr_header));
+            canvas_draw_rbox(canvas, 86, 26, 32, 32, 5);
+            canvas_set_color(canvas, ColorBlack);
+            canvas_draw_frame(canvas, 89, 29, 26, 26);
+            canvas_set_font(canvas, FontKeyboard);
+            canvas_draw_str_aligned(
+                canvas, 102, 38, AlignCenter, AlignBottom, history.exit_line.short_name);
+            canvas_set_font(canvas, FontBigNumbers);
+            furi_string_printf(buffer, "%02d", history.exit_station.station_number);
+            canvas_draw_str(canvas, 91, 53, furi_string_get_cstr(buffer));
+        } else {
+            canvas_draw_rframe(canvas, 86, 23, 32, 32, 5);
+            canvas_draw_frame(canvas, 89, 26, 26, 26);
+            canvas_set_font(canvas, FontKeyboard);
+            canvas_draw_str_aligned(
+                canvas, 102, 35, AlignCenter, AlignBottom, history.exit_line.short_name);
+            canvas_set_font(canvas, FontBigNumbers);
+            furi_string_printf(buffer, "%02d", history.exit_station.station_number);
+            canvas_draw_str(canvas, 91, 50, furi_string_get_cstr(buffer));
+        }
+        break;
+    case SuicaTokyoMetro:
+    case SuicaToei:
+        canvas_draw_disc(canvas, 103, 38, 24);
+        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_disc(canvas, 103, 38, 19);
+        canvas_set_color(canvas, ColorBlack);
+        canvas_draw_icon(
+            canvas,
+            96 + history.exit_line.logo_offset[0],
+            22 + history.exit_line.logo_offset[1],
+            history.exit_line.logo_icon);
+        canvas_set_font(canvas, FontBigNumbers);
+        furi_string_printf(buffer, "%02d", history.exit_station.station_number);
+        canvas_draw_str(canvas, 92, 53, furi_string_get_cstr(buffer));
+        break;
+    case SuicaTWR:
+        canvas_draw_circle(canvas, 103, 38, 24);
+        canvas_draw_circle(canvas, 103, 38, 20);
+        canvas_draw_disc(canvas, 103, 38, 18);
+        canvas_set_color(canvas, ColorWhite);
+        canvas_draw_icon(canvas, 99, 23, history.exit_line.logo_icon);
+        canvas_set_font(canvas, FontBigNumbers);
+        furi_string_printf(buffer, "%02d", history.exit_station.station_number);
+        canvas_draw_str(canvas, 92, 53, furi_string_get_cstr(buffer));
+        canvas_set_color(canvas, ColorBlack);
+        break;
+    case SuicaRailwayTypeMax:
+        canvas_draw_circle(canvas, 103, 38, 24);
+        canvas_draw_circle(canvas, 103, 38, 19);
+        canvas_draw_icon(canvas, 93, 22, &I_Suica_QuestionMarkBig);
+    default:
+        break;
+    }
+
+    uint8_t arrow_bits[3] = {0b100, 0b010, 0b001};
+
+    // Arrow
+    if(model->animator_tick > 2) {
+        // 4 steps of animation
+        model->animator_tick = 0;
+    }
+    uint8_t current_arrow_bits = arrow_bits[model->animator_tick];
+    canvas_draw_icon(
+        canvas,
+        51,
+        32,
+        (current_arrow_bits & 0b100) ? &I_Suica_FilledArrowRight : &I_Suica_EmptyArrowRight);
+    canvas_draw_icon(
+        canvas,
+        59,
+        32,
+        (current_arrow_bits & 0b010) ? &I_Suica_FilledArrowRight : &I_Suica_EmptyArrowRight);
+    canvas_draw_icon(
+        canvas,
+        67,
+        32,
+        (current_arrow_bits & 0b001) ? &I_Suica_FilledArrowRight : &I_Suica_EmptyArrowRight);
+
+    furi_string_free(buffer);
+}
+
+static void
+    suica_draw_birthday_page_2(Canvas* canvas, SuicaHistory history, SuicaHistoryViewModel* model) {
+    UNUSED(history);
+    canvas_draw_icon(canvas, 27, 14, &I_Suica_PenguinHappyBirthday);
+    canvas_draw_icon(canvas, 14, 14, &I_Suica_PenguinTodaysVIP);
+    canvas_draw_rframe(canvas, 12, 12, 13, 52, 2); // VIP frame
+    uint8_t star_bits[4] = {0b11000000, 0b11110000, 0b11111111, 0b00000000};
+
+    // Arrow
+    if(model->animator_tick > 3) {
+        // 4 steps of animation
+        model->animator_tick = 0;
+    }
+    uint8_t current_star_bits = star_bits[model->animator_tick];
+    canvas_draw_icon(
+        canvas, 87, 30, (current_star_bits & 0b10000000) ? &I_Suica_BigStar : &I_Suica_Nothing);
+    canvas_draw_icon(
+        canvas, 90, 12, (current_star_bits & 0b01000000) ? &I_Suica_PlusStar : &I_Suica_Nothing);
+    canvas_draw_icon(
+        canvas, 99, 34, (current_star_bits & 0b00100000) ? &I_Suica_SmallStar : &I_Suica_Nothing);
+    canvas_draw_icon(
+        canvas, 103, 12, (current_star_bits & 0b00010000) ? &I_Suica_SmallStar : &I_Suica_Nothing);
+    canvas_draw_icon(
+        canvas, 106, 21, (current_star_bits & 0b00001000) ? &I_Suica_BigStar : &I_Suica_Nothing);
+    canvas_draw_icon(
+        canvas, 109, 43, (current_star_bits & 0b00000100) ? &I_Suica_PlusStar : &I_Suica_Nothing);
+    canvas_draw_icon(
+        canvas, 117, 28, (current_star_bits & 0b00000010) ? &I_Suica_BigStar : &I_Suica_Nothing);
+    canvas_draw_icon(
+        canvas, 115, 16, (current_star_bits & 0b00000100) ? &I_Suica_PlusStar : &I_Suica_Nothing);
+}
+
+static void suica_draw_vending_machine_page_1(
+    Canvas* canvas,
+    SuicaHistory history,
+    SuicaHistoryViewModel* model) {
+    FuriString* buffer = furi_string_alloc();
+    canvas_draw_icon(canvas, 0, 10, &I_Suica_VendingPage2Full);
+    furi_string_printf(buffer, "%d", history.balance_change);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(
+        canvas, 100, 39, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
+
+    // Animate Bubbles and LCD Refresh
+    if(model->animator_tick > 14) {
+        // 14 steps of animation
+        model->animator_tick = 0;
+    }
+    canvas_set_color(canvas, ColorWhite);
+    canvas_draw_line(canvas, 87, 50 + model->animator_tick, 128, 50 + model->animator_tick);
+    switch(model->animator_tick % 7) {
+    case 0:
+        canvas_draw_circle(canvas, 12, 48, 1);
+        canvas_draw_circle(canvas, 23, 39, 2);
+        break;
+    case 1:
+        canvas_draw_circle(canvas, 11, 46, 1);
+        canvas_draw_circle(canvas, 23, 39, 2);
+        canvas_set_color(canvas, ColorBlack);
+        canvas_draw_line(canvas, 24, 37, 22, 37);
+        canvas_draw_line(canvas, 25, 40, 25, 38);
+        canvas_set_color(canvas, ColorWhite);
+        break;
+    case 2:
+        canvas_draw_circle(canvas, 12, 44, 1);
+        canvas_draw_circle(canvas, 24, 50, 1);
+        break;
+    case 3:
+        canvas_draw_icon(canvas, 12, 41, &I_Suica_SmallStar);
+        canvas_draw_circle(canvas, 25, 48, 1);
+        break;
+    case 4:
+        canvas_draw_icon(canvas, 14, 39, &I_Suica_SmallStar);
+        canvas_draw_circle(canvas, 26, 46, 1);
+        break;
+    case 5:
+        canvas_draw_icon(canvas, 24, 43, &I_Suica_SmallStar);
+        canvas_draw_circle(canvas, 16, 38, 2);
+        break;
+    case 6:
+        canvas_draw_icon(canvas, 23, 41, &I_Suica_SmallStar);
+        canvas_draw_circle(canvas, 16, 38, 2);
+        canvas_set_color(canvas, ColorBlack);
+        canvas_draw_line(canvas, 15, 36, 17, 36);
+        canvas_draw_line(canvas, 18, 39, 18, 37);
+        canvas_set_color(canvas, ColorWhite);
+        break;
+    default:
+        break;
+    }
+    furi_string_free(buffer);
+    canvas_set_color(canvas, ColorBlack);
+}
+
+static void suica_draw_vending_machine_page_2(
+    Canvas* canvas,
+    SuicaHistory history,
+    SuicaHistoryViewModel* model) {
+    FuriString* buffer = furi_string_alloc();
+
+    // Clock Component
+    canvas_set_color(canvas, ColorWhite); // Erase part of old frame to allow for new frame
+    canvas_draw_line(canvas, 91, 9, 94, 6);
+    canvas_draw_line(canvas, 57, 9, 93, 9);
+    canvas_set_color(canvas, ColorBlack);
+    furi_string_printf(buffer, "%02d:%02d", history.hour, history.minute);
+    canvas_draw_line(canvas, 63, 21, 60, 18);
+    canvas_set_font(canvas, FontKeyboard);
+    canvas_draw_str(canvas, 63, 19, furi_string_get_cstr(buffer));
+    canvas_draw_line(canvas, 91, 21, 94, 18);
+    canvas_draw_line(canvas, 64, 21, 91, 21);
+    canvas_draw_line(canvas, 94, 6, 94, 17);
+    canvas_draw_line(canvas, 60, 12, 60, 17);
+    canvas_draw_line(canvas, 60, 12, 57, 9);
+
+    // Vending Machine
+    canvas_draw_icon(canvas, 5, 12, &I_Suica_VendingMachine);
+
+    // Machine Code
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 75, 35, "Machine");
+    canvas_draw_icon(canvas, 119, 25, &I_Suica_ShopPin);
+    furi_string_printf(
+        buffer, "%01d:%03d:%03d", history.area_code, history.shop_code[0], history.shop_code[1]);
+    canvas_set_font(canvas, FontKeyboard);
+    canvas_draw_str(canvas, 75, 45, furi_string_get_cstr(buffer));
+
+    // Animate Vending Machine Flap
+    if(model->animator_tick > 6) {
+        // 6 steps of animation
+        model->animator_tick = 0;
+    }
+    switch(model->animator_tick) {
+    case 0:
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap1);
+        break;
+    case 1:
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap2);
+        break;
+    case 2:
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap3);
+        break;
+    case 3:
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap3);
+        canvas_draw_icon(canvas, 59, 45, &I_Suica_VendingCan1);
+        break;
+    case 4:
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap2);
+        canvas_draw_icon(canvas, 74, 48, &I_Suica_VendingCan2);
+        break;
+    case 5:
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap1);
+        canvas_draw_icon(canvas, 89, 51, &I_Suica_VendingCan3);
+        break;
+    case 6:
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlapHollow);
+        canvas_draw_icon(canvas, 44, 40, &I_Suica_VendingFlap1);
+        canvas_draw_icon(canvas, 110, 54, &I_Suica_VendingCan4);
+        break;
+    default:
+        break;
+    }
+    furi_string_free(buffer);
+}
+
+static void
+    suica_draw_store_page_1(Canvas* canvas, SuicaHistory history, SuicaHistoryViewModel* model) {
+    FuriString* buffer = furi_string_alloc();
+    furi_string_printf(buffer, "%d", history.balance_change);
+    canvas_draw_icon(canvas, 0, 15, &I_Suica_StoreP1Counter);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str_aligned(canvas, 99, 39, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
+    canvas_draw_icon(canvas, 59, 27, &I_Suica_StoreReceiptDashLine);
+    // Animate Taxi and LCD Refresh
+    if(model->animator_tick > 11) {
+        // 14 steps of animation
+        model->animator_tick = 0;
+    }
+
+    switch(model->animator_tick % 6) {
+    case 0:
+    case 1:
+    case 2:
+        canvas_draw_icon(canvas, 41, 18, &I_Suica_StoreReceiptFrame1);
+        break;
+    case 3:
+    case 4:
+    case 5:
+        canvas_draw_icon(canvas, 41, 18, &I_Suica_StoreReceiptFrame2);
+        break;
+    default:
+        break;
+    }
+
+    switch(model->animator_tick % 6) {
+    case 0:
+    case 1:
+        canvas_draw_icon(canvas, 0, 24, &I_Suica_StoreLightningVertical);
+        break;
+    case 2:
+    case 3:
+        canvas_draw_icon(canvas, 3, 31, &I_Suica_StoreLightningHorizontal);
+        break;
+    case 4:
+    case 5:
+        canvas_draw_icon(canvas, 0, 24, &I_Suica_StoreLightningVertical);
+        canvas_draw_icon(canvas, 3, 31, &I_Suica_StoreLightningHorizontal);
+        break;
+    default:
+        break;
+    }
+    furi_string_free(buffer);
+}
+
+static void
+    suica_draw_store_page_2(Canvas* canvas, SuicaHistory history, SuicaHistoryViewModel* model) {
+    FuriString* buffer = furi_string_alloc();
+    // Clock Component
+    canvas_set_color(canvas, ColorWhite); // Erase part of old frame to allow for new frame
+    canvas_draw_line(canvas, 91, 9, 94, 6);
+    canvas_draw_line(canvas, 57, 9, 93, 9);
+    canvas_set_color(canvas, ColorBlack);
+    furi_string_printf(buffer, "%02d:%02d", history.hour, history.minute);
+    canvas_draw_line(canvas, 63, 21, 60, 18);
+    canvas_set_font(canvas, FontKeyboard);
+    canvas_draw_str(canvas, 63, 19, furi_string_get_cstr(buffer));
+    canvas_draw_line(canvas, 91, 21, 94, 18);
+    canvas_draw_line(canvas, 64, 21, 91, 21);
+    canvas_draw_line(canvas, 94, 6, 94, 17);
+    canvas_draw_line(canvas, 60, 12, 60, 17);
+    canvas_draw_line(canvas, 60, 12, 57, 9);
+
+    // Machine Code
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 75, 35, "Store");
+    canvas_draw_icon(canvas, 104, 25, &I_Suica_ShopPin);
+    furi_string_printf(
+        buffer, "%01d:%03d:%03d", history.area_code, history.shop_code[0], history.shop_code[1]);
+    canvas_set_font(canvas, FontKeyboard);
+    canvas_draw_str(canvas, 75, 45, furi_string_get_cstr(buffer));
+
+    // Store Frame
+    canvas_draw_icon(canvas, 0, 13, &I_Suica_StoreFrame);
+    // Sliding Door
+    uint8_t door_position[7] = {20, 18, 14, 6, 2, 0, 0};
+    if(model->animator_tick > 20) {
+        // 14 steps of animation
+        model->animator_tick = 0;
+    }
+
+    if(model->animator_tick < 7) {
+        canvas_draw_icon(
+            canvas, -1 - door_position[6 - model->animator_tick], 28, &I_Suica_StoreSlidingDoor);
+    } else if(model->animator_tick < 14) {
+        canvas_draw_icon(
+            canvas, -1 - door_position[model->animator_tick - 7], 28, &I_Suica_StoreSlidingDoor);
+    } else {
+        canvas_draw_icon(canvas, -1, 28, &I_Suica_StoreSlidingDoor);
+    }
+
+    // Animate Neon and Fan
+    switch(model->animator_tick % 4) {
+    case 0:
+    case 1:
+        canvas_draw_icon(canvas, 37, 18, &I_Suica_StoreFan1);
+        break;
+    case 2:
+    case 3:
+        canvas_draw_icon(canvas, 37, 18, &I_Suica_StoreFan2);
+        break;
+    default:
+        break;
+    }
+    furi_string_free(buffer);
+}
+
+static void
+    suica_draw_balance_page(Canvas* canvas, SuicaHistory history, SuicaHistoryViewModel* model) {
+    FuriString* buffer = furi_string_alloc();
+
+    // Balance
+    canvas_set_font(canvas, FontBigNumbers);
+    canvas_draw_icon(canvas, 0, 48, &I_Suica_YenSign);
+    canvas_draw_icon(canvas, 111, 48, &I_Suica_YenKanji);
+
+    furi_string_printf(buffer, "%d", history.balance);
+    canvas_draw_str_aligned(
+        canvas, 109, 64, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
+
+    furi_string_printf(buffer, "%d", history.previous_balance);
+    canvas_draw_str_aligned(
+        canvas, 109, 26, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
+
+    furi_string_printf(buffer, "%d", history.balance_change);
+    canvas_draw_str_aligned(
+        canvas, 109, 43, AlignRight, AlignBottom, furi_string_get_cstr(buffer));
+
+    // Separator
+    canvas_draw_line(canvas, 26, 45, 128, 45);
+    canvas_draw_line(canvas, 26, 46, 128, 46);
+
+    if(history.balance_sign == SuicaBalanceAdd) {
+        // Animate plus sign
+        if(model->animator_tick > 2) {
+            // 9 steps of animation
+            model->animator_tick = 0;
+        }
+        switch(model->animator_tick) {
+        case 0:
+            canvas_draw_icon(canvas, 28, 28, &I_Suica_PlusSign1);
+            break;
+        case 1:
+            canvas_draw_icon(canvas, 27, 27, &I_Suica_PlusSign2);
+            break;
+        case 2:
+            canvas_draw_icon(canvas, 26, 26, &I_Suica_PlusSign3);
+            break;
+        default:
+            break;
+        }
+    } else if(history.balance_sign == SuicaBalanceSub) {
+        // Animate plus sign
+        if(model->animator_tick > 12) {
+            // 9 steps of animation
+            model->animator_tick = 0;
+        }
+        switch(model->animator_tick) {
+        case 0:
+        case 1:
+        case 2:
+        case 3:
+            canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign0);
+            break;
+        case 4:
+            canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign1);
+            break;
+        case 5:
+            canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign2);
+            break;
+        case 6:
+            canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign3);
+            break;
+        case 7:
+            canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign4);
+            break;
+        case 8:
+            canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign5);
+            break;
+        case 9:
+            canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign6);
+            break;
+        case 10:
+            canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign7);
+            break;
+        case 11:
+            canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign8);
+            break;
+        case 12:
+            canvas_draw_icon(canvas, 28, 32, &I_Suica_MinusSign9);
+            break;
+        default:
+            break;
+        }
+    } else {
+        canvas_draw_str(canvas, 30, 28, "=");
+    }
+}
+
+static void suica_history_draw_callback(Canvas* canvas, void* model) {
+    canvas_set_bitmap_mode(canvas, true);
+    SuicaHistoryViewModel* my_model = (SuicaHistoryViewModel*)model;
+    FuriString* buffer = furi_string_alloc();
+    // catch the case where the page and entry are not initialized
+
+    if(my_model->entry > my_model->size || my_model->entry < 1) {
+        my_model->entry = 1;
+    }
+
+    // Get previous balance if we are not at the earliest entry
+    if(my_model->entry < my_model->size) {
+        my_model->history.previous_balance = my_model->travel_history[(my_model->entry * 16) + 10];
+        my_model->history.previous_balance |= my_model->travel_history[(my_model->entry * 16) + 11]
+                                              << 8;
+    } else {
+        my_model->history.previous_balance = 0;
+    }
+    // Calculate balance change
+    if(my_model->history.previous_balance < my_model->history.balance) {
+        my_model->history.balance_change =
+            my_model->history.balance - my_model->history.previous_balance;
+        my_model->history.balance_sign = SuicaBalanceAdd;
+    } else if(my_model->history.previous_balance > my_model->history.balance) {
+        my_model->history.balance_change =
+            my_model->history.previous_balance - my_model->history.balance;
+        my_model->history.balance_sign = SuicaBalanceSub;
+    } else {
+        my_model->history.balance_change = 0;
+        my_model->history.balance_sign = SuicaBalanceEqual;
+    }
+
+    // Main title
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 0, 8, "Suica");
+
+    // Date
+    furi_string_printf(
+        buffer,
+        "20%02d-%02d-%02d",
+        my_model->history.year,
+        my_model->history.month,
+        my_model->history.day);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 34, 8, furi_string_get_cstr(buffer));
+
+    // Entry Num
+    furi_string_printf(buffer, "%02d/%02d", my_model->entry, my_model->size);
+    canvas_set_font(canvas, FontPrimary);
+    canvas_draw_str(canvas, 99, 8, furi_string_get_cstr(buffer));
+
+    // Frame
+    canvas_draw_line(canvas, 0, 9, 26, 9);
+    canvas_draw_line(canvas, 27, 9, 29, 7);
+    canvas_draw_line(canvas, 29, 0, 29, 6);
+
+    canvas_draw_line(canvas, 31, 0, 31, 7);
+    canvas_draw_line(canvas, 33, 9, 31, 7);
+    canvas_draw_line(canvas, 90, 9, 34, 9);
+    canvas_draw_line(canvas, 91, 9, 94, 6);
+    canvas_draw_line(canvas, 94, 0, 94, 6);
+
+    canvas_draw_line(canvas, 96, 0, 96, 6);
+    canvas_draw_line(canvas, 99, 9, 96, 6);
+    canvas_draw_line(canvas, 100, 9, 128, 9);
+
+    switch((uint8_t)my_model->page) {
+    case 0:
+        switch(my_model->history.history_type) {
+        case SuicaHistoryTrain:
+            suica_draw_train_page_1(canvas, my_model->history, my_model, false);
+            break;
+        case SuicaHistoryHappyBirthday:
+            suica_draw_train_page_1(canvas, my_model->history, my_model, true);
+            break;
+        case SuicaHistoryVendingMachine:
+            suica_draw_vending_machine_page_1(canvas, my_model->history, my_model);
+            break;
+        case SuicaHistoryPosAndTaxi:
+            suica_draw_store_page_1(canvas, my_model->history, my_model);
+            break;
+        default:
+            break;
+        }
+        break;
+    case 1:
+        switch(my_model->history.history_type) {
+        case SuicaHistoryTrain:
+            suica_draw_train_page_2(canvas, my_model->history, my_model);
+            break;
+        case SuicaHistoryHappyBirthday:
+            suica_draw_birthday_page_2(canvas, my_model->history, my_model);
+            break;
+        case SuicaHistoryVendingMachine:
+            suica_draw_vending_machine_page_2(canvas, my_model->history, my_model);
+            break;
+        case SuicaHistoryPosAndTaxi:
+            suica_draw_store_page_2(canvas, my_model->history, my_model);
+            break;
+        default:
+            break;
+        }
+        break;
+    case 2:
+        suica_draw_balance_page(canvas, my_model->history, my_model);
+        break;
+    default:
+        break;
+    }
+    furi_string_free(buffer);
+}
+
+static void suica_parse_detail_callback(GuiButtonType result, InputType type, void* context) {
+    Metroflip* app = context;
+    UNUSED(result);
+    if(type == InputTypeShort) {
+        SuicaHistoryViewModel* my_model = view_get_model(app->suica_context->view_history);
+        suica_parse(my_model);
+        FURI_LOG_I(TAG, "Draw Callback: We have %d entries", my_model->size);
+        view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewCanvas);
+    }
+}
+
+static uint32_t suica_navigation_raw_callback(void* _context) {
+    UNUSED(_context);
+    return MetroflipViewWidget;
+}
+
+static NfcCommand suica_poller_callback(NfcGenericEvent event, void* context) {
+    furi_assert(event.protocol == NfcProtocolFelica);
+    NfcCommand command = NfcCommandContinue;
+    MetroflipPollerEventType stage = MetroflipPollerEventTypeStart;
+
+    Metroflip* app = context;
+    FuriString* parsed_data = furi_string_alloc();
+    SuicaHistoryViewModel* model = view_get_model(app->suica_context->view_history);
+
+    Widget* widget = app->widget;
+
+    const uint16_t service_code[2] = {SERVICE_CODE_HISTORY_IN_LE, SERVICE_CODE_TAPS_LOG_IN_LE};
+
+    const FelicaPollerEvent* felica_event = event.event_data;
+    FelicaPollerReadCommandResponse* rx_resp;
+    rx_resp->SF1 = 0;
+    rx_resp->SF2 = 0;
+    uint8_t blocks[1] = {0x00};
+    FelicaPoller* felica_poller = event.instance;
+    FURI_LOG_I(TAG, "Poller set");
+    if(felica_event->type == FelicaPollerEventTypeRequestAuthContext &&
+       felica_poller->data->pmm.data[1] == SUICA_IC_TYPE_CODE) {
+        view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventCardDetected);
+        command = NfcCommandContinue;
+
+        if(stage == MetroflipPollerEventTypeStart) {
+            nfc_device_set_data(
+                app->nfc_device, NfcProtocolFelica, nfc_poller_get_data(app->poller));
+            furi_string_printf(parsed_data, "\e#Suica\n");
+
+            FelicaError error = FelicaErrorNone;
+            int service_code_index = 0;
+            // Authenticate with the card
+            // Iterate through the two services
+            while(service_code_index < 2 && error == FelicaErrorNone) {
+                furi_string_cat_printf(
+                    parsed_data, "%s: \n", suica_service_names[service_code_index]);
+                rx_resp->SF1 = 0;
+                rx_resp->SF2 = 0;
+                blocks[0] = 0; // firmware api requires this to be a list
+                while((rx_resp->SF1 + rx_resp->SF2) == 0 &&
+                      blocks[0] < SUICA_MAX_HISTORY_ENTRIES && error == FelicaErrorNone) {
+                    uint8_t block_data[16] = {0};
+                    error = felica_poller_read_blocks(
+                        felica_poller, 1, blocks, service_code[service_code_index], &rx_resp);
+                    if(error != FelicaErrorNone) {
+                        view_dispatcher_send_custom_event(
+                            app->view_dispatcher, MetroflipCustomEventCardLost);
+                        command = NfcCommandStop;
+                        break;
+                    }
+                    furi_string_cat_printf(parsed_data, "Block %02X\n", blocks[0]);
+                    blocks[0]++;
+                    for(size_t i = 0; i < FELICA_DATA_BLOCK_SIZE; i++) {
+                        furi_string_cat_printf(parsed_data, "%02X ", rx_resp->data[i]);
+                        block_data[i] = rx_resp->data[i];
+                    }
+                    furi_string_cat_printf(parsed_data, "\n");
+                    if(service_code_index == 0) {
+                        FURI_LOG_I(
+                            TAG,
+                            "Service code %d, adding entry %x",
+                            service_code_index,
+                            model->size);
+                        suica_add_entry(model, block_data);
+                    }
+                }
+                service_code_index++;
+            }
+            metroflip_app_blink_stop(app);
+
+            if(model->size == 1) { // Have to let the poller run once before knowing we failed
+                furi_string_printf(
+                    parsed_data,
+                    "\e#Suica\nSorry, no data found.\nPlease let the developers know and we will add support.");
+            }
+            widget_add_text_scroll_element(
+                widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+            widget_add_button_element(
+                widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+
+            if(model->size > 1) {
+                widget_add_button_element(
+                    widget, GuiButtonTypeCenter, "Parse", suica_parse_detail_callback, app);
+            }
+            view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+        }
+    }
+    furi_string_free(parsed_data);
+    command = NfcCommandStop;
+    return command;
+}
+
+static bool suica_history_input_callback(InputEvent* event, void* context) {
+    Metroflip* app = (Metroflip*)context;
+    if(event->type == InputTypeShort) {
+        switch(event->key) {
+        case InputKeyLeft: {
+            bool redraw = true;
+            with_view_model(
+                app->suica_context->view_history,
+                SuicaHistoryViewModel * model,
+                {
+                    if(model->entry > 1) {
+                        model->entry--;
+                    }
+                    suica_parse(model);
+                    FURI_LOG_I(TAG, "Viewing entry %d", model->entry);
+                },
+                redraw);
+            break;
+        }
+        case InputKeyRight: {
+            bool redraw = true;
+            with_view_model(
+                app->suica_context->view_history,
+                SuicaHistoryViewModel * model,
+                {
+                    if(model->entry < model->size) {
+                        model->entry++;
+                    }
+                    suica_parse(model);
+                    FURI_LOG_I(TAG, "Viewing entry %d", model->entry);
+                },
+                redraw);
+            break;
+        }
+        case InputKeyUp: {
+            bool redraw = true;
+            with_view_model(
+                app->suica_context->view_history,
+                SuicaHistoryViewModel * model,
+                {
+                    if(model->page > 0) {
+                        model->page--;
+                    }
+                },
+                redraw);
+            break;
+        }
+        case InputKeyDown: {
+            bool redraw = true;
+            with_view_model(
+                app->suica_context->view_history,
+                SuicaHistoryViewModel * model,
+                {
+                    if(model->page < HISTORY_VIEW_PAGE_NUM - 1) {
+                        model->page++;
+                    }
+                },
+                redraw);
+            break;
+        }
+        default:
+            // Handle other keys or do nothing
+            break;
+        }
+    }
+
+    return false;
+}
+
+static void suica_view_history_timer_callback(void* context) {
+    Metroflip* app = (Metroflip*)context;
+    view_dispatcher_send_custom_event(app->view_dispatcher, 0);
+}
+
+static void suica_view_history_enter_callback(void* context) {
+    uint32_t period = furi_ms_to_ticks(ARROW_ANIMATION_FRAME_MS);
+    Metroflip* app = (Metroflip*)context;
+    furi_assert(app->suica_context->timer == NULL);
+    app->suica_context->timer =
+        furi_timer_alloc(suica_view_history_timer_callback, FuriTimerTypePeriodic, context);
+    furi_timer_start(app->suica_context->timer, period);
+}
+
+static void suica_view_history_exit_callback(void* context) {
+    Metroflip* app = (Metroflip*)context;
+    furi_timer_stop(app->suica_context->timer);
+    furi_timer_free(app->suica_context->timer);
+    app->suica_context->timer = NULL;
+}
+
+static bool suica_view_history_custom_event_callback(uint32_t event, void* context) {
+    Metroflip* app = (Metroflip*)context;
+    switch(event) {
+    case 0:
+        // Redraw screen by passing true to last parameter of with_view_model.
+        {
+            bool redraw = true;
+            with_view_model(
+                app->suica_context->view_history,
+                SuicaHistoryViewModel * model,
+                { model->animator_tick++; },
+                redraw);
+            return true;
+        }
+    default:
+        return false;
+    }
+}
+
+static void suica_on_enter(Metroflip* app) {
+    // Gui* gui = furi_record_open(RECORD_GUI);
+    dolphin_deed(DolphinDeedNfcRead);
+
+    if(app->data_loaded == false) {
+        app->suica_context = malloc(sizeof(SuicaContext));
+        app->suica_context->view_history = view_alloc();
+        view_set_context(app->suica_context->view_history, app);
+        view_allocate_model(
+            app->suica_context->view_history,
+            ViewModelTypeLockFree,
+            sizeof(SuicaHistoryViewModel));
+    }
+    view_set_input_callback(app->suica_context->view_history, suica_history_input_callback);
+    view_set_previous_callback(app->suica_context->view_history, suica_navigation_raw_callback);
+    view_set_enter_callback(app->suica_context->view_history, suica_view_history_enter_callback);
+    view_set_exit_callback(app->suica_context->view_history, suica_view_history_exit_callback);
+    view_set_custom_callback(
+        app->suica_context->view_history, suica_view_history_custom_event_callback);
+    view_set_draw_callback(app->suica_context->view_history, suica_history_draw_callback);
+
+    view_dispatcher_add_view(
+        app->view_dispatcher, MetroflipViewCanvas, app->suica_context->view_history);
+
+    if(app->data_loaded == false) {
+        popup_set_header(app->popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
+        popup_set_icon(app->popup, 0, 3, &I_RFIDDolphinReceive_97x61);
+        view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
+
+        nfc_scanner_alloc(app->nfc);
+        app->poller = nfc_poller_alloc(app->nfc, NfcProtocolFelica);
+        nfc_poller_start(app->poller, suica_poller_callback, app);
+        FURI_LOG_I(TAG, "Poller started");
+
+        metroflip_app_blink_start(app);
+    } else {
+        SuicaHistoryViewModel* model = view_get_model(app->suica_context->view_history);
+        suica_model_initialize_after_load(model);
+        Widget* widget = app->widget;
+        FuriString* parsed_data = furi_string_alloc();
+        furi_string_printf(parsed_data, "\e#Suica\n");
+
+        for(uint8_t i = 0; i < model->size; i++) {
+            furi_string_cat_printf(parsed_data, "Block %02X\n", i);
+            for(size_t j = 0; j < FELICA_DATA_BLOCK_SIZE; j++) {
+                furi_string_cat_printf(parsed_data, "%02X ", model->travel_history[i * 16 + j]);
+            }
+            furi_string_cat_printf(parsed_data, "\n");
+        }
+        widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
+
+        widget_add_button_element(
+            widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
+
+        if(model->size > 1) {
+            widget_add_button_element(
+                widget, GuiButtonTypeCenter, "Parse", suica_parse_detail_callback, app);
+        }
+        view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
+    }
+}
+
+static bool suica_on_event(Metroflip* app, SceneManagerEvent event) {
+    bool consumed = false;
+    Popup* popup = app->popup;
+    if(event.type == SceneManagerEventTypeCustom) {
+        if(event.event == MetroflipCustomEventCardDetected) {
+            popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
+            consumed = true;
+        } else if(event.event == MetroflipCustomEventCardLost) {
+            popup_set_header(popup, "Card \n lost", 68, 30, AlignLeft, AlignTop);
+            // popup_set_timeout(popup, 2000);
+            // popup_enable_timeout(popup);
+            // view_dispatcher_switch_to_view(app->view_dispatcher, SuicaViewPopup);
+            // popup_disable_timeout(popup);
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, MetroflipSceneStart);
+            consumed = true;
+        } else if(event.event == MetroflipCustomEventWrongCard) {
+            popup_set_header(popup, "WRONG \n CARD", 68, 30, AlignLeft, AlignTop);
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, MetroflipSceneStart);
+            consumed = true;
+        } else if(event.event == MetroflipCustomEventPollerFail) {
+            popup_set_header(popup, "Failed", 68, 30, AlignLeft, AlignTop);
+            scene_manager_search_and_switch_to_previous_scene(
+                app->scene_manager, MetroflipSceneStart);
+            consumed = true;
+        }
+    } else if(event.type == SceneManagerEventTypeBack) {
+        UNUSED(popup);
+        scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
+        consumed = true;
+    }
+
+    return consumed;
+}
+
+static void suica_on_exit(Metroflip* app) {
+    widget_reset(app->widget);
+    view_free(app->suica_context->view_history);
+    view_dispatcher_remove_view(app->view_dispatcher, MetroflipViewCanvas);
+    free(app->suica_context);
+    metroflip_app_blink_stop(app);
+    if(app->poller) {
+        nfc_poller_stop(app->poller);
+        nfc_poller_free(app->poller);
+    }
+}
+
+/* Actual implementation of app<>plugin interface */
+static const MetroflipPlugin suica_plugin = {
+    .card_name = "Suica",
+    .plugin_on_enter = suica_on_enter,
+    .plugin_on_event = suica_on_event,
+    .plugin_on_exit = suica_on_exit,
+
+};
+
+/* Plugin descriptor to comply with basic plugin specification */
+static const FlipperAppPluginDescriptor suica_plugin_descriptor = {
+    .appid = METROFLIP_SUPPORTED_CARD_PLUGIN_APP_ID,
+    .ep_api_version = METROFLIP_SUPPORTED_CARD_PLUGIN_API_VERSION,
+    .entry_point = &suica_plugin,
+};
+
+/* Plugin entry point - must return a pointer to const descriptor  */
+const FlipperAppPluginDescriptor* suica_plugin_ep(void) {
+    return &suica_plugin_descriptor;
+}