[FL-2933] Mf Classic initial write, update, detect reader (#1941)
* nfc: introduce nfc write * nfc: add write logic * nfc worker: add write state * nfc: add mfc update logic * nfc: add update success logic * nfc: add custom card for detect reader * nfc: update write logic * nfc: add halt command, add notifications * nfc: add write fail scene * nfc: fixes and clean up * nfc: fix navigation ad notifications * nfc: fix detect reader nfc data setter Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									09b622d4ae
								
							
						
					
					
						commit
						93a6e17ce5
					
				| @ -36,6 +36,12 @@ ADD_SCENE(nfc, mf_classic_keys_list, MfClassicKeysList) | |||||||
| ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete) | ADD_SCENE(nfc, mf_classic_keys_delete, MfClassicKeysDelete) | ||||||
| ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) | ADD_SCENE(nfc, mf_classic_keys_warn_duplicate, MfClassicKeysWarnDuplicate) | ||||||
| ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack) | ADD_SCENE(nfc, mf_classic_dict_attack, MfClassicDictAttack) | ||||||
|  | ADD_SCENE(nfc, mf_classic_write, MfClassicWrite) | ||||||
|  | ADD_SCENE(nfc, mf_classic_write_success, MfClassicWriteSuccess) | ||||||
|  | ADD_SCENE(nfc, mf_classic_write_fail, MfClassicWriteFail) | ||||||
|  | ADD_SCENE(nfc, mf_classic_update, MfClassicUpdate) | ||||||
|  | ADD_SCENE(nfc, mf_classic_update_success, MfClassicUpdateSuccess) | ||||||
|  | ADD_SCENE(nfc, mf_classic_wrong_card, MfClassicWrongCard) | ||||||
| ADD_SCENE(nfc, emv_read_success, EmvReadSuccess) | ADD_SCENE(nfc, emv_read_success, EmvReadSuccess) | ||||||
| ADD_SCENE(nfc, emv_menu, EmvMenu) | ADD_SCENE(nfc, emv_menu, EmvMenu) | ||||||
| ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence) | ADD_SCENE(nfc, emulate_apdu_sequence, EmulateApduSequence) | ||||||
|  | |||||||
| @ -28,6 +28,11 @@ void nfc_scene_detect_reader_on_enter(void* context) { | |||||||
| 
 | 
 | ||||||
|     detect_reader_set_callback(nfc->detect_reader, nfc_scene_detect_reader_callback, nfc); |     detect_reader_set_callback(nfc->detect_reader, nfc_scene_detect_reader_callback, nfc); | ||||||
|     detect_reader_set_nonces_max(nfc->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX); |     detect_reader_set_nonces_max(nfc->detect_reader, NFC_SCENE_DETECT_READER_PAIR_NONCES_MAX); | ||||||
|  |     NfcDeviceData* dev_data = &nfc->dev->dev_data; | ||||||
|  |     if(dev_data->nfc_data.uid_len) { | ||||||
|  |         detect_reader_set_uid( | ||||||
|  |             nfc->detect_reader, dev_data->nfc_data.uid, dev_data->nfc_data.uid_len); | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     // Store number of collected nonces in scene state
 |     // Store number of collected nonces in scene state
 | ||||||
|     scene_manager_set_scene_state(nfc->scene_manager, NfcSceneDetectReader, 0); |     scene_manager_set_scene_state(nfc->scene_manager, NfcSceneDetectReader, 0); | ||||||
|  | |||||||
							
								
								
									
										98
									
								
								applications/main/nfc/scenes/nfc_scene_mf_classic_update.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								applications/main/nfc/scenes/nfc_scene_mf_classic_update.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,98 @@ | |||||||
|  | #include "../nfc_i.h" | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | 
 | ||||||
|  | enum { | ||||||
|  |     NfcSceneMfClassicUpdateStateCardSearch, | ||||||
|  |     NfcSceneMfClassicUpdateStateCardFound, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | bool nfc_mf_classic_update_worker_callback(NfcWorkerEvent event, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     view_dispatcher_send_custom_event(nfc->view_dispatcher, event); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void nfc_scene_mf_classic_update_setup_view(Nfc* nfc) { | ||||||
|  |     Popup* popup = nfc->popup; | ||||||
|  |     popup_reset(popup); | ||||||
|  |     uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicUpdate); | ||||||
|  | 
 | ||||||
|  |     if(state == NfcSceneMfClassicUpdateStateCardSearch) { | ||||||
|  |         popup_set_text( | ||||||
|  |             nfc->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); | ||||||
|  |         popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); | ||||||
|  |     } else { | ||||||
|  |         popup_set_header(popup, "Updating\nDon't move...", 52, 32, AlignLeft, AlignCenter); | ||||||
|  |         popup_set_icon(popup, 12, 23, &A_Loading_24); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_update_on_enter(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     DOLPHIN_DEED(DolphinDeedNfcEmulate); | ||||||
|  | 
 | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         nfc->scene_manager, NfcSceneMfClassicUpdate, NfcSceneMfClassicUpdateStateCardSearch); | ||||||
|  |     nfc_scene_mf_classic_update_setup_view(nfc); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     nfc_worker_start( | ||||||
|  |         nfc->worker, | ||||||
|  |         NfcWorkerStateMfClassicUpdate, | ||||||
|  |         &nfc->dev->dev_data, | ||||||
|  |         nfc_mf_classic_update_worker_callback, | ||||||
|  |         nfc); | ||||||
|  |     nfc_blink_emulate_start(nfc); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mf_classic_update_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == NfcWorkerEventSuccess) { | ||||||
|  |             nfc_worker_stop(nfc->worker); | ||||||
|  |             if(nfc_device_save_shadow(nfc->dev, nfc->dev->dev_name)) { | ||||||
|  |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdateSuccess); | ||||||
|  |             } else { | ||||||
|  |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); | ||||||
|  |             } | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcWorkerEventWrongCard) { | ||||||
|  |             nfc_worker_stop(nfc->worker); | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcWorkerEventCardDetected) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc->scene_manager, | ||||||
|  |                 NfcSceneMfClassicUpdate, | ||||||
|  |                 NfcSceneMfClassicUpdateStateCardFound); | ||||||
|  |             nfc_scene_mf_classic_update_setup_view(nfc); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcWorkerEventNoCardDetected) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc->scene_manager, | ||||||
|  |                 NfcSceneMfClassicUpdate, | ||||||
|  |                 NfcSceneMfClassicUpdateStateCardSearch); | ||||||
|  |             nfc_scene_mf_classic_update_setup_view(nfc); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_update_on_exit(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     nfc_worker_stop(nfc->worker); | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         nfc->scene_manager, NfcSceneMfClassicUpdate, NfcSceneMfClassicUpdateStateCardSearch); | ||||||
|  |     // Clear view
 | ||||||
|  |     popup_reset(nfc->popup); | ||||||
|  | 
 | ||||||
|  |     nfc_blink_stop(nfc); | ||||||
|  | } | ||||||
| @ -0,0 +1,44 @@ | |||||||
|  | #include "../nfc_i.h" | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_update_success_popup_callback(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_update_success_on_enter(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     DOLPHIN_DEED(DolphinDeedNfcSave); | ||||||
|  | 
 | ||||||
|  |     notification_message(nfc->notifications, &sequence_success); | ||||||
|  | 
 | ||||||
|  |     Popup* popup = nfc->popup; | ||||||
|  |     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||||
|  |     popup_set_header(popup, "Updated!", 11, 20, AlignLeft, AlignBottom); | ||||||
|  |     popup_set_timeout(popup, 1500); | ||||||
|  |     popup_set_context(popup, nfc); | ||||||
|  |     popup_set_callback(popup, nfc_scene_mf_classic_update_success_popup_callback); | ||||||
|  |     popup_enable_timeout(popup); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mf_classic_update_success_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == NfcCustomEventViewExit) { | ||||||
|  |             consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                 nfc->scene_manager, NfcSceneFileSelect); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_update_success_on_exit(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     // Clear view
 | ||||||
|  |     popup_reset(nfc->popup); | ||||||
|  | } | ||||||
							
								
								
									
										92
									
								
								applications/main/nfc/scenes/nfc_scene_mf_classic_write.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								applications/main/nfc/scenes/nfc_scene_mf_classic_write.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,92 @@ | |||||||
|  | #include "../nfc_i.h" | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | 
 | ||||||
