diff --git a/base_pack/picopass/picopass_device.c b/base_pack/picopass/picopass_device.c index 92bd8f3889c..8a40248fd55 100644 --- a/base_pack/picopass/picopass_device.c +++ b/base_pack/picopass/picopass_device.c @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -15,13 +16,21 @@ static const uint32_t picopass_file_version = 1; const uint8_t picopass_iclass_decryptionkey[] = {0xb4, 0x21, 0x2c, 0xca, 0xb7, 0xed, 0x21, 0x0f, 0x7b, 0x93, 0xd4, 0x59, 0x39, 0xc7, 0xdd, 0x36}; +const char unknown_block[] = "?? ?? ?? ?? ?? ?? ?? ??"; PicopassDevice* picopass_device_alloc() { PicopassDevice* picopass_dev = malloc(sizeof(PicopassDevice)); + picopass_dev->dev_data.auth = PicopassDeviceAuthMethodUnset; picopass_dev->dev_data.pacs.legacy = false; picopass_dev->dev_data.pacs.se_enabled = false; + picopass_dev->dev_data.pacs.sio = false; + picopass_dev->dev_data.pacs.biometrics = false; + memset(picopass_dev->dev_data.pacs.key, 0, sizeof(picopass_dev->dev_data.pacs.key)); picopass_dev->dev_data.pacs.elite_kdf = false; picopass_dev->dev_data.pacs.pin_length = 0; + picopass_dev->dev_data.pacs.bitLength = 0; + memset( + picopass_dev->dev_data.pacs.credential, 0, sizeof(picopass_dev->dev_data.pacs.credential)); picopass_dev->storage = furi_record_open(RECORD_STORAGE); picopass_dev->dialogs = furi_record_open(RECORD_DIALOGS); picopass_dev->load_path = furi_string_alloc(); @@ -139,6 +148,7 @@ static bool picopass_device_save_file_lfrfid(PicopassDevice* dev, FuriString* fi FURI_LOG_D(TAG, "LFRFID Brief: %s", furi_string_get_cstr(briefStr)); furi_string_free(briefStr); + storage_simply_mkdir(dev->storage, EXT_PATH("lfrfid")); result = lfrfid_dict_file_save(dict, protocol, furi_string_get_cstr(file_path)); if(result) { FURI_LOG_D(TAG, "Written: %d", result); @@ -157,7 +167,7 @@ static bool picopass_device_save_file( const char* extension, bool use_load_path) { furi_assert(dev); - FURI_LOG_D(TAG, "Save File"); + FURI_LOG_D(TAG, "Save File %s %s %s", folder, dev_name, extension); bool saved = false; FlipperFormat* file = flipper_format_file_alloc(dev->storage); @@ -169,6 +179,7 @@ static bool picopass_device_save_file( if(dev->format == PicopassDeviceSaveFormatPartial) { // Clear key that may have been set when doing key tests for legacy memset(card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0, PICOPASS_BLOCK_LEN); + card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].valid = false; } do { @@ -203,13 +214,21 @@ static bool picopass_device_save_file( PICOPASS_MAX_APP_LIMIT; for(size_t i = 0; i < app_limit; i++) { furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_write_hex( - file, - furi_string_get_cstr(temp_str), - card_data[i].data, - PICOPASS_BLOCK_LEN)) { - block_saved = false; - break; + if(card_data[i].valid) { + if(!flipper_format_write_hex( + file, + furi_string_get_cstr(temp_str), + card_data[i].data, + PICOPASS_BLOCK_LEN)) { + block_saved = false; + break; + } + } else { + if(!flipper_format_write_string_cstr( + file, furi_string_get_cstr(temp_str), unknown_block)) { + block_saved = false; + break; + } } } if(!block_saved) break; @@ -234,10 +253,10 @@ bool picopass_device_save(PicopassDevice* dev, const char* dev_name) { return picopass_device_save_file( dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, PICOPASS_APP_EXTENSION, true); } else if(dev->format == PicopassDeviceSaveFormatLF) { - return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", true); + return picopass_device_save_file(dev, dev_name, ANY_PATH("lfrfid"), ".rfid", false); } else if(dev->format == PicopassDeviceSaveFormatSeader) { return picopass_device_save_file( - dev, dev_name, EXT_PATH("apps_data/seader"), ".credential", true); + dev, dev_name, EXT_PATH("apps_data/seader"), ".credential", false); } else if(dev->format == PicopassDeviceSaveFormatPartial) { return picopass_device_save_file( dev, dev_name, STORAGE_APP_DATA_PATH_PREFIX, PICOPASS_APP_EXTENSION, true); @@ -246,6 +265,19 @@ bool picopass_device_save(PicopassDevice* dev, const char* dev_name) { return false; } +bool picopass_hex_str_to_uint8(const char* value_str, uint8_t* value) { + furi_check(value_str); + furi_check(value); + + bool parse_success = false; + while(*value_str && value_str[1]) { + parse_success = hex_char_to_uint8(*value_str, value_str[1], value++); + if(!parse_success) break; + value_str += 3; + } + return parse_success; +} + static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, bool show_dialog) { bool parsed = false; FlipperFormat* file = flipper_format_file_alloc(dev->storage); @@ -260,26 +292,39 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo } do { + picopass_device_data_clear(&dev->dev_data); if(!flipper_format_file_open_existing(file, furi_string_get_cstr(path))) break; // Read and verify file header uint32_t version = 0; if(!flipper_format_read_header(file, temp_str, &version)) break; - if(furi_string_cmp_str(temp_str, picopass_file_header) || + if(!furi_string_equal_str(temp_str, picopass_file_header) || (version != picopass_file_version)) { deprecated_version = true; break; } + FuriString* block_str = furi_string_alloc(); // Parse header blocks bool block_read = true; for(size_t i = 0; i < 6; i++) { furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), card_data[i].data, PICOPASS_BLOCK_LEN)) { + if(!flipper_format_read_string(file, furi_string_get_cstr(temp_str), block_str)) { block_read = false; break; } + if(furi_string_equal_str(block_str, unknown_block)) { + FURI_LOG_D(TAG, "Block %i: %s (unknown)", i, furi_string_get_cstr(block_str)); + card_data[i].valid = false; + memset(card_data[i].data, 0, PICOPASS_BLOCK_LEN); + } else { + FURI_LOG_D(TAG, "Block %i: %s (hex)", i, furi_string_get_cstr(block_str)); + if(!picopass_hex_str_to_uint8(furi_string_get_cstr(block_str), card_data[i].data)) { + block_read = false; + break; + } + card_data[i].valid = true; + } } size_t app_limit = card_data[PICOPASS_CONFIG_BLOCK_INDEX].data[0]; @@ -287,16 +332,29 @@ static bool picopass_device_load_data(PicopassDevice* dev, FuriString* path, boo if(app_limit > PICOPASS_MAX_APP_LIMIT) app_limit = PICOPASS_MAX_APP_LIMIT; for(size_t i = 6; i < app_limit; i++) { furi_string_printf(temp_str, "Block %d", i); - if(!flipper_format_read_hex( - file, furi_string_get_cstr(temp_str), card_data[i].data, PICOPASS_BLOCK_LEN)) { + if(!flipper_format_read_string(file, furi_string_get_cstr(temp_str), block_str)) { block_read = false; break; } + if(furi_string_equal_str(block_str, unknown_block)) { + FURI_LOG_D(TAG, "Block %i: %s (unknown)", i, furi_string_get_cstr(block_str)); + card_data[i].valid = false; + memset(card_data[i].data, 0, PICOPASS_BLOCK_LEN); + } else { + FURI_LOG_D(TAG, "Block %i: %s (hex)", i, furi_string_get_cstr(block_str)); + if(!picopass_hex_str_to_uint8(furi_string_get_cstr(block_str), card_data[i].data)) { + block_read = false; + break; + } + card_data[i].valid = true; + } } if(!block_read) break; - picopass_device_parse_credential(card_data, pacs); - picopass_device_parse_wiegand(pacs->credential, pacs); + if(card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].valid) { + picopass_device_parse_credential(card_data, pacs); + picopass_device_parse_wiegand(pacs); + } parsed = true; } while(false); @@ -371,14 +429,22 @@ void picopass_device_data_clear(PicopassDeviceData* dev_data) { memset(dev_data->card_data[i].data, 0, sizeof(dev_data->card_data[i].data)); dev_data->card_data[i].valid = false; } + memset(dev_data->pacs.credential, 0, sizeof(dev_data->pacs.credential)); + dev_data->auth = PicopassDeviceAuthMethodUnset; dev_data->pacs.legacy = false; dev_data->pacs.se_enabled = false; dev_data->pacs.elite_kdf = false; + dev_data->pacs.sio = false; dev_data->pacs.pin_length = 0; + dev_data->pacs.bitLength = 0; } bool picopass_device_delete(PicopassDevice* dev, bool use_load_path) { furi_assert(dev); + if(dev->format != PicopassDeviceSaveFormatHF) { + // Never delete other formats (LF, Seader, etc) + return false; + } bool deleted = false; FuriString* file_path; @@ -450,7 +516,8 @@ void picopass_device_parse_credential(PicopassBlock* card_data, PicopassPacs* pa pacs->sio = (card_data[10].data[0] == 0x30); // rough check } -void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs) { +void picopass_device_parse_wiegand(PicopassPacs* pacs) { + uint8_t* credential = pacs->credential; uint32_t* halves = (uint32_t*)credential; if(halves[0] == 0) { uint8_t leading0s = __builtin_clz(REVERSE_BYTES_U32(halves[1])); diff --git a/base_pack/picopass/picopass_device.h b/base_pack/picopass/picopass_device.h index 4b4bed36699..78ed6645c09 100644 --- a/base_pack/picopass/picopass_device.h +++ b/base_pack/picopass/picopass_device.h @@ -74,6 +74,14 @@ typedef enum { PicopassDeviceSaveFormatPartial, } PicopassDeviceSaveFormat; +typedef enum { + PicopassDeviceAuthMethodUnset, + PicopassDeviceAuthMethodNone, // unsecured picopass + PicopassDeviceAuthMethodKey, + PicopassDeviceAuthMethodNrMac, + PicopassDeviceAuthMethodFailed, +} PicopassDeviceAuthMethod; + typedef enum { PicopassEmulatorStateHalt, PicopassEmulatorStateIdle, @@ -105,6 +113,7 @@ typedef struct { typedef struct { PicopassBlock card_data[PICOPASS_MAX_APP_LIMIT]; PicopassPacs pacs; + PicopassDeviceAuthMethod auth; } PicopassDeviceData; typedef struct { @@ -150,5 +159,5 @@ void picopass_device_set_loading_callback( void* context); void picopass_device_parse_credential(PicopassBlock* card_data, PicopassPacs* pacs); -void picopass_device_parse_wiegand(uint8_t* credential, PicopassPacs* pacs); +void picopass_device_parse_wiegand(PicopassPacs* pacs); bool picopass_device_hid_csn(PicopassDevice* dev); diff --git a/base_pack/picopass/protocol/picopass_listener.c b/base_pack/picopass/protocol/picopass_listener.c index 15db3b44b1c..1a91a9c681c 100644 --- a/base_pack/picopass/protocol/picopass_listener.c +++ b/base_pack/picopass/protocol/picopass_listener.c @@ -378,7 +378,7 @@ PicopassListenerCommand uint8_t rmac[4] = {}; uint8_t tmac[4] = {}; const uint8_t* key = instance->data->card_data[instance->key_block_num].data; - bool no_key = picopass_is_memset(key, 0x00, PICOPASS_BLOCK_LEN); + bool no_key = !instance->data->card_data[instance->key_block_num].valid; const uint8_t* rx_data = bit_buffer_get_data(buf); if(no_key) { diff --git a/base_pack/picopass/protocol/picopass_poller.c b/base_pack/picopass/protocol/picopass_poller.c index ec6023915ef..eafb26ed245 100644 --- a/base_pack/picopass/protocol/picopass_poller.c +++ b/base_pack/picopass/protocol/picopass_poller.c @@ -162,6 +162,7 @@ NfcCommand picopass_poller_check_security(PicopassPoller* instance) { case PICOPASS_FUSE_CRYPT0: FURI_LOG_D(TAG, "Non-secured page, skipping auth"); instance->secured = false; + instance->data->auth = PicopassDeviceAuthMethodNone; picopass_poller_prepare_read(instance); instance->state = PicopassPollerStateReadBlock; return command; @@ -193,6 +194,8 @@ NfcCommand picopass_poller_check_security(PicopassPoller* instance) { FURI_LOG_D(TAG, "SE enabled"); } + // Assume failure since we must auth, correct value will be set on success + instance->data->auth = PicopassDeviceAuthMethodFailed; if(instance->mode == PicopassPollerModeRead) { // Always try the NR-MAC auth in case we have the file. instance->state = PicopassPollerStateNrMacAuth; @@ -295,6 +298,7 @@ NfcCommand picopass_poller_nr_mac_auth(PicopassPoller* instance) { PicopassCheckResp check_resp = {}; error = picopass_poller_check(instance, nr_mac, &mac, &check_resp); if(error == PicopassErrorNone) { + instance->data->auth = PicopassDeviceAuthMethodNrMac; memcpy(instance->mac.data, mac.data, sizeof(PicopassMac)); if(instance->mode == PicopassPollerModeRead) { picopass_poller_prepare_read(instance); @@ -383,6 +387,7 @@ NfcCommand picopass_poller_auth_handler(PicopassPoller* instance) { error = picopass_poller_check(instance, NULL, &mac, &check_resp); if(error == PicopassErrorNone) { FURI_LOG_I(TAG, "Found key"); + instance->data->auth = PicopassDeviceAuthMethodKey; memcpy(instance->mac.data, mac.data, sizeof(PicopassMac)); if(instance->mode == PicopassPollerModeRead) { memcpy( @@ -463,7 +468,7 @@ NfcCommand picopass_poller_parse_credential_handler(PicopassPoller* instance) { NfcCommand picopass_poller_parse_wiegand_handler(PicopassPoller* instance) { NfcCommand command = NfcCommandContinue; - picopass_device_parse_wiegand(instance->data->pacs.credential, &instance->data->pacs); + picopass_device_parse_wiegand(&instance->data->pacs); instance->state = PicopassPollerStateSuccess; return command; } diff --git a/base_pack/picopass/scenes/picopass_scene_card_menu.c b/base_pack/picopass/scenes/picopass_scene_card_menu.c index 68081c4f360..6caf5588713 100644 --- a/base_pack/picopass/scenes/picopass_scene_card_menu.c +++ b/base_pack/picopass/scenes/picopass_scene_card_menu.c @@ -28,8 +28,7 @@ void picopass_scene_card_menu_on_enter(void* context) { bool zero_config = picopass_is_memset( card_data[PICOPASS_ICLASS_PACS_CFG_BLOCK_INDEX].data, 0x00, PICOPASS_BLOCK_LEN); bool no_credential = picopass_is_memset(pacs->credential, 0x00, sizeof(pacs->credential)); - bool no_key = picopass_is_memset( - card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN); + bool no_key = !card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].valid; if(secured && zero_config) { submenu_add_item( diff --git a/base_pack/picopass/scenes/picopass_scene_device_info.c b/base_pack/picopass/scenes/picopass_scene_device_info.c index 17d66fdf1d2..c08adfda236 100644 --- a/base_pack/picopass/scenes/picopass_scene_device_info.c +++ b/base_pack/picopass/scenes/picopass_scene_device_info.c @@ -102,8 +102,7 @@ bool picopass_scene_device_info_on_event(void* context, SceneManagerEvent event) consumed = true; } } else if(event.type == SceneManagerEventTypeBack) { - view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewWidget); - consumed = true; + consumed = scene_manager_previous_scene(picopass->scene_manager); } return consumed; } diff --git a/base_pack/picopass/scenes/picopass_scene_loclass.c b/base_pack/picopass/scenes/picopass_scene_loclass.c index 616cba05747..0015bda6840 100644 --- a/base_pack/picopass/scenes/picopass_scene_loclass.c +++ b/base_pack/picopass/scenes/picopass_scene_loclass.c @@ -36,6 +36,7 @@ void picopass_scene_loclass_on_enter(void* context) { loclass_set_callback(picopass->loclass, picopass_loclass_result_callback, picopass); loclass_set_header(picopass->loclass, "Loclass"); + loclass_set_subheader(picopass->loclass, "Hold To Reader"); picopass_blink_emulate_start(picopass); view_dispatcher_switch_to_view(picopass->view_dispatcher, PicopassViewLoclass); diff --git a/base_pack/picopass/scenes/picopass_scene_more_info.c b/base_pack/picopass/scenes/picopass_scene_more_info.c index 4c075825c4d..28790fd5a5b 100644 --- a/base_pack/picopass/scenes/picopass_scene_more_info.c +++ b/base_pack/picopass/scenes/picopass_scene_more_info.c @@ -19,8 +19,12 @@ void picopass_scene_more_info_on_enter(void* context) { for(size_t i = 0; i < app_limit; i++) { for(size_t j = 0; j < PICOPASS_BLOCK_LEN; j += 2) { - furi_string_cat_printf( - str, "%02X%02X ", card_data[i].data[j], card_data[i].data[j + 1]); + if(card_data[i].valid) { + furi_string_cat_printf( + str, "%02X%02X ", card_data[i].data[j], card_data[i].data[j + 1]); + } else { + furi_string_cat_printf(str, "???? "); + } } } diff --git a/base_pack/picopass/scenes/picopass_scene_read_card_success.c b/base_pack/picopass/scenes/picopass_scene_read_card_success.c index 18e9e2d56c0..a6d9ba2abc1 100644 --- a/base_pack/picopass/scenes/picopass_scene_read_card_success.c +++ b/base_pack/picopass/scenes/picopass_scene_read_card_success.c @@ -2,6 +2,8 @@ #include #include +#define TAG "PicopassSceneReadCardSuccess" + void picopass_scene_read_card_success_widget_callback( GuiButtonType result, InputType type, @@ -27,6 +29,28 @@ void picopass_scene_read_card_success_on_enter(void* context) { // Send notification notification_message(picopass->notifications, &sequence_success); + // For initial testing, print auth method + switch(picopass->dev->dev_data.auth) { + case PicopassDeviceAuthMethodUnset: + FURI_LOG_D(TAG, "Auth: Unset"); + break; + case PicopassDeviceAuthMethodNone: + FURI_LOG_D(TAG, "Auth: None"); + break; + case PicopassDeviceAuthMethodKey: + FURI_LOG_D(TAG, "Auth: Key"); + break; + case PicopassDeviceAuthMethodNrMac: + FURI_LOG_D(TAG, "Auth: NR-MAC"); + break; + case PicopassDeviceAuthMethodFailed: + FURI_LOG_D(TAG, "Auth: Failed"); + break; + default: + FURI_LOG_D(TAG, "Auth: Unknown"); + break; + }; + // Setup view PicopassBlock* card_data = picopass->dev->dev_data.card_data; PicopassPacs* pacs = &picopass->dev->dev_data.pacs; @@ -133,8 +157,7 @@ void picopass_scene_read_card_success_on_enter(void* context) { furi_string_cat_printf(credential_str, " +SIO"); } - bool no_key = picopass_is_memset( - card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].data, 0xFF, PICOPASS_BLOCK_LEN); + bool no_key = !card_data[PICOPASS_SECURE_KD_BLOCK_INDEX].valid; if(no_key) { furi_string_cat_printf(key_str, "No Key: used NR-MAC"); diff --git a/base_pack/picopass/scenes/picopass_scene_saved_menu.c b/base_pack/picopass/scenes/picopass_scene_saved_menu.c index 35e4573adb9..e8e0771cda5 100644 --- a/base_pack/picopass/scenes/picopass_scene_saved_menu.c +++ b/base_pack/picopass/scenes/picopass_scene_saved_menu.c @@ -6,6 +6,7 @@ enum SubmenuIndex { SubmenuIndexEmulate, SubmenuIndexRename, SubmenuIndexDelete, + SubmenuIndexSaveAsLF, }; void picopass_scene_saved_menu_submenu_callback(void* context, uint32_t index) { @@ -18,6 +19,13 @@ void picopass_scene_saved_menu_on_enter(void* context) { Picopass* picopass = context; Submenu* submenu = picopass->submenu; + PicopassPacs* pacs = &picopass->dev->dev_data.pacs; + PicopassBlock* card_data = picopass->dev->dev_data.card_data; + + bool secured = (card_data[PICOPASS_CONFIG_BLOCK_INDEX].data[7] & PICOPASS_FUSE_CRYPT10) != + PICOPASS_FUSE_CRYPT0; + bool no_credential = picopass_is_memset(pacs->credential, 0x00, sizeof(pacs->credential)); + submenu_add_item( submenu, "Info", SubmenuIndexInfo, picopass_scene_saved_menu_submenu_callback, picopass); submenu_add_item( @@ -28,6 +36,16 @@ void picopass_scene_saved_menu_on_enter(void* context) { SubmenuIndexEmulate, picopass_scene_saved_menu_submenu_callback, picopass); + + if(secured && !no_credential) { + submenu_add_item( + submenu, + "Save as LFRFID", + SubmenuIndexSaveAsLF, + picopass_scene_saved_menu_submenu_callback, + picopass); + } + submenu_add_item( submenu, "Rename", @@ -71,6 +89,12 @@ bool picopass_scene_saved_menu_on_event(void* context, SceneManagerEvent event) } else if(event.event == SubmenuIndexRename) { scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName); consumed = true; + } else if(event.event == SubmenuIndexSaveAsLF) { + scene_manager_set_scene_state( + picopass->scene_manager, PicopassSceneCardMenu, SubmenuIndexSaveAsLF); + picopass->dev->format = PicopassDeviceSaveFormatLF; + scene_manager_next_scene(picopass->scene_manager, PicopassSceneSaveName); + consumed = true; } } diff --git a/base_pack/picopass/views/loclass.c b/base_pack/picopass/views/loclass.c index 4f9da2a4548..f46a9dfcecd 100644 --- a/base_pack/picopass/views/loclass.c +++ b/base_pack/picopass/views/loclass.c @@ -13,14 +13,16 @@ struct Loclass { typedef struct { FuriString* header; uint8_t num_macs; + FuriString* subheader; } LoclassViewModel; static void loclass_draw_callback(Canvas* canvas, void* model) { LoclassViewModel* m = model; char draw_str[32] = {}; - canvas_set_font(canvas, FontSecondary); + canvas_set_font(canvas, FontPrimary); canvas_draw_str_aligned(canvas, 64, 0, AlignCenter, AlignTop, furi_string_get_cstr(m->header)); + canvas_set_font(canvas, FontSecondary); if(m->num_macs == 255) { return; @@ -37,6 +39,9 @@ static void loclass_draw_callback(Canvas* canvas, void* model) { elements_progress_bar_with_text(canvas, 0, 20, 128, progress, draw_str); + canvas_draw_str_aligned( + canvas, 64, 45, AlignCenter, AlignBottom, furi_string_get_cstr(m->subheader)); + elements_button_center(canvas, "Skip"); } @@ -61,6 +66,11 @@ Loclass* loclass_alloc() { view_set_context(loclass->view, loclass); with_view_model( loclass->view, LoclassViewModel * model, { model->header = furi_string_alloc(); }, false); + with_view_model( + loclass->view, + LoclassViewModel * model, + { model->subheader = furi_string_alloc(); }, + false); return loclass; } @@ -68,6 +78,8 @@ void loclass_free(Loclass* loclass) { furi_assert(loclass); with_view_model( loclass->view, LoclassViewModel * model, { furi_string_free(model->header); }, false); + with_view_model( + loclass->view, LoclassViewModel * model, { furi_string_free(model->subheader); }, false); view_free(loclass->view); free(loclass); } @@ -80,6 +92,7 @@ void loclass_reset(Loclass* loclass) { { model->num_macs = 0; furi_string_reset(model->header); + furi_string_reset(model->subheader); }, false); } @@ -104,6 +117,17 @@ void loclass_set_header(Loclass* loclass, const char* header) { loclass->view, LoclassViewModel * model, { furi_string_set(model->header, header); }, true); } +void loclass_set_subheader(Loclass* loclass, const char* subheader) { + furi_assert(loclass); + furi_assert(subheader); + + with_view_model( + loclass->view, + LoclassViewModel * model, + { furi_string_set(model->subheader, subheader); }, + true); +} + void loclass_set_num_macs(Loclass* loclass, uint16_t num_macs) { furi_assert(loclass); with_view_model( diff --git a/base_pack/picopass/views/loclass.h b/base_pack/picopass/views/loclass.h index 0e39b6083b0..fc5a49d5f1f 100644 --- a/base_pack/picopass/views/loclass.h +++ b/base_pack/picopass/views/loclass.h @@ -19,4 +19,6 @@ void loclass_set_callback(Loclass* loclass, LoclassCallback callback, void* cont void loclass_set_header(Loclass* loclass, const char* header); +void loclass_set_subheader(Loclass* loclass, const char* subheader); + void loclass_set_num_macs(Loclass* loclass, uint16_t num_macs);