Picopass: factory key support, app rename and move to NFC category, minor code cleanup (#2417)
* message on successful card write * auth using factory key * auth using factory default * factory default screen * write standard iclass key * pass block explicitly * Fix array indexing, add empty detection * PicoPass: rename app and move to NFC group, minor code cleanup Co-authored-by: あく <alleteam@gmail.com>
This commit is contained in:
		
							parent
							
								
									eaf965c66f
								
							
						
					
					
						commit
						03f889962b
					
				| @ -1,6 +1,6 @@ | ||||
| App( | ||||
|     appid="picopass", | ||||
|     name="PicoPass Reader", | ||||
|     name="PicoPass", | ||||
|     apptype=FlipperAppType.EXTERNAL, | ||||
|     targets=["f7"], | ||||
|     entry_point="picopass_app", | ||||
| @ -11,7 +11,7 @@ App( | ||||
|     stack_size=4 * 1024, | ||||
|     order=30, | ||||
|     fap_icon="125_10px.png", | ||||
|     fap_category="Tools", | ||||
|     fap_category="NFC", | ||||
|     fap_libs=["mbedtls"], | ||||
|     fap_private_libs=[ | ||||
|         Lib( | ||||
|  | ||||
| @ -171,6 +171,16 @@ void picopass_show_loading_popup(void* context, bool show) { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size) { | ||||
|     bool result = size > 0; | ||||
|     while(size > 0) { | ||||
|         result &= (*data == pattern); | ||||
|         data++; | ||||
|         size--; | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| int32_t picopass_app(void* p) { | ||||
|     UNUSED(p); | ||||
|     Picopass* picopass = picopass_alloc(); | ||||
|  | ||||
| @ -368,7 +368,7 @@ ReturnCode picopass_device_parse_wiegand(uint8_t* data, PicopassWiegandRecord* r | ||||
| 
 | ||||
|         record->CardNumber = (bot >> 1) & 0xFFFF; | ||||
|         record->FacilityCode = (bot >> 17) & 0xFF; | ||||
|         FURI_LOG_D(TAG, "FC:%u CN: %u\n", record->FacilityCode, record->CardNumber); | ||||
|         FURI_LOG_D(TAG, "FC: %u CN: %u", record->FacilityCode, record->CardNumber); | ||||
|         record->valid = true; | ||||
|     } else { | ||||
|         record->CardNumber = 0; | ||||
|  | ||||
| @ -22,6 +22,7 @@ | ||||
| #define PICOPASS_KD_BLOCK_INDEX 3 | ||||
| #define PICOPASS_KC_BLOCK_INDEX 4 | ||||
| #define PICOPASS_AIA_BLOCK_INDEX 5 | ||||
| #define PICOPASS_PACS_CFG_BLOCK_INDEX 6 | ||||
| 
 | ||||
| #define PICOPASS_APP_FOLDER ANY_PATH("picopass") | ||||
| #define PICOPASS_APP_EXTENSION ".picopass" | ||||
|  | ||||
| @ -81,3 +81,15 @@ void picopass_blink_start(Picopass* picopass); | ||||
| void picopass_blink_stop(Picopass* picopass); | ||||
| 
 | ||||
| void picopass_show_loading_popup(void* context, bool show); | ||||
| 
 | ||||
| /** Check if memory is set to pattern
 | ||||
|  * | ||||
|  * @warning    zero size will return false | ||||
|  * | ||||
|  * @param[in]  data     Pointer to the byte array | ||||
|  * @param[in]  pattern  The pattern | ||||
|  * @param[in]  size     The byte array size | ||||
|  * | ||||
|  * @return     True if memory is set to pattern, false otherwise | ||||
|  */ | ||||
| bool picopass_is_memset(const uint8_t* data, const uint8_t pattern, size_t size); | ||||
|  | ||||
| @ -5,7 +5,8 @@ | ||||
| #define TAG "PicopassWorker" | ||||
| 
 | ||||
| const uint8_t picopass_iclass_key[] = {0xaf, 0xa7, 0x85, 0xa7, 0xda, 0xb3, 0x33, 0x78}; | ||||
| const uint8_t picopass_factory_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; | ||||
| const uint8_t picopass_factory_credit_key[] = {0x76, 0x65, 0x54, 0x43, 0x32, 0x21, 0x10, 0x00}; | ||||
| const uint8_t picopass_factory_debit_key[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; | ||||
| 
 | ||||
| static void picopass_worker_enable_field() { | ||||
|     furi_hal_nfc_ll_txrx_on(); | ||||
| @ -197,6 +198,28 @@ static ReturnCode picopass_auth_standard(uint8_t* csn, uint8_t* div_key) { | ||||
|     return rfalPicoPassPollerCheck(mac, &chkRes); | ||||
| } | ||||
| 
 | ||||
| static ReturnCode picopass_auth_factory(uint8_t* csn, uint8_t* div_key) { | ||||
|     rfalPicoPassReadCheckRes rcRes; | ||||
|     rfalPicoPassCheckRes chkRes; | ||||
| 
 | ||||
|     ReturnCode err; | ||||
| 
 | ||||
|     uint8_t mac[4] = {0}; | ||||
|     uint8_t ccnr[12] = {0}; | ||||
| 
 | ||||
|     err = rfalPicoPassPollerReadCheck(&rcRes); | ||||
|     if(err != ERR_NONE) { | ||||
|         FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); | ||||
|         return err; | ||||
|     } | ||||
|     memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
 | ||||
| 
 | ||||
|     loclass_diversifyKey(csn, picopass_factory_debit_key, div_key); | ||||
|     loclass_opt_doReaderMAC(ccnr, div_key, mac); | ||||
| 
 | ||||
|     return rfalPicoPassPollerCheck(mac, &chkRes); | ||||
| } | ||||
| 
 | ||||
| static ReturnCode picopass_auth_dict( | ||||
|     uint8_t* csn, | ||||
|     PicopassPacs* pacs, | ||||
| @ -264,14 +287,23 @@ static ReturnCode picopass_auth_dict( | ||||
| ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { | ||||
|     ReturnCode err; | ||||
| 
 | ||||
|     FURI_LOG_E(TAG, "Trying standard legacy key"); | ||||
|     FURI_LOG_I(TAG, "Trying standard legacy key"); | ||||
|     err = picopass_auth_standard( | ||||
|         AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data); | ||||
|     if(err == ERR_NONE) { | ||||
|         memcpy(pacs->key, picopass_iclass_key, PICOPASS_BLOCK_LEN); | ||||
|         return ERR_NONE; | ||||
|     } | ||||
| 
 | ||||
|     FURI_LOG_E(TAG, "Starting user dictionary attack"); | ||||
|     FURI_LOG_I(TAG, "Trying factory default key"); | ||||
|     err = picopass_auth_factory( | ||||
|         AA1[PICOPASS_CSN_BLOCK_INDEX].data, AA1[PICOPASS_KD_BLOCK_INDEX].data); | ||||
|     if(err == ERR_NONE) { | ||||
|         memcpy(pacs->key, picopass_factory_debit_key, PICOPASS_BLOCK_LEN); | ||||
|         return ERR_NONE; | ||||
|     } | ||||
| 
 | ||||
|     FURI_LOG_I(TAG, "Starting user dictionary attack"); | ||||
|     err = picopass_auth_dict( | ||||
|         AA1[PICOPASS_CSN_BLOCK_INDEX].data, | ||||
|         pacs, | ||||
| @ -281,7 +313,7 @@ ReturnCode picopass_auth(PicopassBlock* AA1, PicopassPacs* pacs) { | ||||
|         return ERR_NONE; | ||||
|     } | ||||
| 
 | ||||
|     FURI_LOG_E(TAG, "Starting in-built dictionary attack"); | ||||
|     FURI_LOG_I(TAG, "Starting system dictionary attack"); | ||||
|     err = picopass_auth_dict( | ||||
|         AA1[PICOPASS_CSN_BLOCK_INDEX].data, | ||||
|         pacs, | ||||
| @ -406,6 +438,84 @@ ReturnCode picopass_write_card(PicopassBlock* AA1) { | ||||
|     return ERR_NONE; | ||||
| } | ||||
| 
 | ||||
| ReturnCode picopass_write_block(PicopassPacs* pacs, uint8_t blockNo, uint8_t* newBlock) { | ||||
|     rfalPicoPassIdentifyRes idRes; | ||||
|     rfalPicoPassSelectRes selRes; | ||||
|     rfalPicoPassReadCheckRes rcRes; | ||||
|     rfalPicoPassCheckRes chkRes; | ||||
| 
 | ||||
|     ReturnCode err; | ||||
| 
 | ||||
|     uint8_t div_key[8] = {0}; | ||||
|     uint8_t mac[4] = {0}; | ||||
|     uint8_t ccnr[12] = {0}; | ||||
| 
 | ||||
|     err = rfalPicoPassPollerIdentify(&idRes); | ||||
|     if(err != ERR_NONE) { | ||||
|         FURI_LOG_E(TAG, "rfalPicoPassPollerIdentify error %d", err); | ||||
|         return err; | ||||
|     } | ||||
| 
 | ||||
|     err = rfalPicoPassPollerSelect(idRes.CSN, &selRes); | ||||
|     if(err != ERR_NONE) { | ||||
|         FURI_LOG_E(TAG, "rfalPicoPassPollerSelect error %d", err); | ||||
|         return err; | ||||
|     } | ||||
| 
 | ||||
|     err = rfalPicoPassPollerReadCheck(&rcRes); | ||||
|     if(err != ERR_NONE) { | ||||
|         FURI_LOG_E(TAG, "rfalPicoPassPollerReadCheck error %d", err); | ||||
|         return err; | ||||
|     } | ||||
|     memcpy(ccnr, rcRes.CCNR, sizeof(rcRes.CCNR)); // last 4 bytes left 0
 | ||||
| 
 | ||||
|     loclass_diversifyKey(selRes.CSN, pacs->key, div_key); | ||||
|     loclass_opt_doReaderMAC(ccnr, div_key, mac); | ||||
| 
 | ||||
|     err = rfalPicoPassPollerCheck(mac, &chkRes); | ||||
|     if(err != ERR_NONE) { | ||||
|         FURI_LOG_E(TAG, "rfalPicoPassPollerCheck error %d", err); | ||||
|         return err; | ||||
|     } | ||||
| 
 | ||||
|     FURI_LOG_D(TAG, "rfalPicoPassPollerWriteBlock %d", blockNo); | ||||
|     uint8_t data[9] = { | ||||
|         blockNo, | ||||
|         newBlock[0], | ||||
|         newBlock[1], | ||||
|         newBlock[2], | ||||
|         newBlock[3], | ||||
|         newBlock[4], | ||||
|         newBlock[5], | ||||
|         newBlock[6], | ||||
|         newBlock[7]}; | ||||
|     loclass_doMAC_N(data, sizeof(data), div_key, mac); | ||||
|     FURI_LOG_D( | ||||
|         TAG, | ||||
|         "loclass_doMAC_N %d %02x%02x%02x%02x%02x%02x%02x%02x %02x%02x%02x%02x", | ||||
|         blockNo, | ||||
|         data[1], | ||||
|         data[2], | ||||
|         data[3], | ||||
|         data[4], | ||||
|         data[5], | ||||
|         data[6], | ||||
|         data[7], | ||||
|         data[8], | ||||
|         mac[0], | ||||
|         mac[1], | ||||
|         mac[2], | ||||
|         mac[3]); | ||||
| 
 | ||||
|     err = rfalPicoPassPollerWriteBlock(data[0], data + 1, mac); | ||||
|     if(err != ERR_NONE) { | ||||
|         FURI_LOG_E(TAG, "rfalPicoPassPollerWriteBlock error %d", err); | ||||
|         return err; | ||||
|     } | ||||
| 
 | ||||
|     return ERR_NONE; | ||||
| } | ||||
| 
 | ||||
| int32_t picopass_worker_task(void* context) { | ||||
|     PicopassWorker* picopass_worker = context; | ||||
| 
 | ||||
| @ -414,6 +524,8 @@ int32_t picopass_worker_task(void* context) { | ||||
|         picopass_worker_detect(picopass_worker); | ||||
|     } else if(picopass_worker->state == PicopassWorkerStateWrite) { | ||||
|         picopass_worker_write(picopass_worker); | ||||
|     } else if(picopass_worker->state == PicopassWorkerStateWriteStandardKey) { | ||||
|         picopass_worker_write_standard_key(picopass_worker); | ||||
|     } | ||||
|     picopass_worker_disable_field(ERR_NONE); | ||||
| 
 | ||||
| @ -448,7 +560,7 @@ void picopass_worker_detect(PicopassWorker* picopass_worker) { | ||||
|             } | ||||
| 
 | ||||
|             // Thank you proxmark!
 | ||||
|             pacs->legacy = (memcmp(AA1[5].data, "\xff\xff\xff\xff\xff\xff\xff\xff", 8) == 0); | ||||
|             pacs->legacy = picopass_is_memset(AA1[5].data, 0xFF, 8); | ||||
|             pacs->se_enabled = (memcmp(AA1[5].data, "\xff\xff\xff\x00\x06\xff\xff\xff", 8) == 0); | ||||
|             if(pacs->se_enabled) { | ||||
|                 FURI_LOG_D(TAG, "SE enabled"); | ||||
| @ -520,3 +632,46 @@ void picopass_worker_write(PicopassWorker* picopass_worker) { | ||||
|         furi_delay_ms(100); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void picopass_worker_write_standard_key(PicopassWorker* picopass_worker) { | ||||
|     PicopassDeviceData* dev_data = picopass_worker->dev_data; | ||||
|     PicopassBlock* AA1 = dev_data->AA1; | ||||
|     PicopassPacs* pacs = &dev_data->pacs; | ||||
|     ReturnCode err; | ||||
|     PicopassWorkerEvent nextState = PicopassWorkerEventSuccess; | ||||
| 
 | ||||
|     uint8_t* csn = AA1[PICOPASS_CSN_BLOCK_INDEX].data; | ||||
|     uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data; | ||||
|     uint8_t fuses = configBlock[7]; | ||||
|     uint8_t* oldKey = AA1[PICOPASS_KD_BLOCK_INDEX].data; | ||||
| 
 | ||||
|     uint8_t newKey[PICOPASS_BLOCK_LEN] = {0}; | ||||
|     loclass_diversifyKey(csn, picopass_iclass_key, newKey); | ||||
| 
 | ||||
|     if((fuses & 0x80) == 0x80) { | ||||
|         FURI_LOG_D(TAG, "Plain write for personalized mode key change"); | ||||
|     } else { | ||||
|         FURI_LOG_D(TAG, "XOR write for application mode key change"); | ||||
|         // XOR when in application mode
 | ||||
|         for(size_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { | ||||
|             newKey[i] ^= oldKey[i]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     while(picopass_worker->state == PicopassWorkerStateWriteStandardKey) { | ||||
|         if(picopass_detect_card(1000) == ERR_NONE) { | ||||
|             err = picopass_write_block(pacs, PICOPASS_KD_BLOCK_INDEX, newKey); | ||||
|             if(err != ERR_NONE) { | ||||
|                 FURI_LOG_E(TAG, "picopass_write_block error %d", err); | ||||
|                 nextState = PicopassWorkerEventFail; | ||||
|             } | ||||
| 
 | ||||
|             // Notify caller and exit
 | ||||
|             if(picopass_worker->callback) { | ||||
|                 picopass_worker->callback(nextState, picopass_worker->context); | ||||
|             } | ||||
|             break; | ||||
|         } | ||||
|         furi_delay_ms(100); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -12,6 +12,7 @@ typedef enum { | ||||
|     // Main worker states
 | ||||
|     PicopassWorkerStateDetect, | ||||
|     PicopassWorkerStateWrite, | ||||
|     PicopassWorkerStateWriteStandardKey, | ||||
|     // Transition
 | ||||
|     PicopassWorkerStateStop, | ||||
| } PicopassWorkerState; | ||||
|  | ||||
| @ -31,3 +31,4 @@ int32_t picopass_worker_task(void* context); | ||||
| 
 | ||||
| void picopass_worker_detect(PicopassWorker* picopass_worker); | ||||
| void picopass_worker_write(PicopassWorker* picopass_worker); | ||||
| void picopass_worker_write_standard_key(PicopassWorker* picopass_worker); | ||||
|  | ||||
| @ -11,3 +11,5 @@ ADD_SCENE(picopass, delete, Delete) | ||||
| ADD_SCENE(picopass, delete_success, DeleteSuccess) | ||||
| ADD_SCENE(picopass, write_card, WriteCard) | ||||
| ADD_SCENE(picopass, write_card_success, WriteCardSuccess) | ||||
| ADD_SCENE(picopass, read_factory_success, ReadFactorySuccess) | ||||
| ADD_SCENE(picopass, write_key, WriteKey) | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| #include "../picopass_i.h" | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| const uint8_t picopass_factory_key_check[] = {0xf0, 0xe1, 0xd2, 0xc3, 0xb4, 0xa5, 0x96, 0x87}; | ||||
| 
 | ||||
| void picopass_read_card_worker_callback(PicopassWorkerEvent event, void* context) { | ||||
|     UNUSED(event); | ||||
|     Picopass* picopass = context; | ||||
| @ -34,7 +36,14 @@ bool picopass_scene_read_card_on_event(void* context, SceneManagerEvent event) { | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == PicopassCustomEventWorkerExit) { | ||||
|             if(memcmp( | ||||
|                    picopass->dev->dev_data.pacs.key, | ||||
|                    picopass_factory_key_check, | ||||
|                    PICOPASS_BLOCK_LEN) == 0) { | ||||
|                 scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadFactorySuccess); | ||||
|             } else { | ||||
|                 scene_manager_next_scene(picopass->scene_manager, PicopassSceneReadCardSuccess); | ||||
|             } | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -15,6 +15,7 @@ void picopass_scene_read_card_success_widget_callback( | ||||
| 
 | ||||
| void picopass_scene_read_card_success_on_enter(void* context) { | ||||
|     Picopass* picopass = context; | ||||
| 
 | ||||
|     FuriString* csn_str = furi_string_alloc_set("CSN:"); | ||||
|     FuriString* credential_str = furi_string_alloc(); | ||||
|     FuriString* wiegand_str = furi_string_alloc(); | ||||
| @ -30,27 +31,31 @@ void picopass_scene_read_card_success_on_enter(void* context) { | ||||
|     PicopassPacs* pacs = &picopass->dev->dev_data.pacs; | ||||
|     Widget* widget = picopass->widget; | ||||
| 
 | ||||
|     uint8_t csn[PICOPASS_BLOCK_LEN]; | ||||
|     memcpy(csn, &AA1->data[PICOPASS_CSN_BLOCK_INDEX], PICOPASS_BLOCK_LEN); | ||||
|     uint8_t csn[PICOPASS_BLOCK_LEN] = {0}; | ||||
|     memcpy(csn, AA1[PICOPASS_CSN_BLOCK_INDEX].data, PICOPASS_BLOCK_LEN); | ||||
|     for(uint8_t i = 0; i < PICOPASS_BLOCK_LEN; i++) { | ||||
|         furi_string_cat_printf(csn_str, "%02X ", csn[i]); | ||||
|     } | ||||
| 
 | ||||
|     // Neither of these are valid.  Indicates the block was all 0x00 or all 0xff
 | ||||
|     if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { | ||||
|     bool no_key = picopass_is_memset(pacs->key, 0x00, PICOPASS_BLOCK_LEN); | ||||
|     bool empty = | ||||
|         picopass_is_memset(AA1[PICOPASS_PACS_CFG_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN); | ||||
| 
 | ||||
|     if(no_key) { | ||||
|         furi_string_cat_printf(wiegand_str, "Read Failed"); | ||||
| 
 | ||||
|         if(pacs->se_enabled) { | ||||
|             furi_string_cat_printf(credential_str, "SE enabled"); | ||||
|         } | ||||
|     } else if(empty) { | ||||
|         furi_string_cat_printf(wiegand_str, "Empty"); | ||||
|     } else if(pacs->record.bitLength == 0 || pacs->record.bitLength == 255) { | ||||
|         // Neither of these are valid.  Indicates the block was all 0x00 or all 0xff
 | ||||
|         furi_string_cat_printf(wiegand_str, "Invalid PACS"); | ||||
| 
 | ||||
|         widget_add_button_element( | ||||
|             widget, | ||||
|             GuiButtonTypeLeft, | ||||
|             "Retry", | ||||
|             picopass_scene_read_card_success_widget_callback, | ||||
|             picopass); | ||||
| 
 | ||||
|         if(pacs->se_enabled) { | ||||
|             furi_string_cat_printf(credential_str, "SE enabled"); | ||||
|         } | ||||
|     } else { | ||||
|         size_t bytesLength = 1 + pacs->record.bitLength / 8; | ||||
|         furi_string_set(credential_str, ""); | ||||
| @ -82,13 +87,6 @@ void picopass_scene_read_card_success_on_enter(void* context) { | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         widget_add_button_element( | ||||
|             widget, | ||||
|             GuiButtonTypeLeft, | ||||
|             "Retry", | ||||
|             picopass_scene_read_card_success_widget_callback, | ||||
|             picopass); | ||||
| 
 | ||||
|         widget_add_button_element( | ||||
|             widget, | ||||
|             GuiButtonTypeRight, | ||||
| @ -97,6 +95,13 @@ void picopass_scene_read_card_success_on_enter(void* context) { | ||||
|             picopass); | ||||
|     } | ||||
| 
 | ||||
|     widget_add_button_element( | ||||
|         widget, | ||||
|         GuiButtonTypeLeft, | ||||
|         "Retry", | ||||
|         picopass_scene_read_card_success_widget_callback, | ||||
|         picopass); | ||||
| 
 | ||||
|     widget_add_string_element( | ||||
|         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(csn_str)); | ||||
|     widget_add_string_element( | ||||
|  | ||||
| @ -0,0 +1,78 @@ | ||||
| #include "../picopass_i.h" | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| void picopass_scene_read_factory_success_widget_callback( | ||||
|     GuiButtonType result, | ||||
|     InputType type, | ||||
|     void* context) { | ||||
|     furi_assert(context); | ||||
|     Picopass* picopass = context; | ||||
| 
 | ||||
|     if(type == InputTypeShort) { | ||||
|         view_dispatcher_send_custom_event(picopass->view_dispatcher, result); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| void picopass_scene_read_factory_success_on_enter(void* context) { | ||||
|     Picopass* picopass = context; | ||||
|     FuriString* title = furi_string_alloc_set("Factory Default"); | ||||
|     FuriString* subtitle = furi_string_alloc_set(""); | ||||
| 
 | ||||
|     DOLPHIN_DEED(DolphinDeedNfcReadSuccess); | ||||
| 
 | ||||
|     // Send notification
 | ||||
|     notification_message(picopass->notifications, &sequence_success); | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     Widget* widget = picopass->widget; | ||||
|     //PicopassPacs* pacs = &picopass->dev->dev_data.pacs;
 | ||||
|     PicopassBlock* AA1 = picopass->dev->dev_data.AA1; | ||||
| 
 | ||||
|     uint8_t* configBlock = AA1[PICOPASS_CONFIG_BLOCK_INDEX].data; | ||||
|     uint8_t fuses = configBlock[7]; | ||||
| 
 | ||||
|     if((fuses & 0x80) == 0x80) { | ||||
|         furi_string_cat_printf(subtitle, "Personalization mode"); | ||||
|     } else { | ||||
|         furi_string_cat_printf(subtitle, "Application mode"); | ||||
|     } | ||||
| 
 | ||||
|     widget_add_button_element( | ||||
|         widget, | ||||
|         GuiButtonTypeCenter, | ||||
|         "Write Standard iClass Key", | ||||
|         picopass_scene_read_factory_success_widget_callback, | ||||
|         picopass); | ||||
| 
 | ||||
|     widget_add_string_element( | ||||
|         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(title)); | ||||
|     widget_add_string_element( | ||||
|         widget, 64, 20, AlignCenter, AlignCenter, FontPrimary, furi_string_get_cstr(subtitle)); | ||||
| 
 | ||||
|     furi_string_free(title); | ||||
|     furi_string_free(subtitle); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); | ||||
| } | ||||
| 
 | ||||
| bool picopass_scene_read_factory_success_on_event(void* context, SceneManagerEvent event) { | ||||
|     Picopass* picopass = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == GuiButtonTypeLeft) { | ||||
|             consumed = scene_manager_previous_scene(picopass->scene_manager); | ||||
|         } else if(event.event == GuiButtonTypeCenter) { | ||||
|             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteKey); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void picopass_scene_read_factory_success_on_exit(void* context) { | ||||
|     Picopass* picopass = context; | ||||
| 
 | ||||
|     // Clear view
 | ||||
|     widget_reset(picopass->widget); | ||||
| } | ||||
| @ -16,6 +16,7 @@ void picopass_scene_write_card_success_widget_callback( | ||||
| void picopass_scene_write_card_success_on_enter(void* context) { | ||||
|     Picopass* picopass = context; | ||||
|     Widget* widget = picopass->widget; | ||||
|     FuriString* str = furi_string_alloc_set("Write Success!"); | ||||
| 
 | ||||
|     DOLPHIN_DEED(DolphinDeedNfcReadSuccess); | ||||
| 
 | ||||
| @ -29,6 +30,18 @@ void picopass_scene_write_card_success_on_enter(void* context) { | ||||
|         picopass_scene_write_card_success_widget_callback, | ||||
|         picopass); | ||||
| 
 | ||||
|     widget_add_button_element( | ||||
|         widget, | ||||
|         GuiButtonTypeRight, | ||||
|         "Menu", | ||||
|         picopass_scene_write_card_success_widget_callback, | ||||
|         picopass); | ||||
| 
 | ||||
|     widget_add_string_element( | ||||
|         widget, 64, 5, AlignCenter, AlignCenter, FontSecondary, furi_string_get_cstr(str)); | ||||
| 
 | ||||
|     furi_string_free(str); | ||||
| 
 | ||||
|     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -0,0 +1,53 @@ | ||||
| #include "../picopass_i.h" | ||||
| #include <dolphin/dolphin.h> | ||||
| 
 | ||||
| void picopass_write_key_worker_callback(PicopassWorkerEvent event, void* context) { | ||||
|     UNUSED(event); | ||||
|     Picopass* picopass = context; | ||||
|     view_dispatcher_send_custom_event(picopass->view_dispatcher, PicopassCustomEventWorkerExit); | ||||
| } | ||||
| 
 | ||||
| void picopass_scene_write_key_on_enter(void* context) { | ||||
|     Picopass* picopass = context; | ||||
|     DOLPHIN_DEED(DolphinDeedNfcSave); | ||||
| 
 | ||||
|     // Setup view
 | ||||
|     Popup* popup = picopass->popup; | ||||
|     popup_set_header(popup, "Writing\niClass\nkey", 68, 30, AlignLeft, AlignTop); | ||||
|     popup_set_icon(popup, 0, 3, &I_RFIDDolphinSend_97x61); | ||||
| 
 | ||||
|     // Start worker
 | ||||
|     view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewPopup); | ||||
|     picopass_worker_start( | ||||
|         picopass->worker, | ||||
|         PicopassWorkerStateWriteStandardKey, | ||||
|         &picopass->dev->dev_data, | ||||
|         picopass_write_key_worker_callback, | ||||
|         picopass); | ||||
| 
 | ||||
|     picopass_blink_start(picopass); | ||||
| } | ||||
| 
 | ||||
| bool picopass_scene_write_key_on_event(void* context, SceneManagerEvent event) { | ||||
|     Picopass* picopass = context; | ||||
|     bool consumed = false; | ||||
| 
 | ||||
|     if(event.type == SceneManagerEventTypeCustom) { | ||||
|         if(event.event == PicopassCustomEventWorkerExit) { | ||||
|             scene_manager_next_scene(picopass->scene_manager, PicopassSceneWriteCardSuccess); | ||||
|             consumed = true; | ||||
|         } | ||||
|     } | ||||
|     return consumed; | ||||
| } | ||||
| 
 | ||||
| void picopass_scene_write_key_on_exit(void* context) { | ||||
|     Picopass* picopass = context; | ||||
| 
 | ||||
|     // Stop worker
 | ||||
|     picopass_worker_stop(picopass->worker); | ||||
|     // Clear view
 | ||||
|     popup_reset(picopass->popup); | ||||
| 
 | ||||
|     picopass_blink_stop(picopass); | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Eric Betts
						Eric Betts