|  | enum { | ||||||
|  |     NfcSceneMfClassicWriteStateCardSearch, | ||||||
|  |     NfcSceneMfClassicWriteStateCardFound, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | bool nfc_mf_classic_write_worker_callback(NfcWorkerEvent event, void* context) { | ||||||
|  |     furi_assert(context); | ||||||
|  | 
 | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     view_dispatcher_send_custom_event(nfc->view_dispatcher, event); | ||||||
|  | 
 | ||||||
|  |     return true; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void nfc_scene_mf_classic_write_setup_view(Nfc* nfc) { | ||||||
|  |     Popup* popup = nfc->popup; | ||||||
|  |     popup_reset(popup); | ||||||
|  |     uint32_t state = scene_manager_get_scene_state(nfc->scene_manager, NfcSceneMfClassicWrite); | ||||||
|  | 
 | ||||||
|  |     if(state == NfcSceneMfClassicWriteStateCardSearch) { | ||||||
|  |         popup_set_text( | ||||||
|  |             nfc->popup, "Apply the initial\ncard only", 128, 32, AlignRight, AlignCenter); | ||||||
|  |         popup_set_icon(nfc->popup, 0, 8, &I_NFC_manual_60x50); | ||||||
|  |     } else { | ||||||
|  |         popup_set_header(popup, "Writing\nDon't move...", 52, 32, AlignLeft, AlignCenter); | ||||||
|  |         popup_set_icon(popup, 12, 23, &A_Loading_24); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_write_on_enter(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     DOLPHIN_DEED(DolphinDeedNfcEmulate); | ||||||
|  | 
 | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch); | ||||||
|  |     nfc_scene_mf_classic_write_setup_view(nfc); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     nfc_worker_start( | ||||||
|  |         nfc->worker, | ||||||
|  |         NfcWorkerStateMfClassicWrite, | ||||||
|  |         &nfc->dev->dev_data, | ||||||
|  |         nfc_mf_classic_write_worker_callback, | ||||||
|  |         nfc); | ||||||
|  |     nfc_blink_emulate_start(nfc); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mf_classic_write_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == NfcWorkerEventSuccess) { | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWriteSuccess); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcWorkerEventFail) { | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWriteFail); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcWorkerEventWrongCard) { | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrongCard); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcWorkerEventCardDetected) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardFound); | ||||||
|  |             nfc_scene_mf_classic_write_setup_view(nfc); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == NfcWorkerEventNoCardDetected) { | ||||||
|  |             scene_manager_set_scene_state( | ||||||
|  |                 nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch); | ||||||
|  |             nfc_scene_mf_classic_write_setup_view(nfc); | ||||||
|  |             consumed = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_write_on_exit(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     nfc_worker_stop(nfc->worker); | ||||||
|  |     scene_manager_set_scene_state( | ||||||
|  |         nfc->scene_manager, NfcSceneMfClassicWrite, NfcSceneMfClassicWriteStateCardSearch); | ||||||
|  |     // Clear view
 | ||||||
|  |     popup_reset(nfc->popup); | ||||||
|  | 
 | ||||||
|  |     nfc_blink_stop(nfc); | ||||||
|  | } | ||||||
| @ -0,0 +1,58 @@ | |||||||
|  | #include "../nfc_i.h" | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_write_fail_widget_callback( | ||||||
|  |     GuiButtonType result, | ||||||
|  |     InputType type, | ||||||
|  |     void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(nfc->view_dispatcher, result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_write_fail_on_enter(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     Widget* widget = nfc->widget; | ||||||
|  | 
 | ||||||
|  |     notification_message(nfc->notifications, &sequence_error); | ||||||
|  | 
 | ||||||
|  |     widget_add_icon_element(widget, 72, 17, &I_DolphinCommon_56x48); | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 7, 4, AlignLeft, AlignTop, FontPrimary, "Writing gone wrong!"); | ||||||
|  |     widget_add_string_multiline_element( | ||||||
|  |         widget, | ||||||
|  |         7, | ||||||
|  |         17, | ||||||
|  |         AlignLeft, | ||||||
|  |         AlignTop, | ||||||
|  |         FontSecondary, | ||||||
|  |         "Not all sectors\nwere written\ncorrectly."); | ||||||
|  | 
 | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, GuiButtonTypeLeft, "Finish", nfc_scene_mf_classic_write_fail_widget_callback, nfc); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mf_classic_write_fail_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == GuiButtonTypeLeft) { | ||||||
|  |             consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                 nfc->scene_manager, NfcSceneFileSelect); | ||||||
|  |         } | ||||||
|  |     } else if(event.type == SceneManagerEventTypeBack) { | ||||||
|  |         consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |             nfc->scene_manager, NfcSceneSavedMenu); | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_write_fail_on_exit(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     widget_reset(nfc->widget); | ||||||
|  | } | ||||||
| @ -0,0 +1,44 @@ | |||||||
|  | #include "../nfc_i.h" | ||||||
|  | #include <dolphin/dolphin.h> | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_write_success_popup_callback(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     view_dispatcher_send_custom_event(nfc->view_dispatcher, NfcCustomEventViewExit); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_write_success_on_enter(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     DOLPHIN_DEED(DolphinDeedNfcSave); | ||||||
|  | 
 | ||||||
|  |     notification_message(nfc->notifications, &sequence_success); | ||||||
|  | 
 | ||||||
