gocard.c 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  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. #include <stdio.h>
  15. #include <stdlib.h>
  16. #include <stdint.h>
  17. #define TAG "Metroflip:Scene:gocard"
  18. typedef enum {
  19. CHILD = 2051, // 0x803
  20. ADULT = 3073 // 0xc01
  21. } ConcessionType;
  22. // Function to print concession type
  23. void printConcessionType(unsigned short concession_type, FuriString* parsed_data) {
  24. switch(concession_type) {
  25. case CHILD:
  26. furi_string_cat_printf(parsed_data, "Concession Type: Child\n");
  27. break;
  28. case ADULT:
  29. furi_string_cat_printf(parsed_data, "Concession Type: Adult\n");
  30. break;
  31. default:
  32. furi_string_cat_printf(parsed_data, "Concession Type: 0x%X\n", concession_type);
  33. break;
  34. }
  35. }
  36. unsigned short byteArrayToIntReversed(unsigned int dec1, unsigned int dec2) {
  37. unsigned char byte1 = (unsigned char)dec1;
  38. unsigned char byte2 = (unsigned char)dec2;
  39. return ((unsigned short)byte2 << 8) | byte1;
  40. }
  41. // Function to extract a substring and convert binary to decimal
  42. uint32_t extract_and_convert(const char* str, int start, int length) {
  43. uint32_t value = 0;
  44. for(int i = 0; i < length; i++) {
  45. if(str[start + i] == '1') {
  46. value |= (1U << (length - 1 - i));
  47. }
  48. }
  49. return value;
  50. }
  51. void parse_gocard_time(const char* bin_str, FuriString* parsed_data) {
  52. int len = strlen(bin_str);
  53. if(len != 32 && len != 33) {
  54. FURI_LOG_I(TAG, "Invalid input length");
  55. return;
  56. }
  57. // Extract values from right to left using bit_slice_to_dec
  58. uint32_t day = bit_slice_to_dec(bin_str, len - 5, len);
  59. uint32_t month = bit_slice_to_dec(bin_str, len - 9, len - 6);
  60. uint32_t year = bit_slice_to_dec(bin_str, len - 15, len - 10);
  61. uint32_t minutes = bit_slice_to_dec(bin_str, len - 26, len - 16);
  62. // Convert year from offset 2000
  63. year += 2000;
  64. // Convert minutes since midnight to HH:MM
  65. uint32_t hours = minutes / 60;
  66. uint32_t mins = minutes % 60;
  67. // Format output string: "YYYY-MM-DD HH:MM"
  68. furi_string_cat_printf(
  69. parsed_data, "%04lu-%02lu-%02lu %02lu:%02lu\n", year, month, day, hours, mins);
  70. }
  71. static bool gocard_parse(FuriString* parsed_data, const MfClassicData* data) {
  72. bool parsed = false;
  73. do {
  74. int balance_slot = 4;
  75. if(data->block[balance_slot].data[13] <= data->block[balance_slot + 1].data[13])
  76. balance_slot++;
  77. unsigned short balancecents = byteArrayToIntReversed(
  78. data->block[balance_slot].data[2], data->block[balance_slot].data[3]);
  79. // Check if the sign flag is set in 'balance'
  80. if((balancecents & 0x8000) == 0x8000) {
  81. balancecents = balancecents & 0x7fff; // Clear the sign flag.
  82. balancecents *= -1; // Negate the balance.
  83. }
  84. // Otherwise, check the sign flag in data->block[4].data[1]
  85. else if((data->block[balance_slot].data[1] & 0x80) == 0x80) {
  86. // seq_go uses a sign flag in an adjacent byte.
  87. balancecents *= -1;
  88. }
  89. double balance = balancecents / 100.0;
  90. furi_string_printf(parsed_data, "\e#go card\nValue: A$%.2f\n", balance); //show balance
  91. int start_index = 4; //byte to start at
  92. int end_index = 7; // byte to end at
  93. int config_block = 6; //block number containing card configuration
  94. int num_bytes = end_index - start_index + 1;
  95. char config_bit_representation[num_bytes * 8 + 1];
  96. for(int i = end_index, j = 0; i >= start_index;
  97. i--, j++) { // Reverse the order of bytes and converty to binary
  98. char bits[9];
  99. byte_to_binary(data->block[config_block].data[i], bits);
  100. memcpy(&config_bit_representation[j * 8], bits, 8);
  101. }
  102. config_bit_representation[num_bytes * 8] = '\0'; //add a null terminator as always
  103. furi_string_cat_printf(parsed_data, "Expiry:\n");
  104. parse_gocard_time(config_bit_representation, parsed_data);
  105. FURI_LOG_I(TAG, "bitrepr: %s", config_bit_representation);
  106. //concession type:
  107. unsigned short concession_type = byteArrayToIntReversed(
  108. data->block[config_block].data[8], data->block[config_block].data[9]);
  109. printConcessionType(concession_type, parsed_data);
  110. parsed = true;
  111. } while(false);
  112. return parsed;
  113. }
  114. static void gocard_on_enter(Metroflip* app) {
  115. dolphin_deed(DolphinDeedNfcRead);
  116. app->sec_num = 0;
  117. if(app->data_loaded) {
  118. Storage* storage = furi_record_open(RECORD_STORAGE);
  119. FlipperFormat* ff = flipper_format_file_alloc(storage);
  120. if(flipper_format_file_open_existing(ff, app->file_path)) {
  121. MfClassicData* mfc_data = mf_classic_alloc();
  122. mf_classic_load(mfc_data, ff, 2);
  123. FuriString* parsed_data = furi_string_alloc();
  124. Widget* widget = app->widget;
  125. furi_string_reset(app->text_box_store);
  126. if(!gocard_parse(parsed_data, mfc_data)) {
  127. furi_string_reset(app->text_box_store);
  128. FURI_LOG_I(TAG, "Unknown card type");
  129. furi_string_printf(parsed_data, "\e#Unknown card\n");
  130. }
  131. widget_add_text_scroll_element(
  132. widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  133. widget_add_button_element(
  134. widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
  135. widget_add_button_element(
  136. widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
  137. mf_classic_free(mfc_data);
  138. furi_string_free(parsed_data);
  139. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  140. }
  141. flipper_format_free(ff);
  142. } else {
  143. // Setup view
  144. Popup* popup = app->popup;
  145. popup_set_header(popup, "unsupported", 68, 30, AlignLeft, AlignTop);
  146. popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
  147. }
  148. }
  149. static bool gocard_on_event(Metroflip* app, SceneManagerEvent event) {
  150. bool consumed = false;
  151. if(event.type == SceneManagerEventTypeCustom) {
  152. if(event.event == MetroflipCustomEventCardDetected) {
  153. Popup* popup = app->popup;
  154. popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
  155. consumed = true;
  156. } else if(event.event == MetroflipCustomEventCardLost) {
  157. Popup* popup = app->popup;
  158. popup_set_header(popup, "Card \n lost", 68, 30, AlignLeft, AlignTop);
  159. consumed = true;
  160. } else if(event.event == MetroflipCustomEventWrongCard) {
  161. Popup* popup = app->popup;
  162. popup_set_header(popup, "WRONG \n CARD", 68, 30, AlignLeft, AlignTop);
  163. consumed = true;
  164. } else if(event.event == MetroflipCustomEventPollerFail) {
  165. Popup* popup = app->popup;
  166. popup_set_header(popup, "Failed", 68, 30, AlignLeft, AlignTop);
  167. consumed = true;
  168. }
  169. } else if(event.type == SceneManagerEventTypeBack) {
  170. scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
  171. scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
  172. consumed = true;
  173. }
  174. return consumed;
  175. }
  176. static void gocard_on_exit(Metroflip* app) {
  177. widget_reset(app->widget);
  178. if(app->poller && !app->data_loaded) {
  179. nfc_poller_stop(app->poller);
  180. nfc_poller_free(app->poller);
  181. }
  182. // Clear view
  183. popup_reset(app->popup);
  184. metroflip_app_blink_stop(app);
  185. }
  186. /* Actual implementation of app<>plugin interface */
  187. static const MetroflipPlugin gocard_plugin = {
  188. .card_name = "gocard",
  189. .plugin_on_enter = gocard_on_enter,
  190. .plugin_on_event = gocard_on_event,
  191. .plugin_on_exit = gocard_on_exit,
  192. };
  193. /* Plugin descriptor to comply with basic plugin specification */
  194. static const FlipperAppPluginDescriptor gocard_plugin_descriptor = {
  195. .appid = METROFLIP_SUPPORTED_CARD_PLUGIN_APP_ID,
  196. .ep_api_version = METROFLIP_SUPPORTED_CARD_PLUGIN_API_VERSION,
  197. .entry_point = &gocard_plugin,
  198. };
  199. /* Plugin entry point - must return a pointer to const descriptor */
  200. const FlipperAppPluginDescriptor* gocard_plugin_ep(void) {
  201. return &gocard_plugin_descriptor;
  202. }