gocard.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. #include <flipper_application.h>
  2. #include "../../metroflip_i.h"
  3. #include <nfc/protocols/mf_classic/mf_classic_poller_sync.h>
  4. #include <nfc/protocols/mf_classic/mf_classic.h>
  5. #include <nfc/protocols/mf_classic/mf_classic_poller.h>
  6. #include <dolphin/dolphin.h>
  7. #include <bit_lib.h>
  8. #include <furi_hal.h>
  9. #include <nfc/nfc.h>
  10. #include <nfc/nfc_device.h>
  11. #include <nfc/nfc_listener.h>
  12. #include "../../api/metroflip/metroflip_api.h"
  13. #include "../../metroflip_plugins.h"
  14. <<<<<<< HEAD
  15. #include <stdio.h>
  16. #include <stdlib.h>
  17. #include <stdint.h>
  18. #define TAG "Metroflip:Scene:gocard"
  19. typedef enum {
  20. CHILD = 2051, // 0x803
  21. ADULT = 3073 // 0xc01
  22. } ConcessionType;
  23. bool hasTravelPassAvailable = false;
  24. // Function to print concession type
  25. void printConcessionType(unsigned short concession_type, FuriString* parsed_data) {
  26. switch(concession_type) {
  27. case CHILD:
  28. furi_string_cat_printf(parsed_data, "Concession Type: Child\n");
  29. break;
  30. case ADULT:
  31. furi_string_cat_printf(parsed_data, "Concession Type: Adult\n");
  32. break;
  33. default:
  34. furi_string_cat_printf(parsed_data, "Concession Type: 0x%X\n", concession_type);
  35. break;
  36. }
  37. }
  38. =======
  39. #define TAG "Metroflip:Scene:gocard"
  40. >>>>>>> ac4922c34e448b30b3fe49a80f4d4ba236bf1bd9
  41. unsigned short byteArrayToIntReversed(unsigned int dec1, unsigned int dec2) {
  42. unsigned char byte1 = (unsigned char)dec1;
  43. unsigned char byte2 = (unsigned char)dec2;
  44. return ((unsigned short)byte2 << 8) | byte1;
  45. }
  46. <<<<<<< HEAD
  47. // Function to extract a substring and convert binary to decimal
  48. uint32_t extract_and_convert(const char* str, int start, int length) {
  49. uint32_t value = 0;
  50. for(int i = 0; i < length; i++) {
  51. if(str[start + i] == '1') {
  52. value |= (1U << (length - 1 - i));
  53. }
  54. }
  55. return value;
  56. }
  57. void parse_gocard_time(int block, int offset, const MfClassicData* data, FuriString* parsed_data) {
  58. //byte to start at
  59. int num_bytes = 4;
  60. char gocard_date_bit_representation[num_bytes * 8 + 1];
  61. memset(gocard_date_bit_representation, 0, sizeof(gocard_date_bit_representation));
  62. for(int i = (offset + num_bytes - 1), j = 0; i >= offset;
  63. i--, j++) { // Reverse the order of bytes and converty to binary
  64. char bits[9];
  65. byte_to_binary(data->block[block].data[i], bits);
  66. memcpy(&gocard_date_bit_representation[j * 8], bits, 8);
  67. }
  68. gocard_date_bit_representation[num_bytes * 8] = '\0';
  69. int len = strlen(gocard_date_bit_representation);
  70. FURI_LOG_I(TAG, "len %d", len); // I get 34
  71. if(len != 32 && len != 33) {
  72. FURI_LOG_I(TAG, "Invalid input length");
  73. return;
  74. }
  75. // Field layout (from rightmost bit):
  76. // - Day: 5 bits
  77. // - Month: 4 bits
  78. // - Year: 6 bits (years since 2000)
  79. // - Minutes: 11 bits (minutes from midnight)
  80. // Extract values from right to left using bit_slice_to_dec
  81. uint32_t day = bit_slice_to_dec(gocard_date_bit_representation, len - 5, len);
  82. uint32_t month = bit_slice_to_dec(gocard_date_bit_representation, len - 9, len - 6);
  83. uint32_t year = bit_slice_to_dec(gocard_date_bit_representation, len - 15, len - 10);
  84. uint32_t minutes = bit_slice_to_dec(gocard_date_bit_representation, len - 26, len - 16);
  85. // Convert year from offset 2000
  86. year += 2000;
  87. // Convert minutes since midnight to HH:MM
  88. uint32_t hours = minutes / 60;
  89. uint32_t mins = minutes % 60;
  90. // Format output string: "YYYY-MM-DD HH:MM"
  91. furi_string_cat_printf(
  92. parsed_data, "%04lu-%02lu-%02lu %02lu:%02lu\n", year, month, day, hours, mins);
  93. }
  94. void parse_gocard_topup_info(FuriString* parsed_data, const MfClassicData* data) {
  95. furi_string_cat_printf(parsed_data, "\n\e#Top-Up Info:");
  96. bool fully_empty = true;
  97. int block_num = 8;
  98. for(int i = block_num; i < block_num + 3; i++) {
  99. /******* Check if it's empty ******/
  100. bool is_block_empty = true;
  101. for(int j = 2; j < 8; j++) {
  102. if(data->block[i].data[j] != 0) {
  103. FURI_LOG_I(TAG, "Not 0, proceeding");
  104. is_block_empty = false;
  105. break;
  106. }
  107. }
  108. if(is_block_empty) {
  109. FURI_LOG_I(TAG, "Block %d is empty", i);
  110. continue;
  111. } else {
  112. fully_empty = false;
  113. }
  114. /**** If not fully empty, proceed ******/
  115. unsigned short creditcents =
  116. byteArrayToIntReversed(data->block[i].data[6], data->block[i].data[7]);
  117. creditcents &= 0x7FFF;
  118. double credit = creditcents / 100.0;
  119. furi_string_cat_printf(parsed_data, "\nCredit Added: A$%.2f\nTime: ", credit);
  120. parse_gocard_time(i, 2, data, parsed_data);
  121. }
  122. if(fully_empty) {
  123. FURI_LOG_I(TAG, "All checked blocks are empty, returning.");
  124. }
  125. }
  126. =======
  127. >>>>>>> ac4922c34e448b30b3fe49a80f4d4ba236bf1bd9
  128. static bool gocard_parse(FuriString* parsed_data, const MfClassicData* data) {
  129. bool parsed = false;
  130. do {
  131. <<<<<<< HEAD
  132. =======
  133. // Verify key
  134. //const uint8_t ticket_sector_number = 1;
  135. //const uint8_t ticket_block_number = 1;
  136. //const MfClassicSectorTrailer* sec_tr =
  137. // mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number);
  138. //const uint64_t key =
  139. // bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
  140. ///if(key != gocard_1k_keys[ticket_sector_number].a) break;
  141. //FURI_LOG_D(TAG, "passed key check");
  142. // Parse data
  143. //const uint8_t start_block_num =
  144. // mf_classic_get_first_block_num_of_sector(ticket_sector_number);
  145. //const uint8_t* block_start_ptr =
  146. // &data->block[start_block_num + ticket_block_number].data[0];
  147. //uint32_t balance = bit_lib_bytes_to_num_le(block_start_ptr, 4) - 100;
  148. //uint32_t balance_lari = balance / 100;
  149. //uint8_t balance_tetri = balance % 100;
  150. >>>>>>> ac4922c34e448b30b3fe49a80f4d4ba236bf1bd9
  151. int balance_slot = 4;
  152. if(data->block[balance_slot].data[13] <= data->block[balance_slot + 1].data[13])
  153. balance_slot++;
  154. unsigned short balancecents = byteArrayToIntReversed(
  155. data->block[balance_slot].data[2], data->block[balance_slot].data[3]);
  156. // Check if the sign flag is set in 'balance'
  157. if((balancecents & 0x8000) == 0x8000) {
  158. balancecents = balancecents & 0x7fff; // Clear the sign flag.
  159. balancecents *= -1; // Negate the balance.
  160. }
  161. // Otherwise, check the sign flag in data->block[4].data[1]
  162. else if((data->block[balance_slot].data[1] & 0x80) == 0x80) {
  163. // seq_go uses a sign flag in an adjacent byte.
  164. balancecents *= -1;
  165. }
  166. double balance = balancecents / 100.0;
  167. <<<<<<< HEAD
  168. furi_string_printf(parsed_data, "\e#go card\nValue: A$%.2f\n", balance); //show balance
  169. hasTravelPassAvailable = (data->block[balance_slot].data[7] != 0x00) ? true : false;
  170. int start_index = 4; //byte to start at
  171. int config_block = 6; //block number containing card configuration
  172. furi_string_cat_printf(parsed_data, "Expiry:\n");
  173. parse_gocard_time(config_block, start_index, data, parsed_data);
  174. //concession type:
  175. unsigned short concession_type = byteArrayToIntReversed(
  176. data->block[config_block].data[8], data->block[config_block].data[9]);
  177. printConcessionType(concession_type, parsed_data);
  178. parse_gocard_topup_info(parsed_data, data);
  179. =======
  180. furi_string_printf(parsed_data, "\e#Go card\nValue: A$%.2f\n", balance);
  181. >>>>>>> ac4922c34e448b30b3fe49a80f4d4ba236bf1bd9
  182. parsed = true;
  183. } while(false);
  184. return parsed;
  185. }
  186. static void gocard_on_enter(Metroflip* app) {
  187. dolphin_deed(DolphinDeedNfcRead);
  188. app->sec_num = 0;
  189. if(app->data_loaded) {
  190. Storage* storage = furi_record_open(RECORD_STORAGE);
  191. FlipperFormat* ff = flipper_format_file_alloc(storage);
  192. if(flipper_format_file_open_existing(ff, app->file_path)) {
  193. MfClassicData* mfc_data = mf_classic_alloc();
  194. mf_classic_load(mfc_data, ff, 2);
  195. FuriString* parsed_data = furi_string_alloc();
  196. Widget* widget = app->widget;
  197. furi_string_reset(app->text_box_store);
  198. if(!gocard_parse(parsed_data, mfc_data)) {
  199. furi_string_reset(app->text_box_store);
  200. FURI_LOG_I(TAG, "Unknown card type");
  201. furi_string_printf(parsed_data, "\e#Unknown card\n");
  202. }
  203. widget_add_text_scroll_element(
  204. widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  205. widget_add_button_element(
  206. widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
  207. widget_add_button_element(
  208. widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
  209. mf_classic_free(mfc_data);
  210. furi_string_free(parsed_data);
  211. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  212. }
  213. flipper_format_free(ff);
  214. } else {
  215. // Setup view
  216. Popup* popup = app->popup;
  217. popup_set_header(popup, "unsupported", 68, 30, AlignLeft, AlignTop);
  218. popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
  219. }
  220. }
  221. static bool gocard_on_event(Metroflip* app, SceneManagerEvent event) {
  222. bool consumed = false;
  223. if(event.type == SceneManagerEventTypeCustom) {
  224. if(event.event == MetroflipCustomEventCardDetected) {
  225. Popup* popup = app->popup;
  226. popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
  227. consumed = true;
  228. } else if(event.event == MetroflipCustomEventCardLost) {
  229. Popup* popup = app->popup;
  230. popup_set_header(popup, "Card \n lost", 68, 30, AlignLeft, AlignTop);
  231. consumed = true;
  232. } else if(event.event == MetroflipCustomEventWrongCard) {
  233. Popup* popup = app->popup;
  234. popup_set_header(popup, "WRONG \n CARD", 68, 30, AlignLeft, AlignTop);
  235. consumed = true;
  236. } else if(event.event == MetroflipCustomEventPollerFail) {
  237. Popup* popup = app->popup;
  238. popup_set_header(popup, "Failed", 68, 30, AlignLeft, AlignTop);
  239. consumed = true;
  240. }
  241. } else if(event.type == SceneManagerEventTypeBack) {
  242. scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
  243. scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
  244. consumed = true;
  245. }
  246. return consumed;
  247. }
  248. static void gocard_on_exit(Metroflip* app) {
  249. widget_reset(app->widget);
  250. if(app->poller && !app->data_loaded) {
  251. nfc_poller_stop(app->poller);
  252. nfc_poller_free(app->poller);
  253. }
  254. // Clear view
  255. popup_reset(app->popup);
  256. metroflip_app_blink_stop(app);
  257. }
  258. /* Actual implementation of app<>plugin interface */
  259. static const MetroflipPlugin gocard_plugin = {
  260. .card_name = "gocard",
  261. .plugin_on_enter = gocard_on_enter,
  262. .plugin_on_event = gocard_on_event,
  263. .plugin_on_exit = gocard_on_exit,
  264. };
  265. /* Plugin descriptor to comply with basic plugin specification */
  266. static const FlipperAppPluginDescriptor gocard_plugin_descriptor = {
  267. .appid = METROFLIP_SUPPORTED_CARD_PLUGIN_APP_ID,
  268. .ep_api_version = METROFLIP_SUPPORTED_CARD_PLUGIN_API_VERSION,
  269. .entry_point = &gocard_plugin,
  270. };
  271. /* Plugin entry point - must return a pointer to const descriptor */
  272. const FlipperAppPluginDescriptor* gocard_plugin_ep(void) {
  273. return &gocard_plugin_descriptor;
  274. }