|  |     Popup* popup = nfc->popup; | ||||||
|  |     popup_set_icon(popup, 32, 5, &I_DolphinNice_96x59); | ||||||
|  |     popup_set_header(popup, "Successfully\nwritten", 13, 22, AlignLeft, AlignBottom); | ||||||
|  |     popup_set_timeout(popup, 1500); | ||||||
|  |     popup_set_context(popup, nfc); | ||||||
|  |     popup_set_callback(popup, nfc_scene_mf_classic_write_success_popup_callback); | ||||||
|  |     popup_enable_timeout(popup); | ||||||
|  | 
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewPopup); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mf_classic_write_success_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == NfcCustomEventViewExit) { | ||||||
|  |             consumed = scene_manager_search_and_switch_to_previous_scene( | ||||||
|  |                 nfc->scene_manager, NfcSceneFileSelect); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_write_success_on_exit(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     // Clear view
 | ||||||
|  |     popup_reset(nfc->popup); | ||||||
|  | } | ||||||
| @ -0,0 +1,53 @@ | |||||||
|  | #include "../nfc_i.h" | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_wrong_card_widget_callback( | ||||||
|  |     GuiButtonType result, | ||||||
|  |     InputType type, | ||||||
|  |     void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     if(type == InputTypeShort) { | ||||||
|  |         view_dispatcher_send_custom_event(nfc->view_dispatcher, result); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_wrong_card_on_enter(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     Widget* widget = nfc->widget; | ||||||
|  | 
 | ||||||
|  |     notification_message(nfc->notifications, &sequence_error); | ||||||
|  | 
 | ||||||
|  |     widget_add_icon_element(widget, 73, 17, &I_DolphinCommon_56x48); | ||||||
|  |     widget_add_string_element( | ||||||
|  |         widget, 3, 4, AlignLeft, AlignTop, FontPrimary, "This is wrong card"); | ||||||
|  |     widget_add_string_multiline_element( | ||||||
|  |         widget, | ||||||
|  |         4, | ||||||
|  |         17, | ||||||
|  |         AlignLeft, | ||||||
|  |         AlignTop, | ||||||
|  |         FontSecondary, | ||||||
|  |         "Data management\nis only possible\nwith initial card"); | ||||||
|  |     widget_add_button_element( | ||||||
|  |         widget, GuiButtonTypeLeft, "Retry", nfc_scene_mf_classic_wrong_card_widget_callback, nfc); | ||||||
|  | 
 | ||||||
|  |     // Setup and start worker
 | ||||||
|  |     view_dispatcher_switch_to_view(nfc->view_dispatcher, NfcViewWidget); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool nfc_scene_mf_classic_wrong_card_on_event(void* context, SceneManagerEvent event) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  |     bool consumed = false; | ||||||
|  | 
 | ||||||
|  |     if(event.type == SceneManagerEventTypeCustom) { | ||||||
|  |         if(event.event == GuiButtonTypeLeft) { | ||||||
|  |             consumed = scene_manager_previous_scene(nfc->scene_manager); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     return consumed; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_scene_mf_classic_wrong_card_on_exit(void* context) { | ||||||
|  |     Nfc* nfc = context; | ||||||
|  | 
 | ||||||
|  |     widget_reset(nfc->widget); | ||||||
|  | } | ||||||
| @ -4,6 +4,9 @@ | |||||||
| enum SubmenuIndex { | enum SubmenuIndex { | ||||||
|     SubmenuIndexEmulate, |     SubmenuIndexEmulate, | ||||||
|     SubmenuIndexEditUid, |     SubmenuIndexEditUid, | ||||||
|  |     SubmenuIndexDetectReader, | ||||||
|  |     SubmenuIndexWrite, | ||||||
|  |     SubmenuIndexUpdate, | ||||||
|     SubmenuIndexRename, |     SubmenuIndexRename, | ||||||
|     SubmenuIndexDelete, |     SubmenuIndexDelete, | ||||||
|     SubmenuIndexInfo, |     SubmenuIndexInfo, | ||||||
| @ -42,6 +45,28 @@ void nfc_scene_saved_menu_on_enter(void* context) { | |||||||
|         submenu_add_item( |         submenu_add_item( | ||||||
|             submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); |             submenu, "Emulate", SubmenuIndexEmulate, nfc_scene_saved_menu_submenu_callback, nfc); | ||||||
|     } |     } | ||||||
|  |     if(nfc->dev->format == NfcDeviceSaveFormatMifareClassic) { | ||||||
|  |         if(!mf_classic_is_card_read(&nfc->dev->dev_data.mf_classic_data)) { | ||||||
|  |             submenu_add_item( | ||||||
|  |                 submenu, | ||||||
|  |                 "Detect reader", | ||||||
|  |                 SubmenuIndexDetectReader, | ||||||
|  |                 nfc_scene_saved_menu_submenu_callback, | ||||||
|  |                 nfc); | ||||||
|  |         } | ||||||
|  |         submenu_add_item( | ||||||
|  |             submenu, | ||||||
|  |             "Write To Initial Card", | ||||||
|  |             SubmenuIndexWrite, | ||||||
|  |             nfc_scene_saved_menu_submenu_callback, | ||||||
|  |             nfc); | ||||||
|  |         submenu_add_item( | ||||||
|  |             submenu, | ||||||
|  |             "Update From Initial Card", | ||||||
|  |             SubmenuIndexUpdate, | ||||||
|  |             nfc_scene_saved_menu_submenu_callback, | ||||||
|  |             nfc); | ||||||
|  |     } | ||||||
|     submenu_add_item( |     submenu_add_item( | ||||||
|         submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc); |         submenu, "Info", SubmenuIndexInfo, nfc_scene_saved_menu_submenu_callback, nfc); | ||||||
|     if(nfc->dev->shadow_file_exist) { |     if(nfc->dev->shadow_file_exist) { | ||||||
| @ -79,6 +104,15 @@ bool nfc_scene_saved_menu_on_event(void* context, SceneManagerEvent event) { | |||||||
|             } |             } | ||||||
|             DOLPHIN_DEED(DolphinDeedNfcEmulate); |             DOLPHIN_DEED(DolphinDeedNfcEmulate); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|  |         } else if(event.event == SubmenuIndexDetectReader) { | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == SubmenuIndexWrite) { | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicWrite); | ||||||
|  |             consumed = true; | ||||||
|  |         } else if(event.event == SubmenuIndexUpdate) { | ||||||
|  |             scene_manager_next_scene(nfc->scene_manager, NfcSceneMfClassicUpdate); | ||||||
|  |             consumed = true; | ||||||
|         } else if(event.event == SubmenuIndexRename) { |         } else if(event.event == SubmenuIndexRename) { | ||||||
|             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); |             scene_manager_next_scene(nfc->scene_manager, NfcSceneSaveName); | ||||||
|             consumed = true; |             consumed = true; | ||||||
|  | |||||||
| @ -53,6 +53,7 @@ bool nfc_scene_start_on_event(void* context, SceneManagerEvent event) { | |||||||
|         } else if(event.event == SubmenuIndexDetectReader) { |         } else if(event.event == SubmenuIndexDetectReader) { | ||||||
|             bool sd_exist = storage_sd_status(nfc->dev->storage) == FSE_OK; |             bool sd_exist = storage_sd_status(nfc->dev->storage) == FSE_OK; | ||||||
|             if(sd_exist) { |             if(sd_exist) { | ||||||
|  |                 nfc_device_data_clear(&nfc->dev->dev_data); | ||||||
|                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); |                 scene_manager_next_scene(nfc->scene_manager, NfcSceneDetectReader); | ||||||
|                 DOLPHIN_DEED(DolphinDeedNfcDetectReader); |                 DOLPHIN_DEED(DolphinDeedNfcDetectReader); | ||||||
|             } else { |             } else { | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ | |||||||
| #include <assets_icons.h> | #include <assets_icons.h> | ||||||
| #include <gui/elements.h> | #include <gui/elements.h> | ||||||
| 
 | 
 | ||||||
|  | #define DETECT_READER_UID_MAX_LEN (10) | ||||||
|  | 
 | ||||||
| struct DetectReader { | struct DetectReader { | ||||||
|     View* view; |     View* view; | ||||||
|     DetectReaderDoneCallback callback; |     DetectReaderDoneCallback callback; | ||||||
| @ -12,6 +14,7 @@ typedef struct { | |||||||
|     uint16_t nonces; |     uint16_t nonces; | ||||||
|     uint16_t nonces_max; |     uint16_t nonces_max; | ||||||
|     DetectReaderState state; |     DetectReaderState state; | ||||||
|  |     FuriString* uid_str; | ||||||
| } DetectReaderViewModel; | } DetectReaderViewModel; | ||||||
| 
 | 
 | ||||||
| static void detect_reader_draw_callback(Canvas* canvas, void* model) { | static void detect_reader_draw_callback(Canvas* canvas, void* model) { | ||||||
| @ -23,6 +26,10 @@ static void detect_reader_draw_callback(Canvas* canvas, void* model) { | |||||||
|     if(m->state == DetectReaderStateStart) { |     if(m->state == DetectReaderStateStart) { | ||||||
|         snprintf(text, sizeof(text), "Touch the reader"); |         snprintf(text, sizeof(text), "Touch the reader"); | ||||||
|         canvas_draw_icon(canvas, 21, 13, &I_Move_flipper_26x39); |         canvas_draw_icon(canvas, 21, 13, &I_Move_flipper_26x39); | ||||||
|  |         if(furi_string_size(m->uid_str)) { | ||||||
|  |             elements_multiline_text_aligned( | ||||||
|  |                 canvas, 64, 64, AlignCenter, AlignBottom, furi_string_get_cstr(m->uid_str)); | ||||||
|  |         } | ||||||
|     } else if(m->state == DetectReaderStateReaderDetected) { |     } else if(m->state == DetectReaderStateReaderDetected) { | ||||||
|         snprintf(text, sizeof(text), "Move the Flipper away"); |         snprintf(text, sizeof(text), "Move the Flipper away"); | ||||||
|         canvas_draw_icon(canvas, 24, 25, &I_Release_arrow_18x15); |         canvas_draw_icon(canvas, 24, 25, &I_Release_arrow_18x15); | ||||||
| @ -86,12 +93,24 @@ DetectReader* detect_reader_alloc() { | |||||||
|     view_set_input_callback(detect_reader->view, detect_reader_input_callback); |     view_set_input_callback(detect_reader->view, detect_reader_input_callback); | ||||||
|     view_set_context(detect_reader->view, detect_reader); |     view_set_context(detect_reader->view, detect_reader); | ||||||
| 
 | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         detect_reader->view, | ||||||
|  |         DetectReaderViewModel * model, | ||||||
|  |         { model->uid_str = furi_string_alloc(); }, | ||||||
|  |         false); | ||||||
|  | 
 | ||||||
|     return detect_reader; |     return detect_reader; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void detect_reader_free(DetectReader* detect_reader) { | void detect_reader_free(DetectReader* detect_reader) { | ||||||
|     furi_assert(detect_reader); |     furi_assert(detect_reader); | ||||||
| 
 | 
 | ||||||
|  |     with_view_model( | ||||||
|  |         detect_reader->view, | ||||||
|  |         DetectReaderViewModel * model, | ||||||
|  |         { furi_string_free(model->uid_str); }, | ||||||
|  |         false); | ||||||
|  | 
 | ||||||
|     view_free(detect_reader->view); |     view_free(detect_reader->view); | ||||||
|     free(detect_reader); |     free(detect_reader); | ||||||
| } | } | ||||||
| @ -106,6 +125,7 @@ void detect_reader_reset(DetectReader* detect_reader) { | |||||||
|             model->nonces = 0; |             model->nonces = 0; | ||||||
|             model->nonces_max = 0; |             model->nonces_max = 0; | ||||||
|             model->state = DetectReaderStateStart; |             model->state = DetectReaderStateStart; | ||||||
|  |             furi_string_reset(model->uid_str); | ||||||
|         }, |         }, | ||||||
|         false); |         false); | ||||||
| } | } | ||||||
| @ -152,3 +172,19 @@ void detect_reader_set_state(DetectReader* detect_reader, DetectReaderState stat | |||||||
|     with_view_model( |     with_view_model( | ||||||
|         detect_reader->view, DetectReaderViewModel * model, { model->state = state; }, true); |         detect_reader->view, DetectReaderViewModel * model, { model->state = state; }, true); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void detect_reader_set_uid(DetectReader* detect_reader, uint8_t* uid, uint8_t uid_len) { | ||||||
|  |     furi_assert(detect_reader); | ||||||
|  |     furi_assert(uid); | ||||||
|  |     furi_assert(uid_len < DETECT_READER_UID_MAX_LEN); | ||||||
|  |     with_view_model( | ||||||
|  |         detect_reader->view, | ||||||
|  |         DetectReaderViewModel * model, | ||||||
|  |         { | ||||||
|  |             furi_string_set_str(model->uid_str, "UID:"); | ||||||
|  |             for(size_t i = 0; i < uid_len; i++) { | ||||||
|  |                 furi_string_cat_printf(model->uid_str, " %02X", uid[i]); | ||||||
|  |             } | ||||||
|  |         }, | ||||||
|  |         true); | ||||||
|  | } | ||||||
|  | |||||||
| @ -32,3 +32,5 @@ void detect_reader_set_nonces_max(DetectReader* detect_reader, uint16_t nonces_m | |||||||
| void detect_reader_set_nonces_collected(DetectReader* detect_reader, uint16_t nonces_collected); | void detect_reader_set_nonces_collected(DetectReader* detect_reader, uint16_t nonces_collected); | ||||||
| 
 | 
 | ||||||
| void detect_reader_set_state(DetectReader* detect_reader, DetectReaderState state); | void detect_reader_set_state(DetectReader* detect_reader, DetectReaderState state); | ||||||
|  | 
 | ||||||
|  | void detect_reader_set_uid(DetectReader* detect_reader, uint8_t* uid, uint8_t uid_len); | ||||||
|  | |||||||
| @ -620,6 +620,10 @@ uint16_t furi_hal_nfc_bitstream_to_data_and_parity( | |||||||
|     uint16_t in_buff_bits, |     uint16_t in_buff_bits, | ||||||
|     uint8_t* out_data, |     uint8_t* out_data, | ||||||
|     uint8_t* out_parity) { |     uint8_t* out_parity) { | ||||||
|  |     if(in_buff_bits < 8) { | ||||||
|  |         out_data[0] = in_buff[0]; | ||||||
|  |         return in_buff_bits; | ||||||
|  |     } | ||||||
|     if(in_buff_bits % 9 != 0) { |     if(in_buff_bits % 9 != 0) { | ||||||
|         return 0; |         return 0; | ||||||
|     } |     } | ||||||
| @ -635,7 +639,7 @@ uint16_t furi_hal_nfc_bitstream_to_data_and_parity( | |||||||
|         bit_processed += 9; |         bit_processed += 9; | ||||||
|         curr_byte++; |         curr_byte++; | ||||||
|     } |     } | ||||||
|     return curr_byte; |     return curr_byte * 8; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { | bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { | ||||||
| @ -692,8 +696,8 @@ bool furi_hal_nfc_tx_rx(FuriHalNfcTxRxContext* tx_rx, uint16_t timeout_ms) { | |||||||
| 
 | 
 | ||||||
|     if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw || |     if(tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRaw || | ||||||
|        tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRxRaw) { |        tx_rx->tx_rx_type == FuriHalNfcTxRxTypeRxRaw) { | ||||||
|         tx_rx->rx_bits = 8 * furi_hal_nfc_bitstream_to_data_and_parity( |         tx_rx->rx_bits = furi_hal_nfc_bitstream_to_data_and_parity( | ||||||
|                                  temp_rx_buff, *temp_rx_bits, tx_rx->rx_data, tx_rx->rx_parity); |             temp_rx_buff, *temp_rx_bits, tx_rx->rx_data, tx_rx->rx_parity); | ||||||
|     } else { |     } else { | ||||||
|         memcpy(tx_rx->rx_data, temp_rx_buff, MIN(*temp_rx_bits / 8, FURI_HAL_NFC_DATA_BUFF_SIZE)); |         memcpy(tx_rx->rx_data, temp_rx_buff, MIN(*temp_rx_bits / 8, FURI_HAL_NFC_DATA_BUFF_SIZE)); | ||||||
|         tx_rx->rx_bits = *temp_rx_bits; |         tx_rx->rx_bits = *temp_rx_bits; | ||||||
|  | |||||||
| @ -201,10 +201,17 @@ NfcProtocol | |||||||
| 
 | 
 | ||||||
| FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance) { | FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance) { | ||||||
|     furi_assert(instance); |     furi_assert(instance); | ||||||
| 
 |     instance->nfc_data = reader_analyzer_nfc_data[ReaderAnalyzerNfcDataMfClassic]; | ||||||
|     return &instance->nfc_data; |     return &instance->nfc_data; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data) { | ||||||
|  |     furi_assert(instance); | ||||||
|  |     furi_assert(nfc_data); | ||||||
|  | 
 | ||||||
|  |     memcpy(&instance->nfc_data, nfc_data, sizeof(FuriHalNfcDevData)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void reader_analyzer_write( | static void reader_analyzer_write( | ||||||
|     ReaderAnalyzer* instance, |     ReaderAnalyzer* instance, | ||||||
|     uint8_t* data, |     uint8_t* data, | ||||||
|  | |||||||
| @ -35,6 +35,8 @@ NfcProtocol | |||||||
| 
 | 
 | ||||||
| FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance); | FuriHalNfcDevData* reader_analyzer_get_nfc_data(ReaderAnalyzer* instance); | ||||||
| 
 | 
 | ||||||
|  | void reader_analyzer_set_nfc_data(ReaderAnalyzer* instance, FuriHalNfcDevData* nfc_data); | ||||||
|  | 
 | ||||||
| void reader_analyzer_prepare_tx_rx( | void reader_analyzer_prepare_tx_rx( | ||||||
|     ReaderAnalyzer* instance, |     ReaderAnalyzer* instance, | ||||||
|     FuriHalNfcTxRxContext* tx_rx, |     FuriHalNfcTxRxContext* tx_rx, | ||||||
|  | |||||||
| @ -1122,6 +1122,13 @@ static bool nfc_device_load_data(NfcDevice* dev, FuriString* path, bool show_dia | |||||||
|         if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break; |         if(!flipper_format_read_hex(file, "UID", data->uid, data->uid_len)) break; | ||||||
|         if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; |         if(!flipper_format_read_hex(file, "ATQA", data->atqa, 2)) break; | ||||||
|         if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; |         if(!flipper_format_read_hex(file, "SAK", &data->sak, 1)) break; | ||||||
|  |         // Load CUID
 | ||||||
|  |         uint8_t* cuid_start = data->uid; | ||||||
|  |         if(data->uid_len == 7) { | ||||||
|  |             cuid_start = &data->uid[3]; | ||||||
|  |         } | ||||||
|  |         data->cuid = (cuid_start[0] << 24) | (cuid_start[1] << 16) | (cuid_start[2] << 8) | | ||||||
|  |                      (cuid_start[3]); | ||||||
|         // Parse other data
 |         // Parse other data
 | ||||||
|         if(dev->format == NfcDeviceSaveFormatMifareUl) { |         if(dev->format == NfcDeviceSaveFormatMifareUl) { | ||||||
|             if(!nfc_device_load_mifare_ul_data(file, dev)) break; |             if(!nfc_device_load_mifare_ul_data(file, dev)) break; | ||||||
|  | |||||||
| @ -99,6 +99,10 @@ int32_t nfc_worker_task(void* context) { | |||||||
|         nfc_worker_emulate_mf_ultralight(nfc_worker); |         nfc_worker_emulate_mf_ultralight(nfc_worker); | ||||||
|     } else if(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { |     } else if(nfc_worker->state == NfcWorkerStateMfClassicEmulate) { | ||||||
|         nfc_worker_emulate_mf_classic(nfc_worker); |         nfc_worker_emulate_mf_classic(nfc_worker); | ||||||
|  |     } else if(nfc_worker->state == NfcWorkerStateMfClassicWrite) { | ||||||
|  |         nfc_worker_write_mf_classic(nfc_worker); | ||||||
|  |     } else if(nfc_worker->state == NfcWorkerStateMfClassicUpdate) { | ||||||
|  |         nfc_worker_update_mf_classic(nfc_worker); | ||||||
|     } else if(nfc_worker->state == NfcWorkerStateReadMfUltralightReadAuth) { |     } else if(nfc_worker->state == NfcWorkerStateReadMfUltralightReadAuth) { | ||||||
|         nfc_worker_mf_ultralight_read_auth(nfc_worker); |         nfc_worker_mf_ultralight_read_auth(nfc_worker); | ||||||
|     } else if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { |     } else if(nfc_worker->state == NfcWorkerStateMfClassicDictAttack) { | ||||||
| @ -666,6 +670,144 @@ void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker) { | |||||||
|     rfal_platform_spi_release(); |     rfal_platform_spi_release(); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | void nfc_worker_write_mf_classic(NfcWorker* nfc_worker) { | ||||||
|  |     FuriHalNfcTxRxContext tx_rx = {}; | ||||||
|  |     bool card_found_notified = false; | ||||||
|  |     FuriHalNfcDevData nfc_data = {}; | ||||||
|  |     MfClassicData* src_data = &nfc_worker->dev_data->mf_classic_data; | ||||||
|  |     MfClassicData dest_data = *src_data; | ||||||
|  | 
 | ||||||
|  |     while(nfc_worker->state == NfcWorkerStateMfClassicWrite) { | ||||||
|  |         if(furi_hal_nfc_detect(&nfc_data, 200)) { | ||||||
|  |             if(!card_found_notified) { | ||||||
|  |                 nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); | ||||||
|  |                 card_found_notified = true; | ||||||
|  |             } | ||||||
|  |             furi_hal_nfc_sleep(); | ||||||
|  | 
 | ||||||
|  |             FURI_LOG_I(TAG, "Check low level nfc data"); | ||||||
|  |             if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData))) { | ||||||
|  |                 FURI_LOG_E(TAG, "Wrong card"); | ||||||
|  |                 nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             FURI_LOG_I(TAG, "Check mf classic type"); | ||||||
|  |             MfClassicType type = | ||||||
|  |                 mf_classic_get_classic_type(nfc_data.atqa[0], nfc_data.atqa[1], nfc_data.sak); | ||||||
|  |             if(type != nfc_worker->dev_data->mf_classic_data.type) { | ||||||
|  |                 FURI_LOG_E(TAG, "Wrong mf classic type"); | ||||||
|  |                 nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Set blocks not read
 | ||||||
|  |             mf_classic_set_sector_data_not_read(&dest_data); | ||||||
|  |             FURI_LOG_I(TAG, "Updating card sectors"); | ||||||
|  |             uint8_t total_sectors = mf_classic_get_total_sectors_num(type); | ||||||
|  |             bool write_success = true; | ||||||
|  |             for(uint8_t i = 0; i < total_sectors; i++) { | ||||||
|  |                 FURI_LOG_I(TAG, "Reading sector %d", i); | ||||||
|  |                 mf_classic_read_sector(&tx_rx, &dest_data, i); | ||||||
|  |                 bool old_data_read = mf_classic_is_sector_data_read(src_data, i); | ||||||
|  |                 bool new_data_read = mf_classic_is_sector_data_read(&dest_data, i); | ||||||
|  |                 if(old_data_read != new_data_read) { | ||||||
|  |                     FURI_LOG_E(TAG, "Failed to update sector %d", i); | ||||||
|  |                     write_success = false; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |                 if(nfc_worker->state != NfcWorkerStateMfClassicWrite) break; | ||||||
|  |                 if(!mf_classic_write_sector(&tx_rx, &dest_data, src_data, i)) { | ||||||
|  |                     FURI_LOG_E(TAG, "Failed to write %d sector", i); | ||||||
|  |                     write_success = false; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             if(nfc_worker->state != NfcWorkerStateMfClassicWrite) break; | ||||||
|  |             if(write_success) { | ||||||
|  |                 nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); | ||||||
|  |                 break; | ||||||
|  |             } else { | ||||||
|  |                 nfc_worker->callback(NfcWorkerEventFail, nfc_worker->context); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |         } else { | ||||||
|  |             if(card_found_notified) { | ||||||
|  |                 nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); | ||||||
|  |                 card_found_notified = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         furi_delay_ms(300); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void nfc_worker_update_mf_classic(NfcWorker* nfc_worker) { | ||||||
|  |     FuriHalNfcTxRxContext tx_rx = {}; | ||||||
|  |     bool card_found_notified = false; | ||||||
|  |     FuriHalNfcDevData nfc_data = {}; | ||||||
|  |     MfClassicData* old_data = &nfc_worker->dev_data->mf_classic_data; | ||||||
|  |     MfClassicData new_data = *old_data; | ||||||
|  | 
 | ||||||
|  |     while(nfc_worker->state == NfcWorkerStateMfClassicUpdate) { | ||||||
|  |         if(furi_hal_nfc_detect(&nfc_data, 200)) { | ||||||
|  |             if(!card_found_notified) { | ||||||
|  |                 nfc_worker->callback(NfcWorkerEventCardDetected, nfc_worker->context); | ||||||
|  |                 card_found_notified = true; | ||||||
|  |             } | ||||||
|  |             furi_hal_nfc_sleep(); | ||||||
|  | 
 | ||||||
|  |             FURI_LOG_I(TAG, "Check low level nfc data"); | ||||||
|  |             if(memcmp(&nfc_data, &nfc_worker->dev_data->nfc_data, sizeof(FuriHalNfcDevData))) { | ||||||
|  |                 FURI_LOG_E(TAG, "Low level nfc data mismatch"); | ||||||
|  |                 nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             FURI_LOG_I(TAG, "Check MF classic type"); | ||||||
|  |             MfClassicType type = | ||||||
|  |                 mf_classic_get_classic_type(nfc_data.atqa[0], nfc_data.atqa[1], nfc_data.sak); | ||||||
|  |             if(type != nfc_worker->dev_data->mf_classic_data.type) { | ||||||
|  |                 FURI_LOG_E(TAG, "MF classic type mismatch"); | ||||||
|  |                 nfc_worker->callback(NfcWorkerEventWrongCard, nfc_worker->context); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Set blocks not read
 | ||||||
|  |             mf_classic_set_sector_data_not_read(&new_data); | ||||||
|  |             FURI_LOG_I(TAG, "Updating card sectors"); | ||||||
|  |             uint8_t total_sectors = mf_classic_get_total_sectors_num(type); | ||||||
|  |             bool update_success = true; | ||||||
|  |             for(uint8_t i = 0; i < total_sectors; i++) { | ||||||
|  |                 FURI_LOG_I(TAG, "Reading sector %d", i); | ||||||
|  |                 mf_classic_read_sector(&tx_rx, &new_data, i); | ||||||
|  |                 bool old_data_read = mf_classic_is_sector_data_read(old_data, i); | ||||||
|  |                 bool new_data_read = mf_classic_is_sector_data_read(&new_data, i); | ||||||
|  |                 if(old_data_read != new_data_read) { | ||||||
|  |                     FURI_LOG_E(TAG, "Failed to update sector %d", i); | ||||||
|  |                     update_success = false; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |                 if(nfc_worker->state != NfcWorkerStateMfClassicUpdate) break; | ||||||
|  |             } | ||||||
|  |             if(nfc_worker->state != NfcWorkerStateMfClassicUpdate) break; | ||||||
|  | 
 | ||||||
|  |             // Check updated data
 | ||||||
|  |             if(update_success) { | ||||||
|  |                 *old_data = new_data; | ||||||
|  |                 nfc_worker->callback(NfcWorkerEventSuccess, nfc_worker->context); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             if(card_found_notified) { | ||||||
|  |                 nfc_worker->callback(NfcWorkerEventNoCardDetected, nfc_worker->context); | ||||||
|  |                 card_found_notified = false; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         furi_delay_ms(300); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
| void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker) { | void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker) { | ||||||
|     furi_assert(nfc_worker); |     furi_assert(nfc_worker); | ||||||
|     furi_assert(nfc_worker->callback); |     furi_assert(nfc_worker->callback); | ||||||
| @ -758,7 +900,13 @@ void nfc_worker_analyze_reader(NfcWorker* nfc_worker) { | |||||||
|     FuriHalNfcTxRxContext tx_rx = {}; |     FuriHalNfcTxRxContext tx_rx = {}; | ||||||
| 
 | 
 | ||||||
|     ReaderAnalyzer* reader_analyzer = nfc_worker->reader_analyzer; |     ReaderAnalyzer* reader_analyzer = nfc_worker->reader_analyzer; | ||||||
|     FuriHalNfcDevData* nfc_data = reader_analyzer_get_nfc_data(reader_analyzer); |     FuriHalNfcDevData* nfc_data = NULL; | ||||||
|  |     if(nfc_worker->dev_data->protocol == NfcDeviceProtocolMifareClassic) { | ||||||
|  |         nfc_data = &nfc_worker->dev_data->nfc_data; | ||||||
|  |         reader_analyzer_set_nfc_data(reader_analyzer, nfc_data); | ||||||
|  |     } else { | ||||||
|  |         nfc_data = reader_analyzer_get_nfc_data(reader_analyzer); | ||||||
|  |     } | ||||||
|     MfClassicEmulator emulator = { |     MfClassicEmulator emulator = { | ||||||
|         .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4), |         .cuid = nfc_util_bytes2num(&nfc_data->uid[nfc_data->uid_len - 4], 4), | ||||||
|         .data = nfc_worker->dev_data->mf_classic_data, |         .data = nfc_worker->dev_data->mf_classic_data, | ||||||
|  | |||||||
| @ -14,6 +14,8 @@ typedef enum { | |||||||
|     NfcWorkerStateUidEmulate, |     NfcWorkerStateUidEmulate, | ||||||
|     NfcWorkerStateMfUltralightEmulate, |     NfcWorkerStateMfUltralightEmulate, | ||||||
|     NfcWorkerStateMfClassicEmulate, |     NfcWorkerStateMfClassicEmulate, | ||||||
|  |     NfcWorkerStateMfClassicWrite, | ||||||
|  |     NfcWorkerStateMfClassicUpdate, | ||||||
|     NfcWorkerStateReadMfUltralightReadAuth, |     NfcWorkerStateReadMfUltralightReadAuth, | ||||||
|     NfcWorkerStateMfClassicDictAttack, |     NfcWorkerStateMfClassicDictAttack, | ||||||
|     NfcWorkerStateAnalyzeReader, |     NfcWorkerStateAnalyzeReader, | ||||||
| @ -48,13 +50,16 @@ typedef enum { | |||||||
|     NfcWorkerEventNoCardDetected, |     NfcWorkerEventNoCardDetected, | ||||||
|     NfcWorkerEventWrongCardDetected, |     NfcWorkerEventWrongCardDetected, | ||||||
| 
 | 
 | ||||||
|     // Mifare Classic events
 |     // Read Mifare Classic events
 | ||||||
|     NfcWorkerEventNoDictFound, |     NfcWorkerEventNoDictFound, | ||||||
|     NfcWorkerEventNewSector, |     NfcWorkerEventNewSector, | ||||||
|     NfcWorkerEventNewDictKeyBatch, |     NfcWorkerEventNewDictKeyBatch, | ||||||
|     NfcWorkerEventFoundKeyA, |     NfcWorkerEventFoundKeyA, | ||||||
|     NfcWorkerEventFoundKeyB, |     NfcWorkerEventFoundKeyB, | ||||||
| 
 | 
 | ||||||
|  |     // Write Mifare Classic events
 | ||||||
|  |     NfcWorkerEventWrongCard, | ||||||
|  | 
 | ||||||
|     // Detect Reader events
 |     // Detect Reader events
 | ||||||
|     NfcWorkerEventDetectReaderDetected, |     NfcWorkerEventDetectReaderDetected, | ||||||
|     NfcWorkerEventDetectReaderLost, |     NfcWorkerEventDetectReaderLost, | ||||||
|  | |||||||
| @ -41,6 +41,10 @@ void nfc_worker_emulate_mf_ultralight(NfcWorker* nfc_worker); | |||||||
| 
 | 
 | ||||||
| void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker); | void nfc_worker_emulate_mf_classic(NfcWorker* nfc_worker); | ||||||
| 
 | 
 | ||||||
|  | void nfc_worker_write_mf_classic(NfcWorker* nfc_worker); | ||||||
|  | 
 | ||||||
|  | void nfc_worker_update_mf_classic(NfcWorker* nfc_worker); | ||||||
|  | 
 | ||||||
| void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker); | void nfc_worker_mf_classic_dict_attack(NfcWorker* nfc_worker); | ||||||
| 
 | 
 | ||||||
| void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker); | void nfc_worker_mf_ultralight_read_auth(NfcWorker* nfc_worker); | ||||||
|  | |||||||
| @ -73,3 +73,55 @@ uint32_t prng_successor(uint32_t x, uint32_t n) { | |||||||
| 
 | 
 | ||||||
|     return SWAPENDIAN(x); |     return SWAPENDIAN(x); | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | void crypto1_decrypt( | ||||||
|  |     Crypto1* crypto, | ||||||
|  |     uint8_t* encrypted_data, | ||||||
|  |     uint16_t encrypted_data_bits, | ||||||
|  |     uint8_t* decrypted_data) { | ||||||
|  |     furi_assert(crypto); | ||||||
|  |     furi_assert(encrypted_data); | ||||||
|  |     furi_assert(decrypted_data); | ||||||
|  | 
 | ||||||
|  |     if(encrypted_data_bits < 8) { | ||||||
|  |         uint8_t decrypted_byte = 0; | ||||||
|  |         decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 0)) << 0; | ||||||
|  |         decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 1)) << 1; | ||||||
|  |         decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 2)) << 2; | ||||||
|  |         decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 3)) << 3; | ||||||
|  |         decrypted_data[0] = decrypted_byte; | ||||||
|  |     } else { | ||||||
|  |         for(size_t i = 0; i < encrypted_data_bits / 8; i++) { | ||||||
|  |             decrypted_data[i] = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void crypto1_encrypt( | ||||||
|  |     Crypto1* crypto, | ||||||
|  |     uint8_t* keystream, | ||||||
|  |     uint8_t* plain_data, | ||||||
|  |     uint16_t plain_data_bits, | ||||||
|  |     uint8_t* encrypted_data, | ||||||
|  |     uint8_t* encrypted_parity) { | ||||||
|  |     furi_assert(crypto); | ||||||
|  |     furi_assert(plain_data); | ||||||
|  |     furi_assert(encrypted_data); | ||||||
|  |     furi_assert(encrypted_parity); | ||||||
|  | 
 | ||||||
|  |     if(plain_data_bits < 8) { | ||||||
|  |         encrypted_data[0] = 0; | ||||||
|  |         for(size_t i = 0; i < plain_data_bits; i++) { | ||||||
|  |             encrypted_data[0] |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i; | ||||||
|  |         } | ||||||
|  |     } else { | ||||||
|  |         memset(encrypted_parity, 0, plain_data_bits / 8 + 1); | ||||||
|  |         for(uint8_t i = 0; i < plain_data_bits / 8; i++) { | ||||||
|  |             encrypted_data[i] = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^ | ||||||
|  |                                 plain_data[i]; | ||||||
|  |             encrypted_parity[i / 8] |= | ||||||
|  |                 (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01) | ||||||
|  |                  << (7 - (i & 0x0007))); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | |||||||
| @ -21,3 +21,17 @@ uint32_t crypto1_word(Crypto1* crypto1, uint32_t in, int is_encrypted); | |||||||
| uint32_t crypto1_filter(uint32_t in); | uint32_t crypto1_filter(uint32_t in); | ||||||
| 
 | 
 | ||||||
| uint32_t prng_successor(uint32_t x, uint32_t n); | uint32_t prng_successor(uint32_t x, uint32_t n); | ||||||
|  | 
 | ||||||
|  | void crypto1_decrypt( | ||||||
|  |     Crypto1* crypto, | ||||||
|  |     uint8_t* encrypted_data, | ||||||
|  |     uint16_t encrypted_data_bits, | ||||||
|  |     uint8_t* decrypted_data); | ||||||
|  | 
 | ||||||
|  | void crypto1_encrypt( | ||||||
|  |     Crypto1* crypto, | ||||||
|  |     uint8_t* keystream, | ||||||
|  |     uint8_t* plain_data, | ||||||
|  |     uint16_t plain_data_bits, | ||||||
|  |     uint8_t* encrypted_data, | ||||||
|  |     uint8_t* encrypted_parity); | ||||||
|  | |||||||
| @ -9,21 +9,8 @@ | |||||||
| 
 | 
 | ||||||
| #define MF_CLASSIC_AUTH_KEY_A_CMD (0x60U) | #define MF_CLASSIC_AUTH_KEY_A_CMD (0x60U) | ||||||
| #define MF_CLASSIC_AUTH_KEY_B_CMD (0x61U) | #define MF_CLASSIC_AUTH_KEY_B_CMD (0x61U) | ||||||
| #define MF_CLASSIC_READ_SECT_CMD (0x30) | #define MF_CLASSIC_READ_BLOCK_CMD (0x30) | ||||||
| 
 | #define MF_CLASSIC_WRITE_BLOCK_CMD (0xA0) | ||||||
| typedef enum { |  | ||||||
|     MfClassicActionDataRead, |  | ||||||
|     MfClassicActionDataWrite, |  | ||||||
|     MfClassicActionDataInc, |  | ||||||
|     MfClassicActionDataDec, |  | ||||||
| 
 |  | ||||||
|     MfClassicActionKeyARead, |  | ||||||
|     MfClassicActionKeyAWrite, |  | ||||||
|     MfClassicActionKeyBRead, |  | ||||||
|     MfClassicActionKeyBWrite, |  | ||||||
|     MfClassicActionACRead, |  | ||||||
|     MfClassicActionACWrite, |  | ||||||
| } MfClassicAction; |  | ||||||
| 
 | 
 | ||||||
| const char* mf_classic_get_type_str(MfClassicType type) { | const char* mf_classic_get_type_str(MfClassicType type) { | ||||||
|     if(type == MfClassicType1k) { |     if(type == MfClassicType1k) { | ||||||
| @ -122,6 +109,24 @@ void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassic | |||||||
|     FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32); |     FURI_BIT_SET(data->block_read_mask[block_num / 32], block_num % 32); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool mf_classic_is_sector_data_read(MfClassicData* data, uint8_t sector_num) { | ||||||
|  |     furi_assert(data); | ||||||
|  | 
 | ||||||
|  |     uint8_t first_block = mf_classic_get_first_block_num_of_sector(sector_num); | ||||||
|  |     uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sector_num); | ||||||
|  |     bool data_read = true; | ||||||
|  |     for(size_t i = first_block; i < first_block + total_blocks; i++) { | ||||||
|  |         data_read &= mf_classic_is_block_read(data, i); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return data_read; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void mf_classic_set_sector_data_not_read(MfClassicData* data) { | ||||||
|  |     furi_assert(data); | ||||||
|  |     memset(data->block_read_mask, 0, sizeof(data->block_read_mask)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) { | bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type) { | ||||||
|     furi_assert(data); |     furi_assert(data); | ||||||
| 
 | 
 | ||||||
| @ -190,6 +195,9 @@ void mf_classic_get_read_sectors_and_keys( | |||||||
|     uint8_t* sectors_read, |     uint8_t* sectors_read, | ||||||
|     uint8_t* keys_found) { |     uint8_t* keys_found) { | ||||||
|     furi_assert(data); |     furi_assert(data); | ||||||
|  |     furi_assert(sectors_read); | ||||||
|  |     furi_assert(keys_found); | ||||||
|  | 
 | ||||||
|     *sectors_read = 0; |     *sectors_read = 0; | ||||||
|     *keys_found = 0; |     *keys_found = 0; | ||||||
|     uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); |     uint8_t sectors_total = mf_classic_get_total_sectors_num(data->type); | ||||||
| @ -225,12 +233,12 @@ bool mf_classic_is_card_read(MfClassicData* data) { | |||||||
|     return card_read; |     return card_read; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool mf_classic_is_allowed_access_sector_trailer( | bool mf_classic_is_allowed_access_sector_trailer( | ||||||
|     MfClassicEmulator* emulator, |     MfClassicData* data, | ||||||
|     uint8_t block_num, |     uint8_t block_num, | ||||||
|     MfClassicKey key, |     MfClassicKey key, | ||||||
|     MfClassicAction action) { |     MfClassicAction action) { | ||||||
|     uint8_t* sector_trailer = emulator->data.block[block_num].value; |     uint8_t* sector_trailer = data->block[block_num].value; | ||||||
|     uint8_t AC = ((sector_trailer[7] >> 5) & 0x04) | ((sector_trailer[8] >> 2) & 0x02) | |     uint8_t AC = ((sector_trailer[7] >> 5) & 0x04) | ((sector_trailer[8] >> 2) & 0x02) | | ||||||
|                  ((sector_trailer[8] >> 7) & 0x01); |                  ((sector_trailer[8] >> 7) & 0x01); | ||||||
|     switch(action) { |     switch(action) { | ||||||
| @ -266,13 +274,13 @@ static bool mf_classic_is_allowed_access_sector_trailer( | |||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool mf_classic_is_allowed_access_data_block( | bool mf_classic_is_allowed_access_data_block( | ||||||
|     MfClassicEmulator* emulator, |     MfClassicData* data, | ||||||
|     uint8_t block_num, |     uint8_t block_num, | ||||||
|     MfClassicKey key, |     MfClassicKey key, | ||||||
|     MfClassicAction action) { |     MfClassicAction action) { | ||||||
|     uint8_t* sector_trailer = |     uint8_t* sector_trailer = | ||||||
|         emulator->data.block[mf_classic_get_sector_trailer_num_by_block(block_num)].value; |         data->block[mf_classic_get_sector_trailer_num_by_block(block_num)].value; | ||||||
| 
 | 
 | ||||||
|     uint8_t sector_block; |     uint8_t sector_block; | ||||||
|     if(block_num <= 128) { |     if(block_num <= 128) { | ||||||
| @ -336,9 +344,10 @@ static bool mf_classic_is_allowed_access( | |||||||
|     MfClassicKey key, |     MfClassicKey key, | ||||||
|     MfClassicAction action) { |     MfClassicAction action) { | ||||||
|     if(mf_classic_is_sector_trailer(block_num)) { |     if(mf_classic_is_sector_trailer(block_num)) { | ||||||
|         return mf_classic_is_allowed_access_sector_trailer(emulator, block_num, key, action); |         return mf_classic_is_allowed_access_sector_trailer( | ||||||
|  |             &emulator->data, block_num, key, action); | ||||||
|     } else { |     } else { | ||||||
|         return mf_classic_is_allowed_access_data_block(emulator, block_num, key, action); |         return mf_classic_is_allowed_access_data_block(&emulator->data, block_num, key, action); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -514,25 +523,17 @@ bool mf_classic_read_block( | |||||||
|     furi_assert(block); |     furi_assert(block); | ||||||
| 
 | 
 | ||||||
|     bool read_block_success = false; |     bool read_block_success = false; | ||||||
|     uint8_t plain_cmd[4] = {MF_CLASSIC_READ_SECT_CMD, block_num, 0x00, 0x00}; |     uint8_t plain_cmd[4] = {MF_CLASSIC_READ_BLOCK_CMD, block_num, 0x00, 0x00}; | ||||||
|     nfca_append_crc16(plain_cmd, 2); |     nfca_append_crc16(plain_cmd, 2); | ||||||
|     memset(tx_rx->tx_data, 0, sizeof(tx_rx->tx_data)); |  | ||||||
|     memset(tx_rx->tx_parity, 0, sizeof(tx_rx->tx_parity)); |  | ||||||
| 
 | 
 | ||||||
|     for(uint8_t i = 0; i < 4; i++) { |     crypto1_encrypt(crypto, NULL, plain_cmd, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); | ||||||
|         tx_rx->tx_data[i] = crypto1_byte(crypto, 0x00, 0) ^ plain_cmd[i]; |  | ||||||
|         tx_rx->tx_parity[0] |= |  | ||||||
|             ((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_cmd[i])) & 0x01) << (7 - i); |  | ||||||
|     } |  | ||||||
|     tx_rx->tx_bits = 4 * 9; |     tx_rx->tx_bits = 4 * 9; | ||||||
|     tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; |     tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; | ||||||
| 
 | 
 | ||||||
|     if(furi_hal_nfc_tx_rx(tx_rx, 50)) { |     if(furi_hal_nfc_tx_rx(tx_rx, 50)) { | ||||||
|         if(tx_rx->rx_bits == 8 * (MF_CLASSIC_BLOCK_SIZE + 2)) { |         if(tx_rx->rx_bits == 8 * (MF_CLASSIC_BLOCK_SIZE + 2)) { | ||||||
|             uint8_t block_received[MF_CLASSIC_BLOCK_SIZE + 2]; |             uint8_t block_received[MF_CLASSIC_BLOCK_SIZE + 2]; | ||||||
|             for(uint8_t i = 0; i < MF_CLASSIC_BLOCK_SIZE + 2; i++) { |             crypto1_decrypt(crypto, tx_rx->rx_data, tx_rx->rx_bits, block_received); | ||||||
|                 block_received[i] = crypto1_byte(crypto, 0, 0) ^ tx_rx->rx_data[i]; |  | ||||||
|             } |  | ||||||
|             uint16_t crc_calc = nfca_get_crc16(block_received, MF_CLASSIC_BLOCK_SIZE); |             uint16_t crc_calc = nfca_get_crc16(block_received, MF_CLASSIC_BLOCK_SIZE); | ||||||
|             uint16_t crc_received = (block_received[MF_CLASSIC_BLOCK_SIZE + 1] << 8) | |             uint16_t crc_received = (block_received[MF_CLASSIC_BLOCK_SIZE + 1] << 8) | | ||||||
|                                     block_received[MF_CLASSIC_BLOCK_SIZE]; |                                     block_received[MF_CLASSIC_BLOCK_SIZE]; | ||||||
| @ -754,49 +755,6 @@ uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data | |||||||
|     return sectors_read; |     return sectors_read; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| void mf_crypto1_decrypt( |  | ||||||
|     Crypto1* crypto, |  | ||||||
|     uint8_t* encrypted_data, |  | ||||||
|     uint16_t encrypted_data_bits, |  | ||||||
|     uint8_t* decrypted_data) { |  | ||||||
|     if(encrypted_data_bits < 8) { |  | ||||||
|         uint8_t decrypted_byte = 0; |  | ||||||
|         decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 0)) << 0; |  | ||||||
|         decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 1)) << 1; |  | ||||||
|         decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 2)) << 2; |  | ||||||
|         decrypted_byte |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(encrypted_data[0], 3)) << 3; |  | ||||||
|         decrypted_data[0] = decrypted_byte; |  | ||||||
|     } else { |  | ||||||
|         for(size_t i = 0; i < encrypted_data_bits / 8; i++) { |  | ||||||
|             decrypted_data[i] = crypto1_byte(crypto, 0, 0) ^ encrypted_data[i]; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| void mf_crypto1_encrypt( |  | ||||||
|     Crypto1* crypto, |  | ||||||
|     uint8_t* keystream, |  | ||||||
|     uint8_t* plain_data, |  | ||||||
|     uint16_t plain_data_bits, |  | ||||||
|     uint8_t* encrypted_data, |  | ||||||
|     uint8_t* encrypted_parity) { |  | ||||||
|     if(plain_data_bits < 8) { |  | ||||||
|         encrypted_data[0] = 0; |  | ||||||
|         for(size_t i = 0; i < plain_data_bits; i++) { |  | ||||||
|             encrypted_data[0] |= (crypto1_bit(crypto, 0, 0) ^ FURI_BIT(plain_data[0], i)) << i; |  | ||||||
|         } |  | ||||||
|     } else { |  | ||||||
|         memset(encrypted_parity, 0, plain_data_bits / 8 + 1); |  | ||||||
|         for(uint8_t i = 0; i < plain_data_bits / 8; i++) { |  | ||||||
|             encrypted_data[i] = crypto1_byte(crypto, keystream ? keystream[i] : 0, 0) ^ |  | ||||||
|                                 plain_data[i]; |  | ||||||
|             encrypted_parity[i / 8] |= |  | ||||||
|                 (((crypto1_filter(crypto->odd) ^ nfc_util_odd_parity8(plain_data[i])) & 0x01) |  | ||||||
|                  << (7 - (i & 0x0007))); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx) { | bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx) { | ||||||
|     furi_assert(emulator); |     furi_assert(emulator); | ||||||
|     furi_assert(tx_rx); |     furi_assert(tx_rx); | ||||||
| @ -819,7 +777,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ | |||||||
|                     tx_rx->rx_bits); |                     tx_rx->rx_bits); | ||||||
|                 break; |                 break; | ||||||
|             } |             } | ||||||
|             mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); |             crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if(plain_data[0] == 0x50 && plain_data[1] == 0x00) { |         if(plain_data[0] == 0x50 && plain_data[1] == 0x00) { | ||||||
| @ -857,7 +815,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ | |||||||
|                 tx_rx->tx_bits = sizeof(nt) * 8; |                 tx_rx->tx_bits = sizeof(nt) * 8; | ||||||
|                 tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; |                 tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; | ||||||
|             } else { |             } else { | ||||||
|                 mf_crypto1_encrypt( |                 crypto1_encrypt( | ||||||
|                     &emulator->crypto, |                     &emulator->crypto, | ||||||
|                     nt_keystream, |                     nt_keystream, | ||||||
|                     nt, |                     nt, | ||||||
| @ -904,7 +862,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ | |||||||
|             uint32_t ans = prng_successor(nonce, 96); |             uint32_t ans = prng_successor(nonce, 96); | ||||||
|             uint8_t responce[4] = {}; |             uint8_t responce[4] = {}; | ||||||
|             nfc_util_num2bytes(ans, 4, responce); |             nfc_util_num2bytes(ans, 4, responce); | ||||||
|             mf_crypto1_encrypt( |             crypto1_encrypt( | ||||||
|                 &emulator->crypto, |                 &emulator->crypto, | ||||||
|                 NULL, |                 NULL, | ||||||
|                 responce, |                 responce, | ||||||
| @ -938,7 +896,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ | |||||||
|                     // Send NACK
 |                     // Send NACK
 | ||||||
|                     uint8_t nack = 0x04; |                     uint8_t nack = 0x04; | ||||||
|                     if(is_encrypted) { |                     if(is_encrypted) { | ||||||
|                         mf_crypto1_encrypt( |                         crypto1_encrypt( | ||||||
|                             &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); |                             &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); | ||||||
|                     } else { |                     } else { | ||||||
|                         tx_rx->tx_data[0] = nack; |                         tx_rx->tx_data[0] = nack; | ||||||
| @ -951,7 +909,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ | |||||||
|             } |             } | ||||||
|             nfca_append_crc16(block_data, 16); |             nfca_append_crc16(block_data, 16); | ||||||
| 
 | 
 | ||||||
|             mf_crypto1_encrypt( |             crypto1_encrypt( | ||||||
|                 &emulator->crypto, |                 &emulator->crypto, | ||||||
|                 NULL, |                 NULL, | ||||||
|                 block_data, |                 block_data, | ||||||
| @ -967,14 +925,14 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ | |||||||
|             } |             } | ||||||
|             // Send ACK
 |             // Send ACK
 | ||||||
|             uint8_t ack = 0x0A; |             uint8_t ack = 0x0A; | ||||||
|             mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); |             crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); | ||||||
|             tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; |             tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; | ||||||
|             tx_rx->tx_bits = 4; |             tx_rx->tx_bits = 4; | ||||||
| 
 | 
 | ||||||
|             if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; |             if(!furi_hal_nfc_tx_rx(tx_rx, 300)) break; | ||||||
|             if(tx_rx->rx_bits != 18 * 8) break; |             if(tx_rx->rx_bits != 18 * 8) break; | ||||||
| 
 | 
 | ||||||
|             mf_crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); |             crypto1_decrypt(&emulator->crypto, tx_rx->rx_data, tx_rx->rx_bits, plain_data); | ||||||
|             uint8_t block_data[16] = {}; |             uint8_t block_data[16] = {}; | ||||||
|             memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); |             memcpy(block_data, emulator->data.block[block].value, MF_CLASSIC_BLOCK_SIZE); | ||||||
|             if(mf_classic_is_sector_trailer(block)) { |             if(mf_classic_is_sector_trailer(block)) { | ||||||
| @ -1002,7 +960,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ | |||||||
|             } |             } | ||||||
|             // Send ACK
 |             // Send ACK
 | ||||||
|             ack = 0x0A; |             ack = 0x0A; | ||||||
|             mf_crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); |             crypto1_encrypt(&emulator->crypto, NULL, &ack, 4, tx_rx->tx_data, tx_rx->tx_parity); | ||||||
|             tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; |             tx_rx->tx_rx_type = FuriHalNfcTxRxTransparent; | ||||||
|             tx_rx->tx_bits = 4; |             tx_rx->tx_bits = 4; | ||||||
|         } else { |         } else { | ||||||
| @ -1015,8 +973,7 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ | |||||||
|         // Send NACK
 |         // Send NACK
 | ||||||
|         uint8_t nack = 0x04; |         uint8_t nack = 0x04; | ||||||
|         if(is_encrypted) { |         if(is_encrypted) { | ||||||
|             mf_crypto1_encrypt( |             crypto1_encrypt(&emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); | ||||||
|                 &emulator->crypto, NULL, &nack, 4, tx_rx->tx_data, tx_rx->tx_parity); |  | ||||||
|         } else { |         } else { | ||||||
|             tx_rx->tx_data[0] = nack; |             tx_rx->tx_data[0] = nack; | ||||||
|         } |         } | ||||||
| @ -1027,3 +984,143 @@ bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_ | |||||||
| 
 | 
 | ||||||
|     return true; |     return true; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | bool mf_classic_write_block( | ||||||
|  |     FuriHalNfcTxRxContext* tx_rx, | ||||||
|  |     MfClassicBlock* src_block, | ||||||
|  |     uint8_t block_num, | ||||||
|  |     MfClassicKey key_type, | ||||||
|  |     uint64_t key) { | ||||||
|  |     furi_assert(tx_rx); | ||||||
|  |     furi_assert(src_block); | ||||||
|  | 
 | ||||||
|  |     Crypto1 crypto = {}; | ||||||
|  |     uint8_t plain_data[18] = {}; | ||||||
|  |     uint8_t resp = 0; | ||||||
|  |     bool write_success = false; | ||||||
|  | 
 | ||||||
|  |     do { | ||||||
|  |         furi_hal_nfc_sleep(); | ||||||
|  |         if(!mf_classic_auth(tx_rx, block_num, key, key_type, &crypto)) { | ||||||
|  |             FURI_LOG_D(TAG, "Auth fail"); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         // Send write command
 | ||||||
|  |         plain_data[0] = MF_CLASSIC_WRITE_BLOCK_CMD; | ||||||
|  |         plain_data[1] = block_num; | ||||||
|  |         nfca_append_crc16(plain_data, 2); | ||||||
|  |         crypto1_encrypt(&crypto, NULL, plain_data, 4 * 8, tx_rx->tx_data, tx_rx->tx_parity); | ||||||
|  |         tx_rx->tx_bits = 4 * 8; | ||||||
|  |         tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; | ||||||
|  | 
 | ||||||
|  |         if(furi_hal_nfc_tx_rx(tx_rx, 50)) { | ||||||
|  |             if(tx_rx->rx_bits == 4) { | ||||||
|  |                 crypto1_decrypt(&crypto, tx_rx->rx_data, 4, &resp); | ||||||
|  |                 if(resp != 0x0A) { | ||||||
|  |                     FURI_LOG_D(TAG, "NACK received on write cmd: %02X", resp); | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 FURI_LOG_D(TAG, "Not ACK received"); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             FURI_LOG_D(TAG, "Failed to send write cmd"); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         // Send data
 | ||||||
|  |         memcpy(plain_data, src_block->value, MF_CLASSIC_BLOCK_SIZE); | ||||||
|  |         nfca_append_crc16(plain_data, MF_CLASSIC_BLOCK_SIZE); | ||||||
|  |         crypto1_encrypt( | ||||||
|  |             &crypto, | ||||||
|  |             NULL, | ||||||
|  |             plain_data, | ||||||
|  |             (MF_CLASSIC_BLOCK_SIZE + 2) * 8, | ||||||
|  |             tx_rx->tx_data, | ||||||
|  |             tx_rx->tx_parity); | ||||||
|  |         tx_rx->tx_bits = (MF_CLASSIC_BLOCK_SIZE + 2) * 8; | ||||||
|  |         tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; | ||||||
|  |         if(furi_hal_nfc_tx_rx(tx_rx, 50)) { | ||||||
|  |             if(tx_rx->rx_bits == 4) { | ||||||
|  |                 crypto1_decrypt(&crypto, tx_rx->rx_data, 4, &resp); | ||||||
|  |                 if(resp != 0x0A) { | ||||||
|  |                     FURI_LOG_D(TAG, "NACK received on sending data"); | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 FURI_LOG_D(TAG, "Not ACK received"); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             FURI_LOG_D(TAG, "Failed to send data"); | ||||||
|  |             break; | ||||||
|  |         } | ||||||
|  |         write_success = true; | ||||||
|  | 
 | ||||||
|  |         // Send Halt
 | ||||||
|  |         plain_data[0] = 0x50; | ||||||
|  |         plain_data[1] = 0x00; | ||||||
|  |         nfca_append_crc16(plain_data, 2); | ||||||
|  |         crypto1_encrypt(&crypto, NULL, plain_data, 2 * 8, tx_rx->tx_data, tx_rx->tx_parity); | ||||||
|  |         tx_rx->tx_bits = 2 * 8; | ||||||
|  |         tx_rx->tx_rx_type = FuriHalNfcTxRxTypeRaw; | ||||||
|  |         // No response is expected
 | ||||||
|  |         furi_hal_nfc_tx_rx(tx_rx, 50); | ||||||
|  |     } while(false); | ||||||
|  | 
 | ||||||
|  |     return write_success; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | bool mf_classic_write_sector( | ||||||
|  |     FuriHalNfcTxRxContext* tx_rx, | ||||||
|  |     MfClassicData* dest_data, | ||||||
|  |     MfClassicData* src_data, | ||||||
|  |     uint8_t sec_num) { | ||||||
|  |     furi_assert(tx_rx); | ||||||
|  |     furi_assert(dest_data); | ||||||
|  |     furi_assert(src_data); | ||||||
|  | 
 | ||||||
|  |     uint8_t first_block = mf_classic_get_first_block_num_of_sector(sec_num); | ||||||
|  |     uint8_t total_blocks = mf_classic_get_blocks_num_in_sector(sec_num); | ||||||
|  |     MfClassicSectorTrailer* sec_tr = mf_classic_get_sector_trailer_by_sector(dest_data, sec_num); | ||||||
|  |     bool key_a_found = mf_classic_is_key_found(dest_data, sec_num, MfClassicKeyA); | ||||||
|  |     bool key_b_found = mf_classic_is_key_found(dest_data, sec_num, MfClassicKeyB); | ||||||
|  | 
 | ||||||
|  |     bool write_success = true; | ||||||
|  |     for(size_t i = first_block; i < first_block + total_blocks; i++) { | ||||||
|  |         // Compare blocks
 | ||||||
|  |         if(memcmp(dest_data->block[i].value, src_data->block[i].value, MF_CLASSIC_BLOCK_SIZE)) { | ||||||
|  |             bool key_a_write_allowed = mf_classic_is_allowed_access_data_block( | ||||||
|  |                 dest_data, i, MfClassicKeyA, MfClassicActionDataWrite); | ||||||
|  |             bool key_b_write_allowed = mf_classic_is_allowed_access_data_block( | ||||||
|  |                 dest_data, i, MfClassicKeyB, MfClassicActionDataWrite); | ||||||
|  | 
 | ||||||
|  |             if(key_a_found && key_a_write_allowed) { | ||||||
|  |                 FURI_LOG_I(TAG, "Writing block %d with key A", i); | ||||||
|  |                 uint64_t key = nfc_util_bytes2num(sec_tr->key_a, 6); | ||||||
|  |                 if(!mf_classic_write_block(tx_rx, &src_data->block[i], i, MfClassicKeyA, key)) { | ||||||
|  |                     FURI_LOG_E(TAG, "Failed to write block %d", i); | ||||||
|  |                     write_success = false; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } else if(key_b_found && key_b_write_allowed) { | ||||||
|  |                 FURI_LOG_I(TAG, "Writing block %d with key A", i); | ||||||
|  |                 uint64_t key = nfc_util_bytes2num(sec_tr->key_b, 6); | ||||||
|  |                 if(!mf_classic_write_block(tx_rx, &src_data->block[i], i, MfClassicKeyB, key)) { | ||||||
|  |                     FURI_LOG_E(TAG, "Failed to write block %d", i); | ||||||
|  |                     write_success = false; | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |             } else { | ||||||
|  |                 FURI_LOG_E(TAG, "Failed to find key with write access"); | ||||||
|  |                 write_success = false; | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |         } else { | ||||||
|  |             FURI_LOG_D(TAG, "Blocks %d are equal", i); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     return write_success; | ||||||
|  | } | ||||||
|  | |||||||
| @ -27,6 +27,20 @@ typedef enum { | |||||||
|     MfClassicKeyB, |     MfClassicKeyB, | ||||||
| } MfClassicKey; | } MfClassicKey; | ||||||
| 
 | 
 | ||||||
|  | typedef enum { | ||||||
|  |     MfClassicActionDataRead, | ||||||
|  |     MfClassicActionDataWrite, | ||||||
|  |     MfClassicActionDataInc, | ||||||
|  |     MfClassicActionDataDec, | ||||||
|  | 
 | ||||||
|  |     MfClassicActionKeyARead, | ||||||
|  |     MfClassicActionKeyAWrite, | ||||||
|  |     MfClassicActionKeyBRead, | ||||||
|  |     MfClassicActionKeyBWrite, | ||||||
|  |     MfClassicActionACRead, | ||||||
|  |     MfClassicActionACWrite, | ||||||
|  | } MfClassicAction; | ||||||
|  | 
 | ||||||
| typedef struct { | typedef struct { | ||||||
|     uint8_t value[MF_CLASSIC_BLOCK_SIZE]; |     uint8_t value[MF_CLASSIC_BLOCK_SIZE]; | ||||||
| } MfClassicBlock; | } MfClassicBlock; | ||||||
| @ -90,6 +104,18 @@ bool mf_classic_is_sector_trailer(uint8_t block); | |||||||
| 
 | 
 | ||||||
| uint8_t mf_classic_get_sector_by_block(uint8_t block); | uint8_t mf_classic_get_sector_by_block(uint8_t block); | ||||||
| 
 | 
 | ||||||
|  | bool mf_classic_is_allowed_access_sector_trailer( | ||||||
|  |     MfClassicData* data, | ||||||
|  |     uint8_t block_num, | ||||||
|  |     MfClassicKey key, | ||||||
|  |     MfClassicAction action); | ||||||
|  | 
 | ||||||
|  | bool mf_classic_is_allowed_access_data_block( | ||||||
|  |     MfClassicData* data, | ||||||
|  |     uint8_t block_num, | ||||||
|  |     MfClassicKey key, | ||||||
|  |     MfClassicAction action); | ||||||
|  | 
 | ||||||
| bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type); | bool mf_classic_is_key_found(MfClassicData* data, uint8_t sector_num, MfClassicKey key_type); | ||||||
| 
 | 
 | ||||||
| void mf_classic_set_key_found( | void mf_classic_set_key_found( | ||||||
| @ -104,6 +130,10 @@ bool mf_classic_is_block_read(MfClassicData* data, uint8_t block_num); | |||||||
| 
 | 
 | ||||||
| void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); | void mf_classic_set_block_read(MfClassicData* data, uint8_t block_num, MfClassicBlock* block_data); | ||||||
| 
 | 
 | ||||||
|  | bool mf_classic_is_sector_data_read(MfClassicData* data, uint8_t sector_num); | ||||||
|  | 
 | ||||||
|  | void mf_classic_set_sector_data_not_read(MfClassicData* data); | ||||||
|  | 
 | ||||||
| bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num); | bool mf_classic_is_sector_read(MfClassicData* data, uint8_t sector_num); | ||||||
| 
 | 
 | ||||||
| bool mf_classic_is_card_read(MfClassicData* data); | bool mf_classic_is_card_read(MfClassicData* data); | ||||||
| @ -145,3 +175,16 @@ uint8_t mf_classic_read_card( | |||||||
| uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data); | uint8_t mf_classic_update_card(FuriHalNfcTxRxContext* tx_rx, MfClassicData* data); | ||||||
| 
 | 
 | ||||||
| bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx); | bool mf_classic_emulator(MfClassicEmulator* emulator, FuriHalNfcTxRxContext* tx_rx); | ||||||
|  | 
 | ||||||
|  | bool mf_classic_write_block( | ||||||
|  |     FuriHalNfcTxRxContext* tx_rx, | ||||||
|  |     MfClassicBlock* src_block, | ||||||
|  |     uint8_t block_num, | ||||||
|  |     MfClassicKey key_type, | ||||||
|  |     uint64_t key); | ||||||
|  | 
 | ||||||
|  | bool mf_classic_write_sector( | ||||||
|  |     FuriHalNfcTxRxContext* tx_rx, | ||||||
|  |     MfClassicData* dest_data, | ||||||
|  |     MfClassicData* src_data, | ||||||
|  |     uint8_t sec_num); | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 gornekich
						gornekich