metromoney.c 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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. #define TAG "Metroflip:Scene:Metromoney"
  15. const MfClassicKeyPair metromoney_1k_keys[16] = {
  16. {.a = 0x2803BCB0C7E1, .b = 0x4FA9EB49F75E},
  17. {.a = 0x9C616585E26D, .b = 0xD1C71E590D16},
  18. {.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
  19. {.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
  20. {.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
  21. {.a = 0x9C616585E26D, .b = 0xA160FCD5EC4C},
  22. {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
  23. {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
  24. {.a = 0x112233445566, .b = 0x361A62F35BC9},
  25. {.a = 0x112233445566, .b = 0x361A62F35BC9},
  26. {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
  27. {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
  28. {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
  29. {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
  30. {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
  31. {.a = 0xFFFFFFFFFFFF, .b = 0xFFFFFFFFFFFF},
  32. };
  33. static bool metromoney_parse(FuriString* parsed_data, const MfClassicData* data) {
  34. bool parsed = false;
  35. do {
  36. // Verify key
  37. const uint8_t ticket_sector_number = 1;
  38. const uint8_t ticket_block_number = 1;
  39. const MfClassicSectorTrailer* sec_tr =
  40. mf_classic_get_sector_trailer_by_sector(data, ticket_sector_number);
  41. const uint64_t key =
  42. bit_lib_bytes_to_num_be(sec_tr->key_a.data, COUNT_OF(sec_tr->key_a.data));
  43. if(key != metromoney_1k_keys[ticket_sector_number].a) break;
  44. FURI_LOG_D(TAG, "passed key check");
  45. // Parse data
  46. const uint8_t start_block_num =
  47. mf_classic_get_first_block_num_of_sector(ticket_sector_number);
  48. const uint8_t* block_start_ptr =
  49. &data->block[start_block_num + ticket_block_number].data[0];
  50. uint32_t balance = bit_lib_bytes_to_num_le(block_start_ptr, 4) - 100;
  51. uint32_t balance_lari = balance / 100;
  52. uint8_t balance_tetri = balance % 100;
  53. size_t uid_len = 0;
  54. const uint8_t* uid = mf_classic_get_uid(data, &uid_len);
  55. uint32_t card_number = bit_lib_bytes_to_num_le(uid, 4);
  56. furi_string_printf(
  57. parsed_data,
  58. "\e#Metromoney\nCard number: %lu\nBalance: %lu.%02u GEL",
  59. card_number,
  60. balance_lari,
  61. balance_tetri);
  62. parsed = true;
  63. } while(false);
  64. return parsed;
  65. }
  66. static NfcCommand metromoney_poller_callback(NfcGenericEvent event, void* context) {
  67. furi_assert(context);
  68. furi_assert(event.event_data);
  69. furi_assert(event.protocol == NfcProtocolMfClassic);
  70. NfcCommand command = NfcCommandContinue;
  71. const MfClassicPollerEvent* mfc_event = event.event_data;
  72. Metroflip* app = context;
  73. if(mfc_event->type == MfClassicPollerEventTypeCardDetected) {
  74. view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventCardDetected);
  75. command = NfcCommandContinue;
  76. } else if(mfc_event->type == MfClassicPollerEventTypeCardLost) {
  77. view_dispatcher_send_custom_event(app->view_dispatcher, MetroflipCustomEventCardLost);
  78. app->sec_num = 0;
  79. command = NfcCommandStop;
  80. } else if(mfc_event->type == MfClassicPollerEventTypeRequestMode) {
  81. mfc_event->data->poller_mode.mode = MfClassicPollerModeRead;
  82. } else if(mfc_event->type == MfClassicPollerEventTypeRequestReadSector) {
  83. MfClassicKey key = {0};
  84. bit_lib_num_to_bytes_be(metromoney_1k_keys[app->sec_num].a, COUNT_OF(key.data), key.data);
  85. MfClassicKeyType key_type = MfClassicKeyTypeA;
  86. mfc_event->data->read_sector_request_data.sector_num = app->sec_num;
  87. mfc_event->data->read_sector_request_data.key = key;
  88. mfc_event->data->read_sector_request_data.key_type = key_type;
  89. mfc_event->data->read_sector_request_data.key_provided = true;
  90. if(app->sec_num == 16) {
  91. mfc_event->data->read_sector_request_data.key_provided = false;
  92. app->sec_num = 0;
  93. }
  94. app->sec_num++;
  95. } else if(mfc_event->type == MfClassicPollerEventTypeSuccess) {
  96. nfc_device_set_data(
  97. app->nfc_device, NfcProtocolMfClassic, nfc_poller_get_data(app->poller));
  98. const MfClassicData* mfc_data = nfc_device_get_data(app->nfc_device, NfcProtocolMfClassic);
  99. FuriString* parsed_data = furi_string_alloc();
  100. Widget* widget = app->widget;
  101. dolphin_deed(DolphinDeedNfcReadSuccess);
  102. furi_string_reset(app->text_box_store);
  103. metromoney_parse(parsed_data, mfc_data);
  104. widget_add_text_scroll_element(widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  105. widget_add_button_element(
  106. widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
  107. widget_add_button_element(
  108. widget, GuiButtonTypeCenter, "Save", metroflip_save_widget_callback, app);
  109. furi_string_free(parsed_data);
  110. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  111. command = NfcCommandStop;
  112. metroflip_app_blink_stop(app);
  113. } else if(mfc_event->type == MfClassicPollerEventTypeFail) {
  114. FURI_LOG_I(TAG, "fail");
  115. command = NfcCommandStop;
  116. }
  117. return command;
  118. }
  119. static void metromoney_on_enter(Metroflip* app) {
  120. dolphin_deed(DolphinDeedNfcRead);
  121. FURI_LOG_I(TAG, "open metromoney");
  122. app->sec_num = 0;
  123. if(app->data_loaded) {
  124. FURI_LOG_I(TAG, "tbilisi loaded");
  125. Storage* storage = furi_record_open(RECORD_STORAGE);
  126. FlipperFormat* ff = flipper_format_file_alloc(storage);
  127. if(flipper_format_file_open_existing(ff, app->file_path)) {
  128. MfClassicData* mfc_data = mf_classic_alloc();
  129. mf_classic_load(mfc_data, ff, 2);
  130. FuriString* parsed_data = furi_string_alloc();
  131. Widget* widget = app->widget;
  132. furi_string_reset(app->text_box_store);
  133. if(!metromoney_parse(parsed_data, mfc_data)) {
  134. furi_string_reset(app->text_box_store);
  135. FURI_LOG_I(TAG, "Unknown card type");
  136. furi_string_printf(parsed_data, "\e#Unknown card\n");
  137. }
  138. widget_add_text_scroll_element(
  139. widget, 0, 0, 128, 64, furi_string_get_cstr(parsed_data));
  140. widget_add_button_element(
  141. widget, GuiButtonTypeRight, "Exit", metroflip_exit_widget_callback, app);
  142. widget_add_button_element(
  143. widget, GuiButtonTypeCenter, "Delete", metroflip_delete_widget_callback, app);
  144. mf_classic_free(mfc_data);
  145. furi_string_free(parsed_data);
  146. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewWidget);
  147. }
  148. flipper_format_free(ff);
  149. } else {
  150. FURI_LOG_I(TAG, "tbilisi not loaded");
  151. // Setup view
  152. Popup* popup = app->popup;
  153. popup_set_header(popup, "Apply\n card to\nthe back", 68, 30, AlignLeft, AlignTop);
  154. popup_set_icon(popup, 0, 3, &I_RFIDDolphinReceive_97x61);
  155. // Start worker
  156. view_dispatcher_switch_to_view(app->view_dispatcher, MetroflipViewPopup);
  157. app->poller = nfc_poller_alloc(app->nfc, NfcProtocolMfClassic);
  158. nfc_poller_start(app->poller, metromoney_poller_callback, app);
  159. metroflip_app_blink_start(app);
  160. }
  161. }
  162. static bool metromoney_on_event(Metroflip* app, SceneManagerEvent event) {
  163. bool consumed = false;
  164. if(event.type == SceneManagerEventTypeCustom) {
  165. if(event.event == MetroflipCustomEventCardDetected) {
  166. Popup* popup = app->popup;
  167. popup_set_header(popup, "DON'T\nMOVE", 68, 30, AlignLeft, AlignTop);
  168. consumed = true;
  169. } else if(event.event == MetroflipCustomEventCardLost) {
  170. Popup* popup = app->popup;
  171. popup_set_header(popup, "Card \n lost", 68, 30, AlignLeft, AlignTop);
  172. consumed = true;
  173. } else if(event.event == MetroflipCustomEventWrongCard) {
  174. Popup* popup = app->popup;
  175. popup_set_header(popup, "WRONG \n CARD", 68, 30, AlignLeft, AlignTop);
  176. consumed = true;
  177. } else if(event.event == MetroflipCustomEventPollerFail) {
  178. Popup* popup = app->popup;
  179. popup_set_header(popup, "Failed", 68, 30, AlignLeft, AlignTop);
  180. consumed = true;
  181. }
  182. } else if(event.type == SceneManagerEventTypeBack) {
  183. scene_manager_search_and_switch_to_previous_scene(app->scene_manager, MetroflipSceneStart);
  184. scene_manager_set_scene_state(app->scene_manager, MetroflipSceneStart, MetroflipSceneAuto);
  185. consumed = true;
  186. }
  187. return consumed;
  188. }
  189. static void metromoney_on_exit(Metroflip* app) {
  190. widget_reset(app->widget);
  191. if(app->poller && !app->data_loaded) {
  192. nfc_poller_stop(app->poller);
  193. nfc_poller_free(app->poller);
  194. }
  195. // Clear view
  196. popup_reset(app->popup);
  197. metroflip_app_blink_stop(app);
  198. }
  199. /* Actual implementation of app<>plugin interface */
  200. static const MetroflipPlugin metromoney_plugin = {
  201. .card_name = "Metromoney",
  202. .plugin_on_enter = metromoney_on_enter,
  203. .plugin_on_event = metromoney_on_event,
  204. .plugin_on_exit = metromoney_on_exit,
  205. };
  206. /* Plugin descriptor to comply with basic plugin specification */
  207. static const FlipperAppPluginDescriptor metromoney_plugin_descriptor = {
  208. .appid = METROFLIP_SUPPORTED_CARD_PLUGIN_APP_ID,
  209. .ep_api_version = METROFLIP_SUPPORTED_CARD_PLUGIN_API_VERSION,
  210. .entry_point = &metromoney_plugin,
  211. };
  212. /* Plugin entry point - must return a pointer to const descriptor */
  213. const FlipperAppPluginDescriptor* metromoney_plugin_ep(void) {
  214. return &metromoney_plugin_descriptor;
  215. }